// Copyright 2019 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package metric import ( "fmt" "sort" "time" "golang.org/x/tools/internal/telemetry/event" ) // Data represents a single point in the time series of a metric. // This provides the common interface to all metrics no matter their data // format. // To get the actual values for the metric you must type assert to a concrete // metric type. type Data interface { // Handle returns the metric handle this data is for. //TODO: rethink the concept of metric handles Handle() string // Groups reports the rows that currently exist for this metric. Groups() [][]event.Tag } // Int64Data is a concrete implementation of Data for int64 scalar metrics. type Int64Data struct { // Info holds the original construction information. Info *Scalar // IsGauge is true for metrics that track values, rather than increasing over time. IsGauge bool // Rows holds the per group values for the metric. Rows []int64 // End is the last time this metric was updated. EndTime time.Time groups [][]event.Tag } // Float64Data is a concrete implementation of Data for float64 scalar metrics. type Float64Data struct { // Info holds the original construction information. Info *Scalar // IsGauge is true for metrics that track values, rather than increasing over time. IsGauge bool // Rows holds the per group values for the metric. Rows []float64 // End is the last time this metric was updated. EndTime time.Time groups [][]event.Tag } // HistogramInt64Data is a concrete implementation of Data for int64 histogram metrics. type HistogramInt64Data struct { // Info holds the original construction information. Info *HistogramInt64 // Rows holds the per group values for the metric. Rows []*HistogramInt64Row // End is the last time this metric was updated. EndTime time.Time groups [][]event.Tag } // HistogramInt64Row holds the values for a single row of a HistogramInt64Data. type HistogramInt64Row struct { // Values is the counts per bucket. Values []int64 // Count is the total count. Count int64 // Sum is the sum of all the values recorded. Sum int64 // Min is the smallest recorded value. Min int64 // Max is the largest recorded value. Max int64 } // HistogramFloat64Data is a concrete implementation of Data for float64 histogram metrics. type HistogramFloat64Data struct { // Info holds the original construction information. Info *HistogramFloat64 // Rows holds the per group values for the metric. Rows []*HistogramFloat64Row // End is the last time this metric was updated. EndTime time.Time groups [][]event.Tag } // HistogramFloat64Row holds the values for a single row of a HistogramFloat64Data. type HistogramFloat64Row struct { // Values is the counts per bucket. Values []int64 // Count is the total count. Count int64 // Sum is the sum of all the values recorded. Sum float64 // Min is the smallest recorded value. Min float64 // Max is the largest recorded value. Max float64 } func tagListEqual(a, b []event.Tag) bool { //TODO: make this more efficient return fmt.Sprint(a) == fmt.Sprint(b) } func tagListLess(a, b []event.Tag) bool { //TODO: make this more efficient return fmt.Sprint(a) < fmt.Sprint(b) } func getGroup(tagMap event.TagMap, g *[][]event.Tag, keys []event.Key) (int, bool) { group := make([]event.Tag, len(keys)) for i, key := range keys { tag := tagMap.Find(key) if tag.Valid() { group[i] = tag } } old := *g index := sort.Search(len(old), func(i int) bool { return !tagListLess(old[i], group) }) if index < len(old) && tagListEqual(group, old[index]) { // not a new group return index, false } *g = make([][]event.Tag, len(old)+1) copy(*g, old[:index]) copy((*g)[index+1:], old[index:]) (*g)[index] = group return index, true } func (data *Int64Data) Handle() string { return data.Info.Name } func (data *Int64Data) Groups() [][]event.Tag { return data.groups } func (data *Int64Data) modify(at time.Time, tagMap event.TagMap, f func(v int64) int64) Data { index, insert := getGroup(tagMap, &data.groups, data.Info.Keys) old := data.Rows if insert { data.Rows = make([]int64, len(old)+1) copy(data.Rows, old[:index]) copy(data.Rows[index+1:], old[index:]) } else { data.Rows = make([]int64, len(old)) copy(data.Rows, old) } data.Rows[index] = f(data.Rows[index]) data.EndTime = at frozen := *data return &frozen } func (data *Int64Data) countInt64(at time.Time, tagMap event.TagMap, tag event.Tag) Data { return data.modify(at, tagMap, func(v int64) int64 { return v + 1 }) } func (data *Int64Data) countFloat64(at time.Time, tagMap event.TagMap, tag event.Tag) Data { return data.modify(at, tagMap, func(v int64) int64 { return v + 1 }) } func (data *Int64Data) sum(at time.Time, tagMap event.TagMap, tag event.Tag) Data { return data.modify(at, tagMap, func(v int64) int64 { return v + tag.Value.(int64) }) } func (data *Int64Data) latest(at time.Time, tagMap event.TagMap, tag event.Tag) Data { return data.modify(at, tagMap, func(v int64) int64 { return tag.Value.(int64) }) } func (data *Float64Data) Handle() string { return data.Info.Name } func (data *Float64Data) Groups() [][]event.Tag { return data.groups } func (data *Float64Data) modify(at time.Time, tagMap event.TagMap, f func(v float64) float64) Data { index, insert := getGroup(tagMap, &data.groups, data.Info.Keys) old := data.Rows if insert { data.Rows = make([]float64, len(old)+1) copy(data.Rows, old[:index]) copy(data.Rows[index+1:], old[index:]) } else { data.Rows = make([]float64, len(old)) copy(data.Rows, old) } data.Rows[index] = f(data.Rows[index]) data.EndTime = at frozen := *data return &frozen } func (data *Float64Data) sum(at time.Time, tagMap event.TagMap, tag event.Tag) Data { return data.modify(at, tagMap, func(v float64) float64 { return v + tag.Value.(float64) }) } func (data *Float64Data) latest(at time.Time, tagMap event.TagMap, tag event.Tag) Data { return data.modify(at, tagMap, func(v float64) float64 { return tag.Value.(float64) }) } func (data *HistogramInt64Data) Handle() string { return data.Info.Name } func (data *HistogramInt64Data) Groups() [][]event.Tag { return data.groups } func (data *HistogramInt64Data) modify(at time.Time, tagMap event.TagMap, f func(v *HistogramInt64Row)) Data { index, insert := getGroup(tagMap, &data.groups, data.Info.Keys) old := data.Rows var v HistogramInt64Row if insert { data.Rows = make([]*HistogramInt64Row, len(old)+1) copy(data.Rows, old[:index]) copy(data.Rows[index+1:], old[index:]) } else { data.Rows = make([]*HistogramInt64Row, len(old)) copy(data.Rows, old) v = *data.Rows[index] } oldValues := v.Values v.Values = make([]int64, len(data.Info.Buckets)) copy(v.Values, oldValues) f(&v) data.Rows[index] = &v data.EndTime = at frozen := *data return &frozen } func (data *HistogramInt64Data) record(at time.Time, tagMap event.TagMap, tag event.Tag) Data { return data.modify(at, tagMap, func(v *HistogramInt64Row) { value := tag.Value.(int64) v.Sum += value if v.Min > value || v.Count == 0 { v.Min = value } if v.Max < value || v.Count == 0 { v.Max = value } v.Count++ for i, b := range data.Info.Buckets { if value <= b { v.Values[i]++ } } }) } func (data *HistogramFloat64Data) Handle() string { return data.Info.Name } func (data *HistogramFloat64Data) Groups() [][]event.Tag { return data.groups } func (data *HistogramFloat64Data) modify(at time.Time, tagMap event.TagMap, f func(v *HistogramFloat64Row)) Data { index, insert := getGroup(tagMap, &data.groups, data.Info.Keys) old := data.Rows var v HistogramFloat64Row if insert { data.Rows = make([]*HistogramFloat64Row, len(old)+1) copy(data.Rows, old[:index]) copy(data.Rows[index+1:], old[index:]) } else { data.Rows = make([]*HistogramFloat64Row, len(old)) copy(data.Rows, old) v = *data.Rows[index] } oldValues := v.Values v.Values = make([]int64, len(data.Info.Buckets)) copy(v.Values, oldValues) f(&v) data.Rows[index] = &v data.EndTime = at frozen := *data return &frozen } func (data *HistogramFloat64Data) record(at time.Time, tagMap event.TagMap, tag event.Tag) Data { return data.modify(at, tagMap, func(v *HistogramFloat64Row) { value := tag.Value.(float64) v.Sum += value if v.Min > value || v.Count == 0 { v.Min = value } if v.Max < value || v.Count == 0 { v.Max = value } v.Count++ for i, b := range data.Info.Buckets { if value <= b { v.Values[i]++ } } }) }