github.com/GuanceCloud/cliutils@v1.1.21/prom2metrics.go (about)

     1  // Unless explicitly stated otherwise all files in this repository are licensed
     2  // under the MIT License.
     3  // This product includes software developed at Guance Cloud (https://www.guance.com/).
     4  // Copyright 2021-present Guance, Inc.
     5  
     6  package cliutils
     7  
     8  import (
     9  	"fmt"
    10  	"io"
    11  	"strings"
    12  	"time"
    13  
    14  	ifxcli "github.com/influxdata/influxdb1-client/v2"
    15  	dto "github.com/prometheus/client_model/go"
    16  	"github.com/prometheus/common/expfmt"
    17  )
    18  
    19  /// prometheus 数据转行协议 metrics
    20  ///
    21  /// 转换规则
    22  /// prometheus 数据是Key/Value 格式,以 `cpu_usage_user{cpu="cpu0"} 1.4112903225816156` 为例
    23  ///     - measurement:
    24  ///          1. 取 Key 字符串的第一个下划线,左右临近字符串。示例 measurement 为 `cpu_usage`
    25  ///          2. 允许手动添加 measurement 前缀,如果前缀为空字符串,则不添加。例如 measurementPrefix 为 `cloudcare`,measurement 为 `cloudcare_cpu_usage`
    26  ///          3. 前缀不会重复,不会出现 `cloudcare_cloudcare_cpu_usage` 的情况
    27  ///          4. 允许设置默认 measurement,当 tags 为空时,使用默认 measurement。默认 measurement 不允许为空字符串。
    28  ///     - tags:
    29  ///          1. 大括号中的所有键值对,全部转换成 tags。例如 `cpu=cpu0`
    30  ///     - fields:
    31  ///          1. 大括号以外的 Key/Value 转换成 fields。例如 `cpu_usage_user=1.4112903225816156`
    32  ///          2. 所有 fields 值都是 float64 类型
    33  ///     - time:
    34  ///          1. 允许设置默认时间,当无法解析 prometheus 数据的 timestamp 时,使用默认时间
    35  ///
    36  ///     如果遇到空数据,则跳过执行下一条。丢弃原有的直方图数据。具体输出,参照测试用例 prom2metrics_test.go
    37  
    38  type prom struct {
    39  	metricName         string
    40  	measurement        string
    41  	defaultMeasurement string
    42  	metrics            []*dto.Metric
    43  	t                  time.Time
    44  	pts                []*ifxcli.Point
    45  }
    46  
    47  func PromTextToMetrics(data io.Reader, measurementPrefix, defaultMeasurement string, t time.Time) ([]*ifxcli.Point, error) {
    48  	if defaultMeasurement == "" {
    49  		return nil, fmt.Errorf("invalid defaultMeasurement, it is empty")
    50  	}
    51  
    52  	var parser expfmt.TextParser
    53  	metrics, err := parser.TextToMetricFamilies(data)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	var pts []*ifxcli.Point
    59  
    60  	for name, metric := range metrics {
    61  		measurement := getMeasurement(name, measurementPrefix)
    62  		p := prom{
    63  			metricName:         name,
    64  			measurement:        measurement,
    65  			defaultMeasurement: defaultMeasurement,
    66  			metrics:            metric.GetMetric(),
    67  			t:                  t,
    68  			pts:                []*ifxcli.Point{},
    69  		}
    70  
    71  		switch metric.GetType() {
    72  		case dto.MetricType_GAUGE:
    73  			p.gauge()
    74  		case dto.MetricType_UNTYPED:
    75  			p.untyped()
    76  		case dto.MetricType_COUNTER:
    77  			p.counter()
    78  		case dto.MetricType_SUMMARY:
    79  			p.summary()
    80  		case dto.MetricType_HISTOGRAM:
    81  			p.histogram()
    82  		case dto.MetricType_GAUGE_HISTOGRAM:
    83  			// do nothing
    84  		}
    85  
    86  		pts = append(pts, p.pts...)
    87  	}
    88  	return pts, nil
    89  }
    90  
    91  func (p *prom) gauge() {
    92  	for _, m := range p.metrics {
    93  		p.getValue(m, m.GetGauge())
    94  	}
    95  }
    96  
    97  func (p *prom) untyped() {
    98  	for _, m := range p.metrics {
    99  		p.getValue(m, m.GetUntyped())
   100  	}
   101  }
   102  
   103  func (p *prom) counter() {
   104  	for _, m := range p.metrics {
   105  		p.getValue(m, m.GetCounter())
   106  	}
   107  }
   108  
   109  func (p *prom) summary() {
   110  	for _, m := range p.metrics {
   111  		p.getCountAndSum(m, m.GetSummary())
   112  	}
   113  }
   114  
   115  func (p *prom) histogram() {
   116  	for _, m := range p.metrics {
   117  		p.getCountAndSum(m, m.GetHistogram())
   118  	}
   119  }
   120  
   121  type value interface {
   122  	GetValue() float64
   123  }
   124  
   125  func (p *prom) getValue(m *dto.Metric, v value) {
   126  	if v == nil {
   127  		return
   128  	}
   129  
   130  	tags := labelToTags(m.GetLabel())
   131  	fields := map[string]interface{}{p.metricName: v.GetValue()}
   132  
   133  	pt, err := p.newPoint(tags, fields, m.GetTimestampMs())
   134  	if err != nil {
   135  		return
   136  	}
   137  	p.pts = append(p.pts, pt)
   138  }
   139  
   140  type countAndSum interface {
   141  	GetSampleCount() uint64
   142  	GetSampleSum() float64
   143  }
   144  
   145  func (p *prom) getCountAndSum(m *dto.Metric, c countAndSum) {
   146  	if c == nil {
   147  		return
   148  	}
   149  
   150  	tags := labelToTags(m.GetLabel())
   151  	fields := map[string]interface{}{
   152  		p.metricName + "_count": float64(c.GetSampleCount()),
   153  		p.metricName + "_sum":   c.GetSampleSum(),
   154  	}
   155  
   156  	pt, err := p.newPoint(tags, fields, m.GetTimestampMs())
   157  	if err != nil {
   158  		return
   159  	}
   160  	p.pts = append(p.pts, pt)
   161  }
   162  
   163  func (p *prom) newPoint(tags map[string]string, fields map[string]interface{}, ts int64) (*ifxcli.Point, error) {
   164  	if ts > 0 {
   165  		p.t = time.Unix(0, ts*int64(time.Millisecond))
   166  	}
   167  	var measurement string
   168  	if tags == nil {
   169  		measurement = p.defaultMeasurement
   170  	} else {
   171  		measurement = p.measurement
   172  	}
   173  	return ifxcli.NewPoint(measurement, tags, fields, p.t)
   174  }
   175  
   176  func getMeasurement(name, measurementPrefix string) string {
   177  	nameBlocks := strings.Split(name, "_")
   178  	if len(nameBlocks) > 2 {
   179  		name = strings.Join(nameBlocks[:2], "_")
   180  	}
   181  	if measurementPrefix != "" {
   182  		if !strings.HasPrefix(name, measurementPrefix) {
   183  			name = measurementPrefix + "_" + name
   184  		}
   185  	}
   186  	return name
   187  }
   188  
   189  func labelToTags(label []*dto.LabelPair) map[string]string {
   190  	if len(label) == 0 {
   191  		return nil
   192  	}
   193  	tags := make(map[string]string, len(label))
   194  	for _, lab := range label {
   195  		tags[lab.GetName()] = lab.GetValue()
   196  	}
   197  	return tags
   198  }