github.com/alibaba/ilogtail/pkg@v0.0.0-20250526110833-c53b480d046c/protocol/decoder/prometheus/decoder.go (about)

     1  // Copyright 2021 iLogtail Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package prometheus
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io"
    21  	"net/http"
    22  	"strings"
    23  
    24  	"github.com/gogo/protobuf/proto"
    25  	"github.com/prometheus/common/expfmt"
    26  	"github.com/prometheus/common/model"
    27  	"github.com/prometheus/prometheus/prompb"
    28  	"github.com/richardartoul/molecule"
    29  	"github.com/richardartoul/molecule/src/codec"
    30  
    31  	"github.com/alibaba/ilogtail/pkg/helper"
    32  	"github.com/alibaba/ilogtail/pkg/models"
    33  	"github.com/alibaba/ilogtail/pkg/protocol"
    34  	"github.com/alibaba/ilogtail/pkg/protocol/decoder/common"
    35  )
    36  
    37  const (
    38  	metricNameKey = "__name__"
    39  	labelsKey     = "__labels__"
    40  	timeNanoKey   = "__time_nano__"
    41  	valueKey      = "__value__"
    42  	tagDB         = "__tag__:db"
    43  	metaDBKey     = "db"
    44  )
    45  
    46  const (
    47  	contentEncodingKey = "Content-Encoding"
    48  	contentTypeKey     = "Content-Type"
    49  	pbContentType      = "application/x-protobuf"
    50  	snappyEncoding     = "snappy"
    51  )
    52  
    53  // field index of the proto message models
    54  // ref: https://github.com/prometheus/prometheus/blob/cb045c0e4b94bbf3eee174d91b5ef2b8553948d5/prompb/types.proto#L123
    55  const (
    56  	promPbFieldIndexTimeSeries      = 1
    57  	promPbFieldIndexLabels          = 1
    58  	promPbFieldIndexSamples         = 2
    59  	promPbFieldIndexLabelName       = 1
    60  	promPbFieldIndexLabelValue      = 2
    61  	promPbFieldIndexSampleValue     = 1
    62  	promPbFieldIndexSampleTimestamp = 2
    63  )
    64  
    65  // Decoder impl
    66  type Decoder struct {
    67  	AllowUnsafeMode bool
    68  }
    69  
    70  // Decode impl
    71  func (d *Decoder) Decode(data []byte, req *http.Request, tags map[string]string) (logs []*protocol.Log, err error) {
    72  	if req.Header.Get(contentEncodingKey) == snappyEncoding &&
    73  		strings.HasPrefix(req.Header.Get(contentTypeKey), pbContentType) {
    74  		return d.decodeInRemoteWriteFormat(data, req)
    75  	}
    76  	return d.decodeInExpFmt(data, req)
    77  }
    78  
    79  func (d *Decoder) decodeInExpFmt(data []byte, _ *http.Request) (logs []*protocol.Log, err error) {
    80  	decoder := expfmt.NewDecoder(bytes.NewReader(data), expfmt.FmtText)
    81  	sampleDecoder := expfmt.SampleDecoder{
    82  		Dec: decoder,
    83  		Opts: &expfmt.DecodeOptions{
    84  			Timestamp: model.Now(),
    85  		},
    86  	}
    87  
    88  	for {
    89  		s := &model.Vector{}
    90  		if err = sampleDecoder.Decode(s); err != nil {
    91  			if err == io.EOF {
    92  				err = nil
    93  				break
    94  			}
    95  			break
    96  		}
    97  		for _, sample := range *s {
    98  			var name string
    99  			var labels helper.MetricLabels
   100  			labelsSet := (model.LabelSet)(sample.Metric)
   101  			for k, v := range labelsSet {
   102  				if k == model.MetricNameLabel {
   103  					name = string(v)
   104  					continue
   105  				}
   106  				labels.Append(string(k), string(v))
   107  			}
   108  			metricLog := helper.NewMetricLog(name, sample.Timestamp.UnixNano(), float64(sample.Value), &labels)
   109  			logs = append(logs, metricLog)
   110  		}
   111  	}
   112  
   113  	return logs, err
   114  }
   115  
   116  func (d *Decoder) ParseRequest(res http.ResponseWriter, req *http.Request, maxBodySize int64) (data []byte, statusCode int, err error) {
   117  	return common.CollectBody(res, req, maxBodySize)
   118  }
   119  
   120  func (d *Decoder) decodeInRemoteWriteFormat(data []byte, req *http.Request) (logs []*protocol.Log, err error) {
   121  	var metrics prompb.WriteRequest
   122  	err = proto.Unmarshal(data, &metrics)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	db := req.FormValue(metaDBKey)
   128  	for _, m := range metrics.Timeseries {
   129  		var metricName string
   130  		var labels helper.MetricLabels
   131  		for _, label := range m.Labels {
   132  			if label.Name == model.MetricNameLabel {
   133  				metricName = label.Value
   134  				continue
   135  			}
   136  			labels.Append(label.Name, label.Value)
   137  		}
   138  		for _, sample := range m.Samples {
   139  			metricLog := helper.NewMetricLog(metricName, sample.Timestamp, sample.Value, &labels)
   140  			if len(db) > 0 {
   141  				metricLog.Contents = append(metricLog.Contents, &protocol.Log_Content{
   142  					Key:   tagDB,
   143  					Value: db,
   144  				})
   145  			}
   146  			logs = append(logs, metricLog)
   147  		}
   148  	}
   149  
   150  	return logs, nil
   151  }
   152  
   153  func (d *Decoder) DecodeV2(data []byte, req *http.Request) (groups []*models.PipelineGroupEvents, err error) {
   154  	if len(data) == 0 {
   155  		return nil, nil
   156  	}
   157  
   158  	meta := models.NewMetadata()
   159  	commonTags := models.NewTags()
   160  	if db := req.FormValue(metaDBKey); len(db) > 0 {
   161  		meta.Add(metaDBKey, db)
   162  	}
   163  
   164  	if req.Header.Get(contentEncodingKey) == snappyEncoding &&
   165  		strings.HasPrefix(req.Header.Get(contentTypeKey), pbContentType) {
   166  		var groupEvents *models.PipelineGroupEvents
   167  		if d.AllowUnsafeMode {
   168  			groupEvents, err = ParsePromPbToPipelineGroupEventsUnsafe(data, meta, commonTags)
   169  		} else {
   170  			groupEvents, err = ConvertPromRequestToPipelineGroupEvents(data, meta, commonTags)
   171  		}
   172  		if err != nil {
   173  			return nil, err
   174  		}
   175  		return []*models.PipelineGroupEvents{groupEvents}, nil
   176  	}
   177  
   178  	return ConvertExpFmtDataToPipelineGroupEvents(data, meta, commonTags)
   179  }
   180  
   181  func ConvertExpFmtDataToPipelineGroupEvents(data []byte, metaInfo models.Metadata, commonTags models.Tags) (pg []*models.PipelineGroupEvents, err error) {
   182  	decoder := expfmt.NewDecoder(bytes.NewReader(data), expfmt.FmtText)
   183  	sampleDecoder := expfmt.SampleDecoder{
   184  		Dec: decoder,
   185  		Opts: &expfmt.DecodeOptions{
   186  			Timestamp: model.Now(),
   187  		},
   188  	}
   189  
   190  	for {
   191  		s := model.Vector{}
   192  		if err = sampleDecoder.Decode(&s); err != nil {
   193  			if err == io.EOF {
   194  				err = nil
   195  				break
   196  			}
   197  			break
   198  		}
   199  		groupEvents, err := ConvertVectorToPipelineGroupEvents(&s, metaInfo, commonTags)
   200  		if err == nil {
   201  			pg = append(pg, groupEvents)
   202  		}
   203  	}
   204  
   205  	return
   206  }
   207  
   208  func ConvertVectorToPipelineGroupEvents(s *model.Vector, metaInfo models.Metadata, commonTags models.Tags) (*models.PipelineGroupEvents, error) {
   209  	groupEvent := &models.PipelineGroupEvents{
   210  		Group: models.NewGroup(metaInfo, commonTags),
   211  	}
   212  
   213  	for _, sample := range *s {
   214  		metricName, tags := parseModelMetric(sample.Metric)
   215  		metric := models.NewSingleValueMetric(
   216  			metricName,
   217  			models.MetricTypeGauge,
   218  			tags,
   219  			sample.Timestamp.UnixNano(),
   220  			sample.Value,
   221  		)
   222  		groupEvent.Events = append(groupEvent.Events, metric)
   223  	}
   224  
   225  	return groupEvent, nil
   226  }
   227  
   228  func parseModelMetric(metric model.Metric) (string, models.Tags) {
   229  	var metricName string
   230  	tags := models.NewTags()
   231  	labels := (model.LabelSet)(metric)
   232  
   233  	for k, v := range labels {
   234  		if k == model.MetricNameLabel {
   235  			metricName = string(v)
   236  			continue
   237  		}
   238  		tags.Add(string(k), string(v))
   239  	}
   240  
   241  	return metricName, tags
   242  }
   243  
   244  func ConvertPromRequestToPipelineGroupEvents(data []byte, metaInfo models.Metadata, commonTags models.Tags) (*models.PipelineGroupEvents, error) {
   245  	var promRequest prompb.WriteRequest
   246  	err := proto.Unmarshal(data, &promRequest)
   247  	if err != nil {
   248  		return nil, err
   249  	}
   250  
   251  	groupEvent := &models.PipelineGroupEvents{
   252  		Group: models.NewGroup(metaInfo, commonTags),
   253  	}
   254  
   255  	for _, ts := range promRequest.Timeseries {
   256  		var metricName string
   257  		tags := models.NewTags()
   258  		for _, label := range ts.Labels {
   259  			if label.Name == metricNameKey {
   260  				metricName = label.Value
   261  				continue
   262  			}
   263  			tags.Add(label.Name, label.Value)
   264  		}
   265  
   266  		for _, dataPoint := range ts.Samples {
   267  			metric := models.NewSingleValueMetric(
   268  				metricName,
   269  				models.MetricTypeGauge,
   270  				tags,
   271  				model.Time(dataPoint.GetTimestamp()).Time().UnixNano(),
   272  				dataPoint.GetValue(),
   273  			)
   274  			groupEvent.Events = append(groupEvent.Events, metric)
   275  		}
   276  	}
   277  
   278  	return groupEvent, nil
   279  }
   280  
   281  func ParsePromPbToPipelineGroupEventsUnsafe(data []byte, metaInfo models.Metadata, commonTags models.Tags) (*models.PipelineGroupEvents, error) {
   282  	groupEvent := &models.PipelineGroupEvents{
   283  		Group: models.NewGroup(metaInfo, commonTags),
   284  	}
   285  
   286  	buffer := codec.NewBuffer(data)
   287  	err1 := molecule.MessageEach(buffer, func(fieldNum int32, v molecule.Value) (bool, error) {
   288  		if fieldNum == promPbFieldIndexTimeSeries {
   289  			serieBytes, err2 := v.AsBytesUnsafe()
   290  			if err2 != nil {
   291  				return false, err2
   292  			}
   293  
   294  			var metricName string
   295  			metricTags := models.NewTags()
   296  
   297  			serieBuffer := codec.NewBuffer(serieBytes)
   298  			err2 = molecule.MessageEach(serieBuffer, func(fieldNum int32, v molecule.Value) (bool, error) {
   299  				switch fieldNum {
   300  				case promPbFieldIndexLabels: // Labels
   301  					labelBytes, err3 := v.AsBytesUnsafe()
   302  					if err3 != nil {
   303  						return false, err3
   304  					}
   305  
   306  					var name, value string
   307  
   308  					labelBuffer := codec.NewBuffer(labelBytes)
   309  					err3 = molecule.MessageEach(labelBuffer, func(fieldNum int32, v molecule.Value) (bool, error) {
   310  						var err4 error
   311  						switch fieldNum {
   312  						case promPbFieldIndexLabelName: // Name
   313  							name, err4 = v.AsStringUnsafe()
   314  							if err4 != nil {
   315  								return false, err4
   316  							}
   317  						case promPbFieldIndexLabelValue: // Value
   318  							value, err4 = v.AsStringUnsafe()
   319  							if err4 != nil {
   320  								return false, err4
   321  							}
   322  						}
   323  						return true, nil
   324  					})
   325  					if err3 != nil {
   326  						return false, err3
   327  					}
   328  
   329  					if name == metricNameKey {
   330  						metricName = value
   331  					} else {
   332  						metricTags.Add(name, value)
   333  					}
   334  				case promPbFieldIndexSamples: // Samples
   335  					sampleBytes, err3 := v.AsBytesUnsafe()
   336  					if err3 != nil {
   337  						return false, err3
   338  					}
   339  
   340  					var value float64
   341  					var timestamp int64
   342  
   343  					sampleBuffer := codec.NewBuffer(sampleBytes)
   344  					err3 = molecule.MessageEach(sampleBuffer, func(fieldNum int32, v molecule.Value) (bool, error) {
   345  						var err4 error
   346  						switch fieldNum {
   347  						case promPbFieldIndexSampleValue: // Value
   348  							value, err4 = v.AsDouble()
   349  							if err4 != nil {
   350  								return false, err4
   351  							}
   352  						case promPbFieldIndexSampleTimestamp: // Timestamp
   353  							timestamp, err4 = v.AsInt64()
   354  							if err4 != nil {
   355  								return false, err4
   356  							}
   357  						}
   358  						return true, nil
   359  					})
   360  					if err3 != nil {
   361  						return false, err3
   362  					}
   363  
   364  					if metricName == "" {
   365  						return false, fmt.Errorf("fields in mix order")
   366  					}
   367  
   368  					metric := models.NewSingleValueMetric(
   369  						metricName,
   370  						models.MetricTypeGauge,
   371  						metricTags,
   372  						timestamp*1e6,
   373  						value,
   374  					)
   375  					groupEvent.Events = append(groupEvent.Events, metric)
   376  				}
   377  				return true, nil
   378  			})
   379  			if err2 != nil {
   380  				return false, err2
   381  			}
   382  		}
   383  		return true, nil
   384  	})
   385  	if err1 != nil {
   386  		return nil, err1
   387  	}
   388  	return groupEvent, nil
   389  }