textparse: Optimized protobuf parser with custom streaming unmarshal. (#15731)
* textparse: Optimized protobuf parser with custom streaming decoder. Signed-off-by: bwplotka <bwplotka@gmail.com> Update model/textparse/protobufparse.go Co-authored-by: George Krajcsovits <krajorama@users.noreply.github.com> Signed-off-by: Bartlomiej Plotka <bwplotka@gmail.com> Addressing comments. Signed-off-by: bwplotka <bwplotka@gmail.com> decoder: reuse histograms and summaries. Signed-off-by: bwplotka <bwplotka@gmail.com> optimize help returning (5% of mem utilization). Signed-off-by: bwplotka <bwplotka@gmail.com> Apply suggestions from code review Co-authored-by: George Krajcsovits <krajorama@users.noreply.github.com> Signed-off-by: Bartlomiej Plotka <bwplotka@gmail.com> Update prompb/io/prometheus/client/decoder.go Co-authored-by: George Krajcsovits <krajorama@users.noreply.github.com> Signed-off-by: Bartlomiej Plotka <bwplotka@gmail.com> Fix build. Signed-off-by: bwplotka <bwplotka@gmail.com> * Update model/textparse/protobufparse.go Signed-off-by: Bartlomiej Plotka <bwplotka@gmail.com> --------- Signed-off-by: bwplotka <bwplotka@gmail.com> Signed-off-by: Bartlomiej Plotka <bwplotka@gmail.com>pull/16031/head
parent
9b4c8f6be2
commit
733a5e9eb4
@ -0,0 +1,780 @@ |
||||
// Copyright 2025 The Prometheus Authors
|
||||
// Licensed 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 io_prometheus_client //nolint:revive
|
||||
|
||||
import ( |
||||
"encoding/binary" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"unicode/utf8" |
||||
"unsafe" |
||||
|
||||
proto "github.com/gogo/protobuf/proto" |
||||
"github.com/prometheus/common/model" |
||||
|
||||
"github.com/prometheus/prometheus/model/labels" |
||||
) |
||||
|
||||
type MetricStreamingDecoder struct { |
||||
in []byte |
||||
inPos int |
||||
|
||||
// TODO(bwplotka): Switch to generator/plugin that won't have those fields accessible e.g. OpaqueAPI
|
||||
// We leverage the fact those two don't collide.
|
||||
*MetricFamily // Without Metric, guarded by overridden GetMetric method.
|
||||
*Metric // Without Label, guarded by overridden GetLabel method.
|
||||
|
||||
mfData []byte |
||||
metrics []pos |
||||
metricIndex int |
||||
|
||||
mData []byte |
||||
labels []pos |
||||
} |
||||
|
||||
// NewMetricStreamingDecoder returns a Go iterator that unmarshals given protobuf bytes one
|
||||
// metric family and metric at the time, allowing efficient streaming.
|
||||
//
|
||||
// Do not modify MetricStreamingDecoder between iterations as it's reused to save allocations.
|
||||
// GetGauge, GetCounter, etc are also cached, which means GetGauge will work for counter
|
||||
// if previously gauge was parsed. It's up to the caller to use Type to decide what
|
||||
// method to use when checking the value.
|
||||
//
|
||||
// TODO(bwplotka): io.Reader approach is possible too, but textparse has access to whole scrape for now.
|
||||
func NewMetricStreamingDecoder(data []byte) *MetricStreamingDecoder { |
||||
return &MetricStreamingDecoder{ |
||||
in: data, |
||||
MetricFamily: &MetricFamily{}, |
||||
Metric: &Metric{}, |
||||
metrics: make([]pos, 0, 100), |
||||
} |
||||
} |
||||
|
||||
var errInvalidVarint = errors.New("clientpb: invalid varint encountered") |
||||
|
||||
func (m *MetricStreamingDecoder) NextMetricFamily() error { |
||||
b := m.in[m.inPos:] |
||||
if len(b) == 0 { |
||||
return io.EOF |
||||
} |
||||
messageLength, varIntLength := proto.DecodeVarint(b) // TODO(bwplotka): Get rid of gogo.
|
||||
if varIntLength == 0 || varIntLength > binary.MaxVarintLen32 { |
||||
return errInvalidVarint |
||||
} |
||||
totalLength := varIntLength + int(messageLength) |
||||
if totalLength > len(b) { |
||||
return fmt.Errorf("clientpb: insufficient length of buffer, expected at least %d bytes, got %d bytes", totalLength, len(b)) |
||||
} |
||||
m.resetMetricFamily() |
||||
m.mfData = b[varIntLength:totalLength] |
||||
|
||||
m.inPos += totalLength |
||||
return m.MetricFamily.unmarshalWithoutMetrics(m, m.mfData) |
||||
} |
||||
|
||||
// resetMetricFamily resets all the fields in m to equal the zero value, but re-using slice memory.
|
||||
func (m *MetricStreamingDecoder) resetMetricFamily() { |
||||
m.metrics = m.metrics[:0] |
||||
m.metricIndex = 0 |
||||
m.MetricFamily.Reset() |
||||
} |
||||
|
||||
func (m *MetricStreamingDecoder) NextMetric() error { |
||||
if m.metricIndex >= len(m.metrics) { |
||||
return io.EOF |
||||
} |
||||
|
||||
m.resetMetric() |
||||
m.mData = m.mfData[m.metrics[m.metricIndex].start:m.metrics[m.metricIndex].end] |
||||
if err := m.Metric.unmarshalWithoutLabels(m, m.mData); err != nil { |
||||
return err |
||||
} |
||||
m.metricIndex++ |
||||
return nil |
||||
} |
||||
|
||||
// resetMetric resets all the fields in m to equal the zero value, but re-using slices memory.
|
||||
func (m *MetricStreamingDecoder) resetMetric() { |
||||
m.labels = m.labels[:0] |
||||
m.TimestampMs = 0 |
||||
|
||||
// TODO(bwplotka): Autogenerate reset functions.
|
||||
if m.Metric.Counter != nil { |
||||
m.Metric.Counter.Value = 0 |
||||
m.Metric.Counter.CreatedTimestamp = nil |
||||
m.Metric.Counter.Exemplar = nil |
||||
} |
||||
if m.Metric.Gauge != nil { |
||||
m.Metric.Gauge.Value = 0 |
||||
} |
||||
if m.Metric.Histogram != nil { |
||||
m.Metric.Histogram.SampleCount = 0 |
||||
m.Metric.Histogram.SampleCountFloat = 0 |
||||
m.Metric.Histogram.SampleSum = 0 |
||||
m.Metric.Histogram.Bucket = m.Metric.Histogram.Bucket[:0] |
||||
m.Metric.Histogram.CreatedTimestamp = nil |
||||
m.Metric.Histogram.Schema = 0 |
||||
m.Metric.Histogram.ZeroThreshold = 0 |
||||
m.Metric.Histogram.ZeroCount = 0 |
||||
m.Metric.Histogram.ZeroCountFloat = 0 |
||||
m.Metric.Histogram.NegativeSpan = m.Metric.Histogram.NegativeSpan[:0] |
||||
m.Metric.Histogram.NegativeDelta = m.Metric.Histogram.NegativeDelta[:0] |
||||
m.Metric.Histogram.NegativeCount = m.Metric.Histogram.NegativeCount[:0] |
||||
m.Metric.Histogram.PositiveSpan = m.Metric.Histogram.PositiveSpan[:0] |
||||
m.Metric.Histogram.PositiveDelta = m.Metric.Histogram.PositiveDelta[:0] |
||||
m.Metric.Histogram.PositiveCount = m.Metric.Histogram.PositiveCount[:0] |
||||
m.Metric.Histogram.Exemplars = m.Metric.Histogram.Exemplars[:0] |
||||
} |
||||
if m.Metric.Summary != nil { |
||||
m.Metric.Summary.SampleCount = 0 |
||||
m.Metric.Summary.SampleSum = 0 |
||||
m.Metric.Summary.Quantile = m.Metric.Summary.Quantile[:0] |
||||
m.Metric.Summary.CreatedTimestamp = nil |
||||
} |
||||
} |
||||
|
||||
func (m *MetricStreamingDecoder) GetMetric() { |
||||
panic("don't use GetMetric, use Metric directly") |
||||
} |
||||
|
||||
func (m *MetricStreamingDecoder) GetLabel() { |
||||
panic("don't use GetLabel, use Label instead") |
||||
} |
||||
|
||||
// Label parses labels into labels scratch builder. Metric name is missing
|
||||
// given the protobuf metric model and has to be deduced from the metric family name.
|
||||
// TODO: The method name intentionally hide MetricStreamingDecoder.Metric.Label
|
||||
// field to avoid direct use (it's not parsed). In future generator will generate
|
||||
// structs tailored for streaming decoding.
|
||||
func (m *MetricStreamingDecoder) Label(b *labels.ScratchBuilder) error { |
||||
for _, l := range m.labels { |
||||
if err := parseLabel(m.mData[l.start:l.end], b); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// parseLabels is essentially LabelPair.Unmarshal but directly adding into scratch builder
|
||||
// and reusing strings.
|
||||
func parseLabel(dAtA []byte, b *labels.ScratchBuilder) error { |
||||
var name, value string |
||||
l := len(dAtA) |
||||
iNdEx := 0 |
||||
for iNdEx < l { |
||||
preIndex := iNdEx |
||||
var wire uint64 |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
wire |= uint64(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
fieldNum := int32(wire >> 3) |
||||
wireType := int(wire & 0x7) |
||||
if wireType == 4 { |
||||
return errors.New("proto: LabelPair: wiretype end group for non-group") |
||||
} |
||||
if fieldNum <= 0 { |
||||
return fmt.Errorf("proto: LabelPair: illegal tag %d (wire type %d)", fieldNum, wire) |
||||
} |
||||
switch fieldNum { |
||||
case 1: |
||||
if wireType != 2 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) |
||||
} |
||||
var stringLen uint64 |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
stringLen |= uint64(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
intStringLen := int(stringLen) |
||||
if intStringLen < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
postIndex := iNdEx + intStringLen |
||||
if postIndex < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if postIndex > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
name = yoloString(dAtA[iNdEx:postIndex]) |
||||
if !model.LabelName(name).IsValid() { |
||||
return fmt.Errorf("invalid label name: %s", name) |
||||
} |
||||
iNdEx = postIndex |
||||
case 2: |
||||
if wireType != 2 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) |
||||
} |
||||
var stringLen uint64 |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
stringLen |= uint64(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
intStringLen := int(stringLen) |
||||
if intStringLen < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
postIndex := iNdEx + intStringLen |
||||
if postIndex < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if postIndex > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
value = yoloString(dAtA[iNdEx:postIndex]) |
||||
if !utf8.ValidString(value) { |
||||
return fmt.Errorf("invalid label value: %s", value) |
||||
} |
||||
iNdEx = postIndex |
||||
default: |
||||
iNdEx = preIndex |
||||
skippy, err := skipMetrics(dAtA[iNdEx:]) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if (skippy < 0) || (iNdEx+skippy) < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if (iNdEx + skippy) > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
iNdEx += skippy |
||||
} |
||||
} |
||||
if iNdEx > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b.Add(name, value) |
||||
return nil |
||||
} |
||||
|
||||
func yoloString(b []byte) string { |
||||
return unsafe.String(unsafe.SliceData(b), len(b)) |
||||
} |
||||
|
||||
type pos struct { |
||||
start, end int |
||||
} |
||||
|
||||
func (m *Metric) unmarshalWithoutLabels(p *MetricStreamingDecoder, dAtA []byte) error { |
||||
l := len(dAtA) |
||||
iNdEx := 0 |
||||
for iNdEx < l { |
||||
preIndex := iNdEx |
||||
var wire uint64 |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
wire |= uint64(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
fieldNum := int32(wire >> 3) |
||||
wireType := int(wire & 0x7) |
||||
if wireType == 4 { |
||||
return errors.New("proto: Metric: wiretype end group for non-group") |
||||
} |
||||
if fieldNum <= 0 { |
||||
return fmt.Errorf("proto: Metric: illegal tag %d (wire type %d)", fieldNum, wire) |
||||
} |
||||
switch fieldNum { |
||||
case 1: |
||||
if wireType != 2 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Label", wireType) |
||||
} |
||||
var msglen int |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
msglen |= int(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
if msglen < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
postIndex := iNdEx + msglen |
||||
if postIndex < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if postIndex > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
p.labels = append(p.labels, pos{start: iNdEx, end: postIndex}) |
||||
iNdEx = postIndex |
||||
case 2: |
||||
if wireType != 2 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Gauge", wireType) |
||||
} |
||||
var msglen int |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
msglen |= int(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
if msglen < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
postIndex := iNdEx + msglen |
||||
if postIndex < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if postIndex > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
if m.Gauge == nil { |
||||
m.Gauge = &Gauge{} |
||||
} |
||||
if err := m.Gauge.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { |
||||
return err |
||||
} |
||||
iNdEx = postIndex |
||||
case 3: |
||||
if wireType != 2 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Counter", wireType) |
||||
} |
||||
var msglen int |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
msglen |= int(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
if msglen < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
postIndex := iNdEx + msglen |
||||
if postIndex < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if postIndex > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
if m.Counter == nil { |
||||
m.Counter = &Counter{} |
||||
} |
||||
if err := m.Counter.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { |
||||
return err |
||||
} |
||||
iNdEx = postIndex |
||||
case 4: |
||||
if wireType != 2 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Summary", wireType) |
||||
} |
||||
var msglen int |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
msglen |= int(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
if msglen < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
postIndex := iNdEx + msglen |
||||
if postIndex < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if postIndex > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
if m.Summary == nil { |
||||
m.Summary = &Summary{} |
||||
} |
||||
if err := m.Summary.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { |
||||
return err |
||||
} |
||||
iNdEx = postIndex |
||||
case 5: |
||||
if wireType != 2 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Untyped", wireType) |
||||
} |
||||
var msglen int |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
msglen |= int(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
if msglen < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
postIndex := iNdEx + msglen |
||||
if postIndex < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if postIndex > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
if m.Untyped == nil { |
||||
m.Untyped = &Untyped{} |
||||
} |
||||
if err := m.Untyped.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { |
||||
return err |
||||
} |
||||
iNdEx = postIndex |
||||
case 6: |
||||
if wireType != 0 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field TimestampMs", wireType) |
||||
} |
||||
m.TimestampMs = 0 |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
m.TimestampMs |= int64(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
case 7: |
||||
if wireType != 2 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Histogram", wireType) |
||||
} |
||||
var msglen int |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
msglen |= int(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
if msglen < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
postIndex := iNdEx + msglen |
||||
if postIndex < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if postIndex > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
if m.Histogram == nil { |
||||
m.Histogram = &Histogram{} |
||||
} |
||||
if err := m.Histogram.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { |
||||
return err |
||||
} |
||||
iNdEx = postIndex |
||||
default: |
||||
iNdEx = preIndex |
||||
skippy, err := skipMetrics(dAtA[iNdEx:]) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if (skippy < 0) || (iNdEx+skippy) < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if (iNdEx + skippy) > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) |
||||
iNdEx += skippy |
||||
} |
||||
} |
||||
|
||||
if iNdEx > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (m *MetricFamily) unmarshalWithoutMetrics(buf *MetricStreamingDecoder, dAtA []byte) error { |
||||
l := len(dAtA) |
||||
iNdEx := 0 |
||||
for iNdEx < l { |
||||
preIndex := iNdEx |
||||
var wire uint64 |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
wire |= uint64(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
fieldNum := int32(wire >> 3) |
||||
wireType := int(wire & 0x7) |
||||
if wireType == 4 { |
||||
return errors.New("proto: MetricFamily: wiretype end group for non-group") |
||||
} |
||||
if fieldNum <= 0 { |
||||
return fmt.Errorf("proto: MetricFamily: illegal tag %d (wire type %d)", fieldNum, wire) |
||||
} |
||||
switch fieldNum { |
||||
case 1: |
||||
if wireType != 2 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) |
||||
} |
||||
var stringLen uint64 |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
stringLen |= uint64(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
intStringLen := int(stringLen) |
||||
if intStringLen < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
postIndex := iNdEx + intStringLen |
||||
if postIndex < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if postIndex > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
m.Name = yoloString(dAtA[iNdEx:postIndex]) |
||||
iNdEx = postIndex |
||||
case 2: |
||||
if wireType != 2 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Help", wireType) |
||||
} |
||||
var stringLen uint64 |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
stringLen |= uint64(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
intStringLen := int(stringLen) |
||||
if intStringLen < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
postIndex := iNdEx + intStringLen |
||||
if postIndex < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if postIndex > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
m.Help = yoloString(dAtA[iNdEx:postIndex]) |
||||
iNdEx = postIndex |
||||
case 3: |
||||
if wireType != 0 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) |
||||
} |
||||
m.Type = 0 |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
m.Type |= MetricType(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
case 4: |
||||
if wireType != 2 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Metric", wireType) |
||||
} |
||||
var msglen int |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
msglen |= int(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
if msglen < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
postIndex := iNdEx + msglen |
||||
if postIndex < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if postIndex > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
buf.metrics = append(buf.metrics, pos{start: iNdEx, end: postIndex}) |
||||
iNdEx = postIndex |
||||
case 5: |
||||
if wireType != 2 { |
||||
return fmt.Errorf("proto: wrong wireType = %d for field Unit", wireType) |
||||
} |
||||
var stringLen uint64 |
||||
for shift := uint(0); ; shift += 7 { |
||||
if shift >= 64 { |
||||
return ErrIntOverflowMetrics |
||||
} |
||||
if iNdEx >= l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
b := dAtA[iNdEx] |
||||
iNdEx++ |
||||
stringLen |= uint64(b&0x7F) << shift |
||||
if b < 0x80 { |
||||
break |
||||
} |
||||
} |
||||
intStringLen := int(stringLen) |
||||
if intStringLen < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
postIndex := iNdEx + intStringLen |
||||
if postIndex < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if postIndex > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
m.Unit = yoloString(dAtA[iNdEx:postIndex]) |
||||
iNdEx = postIndex |
||||
default: |
||||
iNdEx = preIndex |
||||
skippy, err := skipMetrics(dAtA[iNdEx:]) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if (skippy < 0) || (iNdEx+skippy) < 0 { |
||||
return ErrInvalidLengthMetrics |
||||
} |
||||
if (iNdEx + skippy) > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...) |
||||
iNdEx += skippy |
||||
} |
||||
} |
||||
|
||||
if iNdEx > l { |
||||
return io.ErrUnexpectedEOF |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,171 @@ |
||||
// Copyright 2025 The Prometheus Authors
|
||||
// Licensed 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 io_prometheus_client //nolint:revive
|
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/binary" |
||||
"errors" |
||||
"io" |
||||
"testing" |
||||
|
||||
"github.com/gogo/protobuf/proto" |
||||
"github.com/stretchr/testify/require" |
||||
|
||||
"github.com/prometheus/prometheus/model/labels" |
||||
) |
||||
|
||||
const ( |
||||
testGauge = `name: "go_build_info" |
||||
help: "Build information about the main Go module." |
||||
type: GAUGE |
||||
metric: < |
||||
label: < |
||||
name: "checksum" |
||||
value: "" |
||||
> |
||||
label: < |
||||
name: "path" |
||||
value: "github.com/prometheus/client_golang" |
||||
> |
||||
label: < |
||||
name: "version" |
||||
value: "(devel)" |
||||
> |
||||
gauge: < |
||||
value: 1 |
||||
> |
||||
> |
||||
metric: < |
||||
label: < |
||||
name: "checksum" |
||||
value: "" |
||||
> |
||||
label: < |
||||
name: "path" |
||||
value: "github.com/prometheus/prometheus" |
||||
> |
||||
label: < |
||||
name: "version" |
||||
value: "v3.0.0" |
||||
> |
||||
gauge: < |
||||
value: 2 |
||||
> |
||||
> |
||||
|
||||
` |
||||
testCounter = `name: "go_memstats_alloc_bytes_total" |
||||
help: "Total number of bytes allocated, even if freed." |
||||
type: COUNTER |
||||
unit: "bytes" |
||||
metric: < |
||||
counter: < |
||||
value: 1.546544e+06 |
||||
exemplar: < |
||||
label: < |
||||
name: "dummyID" |
||||
value: "42" |
||||
> |
||||
value: 12 |
||||
timestamp: < |
||||
seconds: 1625851151 |
||||
nanos: 233181499 |
||||
> |
||||
> |
||||
> |
||||
> |
||||
|
||||
` |
||||
) |
||||
|
||||
func TestMetricStreamingDecoder(t *testing.T) { |
||||
varintBuf := make([]byte, binary.MaxVarintLen32) |
||||
buf := bytes.Buffer{} |
||||
for _, m := range []string{testGauge, testCounter} { |
||||
mf := &MetricFamily{} |
||||
require.NoError(t, proto.UnmarshalText(m, mf)) |
||||
// From proto message to binary protobuf.
|
||||
protoBuf, err := proto.Marshal(mf) |
||||
require.NoError(t, err) |
||||
|
||||
// Write first length, then binary protobuf.
|
||||
varintLength := binary.PutUvarint(varintBuf, uint64(len(protoBuf))) |
||||
buf.Write(varintBuf[:varintLength]) |
||||
buf.Write(protoBuf) |
||||
} |
||||
|
||||
d := NewMetricStreamingDecoder(buf.Bytes()) |
||||
require.NoError(t, d.NextMetricFamily()) |
||||
nextFn := func() error { |
||||
for { |
||||
err := d.NextMetric() |
||||
if errors.Is(err, io.EOF) { |
||||
if err := d.NextMetricFamily(); err != nil { |
||||
return err |
||||
} |
||||
continue |
||||
} |
||||
return err |
||||
} |
||||
} |
||||
|
||||
var firstMetricLset labels.Labels |
||||
{ |
||||
require.NoError(t, nextFn()) |
||||
|
||||
require.Equal(t, "go_build_info", d.GetName()) |
||||
require.Equal(t, "Build information about the main Go module.", d.GetHelp()) |
||||
require.Equal(t, MetricType_GAUGE, d.GetType()) |
||||
|
||||
require.Equal(t, float64(1), d.GetGauge().GetValue()) |
||||
b := labels.NewScratchBuilder(0) |
||||
require.NoError(t, d.Label(&b)) |
||||
|
||||
firstMetricLset = b.Labels() |
||||
|
||||
require.Equal(t, `{checksum="", path="github.com/prometheus/client_golang", version="(devel)"}`, firstMetricLset.String()) |
||||
} |
||||
|
||||
{ |
||||
require.NoError(t, nextFn()) |
||||
|
||||
require.Equal(t, "go_build_info", d.GetName()) |
||||
require.Equal(t, "Build information about the main Go module.", d.GetHelp()) |
||||
require.Equal(t, MetricType_GAUGE, d.GetType()) |
||||
|
||||
require.Equal(t, float64(2), d.GetGauge().GetValue()) |
||||
b := labels.NewScratchBuilder(0) |
||||
require.NoError(t, d.Label(&b)) |
||||
require.Equal(t, `{checksum="", path="github.com/prometheus/prometheus", version="v3.0.0"}`, b.Labels().String()) |
||||
} |
||||
{ |
||||
// Different mf now.
|
||||
require.NoError(t, nextFn()) |
||||
|
||||
require.Equal(t, "go_memstats_alloc_bytes_total", d.GetName()) |
||||
require.Equal(t, "Total number of bytes allocated, even if freed.", d.GetHelp()) |
||||
require.Equal(t, "bytes", d.GetUnit()) |
||||
require.Equal(t, MetricType_COUNTER, d.GetType()) |
||||
|
||||
require.Equal(t, 1.546544e+06, d.Metric.GetCounter().GetValue()) |
||||
b := labels.NewScratchBuilder(0) |
||||
require.NoError(t, d.Label(&b)) |
||||
require.Equal(t, `{}`, b.Labels().String()) |
||||
} |
||||
require.Equal(t, io.EOF, nextFn()) |
||||
|
||||
// Expect labels and metricBytes to be static and reusable even after parsing.
|
||||
require.Equal(t, `{checksum="", path="github.com/prometheus/client_golang", version="(devel)"}`, firstMetricLset.String()) |
||||
} |
Loading…
Reference in new issue