pax_global_header00006660000000000000000000000064140444415620014516gustar00rootroot0000000000000052 comment=192a2cdf269f0fc1716a647eb4ecd2ace6e3ad08 eagle-0.0.2/000077500000000000000000000000001404444156200125725ustar00rootroot00000000000000eagle-0.0.2/.gitignore000066400000000000000000000003001404444156200145530ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out eagle-0.0.2/LICENSE000066400000000000000000000020611404444156200135760ustar00rootroot00000000000000MIT License Copyright (c) 2018 Alexander Emelin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. eagle-0.0.2/README.md000066400000000000000000000006211404444156200140500ustar00rootroot00000000000000# eagle Package eagle provides a functionality to export Prometheus metrics aggregated over configured time interval. This can be useful when you want to use Prometheus library to instrument your code but still want to periodically export metrics to non Prometheus monitoring systems. At moment only Counters, Gauges and Summaries are supported. [Godoc](https://godoc.org/github.com/FZambia/eagle) eagle-0.0.2/eagle.go000066400000000000000000000214211404444156200141760ustar00rootroot00000000000000// Package eagle provides a functionality to export Prometheus metrics // aggregated over configured time interval. This can be useful when you // want to use Prometheus library to instrument your code but still want // to periodically export metrics to non Prometheus monitoring systems. package eagle import ( "fmt" "math" "strings" "sync" "time" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" ) const defaultQuantileSep = "." // MetricType is a type for different supported metric types. type MetricType string // Various metric types eagle supports. const ( MetricTypeCounter = "counter" MetricTypeGauge = "gauge" MetricTypeSummary = "summary" ) // Eagle allows to periodically export Prometheus metrics // aggregated over configured time interval. type Eagle struct { mu sync.RWMutex gatherer prometheus.Gatherer interval time.Duration sink chan<- Metrics quantileSep string values map[string]float64 deltas map[string]float64 closeOnce sync.Once closeCh chan struct{} } // Config of Eagle instance. type Config struct { Gatherer prometheus.Gatherer Interval time.Duration Sink chan<- Metrics QuantileSep string } // New creates new Eagle. func New(c Config) *Eagle { e := &Eagle{ gatherer: c.Gatherer, interval: c.Interval, sink: c.Sink, quantileSep: defaultQuantileSep, values: make(map[string]float64), deltas: make(map[string]float64), closeCh: make(chan struct{}), } if c.QuantileSep != "" { e.quantileSep = c.QuantileSep } go e.aggregate() return e } // Close closes Eagle. func (e *Eagle) Close() error { e.closeOnce.Do(func() { close(e.closeCh) }) return nil } // MetricValue is a concrete value of certain metric. type MetricValue struct { Name string `json:"name"` Labels []string `json:"labels,omitempty"` Value float64 `json:"value"` } // Metric is a single Prometheus metric that can have many MetricValue. type Metric struct { Type MetricType `json:"type"` Namespace string `json:"namespace"` Subsystem string `json:"subsystem"` Name string `json:"name"` Help string `json:"help,omitempty"` Values []MetricValue `json:"values,omitempty"` } // Metrics represent collection of aggregated Prometheus metrics. type Metrics struct { Items []Metric `json:"items,omitempty"` } // Flatten is a helper method to flatten metrics into map[string]float64. func (m Metrics) Flatten(sep string) map[string]float64 { result := make(map[string]float64) for _, item := range m.Items { for _, metricValue := range item.Values { parts := []string{} if item.Namespace != "" { parts = append(parts, item.Namespace) } if item.Subsystem != "" { parts = append(parts, item.Subsystem) } if item.Name != "" { parts = append(parts, item.Name) } if metricValue.Name != "" { parts = append(parts, metricValue.Name) } parts = append(parts, metricValue.Labels...) key := strings.Join(parts, sep) result[key] = metricValue.Value } } return result } type metricLabel struct { Name string Value string } func getCacheKey(name string, labels []metricLabel, suffix string) string { key := name path := joinLabels(labels, ".") if path != "" { key += "." + path } if suffix != "" { key += "." + suffix } return key } func quantileString(val float64) string { q := fmt.Sprintf("%d", int(val*1000)) if len(q) > 2 { q = strings.TrimSuffix(q, "0") } return q } func (e *Eagle) aggregateOnce() { mfs, err := e.gatherer.Gather() if err != nil { return } e.mu.Lock() defer e.mu.Unlock() for _, mf := range mfs { typ := mf.GetType() name := mf.GetName() for _, m := range mf.GetMetric() { if typ == dto.MetricType_COUNTER { counter := m.GetCounter() cacheKey := getCacheKey(name, getLabels(m.GetLabel()), "") if previousVal, ok := e.values[cacheKey]; ok { e.deltas[cacheKey] = counter.GetValue() - previousVal } else { e.deltas[cacheKey] = counter.GetValue() } e.values[cacheKey] = counter.GetValue() } else if typ == dto.MetricType_SUMMARY { summary := m.GetSummary() count := summary.GetSampleCount() sum := summary.GetSampleSum() cacheKey := getCacheKey(name, getLabels(m.GetLabel()), "sum") if previousVal, ok := e.values[cacheKey]; ok { e.deltas[cacheKey] = sum - previousVal } else { e.deltas[cacheKey] = sum } e.values[cacheKey] = sum cacheKey = getCacheKey(name, getLabels(m.GetLabel()), "count") if previousVal, ok := e.values[cacheKey]; ok { e.deltas[cacheKey] = float64(count) - previousVal } else { e.deltas[cacheKey] = float64(count) } e.values[cacheKey] = float64(count) } } } if e.sink != nil { metrics, err := e.getMetrics(mfs) if err != nil { return } select { case e.sink <- metrics: default: return } } } func (e *Eagle) aggregate() { for { select { case <-time.After(e.interval): e.aggregateOnce() case <-e.closeCh: return } } } func getLabels(pairs []*dto.LabelPair) []metricLabel { labels := []metricLabel{} for _, pair := range pairs { val := pair.GetValue() if val == "" { continue } label := metricLabel{pair.GetName(), val} labels = append(labels, label) } return labels } func flattenLabels(labels []metricLabel) []string { l := []string{} for _, label := range labels { l = append(l, label.Name) l = append(l, label.Value) } return l } func joinLabels(labels []metricLabel, sep string) string { chunks := []string{} for _, lbl := range labels { chunks = append(chunks, lbl.Name) chunks = append(chunks, lbl.Value) } if len(chunks) == 0 { return "" } return strings.Join(chunks, sep) } // Lock must be held outside. func (e *Eagle) getMetrics(mfs []*dto.MetricFamily) (Metrics, error) { metrics := Metrics{ Items: make([]Metric, 0), } for _, mf := range mfs { typ := mf.GetType() name := mf.GetName() parts := strings.SplitN(name, "_", 3) var namespace, subsystem, shortName string if len(parts) < 2 { panic("error: Metric named '" + name + "' does not fit naming convention") } if len(parts) == 3 { namespace, subsystem, shortName = parts[0], parts[1], parts[2] } else { namespace, subsystem = parts[0], parts[1] } metric := Metric{ Namespace: namespace, Subsystem: subsystem, Name: shortName, Help: mf.GetHelp(), Values: make([]MetricValue, 0), } for _, m := range mf.GetMetric() { if typ == dto.MetricType_COUNTER { metric.Type = MetricTypeCounter counter := m.GetCounter() labels := getLabels(m.GetLabel()) cacheKey := getCacheKey(name, labels, "") deltaVal, ok := e.deltas[cacheKey] if !ok { deltaVal = counter.GetValue() } value := MetricValue{ Name: "", Labels: flattenLabels(labels), Value: deltaVal, } metric.Values = append(metric.Values, value) } else if typ == dto.MetricType_GAUGE { metric.Type = MetricTypeGauge gauge := m.GetGauge() labels := getLabels(m.GetLabel()) chunks := []string{} for _, lbl := range labels { chunks = append(chunks, lbl.Name) chunks = append(chunks, lbl.Value) } value := MetricValue{ Name: "", Labels: flattenLabels(labels), Value: gauge.GetValue(), } metric.Values = append(metric.Values, value) } else if typ == dto.MetricType_SUMMARY { metric.Type = MetricTypeSummary summary := m.GetSummary() count := summary.GetSampleCount() sum := summary.GetSampleSum() quantiles := summary.GetQuantile() labels := getLabels(m.GetLabel()) deltaVal, ok := e.deltas[getCacheKey(name, labels, "sum")] if !ok { deltaVal = sum } value := MetricValue{ Name: "sum", Labels: flattenLabels(labels), Value: deltaVal, } metric.Values = append(metric.Values, value) deltaVal, ok = e.deltas[getCacheKey(name, labels, "count")] if !ok { deltaVal = float64(count) } value = MetricValue{ Name: "count", Labels: flattenLabels(labels), Value: deltaVal, } metric.Values = append(metric.Values, value) for _, quantile := range quantiles { value := quantile.GetValue() if math.IsNaN(value) { continue } v := MetricValue{ Name: "quantile" + e.quantileSep + quantileString(quantile.GetQuantile()), Labels: flattenLabels(labels), Value: quantile.GetValue(), } metric.Values = append(metric.Values, v) } } else { continue } } metrics.Items = append(metrics.Items, metric) } return metrics, nil } // Export actual Metrics once. func (e *Eagle) Export() (Metrics, error) { mfs, err := e.gatherer.Gather() if err != nil { return Metrics{}, err } e.mu.Lock() defer e.mu.Unlock() return e.getMetrics(mfs) } eagle-0.0.2/eagle_test.go000066400000000000000000000004011404444156200152300ustar00rootroot00000000000000package eagle import ( "testing" "github.com/stretchr/testify/assert" ) func TestFlattenLabels(t *testing.T) { labels := []metricLabel{metricLabel{"test_name", "test_value"}} flatLabels := flattenLabels(labels) assert.Equal(t, 2, len(flatLabels)) } eagle-0.0.2/go.mod000066400000000000000000000004451404444156200137030ustar00rootroot00000000000000module github.com/FZambia/eagle require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_golang v0.9.2 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 github.com/stretchr/testify v1.2.2 ) eagle-0.0.2/go.sum000066400000000000000000000042751404444156200137350ustar00rootroot00000000000000github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=