// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package prometheus import ( "math" "strconv" "strings" "github.com/elastic/beats/libbeat/common" dto "github.com/prometheus/client_model/go" ) // MetricMap defines the mapping from Prometheus metric to a Metricbeat field type MetricMap interface { // GetOptions returns the list of metric options GetOptions() []MetricOption // GetField returns the resulting field name GetField() string // GetValue returns the resulting value GetValue(m *dto.Metric) interface{} } // MetricOption adds settings to Metric objects behavior type MetricOption interface { // Process a tuple of field, value and labels from a metric, return the same tuple updated Process(field string, value interface{}, labels common.MapStr) (string, interface{}, common.MapStr) } // OpFilter only processes metrics matching the given filter func OpFilter(filter map[string]string) MetricOption { return opFilter{ labels: filter, } } // OpLowercaseValue lowercases the value if it's a string func OpLowercaseValue() MetricOption { return opLowercaseValue{} } // Metric directly maps a Prometheus metric to a Metricbeat field func Metric(field string, options ...MetricOption) MetricMap { return &commonMetric{ field: field, options: options, } } // KeywordMetric maps a Prometheus metric to a Metricbeat field, stores the // given keyword when source metric value is 1 func KeywordMetric(field, keyword string, options ...MetricOption) MetricMap { return &keywordMetric{ commonMetric{ field: field, options: options, }, keyword, } } // BooleanMetric maps a Prometheus metric to a Metricbeat field of bool type func BooleanMetric(field string, options ...MetricOption) MetricMap { return &booleanMetric{ commonMetric{ field: field, options: options, }, } } // LabelMetric maps a Prometheus metric to a Metricbeat field, stores the value // of a given label on it if the gauge value is 1 func LabelMetric(field, label string, options ...MetricOption) MetricMap { return &labelMetric{ commonMetric{ field: field, options: options, }, label, } } // InfoMetric obtains info labels from the given metric and puts them // into events matching all the key labels present in the metric func InfoMetric(options ...MetricOption) MetricMap { return &infoMetric{ commonMetric{ options: options, }, } } type commonMetric struct { field string options []MetricOption } // GetOptions returns the list of metric options func (m *commonMetric) GetOptions() []MetricOption { return m.options } // GetField returns the resulting field name func (m *commonMetric) GetField() string { return m.field } // GetValue returns the resulting value func (m *commonMetric) GetValue(metric *dto.Metric) interface{} { counter := metric.GetCounter() if counter != nil { return int64(counter.GetValue()) } gauge := metric.GetGauge() if gauge != nil { return gauge.GetValue() } summary := metric.GetSummary() if summary != nil { value := common.MapStr{} value["sum"] = summary.GetSampleSum() value["count"] = summary.GetSampleCount() quantiles := summary.GetQuantile() percentileMap := common.MapStr{} for _, quantile := range quantiles { if !math.IsNaN(quantile.GetValue()) { key := strconv.FormatFloat((100 * quantile.GetQuantile()), 'f', -1, 64) percentileMap[key] = quantile.GetValue() } } if len(percentileMap) != 0 { value["percentile"] = percentileMap } return value } histogram := metric.GetHistogram() if histogram != nil { value := common.MapStr{} value["sum"] = histogram.GetSampleSum() value["count"] = histogram.GetSampleCount() buckets := histogram.GetBucket() bucketMap := common.MapStr{} for _, bucket := range buckets { key := strconv.FormatFloat(bucket.GetUpperBound(), 'f', -1, 64) bucketMap[key] = bucket.GetCumulativeCount() } if len(bucketMap) != 0 { value["bucket"] = bucketMap } return value } // Other types are not supported here return nil } type keywordMetric struct { commonMetric keyword string } // GetValue returns the resulting value func (m *keywordMetric) GetValue(metric *dto.Metric) interface{} { if gauge := metric.GetGauge(); gauge != nil && gauge.GetValue() == 1 { return m.keyword } return nil } type booleanMetric struct { commonMetric } // GetValue returns the resulting value func (m *booleanMetric) GetValue(metric *dto.Metric) interface{} { if gauge := metric.GetGauge(); gauge != nil { return gauge.GetValue() == 1 } return nil } type labelMetric struct { commonMetric label string } // GetValue returns the resulting value func (m *labelMetric) GetValue(metric *dto.Metric) interface{} { if gauge := metric.GetGauge(); gauge != nil && gauge.GetValue() == 1 { return getLabel(metric, m.label) } return nil } func getLabel(metric *dto.Metric, name string) string { for _, label := range metric.GetLabel() { if label.GetName() == name { return label.GetValue() } } return "" } type infoMetric struct { commonMetric } // GetValue returns the resulting value func (m *infoMetric) GetValue(metric *dto.Metric) interface{} { return "" } // GetField returns the resulting field name func (m *infoMetric) GetField() string { return "" } type opFilter struct { labels map[string]string } // Process will return nil if labels don't match the filter func (o opFilter) Process(field string, value interface{}, labels common.MapStr) (string, interface{}, common.MapStr) { for k, v := range o.labels { if labels[k] != v { return "", nil, nil } } return field, value, labels } type opLowercaseValue struct{} // Process will lowercase the given value if it's a string func (o opLowercaseValue) Process(field string, value interface{}, labels common.MapStr) (string, interface{}, common.MapStr) { if val, ok := value.(string); ok { value = strings.ToLower(val) } return field, value, labels }