ed14061578
* Work on raft backend * Add logstore locally * Add encryptor and unsealable interfaces * Add clustering support to raft * Remove client and handler * Bootstrap raft on init * Cleanup raft logic a bit * More raft work * Work on TLS config * More work on bootstrapping * Fix build * More work on bootstrapping * More bootstrapping work * fix build * Remove consul dep * Fix build * merged oss/master into raft-storage * Work on bootstrapping * Get bootstrapping to work * Clean up FMS and node-id * Update local node ID logic * Cleanup node-id change * Work on snapshotting * Raft: Add remove peer API (#906) * Add remove peer API * Add some comments * Fix existing snapshotting (#909) * Raft get peers API (#912) * Read raft configuration * address review feedback * Use the Leadership Transfer API to step-down the active node (#918) * Raft join and unseal using Shamir keys (#917) * Raft join using shamir * Store AEAD instead of master key * Split the raft join process to answer the challenge after a successful unseal * get the follower to standby state * Make unseal work * minor changes * Some input checks * reuse the shamir seal access instead of new default seal access * refactor joinRaftSendAnswer function * Synchronously send answer in auto-unseal case * Address review feedback * Raft snapshots (#910) * Fix existing snapshotting * implement the noop snapshotting * Add comments and switch log libraries * add some snapshot tests * add snapshot test file * add TODO * More work on raft snapshotting * progress on the ConfigStore strategy * Don't use two buckets * Update the snapshot store logic to hide the file logic * Add more backend tests * Cleanup code a bit * [WIP] Raft recovery (#938) * Add recovery functionality * remove fmt.Printfs * Fix a few fsm bugs * Add max size value for raft backend (#942) * Add max size value for raft backend * Include physical.ErrValueTooLarge in the message * Raft snapshot Take/Restore API (#926) * Inital work on raft snapshot APIs * Always redirect snapshot install/download requests * More work on the snapshot APIs * Cleanup code a bit * On restore handle special cases * Use the seal to encrypt the sha sum file * Add sealer mechanism and fix some bugs * Call restore while state lock is held * Send restore cb trigger through raft log * Make error messages nicer * Add test helpers * Add snapshot test * Add shamir unseal test * Add more raft snapshot API tests * Fix locking * Change working to initalize * Add underlying raw object to test cluster core * Move leaderUUID to core * Add raft TLS rotation logic (#950) * Add TLS rotation logic * Cleanup logic a bit * Add/Remove from follower state on add/remove peer * add comments * Update more comments * Update request_forwarding_service.proto * Make sure we populate all nodes in the followerstate obj * Update times * Apply review feedback * Add more raft config setting (#947) * Add performance config setting * Add more config options and fix tests * Test Raft Recovery (#944) * Test raft recovery * Leave out a node during recovery * remove unused struct * Update physical/raft/snapshot_test.go * Update physical/raft/snapshot_test.go * fix vendoring * Switch to new raft interface * Remove unused files * Switch a gogo -> proto instance * Remove unneeded vault dep in go.sum * Update helper/testhelpers/testhelpers.go Co-Authored-By: Calvin Leung Huang <cleung2010@gmail.com> * Update vault/cluster/cluster.go * track active key within the keyring itself (#6915) * track active key within the keyring itself * lookup and store using the active key ID * update docstring * minor refactor * Small text fixes (#6912) * Update physical/raft/raft.go Co-Authored-By: Calvin Leung Huang <cleung2010@gmail.com> * review feedback * Move raft logical system into separate file * Update help text a bit * Enforce cluster addr is set and use it for raft bootstrapping * Fix tests * fix http test panic * Pull in latest raft-snapshot library * Add comment
349 lines
8.8 KiB
Go
349 lines
8.8 KiB
Go
package metrics
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"math"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// InmemSink provides a MetricSink that does in-memory aggregation
|
|
// without sending metrics over a network. It can be embedded within
|
|
// an application to provide profiling information.
|
|
type InmemSink struct {
|
|
// How long is each aggregation interval
|
|
interval time.Duration
|
|
|
|
// Retain controls how many metrics interval we keep
|
|
retain time.Duration
|
|
|
|
// maxIntervals is the maximum length of intervals.
|
|
// It is retain / interval.
|
|
maxIntervals int
|
|
|
|
// intervals is a slice of the retained intervals
|
|
intervals []*IntervalMetrics
|
|
intervalLock sync.RWMutex
|
|
|
|
rateDenom float64
|
|
}
|
|
|
|
// IntervalMetrics stores the aggregated metrics
|
|
// for a specific interval
|
|
type IntervalMetrics struct {
|
|
sync.RWMutex
|
|
|
|
// The start time of the interval
|
|
Interval time.Time
|
|
|
|
// Gauges maps the key to the last set value
|
|
Gauges map[string]GaugeValue
|
|
|
|
// Points maps the string to the list of emitted values
|
|
// from EmitKey
|
|
Points map[string][]float32
|
|
|
|
// Counters maps the string key to a sum of the counter
|
|
// values
|
|
Counters map[string]SampledValue
|
|
|
|
// Samples maps the key to an AggregateSample,
|
|
// which has the rolled up view of a sample
|
|
Samples map[string]SampledValue
|
|
}
|
|
|
|
// NewIntervalMetrics creates a new IntervalMetrics for a given interval
|
|
func NewIntervalMetrics(intv time.Time) *IntervalMetrics {
|
|
return &IntervalMetrics{
|
|
Interval: intv,
|
|
Gauges: make(map[string]GaugeValue),
|
|
Points: make(map[string][]float32),
|
|
Counters: make(map[string]SampledValue),
|
|
Samples: make(map[string]SampledValue),
|
|
}
|
|
}
|
|
|
|
// AggregateSample is used to hold aggregate metrics
|
|
// about a sample
|
|
type AggregateSample struct {
|
|
Count int // The count of emitted pairs
|
|
Rate float64 // The values rate per time unit (usually 1 second)
|
|
Sum float64 // The sum of values
|
|
SumSq float64 `json:"-"` // The sum of squared values
|
|
Min float64 // Minimum value
|
|
Max float64 // Maximum value
|
|
LastUpdated time.Time `json:"-"` // When value was last updated
|
|
}
|
|
|
|
// Computes a Stddev of the values
|
|
func (a *AggregateSample) Stddev() float64 {
|
|
num := (float64(a.Count) * a.SumSq) - math.Pow(a.Sum, 2)
|
|
div := float64(a.Count * (a.Count - 1))
|
|
if div == 0 {
|
|
return 0
|
|
}
|
|
return math.Sqrt(num / div)
|
|
}
|
|
|
|
// Computes a mean of the values
|
|
func (a *AggregateSample) Mean() float64 {
|
|
if a.Count == 0 {
|
|
return 0
|
|
}
|
|
return a.Sum / float64(a.Count)
|
|
}
|
|
|
|
// Ingest is used to update a sample
|
|
func (a *AggregateSample) Ingest(v float64, rateDenom float64) {
|
|
a.Count++
|
|
a.Sum += v
|
|
a.SumSq += (v * v)
|
|
if v < a.Min || a.Count == 1 {
|
|
a.Min = v
|
|
}
|
|
if v > a.Max || a.Count == 1 {
|
|
a.Max = v
|
|
}
|
|
a.Rate = float64(a.Sum) / rateDenom
|
|
a.LastUpdated = time.Now()
|
|
}
|
|
|
|
func (a *AggregateSample) String() string {
|
|
if a.Count == 0 {
|
|
return "Count: 0"
|
|
} else if a.Stddev() == 0 {
|
|
return fmt.Sprintf("Count: %d Sum: %0.3f LastUpdated: %s", a.Count, a.Sum, a.LastUpdated)
|
|
} else {
|
|
return fmt.Sprintf("Count: %d Min: %0.3f Mean: %0.3f Max: %0.3f Stddev: %0.3f Sum: %0.3f LastUpdated: %s",
|
|
a.Count, a.Min, a.Mean(), a.Max, a.Stddev(), a.Sum, a.LastUpdated)
|
|
}
|
|
}
|
|
|
|
// NewInmemSinkFromURL creates an InmemSink from a URL. It is used
|
|
// (and tested) from NewMetricSinkFromURL.
|
|
func NewInmemSinkFromURL(u *url.URL) (MetricSink, error) {
|
|
params := u.Query()
|
|
|
|
interval, err := time.ParseDuration(params.Get("interval"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Bad 'interval' param: %s", err)
|
|
}
|
|
|
|
retain, err := time.ParseDuration(params.Get("retain"))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Bad 'retain' param: %s", err)
|
|
}
|
|
|
|
return NewInmemSink(interval, retain), nil
|
|
}
|
|
|
|
// NewInmemSink is used to construct a new in-memory sink.
|
|
// Uses an aggregation interval and maximum retention period.
|
|
func NewInmemSink(interval, retain time.Duration) *InmemSink {
|
|
rateTimeUnit := time.Second
|
|
i := &InmemSink{
|
|
interval: interval,
|
|
retain: retain,
|
|
maxIntervals: int(retain / interval),
|
|
rateDenom: float64(interval.Nanoseconds()) / float64(rateTimeUnit.Nanoseconds()),
|
|
}
|
|
i.intervals = make([]*IntervalMetrics, 0, i.maxIntervals)
|
|
return i
|
|
}
|
|
|
|
func (i *InmemSink) SetGauge(key []string, val float32) {
|
|
i.SetGaugeWithLabels(key, val, nil)
|
|
}
|
|
|
|
func (i *InmemSink) SetGaugeWithLabels(key []string, val float32, labels []Label) {
|
|
k, name := i.flattenKeyLabels(key, labels)
|
|
intv := i.getInterval()
|
|
|
|
intv.Lock()
|
|
defer intv.Unlock()
|
|
intv.Gauges[k] = GaugeValue{Name: name, Value: val, Labels: labels}
|
|
}
|
|
|
|
func (i *InmemSink) EmitKey(key []string, val float32) {
|
|
k := i.flattenKey(key)
|
|
intv := i.getInterval()
|
|
|
|
intv.Lock()
|
|
defer intv.Unlock()
|
|
vals := intv.Points[k]
|
|
intv.Points[k] = append(vals, val)
|
|
}
|
|
|
|
func (i *InmemSink) IncrCounter(key []string, val float32) {
|
|
i.IncrCounterWithLabels(key, val, nil)
|
|
}
|
|
|
|
func (i *InmemSink) IncrCounterWithLabels(key []string, val float32, labels []Label) {
|
|
k, name := i.flattenKeyLabels(key, labels)
|
|
intv := i.getInterval()
|
|
|
|
intv.Lock()
|
|
defer intv.Unlock()
|
|
|
|
agg, ok := intv.Counters[k]
|
|
if !ok {
|
|
agg = SampledValue{
|
|
Name: name,
|
|
AggregateSample: &AggregateSample{},
|
|
Labels: labels,
|
|
}
|
|
intv.Counters[k] = agg
|
|
}
|
|
agg.Ingest(float64(val), i.rateDenom)
|
|
}
|
|
|
|
func (i *InmemSink) AddSample(key []string, val float32) {
|
|
i.AddSampleWithLabels(key, val, nil)
|
|
}
|
|
|
|
func (i *InmemSink) AddSampleWithLabels(key []string, val float32, labels []Label) {
|
|
k, name := i.flattenKeyLabels(key, labels)
|
|
intv := i.getInterval()
|
|
|
|
intv.Lock()
|
|
defer intv.Unlock()
|
|
|
|
agg, ok := intv.Samples[k]
|
|
if !ok {
|
|
agg = SampledValue{
|
|
Name: name,
|
|
AggregateSample: &AggregateSample{},
|
|
Labels: labels,
|
|
}
|
|
intv.Samples[k] = agg
|
|
}
|
|
agg.Ingest(float64(val), i.rateDenom)
|
|
}
|
|
|
|
// Data is used to retrieve all the aggregated metrics
|
|
// Intervals may be in use, and a read lock should be acquired
|
|
func (i *InmemSink) Data() []*IntervalMetrics {
|
|
// Get the current interval, forces creation
|
|
i.getInterval()
|
|
|
|
i.intervalLock.RLock()
|
|
defer i.intervalLock.RUnlock()
|
|
|
|
n := len(i.intervals)
|
|
intervals := make([]*IntervalMetrics, n)
|
|
|
|
copy(intervals[:n-1], i.intervals[:n-1])
|
|
current := i.intervals[n-1]
|
|
|
|
// make its own copy for current interval
|
|
intervals[n-1] = &IntervalMetrics{}
|
|
copyCurrent := intervals[n-1]
|
|
current.RLock()
|
|
*copyCurrent = *current
|
|
|
|
copyCurrent.Gauges = make(map[string]GaugeValue, len(current.Gauges))
|
|
for k, v := range current.Gauges {
|
|
copyCurrent.Gauges[k] = v
|
|
}
|
|
// saved values will be not change, just copy its link
|
|
copyCurrent.Points = make(map[string][]float32, len(current.Points))
|
|
for k, v := range current.Points {
|
|
copyCurrent.Points[k] = v
|
|
}
|
|
copyCurrent.Counters = make(map[string]SampledValue, len(current.Counters))
|
|
for k, v := range current.Counters {
|
|
copyCurrent.Counters[k] = v.deepCopy()
|
|
}
|
|
copyCurrent.Samples = make(map[string]SampledValue, len(current.Samples))
|
|
for k, v := range current.Samples {
|
|
copyCurrent.Samples[k] = v.deepCopy()
|
|
}
|
|
current.RUnlock()
|
|
|
|
return intervals
|
|
}
|
|
|
|
func (i *InmemSink) getExistingInterval(intv time.Time) *IntervalMetrics {
|
|
i.intervalLock.RLock()
|
|
defer i.intervalLock.RUnlock()
|
|
|
|
n := len(i.intervals)
|
|
if n > 0 && i.intervals[n-1].Interval == intv {
|
|
return i.intervals[n-1]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (i *InmemSink) createInterval(intv time.Time) *IntervalMetrics {
|
|
i.intervalLock.Lock()
|
|
defer i.intervalLock.Unlock()
|
|
|
|
// Check for an existing interval
|
|
n := len(i.intervals)
|
|
if n > 0 && i.intervals[n-1].Interval == intv {
|
|
return i.intervals[n-1]
|
|
}
|
|
|
|
// Add the current interval
|
|
current := NewIntervalMetrics(intv)
|
|
i.intervals = append(i.intervals, current)
|
|
n++
|
|
|
|
// Truncate the intervals if they are too long
|
|
if n >= i.maxIntervals {
|
|
copy(i.intervals[0:], i.intervals[n-i.maxIntervals:])
|
|
i.intervals = i.intervals[:i.maxIntervals]
|
|
}
|
|
return current
|
|
}
|
|
|
|
// getInterval returns the current interval to write to
|
|
func (i *InmemSink) getInterval() *IntervalMetrics {
|
|
intv := time.Now().Truncate(i.interval)
|
|
if m := i.getExistingInterval(intv); m != nil {
|
|
return m
|
|
}
|
|
return i.createInterval(intv)
|
|
}
|
|
|
|
// Flattens the key for formatting, removes spaces
|
|
func (i *InmemSink) flattenKey(parts []string) string {
|
|
buf := &bytes.Buffer{}
|
|
replacer := strings.NewReplacer(" ", "_")
|
|
|
|
if len(parts) > 0 {
|
|
replacer.WriteString(buf, parts[0])
|
|
}
|
|
for _, part := range parts[1:] {
|
|
replacer.WriteString(buf, ".")
|
|
replacer.WriteString(buf, part)
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// Flattens the key for formatting along with its labels, removes spaces
|
|
func (i *InmemSink) flattenKeyLabels(parts []string, labels []Label) (string, string) {
|
|
buf := &bytes.Buffer{}
|
|
replacer := strings.NewReplacer(" ", "_")
|
|
|
|
if len(parts) > 0 {
|
|
replacer.WriteString(buf, parts[0])
|
|
}
|
|
for _, part := range parts[1:] {
|
|
replacer.WriteString(buf, ".")
|
|
replacer.WriteString(buf, part)
|
|
}
|
|
|
|
key := buf.String()
|
|
|
|
for _, label := range labels {
|
|
replacer.WriteString(buf, fmt.Sprintf(";%s=%s", label.Name, label.Value))
|
|
}
|
|
|
|
return buf.String(), key
|
|
}
|