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 }