<commit_before>package main

import (
	"context"
	"net/http"
	"runtime"
	"sync"
	"time"

	"github.com/lxc/lxd/lxd/db"
	dbCluster "github.com/lxc/lxd/lxd/db/cluster"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/metrics"
	"github.com/lxc/lxd/lxd/response"
	"github.com/lxc/lxd/shared/logger"
)

type metricsCacheEntry struct {
	metrics *metrics.MetricSet
	expiry  time.Time
}

var metricsCache map[string]metricsCacheEntry
var metricsCacheLock sync.Mutex
var metricsLock sync.Mutex

var metricsCmd = APIEndpoint{
	Path: "metrics",

	Get: APIEndpointAction{Handler: metricsGet, AccessHandler: allowMetrics, AllowUntrusted: true},
}

func allowMetrics(d *Daemon, r *http.Request) response.Response {
	// Check if API is wide open.
	if !d.State().GlobalConfig.MetricsAuthentication() {
		return response.EmptySyncResponse
	}

	// If not wide open, apply project access restrictions.
	return allowProjectPermission("containers", "view")(d, r)
}

// swagger:operation GET /1.0/metrics metrics metrics_get
//
// Get metrics
//
// Gets metrics of instances.
//
// ---
// produces:
//   - text/plain
// parameters:
//   - in: query
//     name: project
//     description: Project name
//     type: string
//     example: default
//   - in: query
//     name: target
//     description: Cluster member name
//     type: string
//     example: lxd01
// responses:
//   "200":
//     description: Metrics
//     schema:
//       type: string
//       description: Instance metrics
//   "403":
//     $ref: "#/responses/Forbidden"
//   "500":
//     $ref: "#/responses/InternalServerError"
func metricsGet(d *Daemon, r *http.Request) response.Response {
	projectName := queryParam(r, "project")

	// Forward if requested.
	resp := forwardedResponseIfTargetIsRemote(d, r)
	if resp != nil {
		return resp
	}

	// Wait until daemon is fully started.
	<-d.waitReady.Done()

	// Figure out the projects to retrieve.
	var projectNames []string

	if projectName != "" {
		projectNames = []string{projectName}
	} else {
		// Get all projects.
		err := d.db.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
			projects, err := dbCluster.GetProjects(ctx, tx.Tx())
			if err != nil {
				return err
			}

			for _, project := range projects {
				projectNames = append(projectNames, project.Name)
			}

			return nil
		})
		if err != nil {
			return response.SmartError(err)
		}
	}

	// Prepare response.
	metricSet := metrics.NewMetricSet(nil)

	// Add internal metrics.
	metricSet.Merge(internalMetrics(d))

	// Review the cache.
	metricsCacheLock.Lock()
	projectMissing := []string{}
	for _, project := range projectNames {
		cache, ok := metricsCache[project]
		if !ok || cache.expiry.Before(time.Now()) {
			// If missing or expired, record it.
			projectMissing = append(projectMissing, project)
			continue
		}

		// If present and valid, merge the existing data.
		metricSet.Merge(cache.metrics)
	}

	metricsCacheLock.Unlock()

	// If all valid, return immediately.
	if len(projectMissing) == 0 {
		return response.SyncResponsePlain(true, metricSet.String())
	}

	// Acquire update lock.
	metricsLock.Lock()
	defer metricsLock.Unlock()

	// Check if any of the missing data has been filled in.
	metricsCacheLock.Lock()
	toFetch := []string{}
	for _, project := range projectMissing {
		cache, ok := metricsCache[project]
		if !ok || cache.expiry.Before(time.Now()) {
			// Still missing, queue a re-fetch.
			toFetch = append(toFetch, project)
			continue
		}

		// If present and valid, merge the existing data.
		metricSet.Merge(cache.metrics)
	}

	metricsCacheLock.Unlock()

	// If all valid, return immediately.
	if len(toFetch) == 0 {
		return response.SyncResponsePlain(true, metricSet.String())
	}

	// Prepare temporary metrics storage.
	newMetrics := map[string]*metrics.MetricSet{}
	newMetricsLock := sync.Mutex{}

	// Fetch what's missing.
	wgInstances := sync.WaitGroup{}
	for _, project := range toFetch {
		newMetrics[project] = metrics.NewMetricSet(nil)

		// Get the instances.
		instances, err := instanceLoadNodeProjectAll(d.State(), project, instancetype.Any)
		if err != nil {
			return response.SmartError(err)
		}

		for _, inst := range instances {
			// Ignore stopped instances.
			if !inst.IsRunning() {
				continue
			}

			wgInstances.Add(1)
			go func(inst instance.Instance) {
				defer wgInstances.Done()

				instanceMetrics, err := inst.Metrics()
				if err != nil {
					logger.Warn("Failed to get instance metrics", logger.Ctx{"instance": inst.Name(), "project": inst.Project(), "err": err})
					return
				}

				// Add the metrics.
				newMetricsLock.Lock()
				defer newMetricsLock.Unlock()

				newMetrics[inst.Project().Name].Merge(instanceMetrics)
			}(inst)
		}
	}

	wgInstances.Wait()

	// Put the new data in the global cache and in response.
	metricsCacheLock.Lock()

	if metricsCache == nil {
		metricsCache = map[string]metricsCacheEntry{}
	}

	for project, entries := range newMetrics {
		metricsCache[project] = metricsCacheEntry{
			expiry:  time.Now().Add(8 * time.Second),
			metrics: entries,
		}

		metricSet.Merge(entries)
	}

	metricsCacheLock.Unlock()

	return response.SyncResponsePlain(true, metricSet.String())
}

func internalMetrics(d *Daemon) *metrics.MetricSet {
	out := metrics.NewMetricSet(nil)

	_ = d.db.Cluster.Transaction(d.shutdownCtx, func(ctx context.Context, tx *db.ClusterTx) error {
		warnings, err := dbCluster.GetWarnings(ctx, tx.Tx())
		if err != nil {
			logger.Warn("Failed to get warnings", logger.Ctx{"err": err})
		} else {
			// Total number of warnings
			out.AddSamples(metrics.WarningsTotal, metrics.Sample{Value: float64(len(warnings))})
		}

		operations, err := dbCluster.GetOperations(ctx, tx.Tx())
		if err != nil {
			logger.Warn("Failed to get operations", logger.Ctx{"err": err})
		} else {
			// Total number of operations
			out.AddSamples(metrics.OperationsTotal, metrics.Sample{Value: float64(len(operations))})
		}

		return nil
	})

	// Daemon uptime
	out.AddSamples(metrics.UptimeSeconds, metrics.Sample{Value: time.Since(d.startTime).Seconds()})

	// Number of goroutines
	out.AddSamples(metrics.GoGoroutines, metrics.Sample{Value: float64(runtime.NumGoroutine())})

	// Go memory stats
	var ms runtime.MemStats

	runtime.ReadMemStats(&ms)

	out.AddSamples(metrics.GoAllocBytes, metrics.Sample{Value: float64(ms.Alloc)})
	out.AddSamples(metrics.GoAllocBytesTotal, metrics.Sample{Value: float64(ms.TotalAlloc)})
	out.AddSamples(metrics.GoBuckHashSysBytes, metrics.Sample{Value: float64(ms.BuckHashSys)})
	out.AddSamples(metrics.GoFreesTotal, metrics.Sample{Value: float64(ms.Frees)})
	out.AddSamples(metrics.GoGCSysBytes, metrics.Sample{Value: float64(ms.GCSys)})
	out.AddSamples(metrics.GoHeapAllocBytes, metrics.Sample{Value: float64(ms.HeapAlloc)})
	out.AddSamples(metrics.GoHeapIdleBytes, metrics.Sample{Value: float64(ms.HeapIdle)})
	out.AddSamples(metrics.GoHeapInuseBytes, metrics.Sample{Value: float64(ms.HeapInuse)})
	out.AddSamples(metrics.GoHeapObjects, metrics.Sample{Value: float64(ms.HeapObjects)})
	out.AddSamples(metrics.GoHeapReleasedBytes, metrics.Sample{Value: float64(ms.HeapReleased)})
	out.AddSamples(metrics.GoHeapSysBytes, metrics.Sample{Value: float64(ms.HeapSys)})
	out.AddSamples(metrics.GoLookupsTotal, metrics.Sample{Value: float64(ms.Lookups)})
	out.AddSamples(metrics.GoMallocsTotal, metrics.Sample{Value: float64(ms.Mallocs)})
	out.AddSamples(metrics.GoMCacheInuseBytes, metrics.Sample{Value: float64(ms.MCacheInuse)})
	out.AddSamples(metrics.GoMCacheSysBytes, metrics.Sample{Value: float64(ms.MCacheSys)})
	out.AddSamples(metrics.GoMSpanInuseBytes, metrics.Sample{Value: float64(ms.MSpanInuse)})
	out.AddSamples(metrics.GoMSpanSysBytes, metrics.Sample{Value: float64(ms.MSpanSys)})
	out.AddSamples(metrics.GoNextGCBytes, metrics.Sample{Value: float64(ms.NextGC)})
	out.AddSamples(metrics.GoOtherSysBytes, metrics.Sample{Value: float64(ms.OtherSys)})
	out.AddSamples(metrics.GoStackInuseBytes, metrics.Sample{Value: float64(ms.StackInuse)})
	out.AddSamples(metrics.GoStackSysBytes, metrics.Sample{Value: float64(ms.StackSys)})
	out.AddSamples(metrics.GoSysBytes, metrics.Sample{Value: float64(ms.Sys)})

	return out
}
<commit_msg>lxd/api/metrics: inst.Metrics usage<commit_after>package main

import (
	"context"
	"net"
	"net/http"
	"runtime"
	"sync"
	"time"

	"github.com/lxc/lxd/lxd/db"
	dbCluster "github.com/lxc/lxd/lxd/db/cluster"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/metrics"
	"github.com/lxc/lxd/lxd/response"
	"github.com/lxc/lxd/shared/logger"
)

type metricsCacheEntry struct {
	metrics *metrics.MetricSet
	expiry  time.Time
}

var metricsCache map[string]metricsCacheEntry
var metricsCacheLock sync.Mutex
var metricsLock sync.Mutex

var metricsCmd = APIEndpoint{
	Path: "metrics",

	Get: APIEndpointAction{Handler: metricsGet, AccessHandler: allowMetrics, AllowUntrusted: true},
}

func allowMetrics(d *Daemon, r *http.Request) response.Response {
	// Check if API is wide open.
	if !d.State().GlobalConfig.MetricsAuthentication() {
		return response.EmptySyncResponse
	}

	// If not wide open, apply project access restrictions.
	return allowProjectPermission("containers", "view")(d, r)
}

// swagger:operation GET /1.0/metrics metrics metrics_get
//
// Get metrics
//
// Gets metrics of instances.
//
// ---
// produces:
//   - text/plain
// parameters:
//   - in: query
//     name: project
//     description: Project name
//     type: string
//     example: default
//   - in: query
//     name: target
//     description: Cluster member name
//     type: string
//     example: lxd01
// responses:
//   "200":
//     description: Metrics
//     schema:
//       type: string
//       description: Instance metrics
//   "403":
//     $ref: "#/responses/Forbidden"
//   "500":
//     $ref: "#/responses/InternalServerError"
func metricsGet(d *Daemon, r *http.Request) response.Response {
	projectName := queryParam(r, "project")

	// Forward if requested.
	resp := forwardedResponseIfTargetIsRemote(d, r)
	if resp != nil {
		return resp
	}

	// Wait until daemon is fully started.
	<-d.waitReady.Done()

	// Figure out the projects to retrieve.
	var projectNames []string

	if projectName != "" {
		projectNames = []string{projectName}
	} else {
		// Get all projects.
		err := d.db.Cluster.Transaction(context.TODO(), func(ctx context.Context, tx *db.ClusterTx) error {
			projects, err := dbCluster.GetProjects(ctx, tx.Tx())
			if err != nil {
				return err
			}

			for _, project := range projects {
				projectNames = append(projectNames, project.Name)
			}

			return nil
		})
		if err != nil {
			return response.SmartError(err)
		}
	}

	// Prepare response.
	metricSet := metrics.NewMetricSet(nil)

	// Add internal metrics.
	metricSet.Merge(internalMetrics(d))

	// Review the cache.
	metricsCacheLock.Lock()
	projectMissing := []string{}
	for _, project := range projectNames {
		cache, ok := metricsCache[project]
		if !ok || cache.expiry.Before(time.Now()) {
			// If missing or expired, record it.
			projectMissing = append(projectMissing, project)
			continue
		}

		// If present and valid, merge the existing data.
		metricSet.Merge(cache.metrics)
	}

	metricsCacheLock.Unlock()

	// If all valid, return immediately.
	if len(projectMissing) == 0 {
		return response.SyncResponsePlain(true, metricSet.String())
	}

	// Acquire update lock.
	metricsLock.Lock()
	defer metricsLock.Unlock()

	// Check if any of the missing data has been filled in.
	metricsCacheLock.Lock()
	toFetch := []string{}
	for _, project := range projectMissing {
		cache, ok := metricsCache[project]
		if !ok || cache.expiry.Before(time.Now()) {
			// Still missing, queue a re-fetch.
			toFetch = append(toFetch, project)
			continue
		}

		// If present and valid, merge the existing data.
		metricSet.Merge(cache.metrics)
	}

	metricsCacheLock.Unlock()

	// If all valid, return immediately.
	if len(toFetch) == 0 {
		return response.SyncResponsePlain(true, metricSet.String())
	}

	hostInterfaces, _ := net.Interfaces()

	// Prepare temporary metrics storage.
	newMetrics := map[string]*metrics.MetricSet{}
	newMetricsLock := sync.Mutex{}

	// Fetch what's missing.
	wgInstances := sync.WaitGroup{}
	for _, project := range toFetch {
		newMetrics[project] = metrics.NewMetricSet(nil)

		// Get the instances.
		instances, err := instanceLoadNodeProjectAll(d.State(), project, instancetype.Any)
		if err != nil {
			return response.SmartError(err)
		}

		for _, inst := range instances {
			// Ignore stopped instances.
			if !inst.IsRunning() {
				continue
			}

			wgInstances.Add(1)
			go func(inst instance.Instance) {
				defer wgInstances.Done()

				instanceMetrics, err := inst.Metrics(hostInterfaces)
				if err != nil {
					logger.Warn("Failed to get instance metrics", logger.Ctx{"instance": inst.Name(), "project": inst.Project(), "err": err})
					return
				}

				// Add the metrics.
				newMetricsLock.Lock()
				defer newMetricsLock.Unlock()

				newMetrics[inst.Project().Name].Merge(instanceMetrics)
			}(inst)
		}
	}

	wgInstances.Wait()

	// Put the new data in the global cache and in response.
	metricsCacheLock.Lock()

	if metricsCache == nil {
		metricsCache = map[string]metricsCacheEntry{}
	}

	for project, entries := range newMetrics {
		metricsCache[project] = metricsCacheEntry{
			expiry:  time.Now().Add(8 * time.Second),
			metrics: entries,
		}

		metricSet.Merge(entries)
	}

	metricsCacheLock.Unlock()

	return response.SyncResponsePlain(true, metricSet.String())
}

func internalMetrics(d *Daemon) *metrics.MetricSet {
	out := metrics.NewMetricSet(nil)

	_ = d.db.Cluster.Transaction(d.shutdownCtx, func(ctx context.Context, tx *db.ClusterTx) error {
		warnings, err := dbCluster.GetWarnings(ctx, tx.Tx())
		if err != nil {
			logger.Warn("Failed to get warnings", logger.Ctx{"err": err})
		} else {
			// Total number of warnings
			out.AddSamples(metrics.WarningsTotal, metrics.Sample{Value: float64(len(warnings))})
		}

		operations, err := dbCluster.GetOperations(ctx, tx.Tx())
		if err != nil {
			logger.Warn("Failed to get operations", logger.Ctx{"err": err})
		} else {
			// Total number of operations
			out.AddSamples(metrics.OperationsTotal, metrics.Sample{Value: float64(len(operations))})
		}

		return nil
	})

	// Daemon uptime
	out.AddSamples(metrics.UptimeSeconds, metrics.Sample{Value: time.Since(d.startTime).Seconds()})

	// Number of goroutines
	out.AddSamples(metrics.GoGoroutines, metrics.Sample{Value: float64(runtime.NumGoroutine())})

	// Go memory stats
	var ms runtime.MemStats

	runtime.ReadMemStats(&ms)

	out.AddSamples(metrics.GoAllocBytes, metrics.Sample{Value: float64(ms.Alloc)})
	out.AddSamples(metrics.GoAllocBytesTotal, metrics.Sample{Value: float64(ms.TotalAlloc)})
	out.AddSamples(metrics.GoBuckHashSysBytes, metrics.Sample{Value: float64(ms.BuckHashSys)})
	out.AddSamples(metrics.GoFreesTotal, metrics.Sample{Value: float64(ms.Frees)})
	out.AddSamples(metrics.GoGCSysBytes, metrics.Sample{Value: float64(ms.GCSys)})
	out.AddSamples(metrics.GoHeapAllocBytes, metrics.Sample{Value: float64(ms.HeapAlloc)})
	out.AddSamples(metrics.GoHeapIdleBytes, metrics.Sample{Value: float64(ms.HeapIdle)})
	out.AddSamples(metrics.GoHeapInuseBytes, metrics.Sample{Value: float64(ms.HeapInuse)})
	out.AddSamples(metrics.GoHeapObjects, metrics.Sample{Value: float64(ms.HeapObjects)})
	out.AddSamples(metrics.GoHeapReleasedBytes, metrics.Sample{Value: float64(ms.HeapReleased)})
	out.AddSamples(metrics.GoHeapSysBytes, metrics.Sample{Value: float64(ms.HeapSys)})
	out.AddSamples(metrics.GoLookupsTotal, metrics.Sample{Value: float64(ms.Lookups)})
	out.AddSamples(metrics.GoMallocsTotal, metrics.Sample{Value: float64(ms.Mallocs)})
	out.AddSamples(metrics.GoMCacheInuseBytes, metrics.Sample{Value: float64(ms.MCacheInuse)})
	out.AddSamples(metrics.GoMCacheSysBytes, metrics.Sample{Value: float64(ms.MCacheSys)})
	out.AddSamples(metrics.GoMSpanInuseBytes, metrics.Sample{Value: float64(ms.MSpanInuse)})
	out.AddSamples(metrics.GoMSpanSysBytes, metrics.Sample{Value: float64(ms.MSpanSys)})
	out.AddSamples(metrics.GoNextGCBytes, metrics.Sample{Value: float64(ms.NextGC)})
	out.AddSamples(metrics.GoOtherSysBytes, metrics.Sample{Value: float64(ms.OtherSys)})
	out.AddSamples(metrics.GoStackInuseBytes, metrics.Sample{Value: float64(ms.StackInuse)})
	out.AddSamples(metrics.GoStackSysBytes, metrics.Sample{Value: float64(ms.StackSys)})
	out.AddSamples(metrics.GoSysBytes, metrics.Sample{Value: float64(ms.Sys)})

	return out
}
<|endoftext|>


<commit_before>package main

import (
	"net/http"
	"sync"
	"time"

	log "gopkg.in/inconshreveable/log15.v2"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/metrics"
	"github.com/lxc/lxd/lxd/response"
	"github.com/lxc/lxd/shared/logger"
)

type metricsCacheEntry struct {
	metrics *metrics.MetricSet
	expiry  time.Time
}

var metricsCache map[string]metricsCacheEntry
var metricsCacheLock sync.Mutex
var metricsLock sync.Mutex

var metricsCmd = APIEndpoint{
	Path: "metrics",

	Get: APIEndpointAction{Handler: metricsGet, AccessHandler: allowProjectPermission("containers", "view")},
}

// swagger:operation GET /1.0/metrics metrics metrics_get
//
// Get metrics
//
// Gets metrics of instances.
//
// ---
// produces:
//   - text/plain
// parameters:
//   - in: query
//     name: project
//     description: Project name
//     type: string
//     example: default
// responses:
//   "200":
//     description: Metrics
//     schema:
//       type: string
//       description: Instance metrics
//   "403":
//     $ref: "#/responses/Forbidden"
//   "500":
//     $ref: "#/responses/InternalServerError"
func metricsGet(d *Daemon, r *http.Request) response.Response {
	projectName := queryParam(r, "project")

	// Figure out the projects to retrieve.
	var projectNames []string

	if projectName != "" {
		projectNames = []string{projectName}
	} else {
		// Get all projects.
		err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
			projects, err := tx.GetProjects(db.ProjectFilter{})
			if err != nil {
				return err
			}

			for _, project := range projects {
				projectNames = append(projectNames, project.Name)
			}

			return nil
		})
		if err != nil {
			return response.SmartError(err)
		}
	}

	// Prepare response.
	metricSet := metrics.NewMetricSet(nil)

	// Review the cache.
	metricsCacheLock.Lock()
	projectMissing := []string{}
	for _, project := range projectNames {
		cache, ok := metricsCache[project]
		if !ok || cache.expiry.Before(time.Now()) {
			// If missing or expired, record it.
			projectMissing = append(projectMissing, project)
			continue
		}

		// If present and valid, merge the existing data.
		metricSet.Merge(cache.metrics)
	}
	metricsCacheLock.Unlock()

	// If all valid, return immediately.
	if len(projectMissing) == 0 {
		return response.SyncResponsePlain(true, metricSet.String())
	}

	// Acquire update lock.
	metricsLock.Lock()
	defer metricsLock.Unlock()

	// Check if any of the missing data has been filled in.
	metricsCacheLock.Lock()
	toFetch := []string{}
	for _, project := range projectMissing {
		cache, ok := metricsCache[project]
		if !ok || cache.expiry.Before(time.Now()) {
			// Still missing, queue a re-fetch.
			toFetch = append(toFetch, project)
			continue
		}

		// If present and valid, merge the existing data.
		metricSet.Merge(cache.metrics)
	}
	metricsCacheLock.Unlock()

	// If all valid, return immediately.
	if len(toFetch) == 0 {
		return response.SyncResponsePlain(true, metricSet.String())
	}

	// Prepare temporary metrics storage.
	newMetrics := map[string]*metrics.MetricSet{}
	newMetricsLock := sync.Mutex{}

	// Fetch what's missing.
	wgInstances := sync.WaitGroup{}
	for _, project := range toFetch {
		newMetrics[project] = metrics.NewMetricSet(nil)

		// Get the instances.
		instances, err := instanceLoadNodeProjectAll(d.State(), project, instancetype.Any)
		if err != nil {
			return response.SmartError(err)
		}

		for _, inst := range instances {
			// Ignore stopped instances.
			if !inst.IsRunning() {
				continue
			}

			wgInstances.Add(1)
			go func(inst instance.Instance) {
				defer wgInstances.Done()

				instanceMetrics, err := inst.Metrics()
				if err != nil {
					logger.Warn("Failed to get instance metrics", log.Ctx{"instance": inst.Name(), "project": inst.Project(), "err": err})
					return
				}

				// Add the metrics.
				newMetricsLock.Lock()
				defer newMetricsLock.Unlock()

				newMetrics[inst.Project()].Merge(instanceMetrics)
			}(inst)
		}
	}

	wgInstances.Wait()

	// Put the new data in the global cache and in response.
	metricsCacheLock.Lock()

	if metricsCache == nil {
		metricsCache = map[string]metricsCacheEntry{}
	}

	for project, entries := range newMetrics {
		metricsCache[project] = metricsCacheEntry{
			expiry:  time.Now().Add(8 * time.Second),
			metrics: entries,
		}

		metricSet.Merge(entries)
	}
	metricsCacheLock.Unlock()

	return response.SyncResponsePlain(true, metricSet.String())
}
<commit_msg>lxd/api_metrics: Support target<commit_after>package main

import (
	"net/http"
	"sync"
	"time"

	log "gopkg.in/inconshreveable/log15.v2"

	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/metrics"
	"github.com/lxc/lxd/lxd/response"
	"github.com/lxc/lxd/shared/logger"
)

type metricsCacheEntry struct {
	metrics *metrics.MetricSet
	expiry  time.Time
}

var metricsCache map[string]metricsCacheEntry
var metricsCacheLock sync.Mutex
var metricsLock sync.Mutex

var metricsCmd = APIEndpoint{
	Path: "metrics",

	Get: APIEndpointAction{Handler: metricsGet, AccessHandler: allowProjectPermission("containers", "view")},
}

// swagger:operation GET /1.0/metrics metrics metrics_get
//
// Get metrics
//
// Gets metrics of instances.
//
// ---
// produces:
//   - text/plain
// parameters:
//   - in: query
//     name: project
//     description: Project name
//     type: string
//     example: default
//   - in: query
//     name: target
//     description: Cluster member name
//     type: string
//     example: lxd01
// responses:
//   "200":
//     description: Metrics
//     schema:
//       type: string
//       description: Instance metrics
//   "403":
//     $ref: "#/responses/Forbidden"
//   "500":
//     $ref: "#/responses/InternalServerError"
func metricsGet(d *Daemon, r *http.Request) response.Response {
	projectName := queryParam(r, "project")

	// Forward if requested.
	resp := forwardedResponseIfTargetIsRemote(d, r)
	if resp != nil {
		return resp
	}

	// Figure out the projects to retrieve.
	var projectNames []string

	if projectName != "" {
		projectNames = []string{projectName}
	} else {
		// Get all projects.
		err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
			projects, err := tx.GetProjects(db.ProjectFilter{})
			if err != nil {
				return err
			}

			for _, project := range projects {
				projectNames = append(projectNames, project.Name)
			}

			return nil
		})
		if err != nil {
			return response.SmartError(err)
		}
	}

	// Prepare response.
	metricSet := metrics.NewMetricSet(nil)

	// Review the cache.
	metricsCacheLock.Lock()
	projectMissing := []string{}
	for _, project := range projectNames {
		cache, ok := metricsCache[project]
		if !ok || cache.expiry.Before(time.Now()) {
			// If missing or expired, record it.
			projectMissing = append(projectMissing, project)
			continue
		}

		// If present and valid, merge the existing data.
		metricSet.Merge(cache.metrics)
	}
	metricsCacheLock.Unlock()

	// If all valid, return immediately.
	if len(projectMissing) == 0 {
		return response.SyncResponsePlain(true, metricSet.String())
	}

	// Acquire update lock.
	metricsLock.Lock()
	defer metricsLock.Unlock()

	// Check if any of the missing data has been filled in.
	metricsCacheLock.Lock()
	toFetch := []string{}
	for _, project := range projectMissing {
		cache, ok := metricsCache[project]
		if !ok || cache.expiry.Before(time.Now()) {
			// Still missing, queue a re-fetch.
			toFetch = append(toFetch, project)
			continue
		}

		// If present and valid, merge the existing data.
		metricSet.Merge(cache.metrics)
	}
	metricsCacheLock.Unlock()

	// If all valid, return immediately.
	if len(toFetch) == 0 {
		return response.SyncResponsePlain(true, metricSet.String())
	}

	// Prepare temporary metrics storage.
	newMetrics := map[string]*metrics.MetricSet{}
	newMetricsLock := sync.Mutex{}

	// Fetch what's missing.
	wgInstances := sync.WaitGroup{}
	for _, project := range toFetch {
		newMetrics[project] = metrics.NewMetricSet(nil)

		// Get the instances.
		instances, err := instanceLoadNodeProjectAll(d.State(), project, instancetype.Any)
		if err != nil {
			return response.SmartError(err)
		}

		for _, inst := range instances {
			// Ignore stopped instances.
			if !inst.IsRunning() {
				continue
			}

			wgInstances.Add(1)
			go func(inst instance.Instance) {
				defer wgInstances.Done()

				instanceMetrics, err := inst.Metrics()
				if err != nil {
					logger.Warn("Failed to get instance metrics", log.Ctx{"instance": inst.Name(), "project": inst.Project(), "err": err})
					return
				}

				// Add the metrics.
				newMetricsLock.Lock()
				defer newMetricsLock.Unlock()

				newMetrics[inst.Project()].Merge(instanceMetrics)
			}(inst)
		}
	}

	wgInstances.Wait()

	// Put the new data in the global cache and in response.
	metricsCacheLock.Lock()

	if metricsCache == nil {
		metricsCache = map[string]metricsCacheEntry{}
	}

	for project, entries := range newMetrics {
		metricsCache[project] = metricsCacheEntry{
			expiry:  time.Now().Add(8 * time.Second),
			metrics: entries,
		}

		metricSet.Merge(entries)
	}
	metricsCacheLock.Unlock()

	return response.SyncResponsePlain(true, metricSet.String())
}
<|endoftext|>


<commit_before>package config

import "sync"

type Values struct {
	lock *sync.RWMutex
	root *node
}

func NewValues() *Values {
	return &Values{
		lock: &sync.RWMutex{},
		root: newNode(),
	}
}

func newValues(root *node) *Values {
	return &Values{
		lock: &sync.RWMutex{},
		root: root,
	}
}

func (v *Values) Merge(key Key, other *Values) bool {
	v.lock.Lock()
	defer v.lock.Unlock()
	changed := false
	other.EachKeyValue(func(otherKey Key, value interface{}) {
		actualKey := key.Append(otherKey)
		changed = v.put(actualKey, value) || changed
	})
	return changed
}

func (v *Values) EachKeyValue(visitor func(key Key, value interface{})) {
	v.lock.RLock()
	defer v.lock.RUnlock()
	v.root.eachKeyValue(nil, visitor)
}

func (v *Values) Equal(other *Values) bool {
	v.lock.RLock()
	defer v.lock.RUnlock()
	other.lock.RLock()
	defer other.lock.RUnlock()
	return v.root.equal(other.root)
}

func (v *Values) Put(key Key, value interface{}) bool {
	v.lock.Lock()
	defer v.lock.Unlock()
	return v.put(key, value)
}

func (v *Values) put(key Key, value interface{}) bool {
	return v.root.put(key, value)
}

func (v *Values) IsEmpty() bool {
	v.lock.RLock()
	defer v.lock.RUnlock()
	return v.root.isEmpty()
}

func (v *Values) Get(key Key) interface{} {
	value, _ := v.GetOk(key)
	return value
}

func (v *Values) GetOk(key Key) (interface{}, bool) {
	v.lock.RLock()
	defer v.lock.RUnlock()
	value, ok := v.root.getValueOrNodeOk(key)
	if !ok {
		return nil, false
	}
	if node, ok := value.(*node); ok {
		return newValues(node.clone()), true
	}
	return value, true
}

func (v *Values) Clone() *Values {
	v.lock.RLock()
	defer v.lock.RUnlock()
	return newValues(v.root)
}

type node struct {
	value    interface{}
	children map[string]*node
}

func newNodeValue(value interface{}) *node {
	n := newNode()
	n.value = value
	n.children = nil
	return n
}

func newNode() *node {
	return &node{
		value:    nil,
		children: map[string]*node{},
	}
}

func (n *node) isEmpty() bool {
	return !n.isSet() && len(n.children) == 0
}

func (n *node) isSet() bool {
	return n.children == nil
}

func (n *node) put(key Key, value interface{}) bool {
	if key.IsEmpty() {
		return n.setValue(value)
	}
	child, changed := n.getChild(key[0])
	remainingKey := key[1:]
	return child.put(remainingKey, value) || changed
}

func (n *node) getChild(keyPart string) (*node, bool) {
	changed := false
	if n.isSet() {
		n.value = nil
		changed = true
		n.children = map[string]*node{}
	}
	child, ok := n.children[keyPart]
	if !ok {
		n.children[keyPart] = newNode()
		child = n.children[keyPart]
		changed = true
	}
	return child, changed
}

func (n *node) setValue(value interface{}) bool {
	if values, ok := value.(*Values); ok {
		return n.setValues(values)
	}
	changed := false
	if n.isSet() {
		changed = value != n.value
	} else {
		changed = true
	}
	n.value = value
	n.children = nil
	return changed
}

func (n *node) setValues(values *Values) bool {
	if values.IsEmpty() {
		if n.isEmpty() {
			return false
		}
		n.value = nil
		n.children = nil
		return true
	}
	changed := false
	values.EachKeyValue(func(key Key, value interface{}) {
		changed = n.put(key, value) || changed
	})
	return changed
}

func (n *node) getValueOrNodeOk(key Key) (interface{}, bool) {
	if key.IsEmpty() {
		if n.isSet() {
			return n.value, true
		}
		return n, true
	}
	child, ok := n.children[key[0]]
	if !ok {
		return nil, false
	}
	return child.getValueOrNodeOk(key[1:])
}

func (n *node) clone() *node {
	return &node{
		value:    n.value,
		children: n.cloneChildren(),
	}
}

func (n *node) cloneChildren() map[string]*node {
	if n.children == nil {
		return nil
	}
	result := map[string]*node{}
	for key, child := range n.children {
		result[key] = child.clone()
	}
	return result
}

func (n *node) eachKeyValue(key Key, visitor func(key Key, value interface{})) {
	if n.isSet() {
		visitor(key, n.value)
		return
	}
	for keyPart, childNode := range n.children {
		childNode.eachKeyValue(append(NewKey(key...), keyPart), visitor)
	}
}

func (n *node) equal(other *node) bool {
	if n.value != other.value {
		return false
	}
	return n.childrenEqual(other)
}

func (n *node) childrenEqual(other *node) bool {
	if len(n.children) != len(other.children) {
		return false
	}
	for keyPart, child := range n.children {
		otherChild, ok := other.children[keyPart]
		if !ok || !child.equal(otherChild) {
			return false
		}
	}
	return true
}

func (v *Values) Remove(key Key) (interface{}, bool) {
	v.lock.Lock()
	defer v.lock.Unlock()

	if key.IsEmpty() {
		if v.root.isSet() {
			result := v.root.value
			v.root = newNode()
			return result, true
		}
		return nil, false
	}

	parent, found, _, _ := v.root.findDescendent(nil, key, false, false)
	if found == nil {
		return nil, false
	}
	var result interface{} = nil
	if found.isSet() {
		result = found.value
	} else {
		result = newValues(found)
	}
	if parent != nil {
		delete(parent.children, key[key.Len()-1])
	}
	return result, true
}

func (n *node) findDescendent(parent *node, key Key, canChange, hasChanged bool) (*node, *node, Key, bool) {
	if key.IsEmpty() {
		return parent, n, key, hasChanged
	}
	child, changed := n.getChildAnother(key[0], canChange)
	if child == nil {
		return parent, nil, key, changed
	}
	return child.findDescendent(n, key[1:], canChange, changed || hasChanged)
}

func (n *node) getChildAnother(keyPart string, canChange bool) (*node, bool) {
	changed := false
	if n.isSet() {
		if !canChange {
			return nil, false
		}
		n.value, changed = nil, true
		n.children = map[string]*node{}
	}
	child, ok := n.children[keyPart]
	if !ok {
		if !canChange {
			return nil, false
		}
		n.children[keyPart] = newNode()
		child = n.children[keyPart]
		changed = true
	}
	return child, changed
}
<commit_msg>Moves *Values.Remove() with other *Values methods.<commit_after>package config

import "sync"

type Values struct {
	lock *sync.RWMutex
	root *node
}

func NewValues() *Values {
	return &Values{
		lock: &sync.RWMutex{},
		root: newNode(),
	}
}

func newValues(root *node) *Values {
	return &Values{
		lock: &sync.RWMutex{},
		root: root,
	}
}

func (v *Values) Merge(key Key, other *Values) bool {
	v.lock.Lock()
	defer v.lock.Unlock()
	changed := false
	other.EachKeyValue(func(otherKey Key, value interface{}) {
		actualKey := key.Append(otherKey)
		changed = v.put(actualKey, value) || changed
	})
	return changed
}

func (v *Values) EachKeyValue(visitor func(key Key, value interface{})) {
	v.lock.RLock()
	defer v.lock.RUnlock()
	v.root.eachKeyValue(nil, visitor)
}

func (v *Values) Equal(other *Values) bool {
	v.lock.RLock()
	defer v.lock.RUnlock()
	other.lock.RLock()
	defer other.lock.RUnlock()
	return v.root.equal(other.root)
}

func (v *Values) Put(key Key, value interface{}) bool {
	v.lock.Lock()
	defer v.lock.Unlock()
	return v.put(key, value)
}

func (v *Values) put(key Key, value interface{}) bool {
	return v.root.put(key, value)
}

func (v *Values) IsEmpty() bool {
	v.lock.RLock()
	defer v.lock.RUnlock()
	return v.root.isEmpty()
}

func (v *Values) Get(key Key) interface{} {
	value, _ := v.GetOk(key)
	return value
}

func (v *Values) GetOk(key Key) (interface{}, bool) {
	v.lock.RLock()
	defer v.lock.RUnlock()
	value, ok := v.root.getValueOrNodeOk(key)
	if !ok {
		return nil, false
	}
	if node, ok := value.(*node); ok {
		return newValues(node.clone()), true
	}
	return value, true
}

func (v *Values) Clone() *Values {
	v.lock.RLock()
	defer v.lock.RUnlock()
	return newValues(v.root)
}

func (v *Values) Remove(key Key) (interface{}, bool) {
	v.lock.Lock()
	defer v.lock.Unlock()

	if key.IsEmpty() {
		if v.root.isSet() {
			result := v.root.value
			v.root = newNode()
			return result, true
		}
		return nil, false
	}

	parent, found, _, _ := v.root.findDescendent(nil, key, false, false)
	if found == nil {
		return nil, false
	}
	var result interface{} = nil
	if found.isSet() {
		result = found.value
	} else {
		result = newValues(found)
	}
	if parent != nil {
		delete(parent.children, key[key.Len()-1])
	}
	return result, true
}

type node struct {
	value    interface{}
	children map[string]*node
}

func newNodeValue(value interface{}) *node {
	n := newNode()
	n.value = value
	n.children = nil
	return n
}

func newNode() *node {
	return &node{
		value:    nil,
		children: map[string]*node{},
	}
}

func (n *node) isEmpty() bool {
	return !n.isSet() && len(n.children) == 0
}

func (n *node) isSet() bool {
	return n.children == nil
}

func (n *node) put(key Key, value interface{}) bool {
	if key.IsEmpty() {
		return n.setValue(value)
	}
	child, changed := n.getChild(key[0])
	remainingKey := key[1:]
	return child.put(remainingKey, value) || changed
}

func (n *node) getChild(keyPart string) (*node, bool) {
	changed := false
	if n.isSet() {
		n.value = nil
		changed = true
		n.children = map[string]*node{}
	}
	child, ok := n.children[keyPart]
	if !ok {
		n.children[keyPart] = newNode()
		child = n.children[keyPart]
		changed = true
	}
	return child, changed
}

func (n *node) setValue(value interface{}) bool {
	if values, ok := value.(*Values); ok {
		return n.setValues(values)
	}
	changed := false
	if n.isSet() {
		changed = value != n.value
	} else {
		changed = true
	}
	n.value = value
	n.children = nil
	return changed
}

func (n *node) setValues(values *Values) bool {
	if values.IsEmpty() {
		if n.isEmpty() {
			return false
		}
		n.value = nil
		n.children = nil
		return true
	}
	changed := false
	values.EachKeyValue(func(key Key, value interface{}) {
		changed = n.put(key, value) || changed
	})
	return changed
}

func (n *node) getValueOrNodeOk(key Key) (interface{}, bool) {
	if key.IsEmpty() {
		if n.isSet() {
			return n.value, true
		}
		return n, true
	}
	child, ok := n.children[key[0]]
	if !ok {
		return nil, false
	}
	return child.getValueOrNodeOk(key[1:])
}

func (n *node) clone() *node {
	return &node{
		value:    n.value,
		children: n.cloneChildren(),
	}
}

func (n *node) cloneChildren() map[string]*node {
	if n.children == nil {
		return nil
	}
	result := map[string]*node{}
	for key, child := range n.children {
		result[key] = child.clone()
	}
	return result
}

func (n *node) eachKeyValue(key Key, visitor func(key Key, value interface{})) {
	if n.isSet() {
		visitor(key, n.value)
		return
	}
	for keyPart, childNode := range n.children {
		childNode.eachKeyValue(append(NewKey(key...), keyPart), visitor)
	}
}

func (n *node) equal(other *node) bool {
	if n.value != other.value {
		return false
	}
	return n.childrenEqual(other)
}

func (n *node) childrenEqual(other *node) bool {
	if len(n.children) != len(other.children) {
		return false
	}
	for keyPart, child := range n.children {
		otherChild, ok := other.children[keyPart]
		if !ok || !child.equal(otherChild) {
			return false
		}
	}
	return true
}

func (n *node) findDescendent(parent *node, key Key, canChange, hasChanged bool) (*node, *node, Key, bool) {
	if key.IsEmpty() {
		return parent, n, key, hasChanged
	}
	child, changed := n.getChildAnother(key[0], canChange)
	if child == nil {
		return parent, nil, key, changed
	}
	return child.findDescendent(n, key[1:], canChange, changed || hasChanged)
}

func (n *node) getChildAnother(keyPart string, canChange bool) (*node, bool) {
	changed := false
	if n.isSet() {
		if !canChange {
			return nil, false
		}
		n.value, changed = nil, true
		n.children = map[string]*node{}
	}
	child, ok := n.children[keyPart]
	if !ok {
		if !canChange {
			return nil, false
		}
		n.children[keyPart] = newNode()
		child = n.children[keyPart]
		changed = true
	}
	return child, changed
}
<|endoftext|>


<commit_before>package config

import "sync"

type Values struct {
	lock *sync.RWMutex
	root *node
}

func NewValues() *Values {
	return &Values{
		lock: &sync.RWMutex{},
		root: newNode(),
	}
}

func newValues(root *node) *Values {
	return &Values{
		lock: &sync.RWMutex{},
		root: root,
	}
}

func (v *Values) Merge(key Key, other *Values) bool {
	v.lock.Lock()
	defer v.lock.Unlock()
	changed := false
	other.EachKeyValue(func(otherKey Key, value interface{}) {
		actualKey := key.Append(otherKey)
		changed = v.put(actualKey, value) || changed
	})
	return changed
}

func (v *Values) EachKeyValue(visitor func(key Key, value interface{})) {
	v.lock.RLock()
	defer v.lock.RUnlock()
	v.root.eachKeyValue(nil, visitor)
}

func (v *Values) Equal(other *Values) bool {
	v.lock.RLock()
	defer v.lock.RUnlock()
	other.lock.RLock()
	defer other.lock.RUnlock()
	return v.root.equal(other.root)
}

func (v *Values) Put(key Key, value interface{}) bool {
	v.lock.Lock()
	defer v.lock.Unlock()
	return v.put(key, value)
}

func (v *Values) put(key Key, value interface{}) bool {
	return v.root.put(key, value)
}

func (v *Values) IsEmpty() bool {
	v.lock.RLock()
	defer v.lock.RUnlock()
	return v.root.isEmpty()
}

func (v *Values) Get(key Key) interface{} {
	value, _ := v.GetOk(key)
	return value
}

func (v *Values) GetOk(key Key) (interface{}, bool) {
	v.lock.RLock()
	defer v.lock.RUnlock()
	value, ok := v.root.getValueOrNodeOk(key)
	if !ok {
		return nil, false
	}
	if node, ok := value.(*node); ok {
		return newValues(node.clone()), true
	}
	return value, true
}

func (v *Values) Clone() *Values {
	v.lock.RLock()
	defer v.lock.RUnlock()
	return newValues(v.root)
}

func (v *Values) Remove(key Key) (interface{}, bool) {
	v.lock.Lock()
	defer v.lock.Unlock()

	if key.IsEmpty() {
		if v.root.isSet() {
			result := v.root.value
			v.root = newNode()
			return result, true
		}
		return nil, false
	}

	parent, found, _, _ := v.root.findDescendent(nil, key, false, false)
	if found == nil {
		return nil, false
	}
	var result interface{} = nil
	if found.isSet() {
		result = found.value
	} else {
		result = newValues(found)
	}
	if parent != nil {
		delete(parent.children, key[key.Len()-1])
	}
	return result, true
}

type node struct {
	value    interface{}
	children map[string]*node
}

func newNodeValue(value interface{}) *node {
	n := newNode()
	n.value = value
	n.children = nil
	return n
}

func newNode() *node {
	return &node{
		value:    nil,
		children: map[string]*node{},
	}
}

func (n *node) isEmpty() bool {
	return !n.isSet() && len(n.children) == 0
}

func (n *node) isSet() bool {
	return n.children == nil
}

func (n *node) put(key Key, value interface{}) bool {
	if key.IsEmpty() {
		return n.setValue(value)
	}
	child, changed := n.getChild(key[0])
	remainingKey := key[1:]
	return child.put(remainingKey, value) || changed
}

func (n *node) getChild(keyPart string) (*node, bool) {
	changed := false
	if n.isSet() {
		n.value = nil
		changed = true
		n.children = map[string]*node{}
	}
	child, ok := n.children[keyPart]
	if !ok {
		n.children[keyPart] = newNode()
		child = n.children[keyPart]
		changed = true
	}
	return child, changed
}

func (n *node) setValue(value interface{}) bool {
	if values, ok := value.(*Values); ok {
		return n.setValues(values)
	}
	changed := false
	if n.isSet() {
		changed = value != n.value
	} else {
		changed = true
	}
	n.value = value
	n.children = nil
	return changed
}

func (n *node) setValues(values *Values) bool {
	if values.IsEmpty() {
		if n.isEmpty() {
			return false
		}
		n.value = nil
		n.children = nil
		return true
	}
	changed := false
	values.EachKeyValue(func(key Key, value interface{}) {
		changed = n.put(key, value) || changed
	})
	return changed
}

func (n *node) getValueOrNodeOk(key Key) (interface{}, bool) {
	if key.IsEmpty() {
		if n.isSet() {
			return n.value, true
		}
		return n, true
	}
	child, ok := n.children[key[0]]
	if !ok {
		return nil, false
	}
	return child.getValueOrNodeOk(key[1:])
}

func (n *node) clone() *node {
	return &node{
		value:    n.value,
		children: n.cloneChildren(),
	}
}

func (n *node) cloneChildren() map[string]*node {
	if n.children == nil {
		return nil
	}
	result := map[string]*node{}
	for key, child := range n.children {
		result[key] = child.clone()
	}
	return result
}

func (n *node) eachKeyValue(key Key, visitor func(key Key, value interface{})) {
	if n.isSet() {
		visitor(key, n.value)
		return
	}
	for keyPart, childNode := range n.children {
		childNode.eachKeyValue(append(NewKey(key...), keyPart), visitor)
	}
}

func (n *node) equal(other *node) bool {
	if n.value != other.value {
		return false
	}
	return n.childrenEqual(other)
}

func (n *node) childrenEqual(other *node) bool {
	if len(n.children) != len(other.children) {
		return false
	}
	for keyPart, child := range n.children {
		otherChild, ok := other.children[keyPart]
		if !ok || !child.equal(otherChild) {
			return false
		}
	}
	return true
}

func (n *node) findDescendent(parent *node, key Key, canChange, hasChanged bool) (*node, *node, Key, bool) {
	if key.IsEmpty() {
		return parent, n, key, hasChanged
	}
	child, changed := n.getChildAnother(key[0], canChange)
	if child == nil {
		return parent, nil, key, changed
	}
	return child.findDescendent(n, key[1:], canChange, changed || hasChanged)
}

func (n *node) getChildAnother(keyPart string, canChange bool) (*node, bool) {
	changed := false
	if n.isSet() {
		if !canChange {
			return nil, false
		}
		n.value, changed = nil, true
		n.children = map[string]*node{}
	}
	child, ok := n.children[keyPart]
	if !ok {
		if !canChange {
			return nil, false
		}
		n.children[keyPart] = newNode()
		child = n.children[keyPart]
		changed = true
	}
	return child, changed
}
<commit_msg>Updates *Values internal implementation.<commit_after>package config

import "sync"

type Values struct {
	lock *sync.RWMutex
	root *node
}

func NewValues() *Values {
	return &Values{
		lock: &sync.RWMutex{},
		root: newNode(),
	}
}

func newValues(root *node) *Values {
	return &Values{
		lock: &sync.RWMutex{},
		root: root,
	}
}

func (v *Values) Merge(key Key, other *Values) bool {
	v.lock.Lock()
	defer v.lock.Unlock()

	changed := false
	other.EachKeyValue(func(otherKey Key, value interface{}) {
		actualKey := key.Append(otherKey)
		changed = v.put(actualKey, value) || changed
	})
	return changed
}

func (v *Values) EachKeyValue(visitor func(key Key, value interface{})) {
	v.lock.RLock()
	defer v.lock.RUnlock()

	v.root.eachKeyValue(nil, visitor)
}

func (v *Values) Equal(other *Values) bool {
	v.lock.RLock()
	defer v.lock.RUnlock()
	other.lock.RLock()
	defer other.lock.RUnlock()

	return v.root.equal(other.root)
}

func (v *Values) Put(key Key, value interface{}) bool {
	v.lock.Lock()
	defer v.lock.Unlock()

	return v.put(key, value)
}

func (v *Values) put(key Key, value interface{}) bool {
	return v.root.put(key, value)
}

func (v *Values) IsEmpty() bool {
	v.lock.RLock()
	defer v.lock.RUnlock()

	return v.root.isEmpty()
}

func (v *Values) Get(key Key) interface{} {
	value, _ := v.GetOk(key)
	return value
}

func (v *Values) GetOk(key Key) (interface{}, bool) {
	v.lock.RLock()
	defer v.lock.RUnlock()

	_, found, _ := v.root.findDescendent(nil, key, false, false)
	if found == nil {
		return nil, false
	}
	if found.isSet() {
		return found.value, true
	}
	return newValues(found.clone()), true
}

func (v *Values) Clone() *Values {
	v.lock.RLock()
	defer v.lock.RUnlock()

	return newValues(v.root)
}

func (v *Values) Remove(key Key) (interface{}, bool) {
	v.lock.Lock()
	defer v.lock.Unlock()

	if key.IsEmpty() {
		if v.root.isSet() {
			result := v.root.value
			v.root = newNode()
			return result, true
		}
		return nil, false
	}

	parent, found, _ := v.root.findDescendent(nil, key, false, false)
	if found == nil {
		return nil, false
	}
	var result interface{} = nil
	if found.isSet() {
		result = found.value
	} else {
		result = newValues(found)
	}
	if parent != nil {
		delete(parent.children, key[key.Len()-1])
	}
	return result, true
}

type node struct {
	value    interface{}
	children map[string]*node
}

func newNodeValue(value interface{}) *node {
	n := newNode()
	n.value = value
	n.children = nil
	return n
}

func newNode() *node {
	return &node{
		value:    nil,
		children: map[string]*node{},
	}
}

func (n *node) isEmpty() bool {
	return !n.isSet() && len(n.children) == 0
}

func (n *node) isSet() bool {
	return n.children == nil
}

func (n *node) put(key Key, value interface{}) bool {
	if key.IsEmpty() {
		return n.setValue(value)
	}
	child, changed := n.findChild(key[0], true)
	remainingKey := key[1:]
	return child.put(remainingKey, value) || changed
}

func (n *node) setValue(value interface{}) bool {
	if values, ok := value.(*Values); ok {
		return n.setValues(values)
	}
	changed := false
	if n.isSet() {
		changed = value != n.value
	} else {
		changed = true
	}
	n.value = value
	n.children = nil
	return changed
}

func (n *node) setValues(values *Values) bool {
	if values.IsEmpty() {
		if n.isEmpty() {
			return false
		}
		n.value = nil
		n.children = nil
		return true
	}
	changed := false
	values.EachKeyValue(func(key Key, value interface{}) {
		changed = n.put(key, value) || changed
	})
	return changed
}

func (n *node) clone() *node {
	return &node{
		value:    n.value,
		children: n.cloneChildren(),
	}
}

func (n *node) cloneChildren() map[string]*node {
	if n.children == nil {
		return nil
	}
	result := map[string]*node{}
	for key, child := range n.children {
		result[key] = child.clone()
	}
	return result
}

func (n *node) eachKeyValue(key Key, visitor func(key Key, value interface{})) {
	if n.isSet() {
		visitor(key, n.value)
		return
	}
	for keyPart, childNode := range n.children {
		childNode.eachKeyValue(append(NewKey(key...), keyPart), visitor)
	}
}

func (n *node) equal(other *node) bool {
	if n.value != other.value {
		return false
	}
	return n.childrenEqual(other)
}

func (n *node) childrenEqual(other *node) bool {
	if len(n.children) != len(other.children) {
		return false
	}
	for keyPart, child := range n.children {
		otherChild, ok := other.children[keyPart]
		if !ok || !child.equal(otherChild) {
			return false
		}
	}
	return true
}

func (n *node) findDescendent(parent *node, key Key, canChange, hasChanged bool) (*node, *node, bool) {
	if key.IsEmpty() {
		return parent, n, hasChanged
	}
	child, changed := n.findChild(key[0], canChange)
	if child == nil {
		return parent, nil, changed
	}
	return child.findDescendent(n, key[1:], canChange, changed || hasChanged)
}

func (n *node) findChild(keyPart string, canChange bool) (*node, bool) {
	changed := false
	if n.isSet() {
		n.value, changed = nil, true
		n.children = map[string]*node{}
	}
	child, ok := n.children[keyPart]
	if !ok {
		if !canChange {
			return nil, false
		}
		n.children[keyPart] = newNode()
		child = n.children[keyPart]
		changed = true
	}
	return child, changed
}
<|endoftext|>


<commit_before>package vanity

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"
)

type (
	handler struct {
		log              Logger
		vcs              string
		vcsURL           string
		moduleServerURL  string
		static           *staticDir
		indexPageHandler http.Handler
		robotsTxt        string
	}
	staticDir struct {
		uRLPath string
		path    string
		fs      http.Handler
	}
	// Option represents a functional option for configuring the vanity middleware.
	Option func(http.Handler)
	// Logger describes functions available for logging purposes.
	Logger interface {
		Printf(format string, v ...interface{})
	}
)

const (
	// mPkgGoDev is the default module server by Google.
	mPkgGoDev = "https://pkg.go.dev/"
	// mSearchGocenterIo is a module server by JFrog.
	mSearchGocenterIo = "https://search.gocenter.io/"
	// mGitHub is not an actual module server, but a source code repository.
	mGitHub = "https://github.com/"
)

// DefaultIndexPageHandler serves given indexFilePath over HTTP via http.ServeFile(w, r, name).
func DefaultIndexPageHandler(indexFilePath string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, indexFilePath)
	})
}

func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Scheme == "http" {
		r.URL.Scheme = "https"
		http.Redirect(w, r, r.URL.String(), http.StatusMovedPermanently)
		return
	}
	if r.Method != http.MethodGet {
		status := http.StatusMethodNotAllowed
		http.Error(w, http.StatusText(status), status)
		return
	}

	if h.static != nil && strings.HasPrefix(r.URL.Path, h.static.uRLPath) {
		h.static.fs.ServeHTTP(w, r)
		return
	}

	if r.URL.Path == "/" || r.URL.Path == "" {
		if h.indexPageHandler != nil {
			h.indexPageHandler.ServeHTTP(w, r)
			return
		}
		DefaultIndexPageHandler(h.static.path+"/index.html").ServeHTTP(w, r)
	}

	if r.URL.Path == "/robots.txt" {
		fmt.Fprintf(w, "%s", h.robotsTxt)
		return
	}

	// Respond to Go tool with vcs info meta tag
	if r.FormValue("go-get") == "1" {
		path := r.URL.Path
		const cmd = "/cmd/"
		if strings.HasPrefix(r.URL.Path, cmd) {
			path = path[len(cmd):]
		}
		// redirect github.com/kare/pkg/sub -> github.com/kare/pkg
		vcsroot := h.vcsURL
		shortPath := pathComponents(path)
		if len(shortPath) > 0 {
			vcsroot = h.vcsURL + shortPath[0]
		}

		importRoot := strings.TrimSuffix(r.Host+r.URL.Path, "/")
		metaTag := fmt.Sprintf(`<meta name="go-import" content="%v %v %v">`, importRoot, h.vcs, vcsroot)
		if _, err := w.Write([]byte(metaTag)); err != nil {
			h.log.Printf("vanity: i/o error writing go tool http response: %v", err)
		}
		return
	}

	// Redirect browsers to Go module site.
	url := h.browserURL(r.Host, r.URL.Path)
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func pathComponents(path string) []string {
	f := func(c rune) bool {
		return c == '/'
	}
	return strings.FieldsFunc(path, f)
}

func (h *handler) browserURL(host, path string) string {
	// host = kkn.fi
	// path = /foo/bar

	if strings.HasPrefix(h.moduleServerURL, mGitHub) {
		pkg := path
		components := pathComponents(pkg)
		return stripSuffixSlash(h.moduleServerURL) + "/" + components[len(components)-1]
	}
	switch h.moduleServerURL {
	case mSearchGocenterIo:
		pkg := strings.ReplaceAll(path, "/", "~2F")
		return fmt.Sprintf("%v%v%v/info", mSearchGocenterIo, host, pkg)
	case mPkgGoDev:
		fallthrough
	default:
		pkg := path
		return fmt.Sprintf("%v%v%v", mPkgGoDev, host, pkg)
	}
}

func stripSuffixSlash(s string) string {
	if strings.HasSuffix(s, "/") {
		return s[0 : len(s)-1]
	}
	return s
}

// Handler is an HTTP middleware that redirects browsers to Go module server
// (pkg.go.dev or similar) or Go tool to VCS repository. VCS repository is git
// by default. VCS can be set with VCS(). Configurable Logger defaults to
// os.Stdout. Logger can be configured with SetLogger(). Module server URL is
// https://pkg.go.dev/ and it can be configured via ModuleServerURL() func.
// VCSURL() func must be used to set VCS repository URL (such as
// https://github.com/kare/).
func Handler(opts ...Option) http.Handler {
	v := &handler{
		log:             log.New(os.Stdout, "", log.LstdFlags),
		vcs:             "git",
		moduleServerURL: mPkgGoDev,
	}
	for _, option := range opts {
		option(v)
	}
	return v
}

// VCS sets the version control type.
func VCS(vcs string) Option {
	return func(h http.Handler) {
		v := h.(*handler)
		v.vcs = vcs
	}
}

func addSuffixSlash(s string) string {
	if strings.HasSuffix(s, "/") {
		return s
	}
	return s + "/"
}

// VCSURL sets the VCS repository url address.
func VCSURL(vcsURL string) Option {
	return func(h http.Handler) {
		v := h.(*handler)
		v.vcsURL = addSuffixSlash(vcsURL)
	}
}

// Log sets the logger used by vanity package's error logger.
func Log(l Logger) Option {
	return func(h http.Handler) {
		v := h.(*handler)
		v.log = l
	}
}

// ModuleServerURL sets Go module server address for browser redirect.
func ModuleServerURL(moduleServerURL string) Option {
	return func(h http.Handler) {
		v := h.(*handler)
		v.moduleServerURL = addSuffixSlash(moduleServerURL)
	}
}

// StaticDir serves a file system directory over HTTP. Given path is the local
// file system path to directory. Given urlPath is the path portition of the URL for the server.
func StaticDir(path, URLPath string) Option {
	return func(h http.Handler) {
		// TODO: path must be a readable directory or fail
		v := h.(*handler)
		dir := http.Dir(path)
		server := http.FileServer(dir)
		v.static = &staticDir{
			path:    path,
			uRLPath: URLPath,
			fs:      http.StripPrefix(URLPath, server),
		}
	}
}

// IndexPageHandler sets a handler for index.html page.
func IndexPageHandler(index http.Handler) Option {
	return func(h http.Handler) {
		v := h.(*handler)
		v.indexPageHandler = index
	}
}

// DefaultRobotsTxt is the default value for /robots.txt file.
var DefaultRobotsTxt = `user-agent: *
Allow: /$
Allow: /.static/*$
Disallow: /`

// RobotsTxt takes in option robotsTxt value. If value is empty, the value of DefaultRobotsTxt is used
func RobotsTxt(robotsTxt string) Option {
	return func(h http.Handler) {
		v := h.(*handler)
		if robotsTxt != "" {
			v.robotsTxt = robotsTxt
		} else {
			v.robotsTxt = DefaultRobotsTxt
		}
	}
}
<commit_msg>Remove deprecated search.gocenter.io<commit_after>package vanity

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"
)

type (
	handler struct {
		log              Logger
		vcs              string
		vcsURL           string
		moduleServerURL  string
		static           *staticDir
		indexPageHandler http.Handler
		robotsTxt        string
	}
	staticDir struct {
		uRLPath string
		path    string
		fs      http.Handler
	}
	// Option represents a functional option for configuring the vanity middleware.
	Option func(http.Handler)
	// Logger describes functions available for logging purposes.
	Logger interface {
		Printf(format string, v ...interface{})
	}
)

const (
	// mPkgGoDev is the default module server by Google.
	mPkgGoDev = "https://pkg.go.dev/"
	// mGitHub is not an actual module server, but a source code repository.
	mGitHub = "https://github.com/"
)

// DefaultIndexPageHandler serves given indexFilePath over HTTP via http.ServeFile(w, r, name).
func DefaultIndexPageHandler(indexFilePath string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.ServeFile(w, r, indexFilePath)
	})
}

func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.URL.Scheme == "http" {
		r.URL.Scheme = "https"
		http.Redirect(w, r, r.URL.String(), http.StatusMovedPermanently)
		return
	}
	if r.Method != http.MethodGet {
		status := http.StatusMethodNotAllowed
		http.Error(w, http.StatusText(status), status)
		return
	}

	if h.static != nil && strings.HasPrefix(r.URL.Path, h.static.uRLPath) {
		h.static.fs.ServeHTTP(w, r)
		return
	}

	if r.URL.Path == "/" || r.URL.Path == "" {
		if h.indexPageHandler != nil {
			h.indexPageHandler.ServeHTTP(w, r)
			return
		}
		DefaultIndexPageHandler(h.static.path+"/index.html").ServeHTTP(w, r)
	}

	if r.URL.Path == "/robots.txt" {
		fmt.Fprintf(w, "%s", h.robotsTxt)
		return
	}

	// Respond to Go tool with vcs info meta tag
	if r.FormValue("go-get") == "1" {
		path := r.URL.Path
		const cmd = "/cmd/"
		if strings.HasPrefix(r.URL.Path, cmd) {
			path = path[len(cmd):]
		}
		// redirect github.com/kare/pkg/sub -> github.com/kare/pkg
		vcsroot := h.vcsURL
		shortPath := pathComponents(path)
		if len(shortPath) > 0 {
			vcsroot = h.vcsURL + shortPath[0]
		}

		importRoot := strings.TrimSuffix(r.Host+r.URL.Path, "/")
		metaTag := fmt.Sprintf(`<meta name="go-import" content="%v %v %v">`, importRoot, h.vcs, vcsroot)
		if _, err := w.Write([]byte(metaTag)); err != nil {
			h.log.Printf("vanity: i/o error writing go tool http response: %v", err)
		}
		return
	}

	// Redirect browsers to Go module site.
	url := h.browserURL(r.Host, r.URL.Path)
	http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}

func pathComponents(path string) []string {
	f := func(c rune) bool {
		return c == '/'
	}
	return strings.FieldsFunc(path, f)
}

func (h *handler) browserURL(host, path string) string {
	// host = kkn.fi
	// path = /foo/bar

	if strings.HasPrefix(h.moduleServerURL, mGitHub) {
		pkg := path
		components := pathComponents(pkg)
		return stripSuffixSlash(h.moduleServerURL) + "/" + components[len(components)-1]
	}
	switch h.moduleServerURL {
	case mPkgGoDev:
		fallthrough
	default:
		pkg := path
		return fmt.Sprintf("%v%v%v", mPkgGoDev, host, pkg)
	}
}

func stripSuffixSlash(s string) string {
	if strings.HasSuffix(s, "/") {
		return s[0 : len(s)-1]
	}
	return s
}

// Handler is an HTTP middleware that redirects browsers to Go module server
// (pkg.go.dev or similar) or Go tool to VCS repository. VCS repository is git
// by default. VCS can be set with VCS(). Configurable Logger defaults to
// os.Stdout. Logger can be configured with SetLogger(). Module server URL is
// https://pkg.go.dev/ and it can be configured via ModuleServerURL() func.
// VCSURL() func must be used to set VCS repository URL (such as
// https://github.com/kare/).
func Handler(opts ...Option) http.Handler {
	v := &handler{
		log:             log.New(os.Stdout, "", log.LstdFlags),
		vcs:             "git",
		moduleServerURL: mPkgGoDev,
	}
	for _, option := range opts {
		option(v)
	}
	return v
}

// VCS sets the version control type.
func VCS(vcs string) Option {
	return func(h http.Handler) {
		v := h.(*handler)
		v.vcs = vcs
	}
}

func addSuffixSlash(s string) string {
	if strings.HasSuffix(s, "/") {
		return s
	}
	return s + "/"
}

// VCSURL sets the VCS repository url address.
func VCSURL(vcsURL string) Option {
	return func(h http.Handler) {
		v := h.(*handler)
		v.vcsURL = addSuffixSlash(vcsURL)
	}
}

// Log sets the logger used by vanity package's error logger.
func Log(l Logger) Option {
	return func(h http.Handler) {
		v := h.(*handler)
		v.log = l
	}
}

// ModuleServerURL sets Go module server address for browser redirect.
func ModuleServerURL(moduleServerURL string) Option {
	return func(h http.Handler) {
		v := h.(*handler)
		v.moduleServerURL = addSuffixSlash(moduleServerURL)
	}
}

// StaticDir serves a file system directory over HTTP. Given path is the local
// file system path to directory. Given urlPath is the path portition of the URL for the server.
func StaticDir(path, URLPath string) Option {
	return func(h http.Handler) {
		// TODO: path must be a readable directory or fail
		v := h.(*handler)
		dir := http.Dir(path)
		server := http.FileServer(dir)
		v.static = &staticDir{
			path:    path,
			uRLPath: URLPath,
			fs:      http.StripPrefix(URLPath, server),
		}
	}
}

// IndexPageHandler sets a handler for index.html page.
func IndexPageHandler(index http.Handler) Option {
	return func(h http.Handler) {
		v := h.(*handler)
		v.indexPageHandler = index
	}
}

// DefaultRobotsTxt is the default value for /robots.txt file.
var DefaultRobotsTxt = `user-agent: *
Allow: /$
Allow: /.static/*$
Disallow: /`

// RobotsTxt takes in option robotsTxt value. If value is empty, the value of DefaultRobotsTxt is used
func RobotsTxt(robotsTxt string) Option {
	return func(h http.Handler) {
		v := h.(*handler)
		if robotsTxt != "" {
			v.robotsTxt = robotsTxt
		} else {
			v.robotsTxt = DefaultRobotsTxt
		}
	}
}
<|endoftext|>


<commit_before>package mailman

import (
	"bytes"
	"errors"
	"fmt"
	"path"
	"text/template"

	"github.com/eyecuelab/kit/assets"
)

type Address struct {
	Name  string
	Email string
}

type Config struct {
	From   *Address
	Domain string
}

type Mailer interface {
	Config() *Config
	Send(to *Address, content *Content, vars *MergeVars) error
}

type Template struct {
	Subject   *template.Template
	HtmlBody  *template.Template
	PlainBody *template.Template
}

type Content struct {
	Subject   *bytes.Buffer
	HtmlBody  *bytes.Buffer
	PlainBody *bytes.Buffer
}

type MergeVars struct {
	SubjectVars map[string]string
	BodyVars    map[string]string
}

type mergeVars interface{}

var (
	TemplateDir string
	Templates   map[string]*Template
	mailer      Mailer
)

func newTemplate(s string) (*template.Template, error) {
	return template.New("").Parse(s)
}

func setDefaultTemplateDir() {
	dir := path.Join("data", "bin", "mail")

	if _, err := assets.Dir(dir); err == nil {
		TemplateDir = dir
	}
}

func Configure(m Mailer) {
	setDefaultTemplateDir()

	Templates = make(map[string]*Template)
	mailer = m
}

func AddTemplate(key string, subject string, htmlBody string, plainBody string) error {
	var s, h, p *template.Template
	var err error
	if s, err = newTemplate(subject); err != nil {
		return err
	}
	if h, err = newTemplate(htmlBody); err != nil {
		return err
	}
	if p, err = newTemplate(plainBody); err != nil {
		return err
	}

	Templates[key] = &Template{s, h, p}
	return nil
}

func readEmailTemplates(key string) ([][]byte, error) {
	templates := make([][]byte, 3)
	var err error

	for i, part := range []string{"subject", "html", "plain"} {
		fileName := fmt.Sprintf("%s_%s.tmpl", key, part)
		p := path.Join(TemplateDir, fileName)

		if templates[i], err = assets.Get(p); err != nil {
			return templates, err
		}
	}

	return templates, nil
}

func RegisterTemplate(key string) error {
	if len(TemplateDir) == 0 {
		return errors.New("TemplateDir not set")
	}

	parts, err := readEmailTemplates(key)
	if err != nil {
		return err
	}

	return AddTemplate(key, string(parts[0]), string(parts[1]), string(parts[2]))
}

func Send(to *Address, templateKey string, vars *MergeVars) error {
	if mailer == nil {
		return errors.New("Mail has not been configured")
	}

	template := Templates[templateKey]
	if template == nil {
		return errors.New(fmt.Sprintf("Template not found for key [%s]", templateKey))
	}

	content := &Content{
		new(bytes.Buffer),
		new(bytes.Buffer),
		new(bytes.Buffer),
	}

	if err := template.Subject.Execute(content.Subject, vars.SubjectVars); err != nil {
		return err
	}
	if err := template.HtmlBody.Execute(content.HtmlBody, vars.BodyVars); err != nil {
		return err
	}
	if err := template.PlainBody.Execute(content.PlainBody, vars.BodyVars); err != nil {
		return err
	}

	return mailer.Send(to, content, vars)
}
<commit_msg>allow registering multiple email templates<commit_after>package mailman

import (
	"bytes"
	"errors"
	"fmt"
	"path"
	"text/template"

	"github.com/eyecuelab/kit/assets"
)

type Address struct {
	Name  string
	Email string
}

type Config struct {
	From   *Address
	Domain string
}

type Mailer interface {
	Config() *Config
	Send(to *Address, content *Content, vars *MergeVars) error
}

type Template struct {
	Subject   *template.Template
	HtmlBody  *template.Template
	PlainBody *template.Template
}

type Content struct {
	Subject   *bytes.Buffer
	HtmlBody  *bytes.Buffer
	PlainBody *bytes.Buffer
}

type MergeVars struct {
	SubjectVars map[string]string
	BodyVars    map[string]string
}

type mergeVars interface{}

var (
	TemplateDir string
	Templates   map[string]*Template
	mailer      Mailer
)

func newTemplate(s string) (*template.Template, error) {
	return template.New("").Parse(s)
}

func setDefaultTemplateDir() {
	dir := path.Join("data", "bin", "mail")

	if _, err := assets.Dir(dir); err == nil {
		TemplateDir = dir
	}
}

func Configure(m Mailer) {
	setDefaultTemplateDir()

	Templates = make(map[string]*Template)
	mailer = m
}

func AddTemplate(key string, subject string, htmlBody string, plainBody string) error {
	var s, h, p *template.Template
	var err error
	if s, err = newTemplate(subject); err != nil {
		return err
	}
	if h, err = newTemplate(htmlBody); err != nil {
		return err
	}
	if p, err = newTemplate(plainBody); err != nil {
		return err
	}

	Templates[key] = &Template{s, h, p}
	return nil
}

func readEmailTemplates(key string) ([][]byte, error) {
	templates := make([][]byte, 3)
	var err error

	for i, part := range []string{"subject", "html", "plain"} {
		fileName := fmt.Sprintf("%s_%s.tmpl", key, part)
		p := path.Join(TemplateDir, fileName)

		if templates[i], err = assets.Get(p); err != nil {
			return templates, err
		}
	}

	return templates, nil
}

func RegisterTemplate(keys ...string) error {
	if len(TemplateDir) == 0 {
		return errors.New("TemplateDir not set")
	}
	for _, key := range keys {
		parts, err := readEmailTemplates(key)
		if err != nil {
			return err
		}
		if err := AddTemplate(key, string(parts[0]), string(parts[1]), string(parts[2])); err != nil {
			return err
		}
	}
	return nil
}

func Send(to *Address, templateKey string, vars *MergeVars) error {
	if mailer == nil {
		return errors.New("Mail has not been configured")
	}

	template := Templates[templateKey]
	if template == nil {
		return errors.New(fmt.Sprintf("Template not found for key [%s]", templateKey))
	}

	content := &Content{
		new(bytes.Buffer),
		new(bytes.Buffer),
		new(bytes.Buffer),
	}

	if err := template.Subject.Execute(content.Subject, vars.SubjectVars); err != nil {
		return err
	}
	if err := template.HtmlBody.Execute(content.HtmlBody, vars.BodyVars); err != nil {
		return err
	}
	if err := template.PlainBody.Execute(content.PlainBody, vars.BodyVars); err != nil {
		return err
	}

	return mailer.Send(to, content, vars)
}
<|endoftext|>


<commit_before>package viewservice

import "net"
import "net/rpc"
import "log"
import "time"
import "sync"
import "fmt"
import "os"
import "sync/atomic"
import "errors"

type ViewServer struct {
	mu       sync.Mutex
	l        net.Listener
	dead     int32 // for testing
	rpccount int32 // for testing
	me       string

	// Your declarations here.
	recentTimes  map[string]time.Time //recentTime
	currView     View                 //
	idleServers  []string             //volunteers
	serverMap    map[string]bool      //all servers, including primary and backup
	primaryAcked bool                 //
}

func (vs *ViewServer) pickFirstIdleServer() string {
	rs := ""
	if len(vs.idleServers) > 0 {
		rs = vs.idleServers[0]
		vs.idleServers = vs.idleServers[1:]
	}

	return rs
}

func (vs *ViewServer) addIdleServer(server string) {
	vs.idleServers = append(vs.idleServers, server)
}

//
// server Ping RPC handler.
//
func (vs *ViewServer) Ping(args *PingArgs, reply *PingReply) error {

	// Your code here.
	vs.mu.Lock()
	defer vs.mu.Unlock()

	log.Printf("Ping Args.Me %s, Args.Viewnum %d, Viewnum %d, Primary %s, Backup %s\n", args.Me, args.Viewnum, vs.currView.Viewnum, vs.currView.Primary, vs.currView.Backup)

	if args.Viewnum == 0 { //new server
		if vs.currView.Viewnum == 0 { //first server ping
			vs.recentTimes[args.Me] = time.Now()
			vs.serverMap[args.Me] = true

			vs.currView.Viewnum++
			vs.currView.Primary = args.Me
			vs.primaryAcked = false

			reply.View = vs.currView

			return nil
		} else { //vs.currView.Viewnum !=0
			vs.recentTimes[args.Me] = time.Now()

			_, has := vs.serverMap[args.Me]
			if has { //a server that has restarted
				if args.Me == vs.currView.Primary {
					if vs.primaryAcked {
						vs.currView.Viewnum++
						vs.currView.Primary = vs.currView.Backup
						vs.currView.Backup = vs.pickFirstIdleServer()
						vs.primaryAcked = false
						vs.addIdleServer(args.Me)

						reply.View = vs.currView

						return nil
					} else {
						vs.addIdleServer(args.Me)

						reply.View = vs.currView

						return nil
					}
				} else if args.Me == vs.currView.Backup {
					if vs.primaryAcked {
						vs.currView.Viewnum++
						vs.currView.Backup = vs.pickFirstIdleServer()
						vs.primaryAcked = false
						vs.addIdleServer(args.Me)

						reply.View = vs.currView

						return nil
					} else {
						vs.addIdleServer(args.Me)

						reply.View = vs.currView

						return nil
					}
				} else {
					//nothing
				}
			} else { //new server
				vs.serverMap[args.Me] = true
				vs.idleServers = append(vs.idleServers, args.Me)

				reply.View = vs.currView
				return nil
			}
		}
	} else { //args.Viewnum !=0
		if vs.currView.Viewnum == 0 {
			return errors.New("invalid Viewnum")
		} else {
			if args.Me == vs.currView.Primary && args.Viewnum == vs.currView.Viewnum {
				vs.primaryAcked = true

				if vs.currView.Backup == "" && len(vs.idleServers) != 0 {
					vs.currView.Backup = vs.pickFirstIdleServer()
					vs.currView.Viewnum++
					vs.primaryAcked = false

					log.Printf("new Backup %s\n", vs.currView.Backup)
				}
			}

			reply.View = vs.currView

			return nil
		}
	}

	/*
		if vs.currView.Viewnum == 0 && args.Viewnum != 0 {
			return errors.New("invalid Viewnum")
		}

		_, has := vs.serverMap[args.Me]
		if !has {
			vs.serverMap[args.Me] = true
		}

		vs.recentTimes[args.Me] = time.Now()

		if vs.currView.Viewnum == 0 { //首次有server ping
			if args.Viewnum == 0 {
				vs.currView.Viewnum++
				vs.currView.Primary = args.Me
				vs.primaryAcked = false

				reply.View = vs.currView

				return nil
			}
		} else {
			if args.Viewnum == 0 { //新server
				log.Println("vs.primaryAcked", vs.primaryAcked)
				if !has {
					vs.idleServers = append(vs.idleServers, args.Me)
				}
				if vs.primaryAcked {
					if args.Me == vs.currView.Primary { //primary restart
						vs.currView.Viewnum++
						vs.primaryAcked = false
						vs.currView.Primary = vs.currView.Backup
						vs.currView.Backup = vs.pickFirstIdleServer()
					} else if args.Me == vs.currView.Backup { //backup restart
						vs.currView.Viewnum++
						vs.primaryAcked = false
						vs.currView.Backup = vs.pickFirstIdleServer()
						vs.idleServers = append(vs.idleServers, args.Me)
					} else {
						if vs.currView.Backup == "" {
							vs.currView.Viewnum++
							vs.primaryAcked = false
							vs.currView.Backup = vs.pickFirstIdleServer()
						}
					}
				}
				reply.View = vs.currView

				return nil
			} else {
				if args.Me == vs.currView.Primary && args.Viewnum == vs.currView.Viewnum {
					vs.primaryAcked = true

					if vs.currView.Backup == "" && len(vs.idleServers) != 0 {
						vs.currView.Backup = vs.pickFirstIdleServer()
						vs.currView.Viewnum++
						vs.primaryAcked = false

						log.Printf("new Backup %s\n", vs.currView.Backup)
					}
				}

				reply.View = vs.currView

				return nil
			}
		}
	*/

	return nil
}

//
// server Get() RPC handler.
//
func (vs *ViewServer) Get(args *GetArgs, reply *GetReply) error {

	// Your code here.
	vs.mu.Lock()
	defer vs.mu.Unlock()

	if vs.currView.Primary != "" || vs.currView.Backup != "" {
		log.Printf("Get Primary %s, Backup %s, Viewnum %d \n", vs.currView.Primary, vs.currView.Backup, vs.currView.Viewnum)
	}

	reply.View = vs.currView

	return nil
}

//
// tick() is called once per PingInterval; it should notice
// if servers have died or recovered, and change the view
// accordingly.
//
func (vs *ViewServer) tick() {

	vs.mu.Lock()
	defer vs.mu.Unlock()

	now := time.Now()
	log.Printf("ViewServer tick Primary %s, Backup %s, Viewnum %d\n", vs.currView.Primary, vs.currView.Backup, vs.currView.Viewnum)

	for s, t := range vs.recentTimes {
		if now.Sub(t) > PingInterval*DeadPings {
			log.Printf("Server %s is inactive", s)

			delete(vs.serverMap, s)
			delete(vs.recentTimes, s)
			if s == vs.currView.Primary {
				vs.currView.Primary = ""
				if vs.primaryAcked {
					vs.currView.Primary = vs.currView.Backup
					vs.currView.Backup = vs.pickFirstIdleServer()
					vs.currView.Viewnum++
					vs.primaryAcked = false
				}
			} else if s == vs.currView.Backup {
				vs.currView.Backup = ""
				if vs.primaryAcked {
					vs.currView.Backup = vs.pickFirstIdleServer()
					vs.currView.Viewnum++
					vs.primaryAcked = false
				}
			} else {
				var t []string
				for _, v := range vs.idleServers {
					if v != s {
						t = append(t, v)
					}
				}

				vs.idleServers = t
			}
		}
	}
	// Your code here.
}

//
// tell the server to shut itself down.
// for testing.
// please don't change these two functions.
//
func (vs *ViewServer) Kill() {
	atomic.StoreInt32(&vs.dead, 1)
	vs.l.Close()
}

//
// has this server been asked to shut down?
//
func (vs *ViewServer) isdead() bool {
	return atomic.LoadInt32(&vs.dead) != 0
}

// please don't change this function.
func (vs *ViewServer) GetRPCCount() int32 {
	return atomic.LoadInt32(&vs.rpccount)
}

func StartServer(me string) *ViewServer {
	vs := new(ViewServer)
	vs.me = me
	// Your vs.* initializations here.
	vs.recentTimes = make(map[string]time.Time)
	vs.primaryAcked = false
	vs.serverMap = make(map[string]bool)

	// tell net/rpc about our RPC server and handlers.
	rpcs := rpc.NewServer()
	rpcs.Register(vs)

	// prepare to receive connections from clients.
	// change "unix" to "tcp" to use over a network.
	os.Remove(vs.me) // only needed for "unix"
	l, e := net.Listen("unix", vs.me)
	if e != nil {
		log.Fatal("listen error: ", e)
	}
	vs.l = l

	// please don't change any of the following code,
	// or do anything to subvert it.

	// create a thread to accept RPC connections from clients.
	go func() {
		for vs.isdead() == false {
			conn, err := vs.l.Accept()
			if err == nil && vs.isdead() == false {
				atomic.AddInt32(&vs.rpccount, 1)
				go rpcs.ServeConn(conn)
			} else if err == nil {
				conn.Close()
			}
			if err != nil && vs.isdead() == false {
				fmt.Printf("ViewServer(%v) accept: %v\n", me, err.Error())
				vs.Kill()
			}
		}
	}()

	// create a thread to call tick() periodically.
	go func() {
		for vs.isdead() == false {
			vs.tick()
			time.Sleep(PingInterval)
		}
	}()

	return vs
}
<commit_msg>nothing<commit_after>package viewservice

import "net"
import "net/rpc"
import "log"
import "time"
import "sync"
import "fmt"
import "os"
import "sync/atomic"
import "errors"

type ViewServer struct {
	mu       sync.Mutex
	l        net.Listener
	dead     int32 // for testing
	rpccount int32 // for testing
	me       string

	// Your declarations here.
	recentTimes    map[string]time.Time //recentTime
	currView       View                 //
	idleServers    []string             //volunteers
	serverMap      map[string]bool      //all servers, including primary and backup
	primaryAcked   bool                 //
	primaryRestart bool
	backupRestart  bool
}

func (vs *ViewServer) pickFirstIdleServer() string {
	rs := ""
	if len(vs.idleServers) > 0 {
		rs = vs.idleServers[0]
		vs.idleServers = vs.idleServers[1:]
	}

	return rs
}

func (vs *ViewServer) addIdleServer(server string) {
	vs.idleServers = append(vs.idleServers, server)
}

func (vs *ViewServer) hasIdleServer() bool {
	return len(vs.idleServers) > 0
}

//
// server Ping RPC handler.
//
func (vs *ViewServer) Ping(args *PingArgs, reply *PingReply) error {

	// Your code here.
	vs.mu.Lock()
	defer vs.mu.Unlock()

	defer func() {
		log.Printf("Ping Args.Me %s, Args.Viewnum %d, Viewnum %d, Primary %s, Backup %s, Acked %v\n", args.Me, args.Viewnum, vs.currView.Viewnum, vs.currView.Primary, vs.currView.Backup, vs.primaryAcked)
	}()

	log.Printf("Ping Args.Me %s, Viewnum %d, Primary %s, Backup %s, Acked %v\n", args.Me, vs.currView.Viewnum, vs.currView.Primary, vs.currView.Backup, vs.primaryAcked)

	if args.Viewnum == 0 { //new server
		if vs.currView.Viewnum == 0 { //first server ping
			vs.recentTimes[args.Me] = time.Now()
			vs.serverMap[args.Me] = true

			vs.currView.Viewnum++
			vs.currView.Primary = args.Me
			vs.primaryAcked = false

			reply.View = vs.currView

			return nil
		} else { //vs.currView.Viewnum !=0
			vs.recentTimes[args.Me] = time.Now()

			if args.Me == vs.currView.Primary {
				if vs.primaryAcked {
					vs.currView.Viewnum++
					vs.currView.Primary = vs.currView.Backup
					vs.currView.Backup = vs.pickFirstIdleServer()
					vs.primaryAcked = false

					vs.addIdleServer(args.Me)

					reply.View = vs.currView

					return nil
				} else {
					vs.addIdleServer(args.Me)

					reply.View = vs.currView

					return nil
				}
			} else if args.Me == vs.currView.Backup {
				if vs.primaryAcked {
					vs.currView.Viewnum++
					vs.currView.Backup = vs.pickFirstIdleServer()
					vs.primaryAcked = false

					vs.addIdleServer(args.Me)

					reply.View = vs.currView

					return nil
				} else {
					vs.addIdleServer(args.Me)

					reply.View = vs.currView

					return nil
				}
			} else { //TODO
				_, has := vs.serverMap[args.Me]
				if !has {
					vs.serverMap[args.Me] = true
					vs.addIdleServer(args.Me)
				}

				if vs.primaryAcked && vs.currView.Backup == "" {
					vs.currView.Viewnum++
					vs.currView.Backup = vs.pickFirstIdleServer()
					vs.primaryAcked = false
				}

				reply.View = vs.currView
				return nil
			}
		}
	} else { //args.Viewnum !=0
		if vs.currView.Viewnum == 0 {
			return errors.New("invalid Viewnum")
		} else {
			vs.recentTimes[args.Me] = time.Now()

			if args.Me == vs.currView.Primary && args.Viewnum == vs.currView.Viewnum {
				vs.primaryAcked = true

				if vs.currView.Backup == "" && vs.hasIdleServer() {
					vs.currView.Backup = vs.pickFirstIdleServer()
					vs.currView.Viewnum++
					vs.primaryAcked = false

					log.Printf("new Backup %s\n", vs.currView.Backup)
				}
			}

			reply.View = vs.currView

			return nil
		}
	}

	return nil
}

//
// server Get() RPC handler.
//
func (vs *ViewServer) Get(args *GetArgs, reply *GetReply) error {

	// Your code here.
	vs.mu.Lock()
	defer vs.mu.Unlock()

	if vs.currView.Primary != "" || vs.currView.Backup != "" {
		log.Printf("Get Primary %s, Backup %s, Viewnum %d \n", vs.currView.Primary, vs.currView.Backup, vs.currView.Viewnum)
	}

	reply.View = vs.currView

	return nil
}

//
// tick() is called once per PingInterval; it should notice
// if servers have died or recovered, and change the view
// accordingly.
//
func (vs *ViewServer) tick() {

	vs.mu.Lock()
	defer vs.mu.Unlock()

	now := time.Now()
	log.Printf("ViewServer tick Primary %s, Backup %s, Viewnum %d, PrimaryAcked %v\n", vs.currView.Primary, vs.currView.Backup, vs.currView.Viewnum, vs.primaryAcked)

	for s, t := range vs.recentTimes {
		if now.Sub(t) > PingInterval*DeadPings {
			log.Printf("Server %s is inactive", s)

			delete(vs.serverMap, s)
			delete(vs.recentTimes, s)
			if s == vs.currView.Primary {
				vs.currView.Primary = ""
				if vs.primaryAcked {
					vs.currView.Primary = vs.currView.Backup
					vs.currView.Backup = vs.pickFirstIdleServer()
					vs.currView.Viewnum++
					vs.primaryAcked = false
				}
			} else if s == vs.currView.Backup {
				vs.currView.Backup = ""
				if vs.primaryAcked {
					vs.currView.Backup = vs.pickFirstIdleServer()
					vs.currView.Viewnum++
					vs.primaryAcked = false
				}
			} else {
				var t []string
				for _, v := range vs.idleServers {
					if v != s {
						t = append(t, v)
					}
				}

				vs.idleServers = t
			}
		}
	}
	// Your code here.
}

//
// tell the server to shut itself down.
// for testing.
// please don't change these two functions.
//
func (vs *ViewServer) Kill() {
	atomic.StoreInt32(&vs.dead, 1)
	vs.l.Close()
}

//
// has this server been asked to shut down?
//
func (vs *ViewServer) isdead() bool {
	return atomic.LoadInt32(&vs.dead) != 0
}

// please don't change this function.
func (vs *ViewServer) GetRPCCount() int32 {
	return atomic.LoadInt32(&vs.rpccount)
}

func StartServer(me string) *ViewServer {
	vs := new(ViewServer)
	vs.me = me
	// Your vs.* initializations here.
	vs.recentTimes = make(map[string]time.Time)
	vs.primaryAcked = false
	vs.serverMap = make(map[string]bool)

	// tell net/rpc about our RPC server and handlers.
	rpcs := rpc.NewServer()
	rpcs.Register(vs)

	// prepare to receive connections from clients.
	// change "unix" to "tcp" to use over a network.
	os.Remove(vs.me) // only needed for "unix"
	l, e := net.Listen("unix", vs.me)
	if e != nil {
		log.Fatal("listen error: ", e)
	}
	vs.l = l

	// please don't change any of the following code,
	// or do anything to subvert it.

	// create a thread to accept RPC connections from clients.
	go func() {
		for vs.isdead() == false {
			conn, err := vs.l.Accept()
			if err == nil && vs.isdead() == false {
				atomic.AddInt32(&vs.rpccount, 1)
				go rpcs.ServeConn(conn)
			} else if err == nil {
				conn.Close()
			}
			if err != nil && vs.isdead() == false {
				fmt.Printf("ViewServer(%v) accept: %v\n", me, err.Error())
				vs.Kill()
			}
		}
	}()

	// create a thread to call tick() periodically.
	go func() {
		for vs.isdead() == false {
			vs.tick()
			time.Sleep(PingInterval)
		}
	}()

	return vs
}
<|endoftext|>