1、定义数据采集实体类
首先如下定义数据采集实体类,其中用metricName
注解来标识最终的指标名称,promValueType
用来标识采集的数据类型。值得注意的是用*float
为类型来表示数据的结果,可能存在部分指标没有情况下,如果使用float
无法正确识别数据是否为空。
type Client struct {
ProcessCpu *float64 `metricName:"ClientProcessCpu" promValueType:"Gauge"`
SystemCpu *float64 `metricName:"ClientSystemCpu" promValueType:"Gauge"`
}
通过下列方法,判断数据中是否需要将该指标暴露,并且统一成PromValueRes
的方式进行数据返回
func GetPromValueRes(param interface{}) []PromValueRes {
t := reflect.TypeOf(param)
v := reflect.ValueOf(param)
resList := []PromValueRes{}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
var valueType prometheus.ValueType
promValueType := field.Tag.Get("promValueType")
switch promValueType {
case "Gauge":
valueType = prometheus.GaugeValue
case "Counter":
valueType = prometheus.CounterValue
default:
valueType = prometheus.UntypedValue
}
metricName := field.Tag.Get("metricName")
if metricName == "" || metricName == "-" {
// metricName为空
continue
}
val := v.Field(i)
// 判断字段是否赋值
zero := reflect.Zero(reflect.TypeOf(val.Interface()))
if reflect.DeepEqual(val.Interface(), zero.Interface()) {
// 字段为空
continue
}
valf := val.Elem().Float()
resList = append(resList, PromValueRes{
MetricName: metricName,
ValueType: valueType,
Value: valf,
})
}
return resList
}
type PromValueRes struct {
MetricName string
ValueType prometheus.ValueType
Value float64
}
2、标签处理
同理的,可以对标签也用对象抽象,如下,使用promLabel
标识标签数据名称
type ClientLabel struct {
Os string `promLabel:"os"`
Ver string `promLabel:"ver"`
Ip string `promLabel:"ip"`
}
通过下列方法获取分别获取指标名称,和对应指标值
func GetPromLabelName(param interface{}) []string {
ss := []string{}
t := reflect.TypeOf(param)
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
tag := field.Tag.Get("promLabel")
if tag != "" {
ss = append(ss, tag)
}
}
return ss
}
func GetPromLabelValue(param interface{}) []string {
ss := []string{}
v := reflect.ValueOf(param)
t := reflect.TypeOf(param)
for i := 0; i < v.NumField(); i++ {
val := v.Field(i)
field := t.Field(i)
tag := field.Tag.Get("promLabel")
if tag != "" {
ss = append(ss, val.String())
}
}
return ss
}
3、数据转化
通过上述两个步骤中反射的方法,我们获取Client{}
这个类对应的指标数据和标签名称ClientLabel{}
再对两份数据进行处理,得到最终可以上报接口数据如下:
type PromMetric struct {
Label PromLabel
Value PromValue
// 毫秒
Ts int64
}
type PromLabel interface {
GetLabelName() []string
GetLabelValue() []string
}
type PromValue interface {
GetPromValue() []PromValueRes
}
func (e *Exporter) consumer(ch chan<- prometheus.Metric) {
pm := model.PromMetric{
Label: ClientLabel{
Os: "windows",
Ver: "1.0.0",
Ip: "127.0.0.1",
},
Value: Client{
ProcessCpu: 86.7,
SystemCpu: 59.1,
},
Ts: 1731464540,
}
labels := pm.Label.GetLabelValue()
tm := time.UnixMilli(pm.Ts)
for _, res := range pm.Value.GetPromValue() {
metricName := res.MetricName
if desc, ok := e.metrics[metricName]; ok {
ch <- prometheus.NewMetricWithTimestamp(
tm,
prometheus.MustNewConstMetric(
desc, res.ValueType, res.Value, labels...,
),
)
}
}
}
最终得到Prometheus格式数据如下:
ProcessCpu{Os="windows", Ver="1.0.0", Ip="127.0.0.1"} 86.7
SystemCpu{Os="windows", Ver="1.0.0", Ip="127.0.0.1"} 59.1