mirror of
https://github.com/hanzoai/metrics.git
synced 2026-10-30 20:03:39 +00:00
This increases the parser performance by 4x-6x. This commit uses the technique similar to https://github.com/VictoriaMetrics/VictoriaLogs/pull/720 goos: linux goarch: amd64 pkg: github.com/VictoriaMetrics/VictoriaMetrics/lib/protoparser/opentelemetry/stream cpu: AMD Ryzen 7 PRO 5850U with Radeon Graphics │ old.txt │ new.txt │ │ sec/op │ sec/op vs base │ ParseStream/default-metrics-labels-formatting-16 15.565µ ± 1% 2.150µ ± 3% -86.19% (p=0.000 n=10) ParseStream/prometheus-metrics-labels-formatting-16 24.228µ ± 2% 4.355µ ± 1% -82.02% (p=0.000 n=10) ParseStream/prometheus-metrics-formatting-16 23.028µ ± 2% 3.395µ ± 1% -85.26% (p=0.000 n=10) geomean 20.55µ 3.168µ -84.59% │ old.txt │ new.txt │ │ B/s │ B/s vs base │ ParseStream/default-metrics-labels-formatting-16 127.9Mi ± 1% 918.3Mi ± 3% +617.82% (p=0.000 n=10) ParseStream/prometheus-metrics-labels-formatting-16 82.19Mi ± 2% 453.32Mi ± 1% +451.57% (p=0.000 n=10) ParseStream/prometheus-metrics-formatting-16 86.47Mi ± 2% 581.56Mi ± 1% +572.52% (p=0.000 n=10) geomean 96.88Mi 623.3Mi +543.34% │ old.txt │ new.txt │ │ B/op │ B/op vs base │ ParseStream/default-metrics-labels-formatting-16 12.53Ki ± 0% 0.00Ki ± 0% -100.00% (p=0.000 n=10) ParseStream/prometheus-metrics-labels-formatting-16 21.15Ki ± 1% 0.00Ki ± ? -100.00% (p=0.000 n=10) ParseStream/prometheus-metrics-formatting-16 20.74Ki ± 1% 0.00Ki ± ? -100.00% (p=0.000 n=10) geomean 17.65Ki ? ¹ ² ¹ summaries must be >0 to compute geomean ² ratios must be >0 to compute geomean │ old.txt │ new.txt │ │ allocs/op │ allocs/op vs base │ ParseStream/default-metrics-labels-formatting-16 426.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=10) ParseStream/prometheus-metrics-labels-formatting-16 514.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=10) ParseStream/prometheus-metrics-formatting-16 514.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=10) geomean 482.8 ? ¹ ²
198 lines
4.5 KiB
Go
198 lines
4.5 KiB
Go
package pb
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
|
|
"github.com/VictoriaMetrics/easyproto"
|
|
"github.com/valyala/fastjson"
|
|
)
|
|
|
|
// decodeArrayValueToJSON decodes a protobuf ArrayValue message into a JSON array represented by fastjson.Value.
|
|
func decodeArrayValueToJSON(src []byte, a *fastjson.Arena, fb *fmtBuffer) (*fastjson.Value, error) {
|
|
// message ArrayValue {
|
|
// repeated AnyValue values = 1;
|
|
// }
|
|
|
|
dst := a.NewArray()
|
|
|
|
var fc easyproto.FieldContext
|
|
i := 0
|
|
for len(src) > 0 {
|
|
var err error
|
|
src, err = fc.NextField(src)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot read the next field: %w", err)
|
|
}
|
|
|
|
switch fc.FieldNum {
|
|
case 1:
|
|
data, ok := fc.MessageData()
|
|
if !ok {
|
|
return nil, fmt.Errorf("cannot read Value data")
|
|
}
|
|
|
|
v, err := decodeAnyValueToJSON(data, a, fb)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot decode AnyValue: %w", err)
|
|
}
|
|
dst.SetArrayItem(i, v)
|
|
i++
|
|
}
|
|
}
|
|
|
|
return dst, nil
|
|
}
|
|
|
|
func decodeAnyValueToJSON(src []byte, a *fastjson.Arena, fb *fmtBuffer) (*fastjson.Value, error) {
|
|
// message AnyValue {
|
|
// oneof value {
|
|
// string string_value = 1;
|
|
// bool bool_value = 2;
|
|
// int64 int_value = 3;
|
|
// double double_value = 4;
|
|
// ArrayValue array_value = 5;
|
|
// KeyValueList kvlist_value = 6;
|
|
// bytes bytes_value = 7;
|
|
// }
|
|
// }
|
|
|
|
var fc easyproto.FieldContext
|
|
for len(src) > 0 {
|
|
var err error
|
|
src, err = fc.NextField(src)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot read the next field: %w", err)
|
|
}
|
|
switch fc.FieldNum {
|
|
case 1:
|
|
stringValue, ok := fc.String()
|
|
if !ok {
|
|
return nil, fmt.Errorf("cannot read StringValue")
|
|
}
|
|
return a.NewString(stringValue), nil
|
|
case 2:
|
|
boolValue, ok := fc.Bool()
|
|
if !ok {
|
|
return nil, fmt.Errorf("cannot read BoolValue")
|
|
}
|
|
if boolValue {
|
|
return a.NewTrue(), nil
|
|
} else {
|
|
return a.NewFalse(), nil
|
|
}
|
|
case 3:
|
|
intValue, ok := fc.Int64()
|
|
if !ok {
|
|
return nil, fmt.Errorf("cannot read IntValue")
|
|
}
|
|
if intValue >= math.MinInt && intValue <= math.MaxInt {
|
|
return a.NewNumberInt(int(intValue)), nil
|
|
}
|
|
return a.NewNumberFloat64(float64(intValue)), nil
|
|
case 4:
|
|
doubleValue, ok := fc.Double()
|
|
if !ok {
|
|
return nil, fmt.Errorf("cannot read DoubleValue")
|
|
}
|
|
return a.NewNumberFloat64(doubleValue), nil
|
|
case 5:
|
|
data, ok := fc.MessageData()
|
|
if !ok {
|
|
return nil, fmt.Errorf("cannot read ArrayValue")
|
|
}
|
|
arr, err := decodeArrayValueToJSON(data, a, fb)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot decode ArrayValue: %w", err)
|
|
}
|
|
return arr, nil
|
|
case 6:
|
|
data, ok := fc.MessageData()
|
|
if !ok {
|
|
return nil, fmt.Errorf("cannot read KeyValueList")
|
|
}
|
|
obj, err := decodeKeyValueListToJSON(data, a, fb)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot decode KeyValueList: %w", err)
|
|
}
|
|
return obj, nil
|
|
case 7:
|
|
bytesValue, ok := fc.Bytes()
|
|
if !ok {
|
|
return nil, fmt.Errorf("cannot read BytesValue")
|
|
}
|
|
v := fb.formatBase64(bytesValue)
|
|
return a.NewString(v), nil
|
|
}
|
|
}
|
|
return a.NewNull(), nil
|
|
}
|
|
|
|
func decodeKeyValueListToJSON(src []byte, a *fastjson.Arena, fb *fmtBuffer) (*fastjson.Value, error) {
|
|
// message KeyValueList {
|
|
// repeated KeyValue values = 1;
|
|
// }
|
|
|
|
dst := a.NewObject()
|
|
|
|
var fc easyproto.FieldContext
|
|
for len(src) > 0 {
|
|
var err error
|
|
src, err = fc.NextField(src)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot read the next field: %w", err)
|
|
}
|
|
switch fc.FieldNum {
|
|
case 1:
|
|
data, ok := fc.MessageData()
|
|
if !ok {
|
|
return nil, fmt.Errorf("cannot read Value data")
|
|
}
|
|
|
|
if err := decodeKeyValueToJSON(data, dst, a, fb); err != nil {
|
|
return nil, fmt.Errorf("cannot decode KeyValue: %w", err)
|
|
}
|
|
}
|
|
}
|
|
return dst, nil
|
|
}
|
|
|
|
func decodeKeyValueToJSON(src []byte, dst *fastjson.Value, a *fastjson.Arena, fb *fmtBuffer) error {
|
|
// message KeyValue {
|
|
// string key = 1;
|
|
// AnyValue value = 2;
|
|
// }
|
|
|
|
// Decode key
|
|
fieldName, ok, err := easyproto.GetString(src, 1)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot find Key in KeyValue: %w", err)
|
|
}
|
|
if !ok {
|
|
// Key is missing, skip it.
|
|
// See https://github.com/VictoriaMetrics/VictoriaLogs/issues/869#issuecomment-3631307996
|
|
return nil
|
|
}
|
|
|
|
// Decode value
|
|
valueData, ok, err := easyproto.GetMessageData(src, 2)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot find Value in KeyValue: %w", err)
|
|
}
|
|
if !ok {
|
|
// Value is null, skip it.
|
|
return nil
|
|
}
|
|
|
|
v, err := decodeAnyValueToJSON(valueData, a, fb)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot decode AnyValue: %w", err)
|
|
}
|
|
|
|
dst.Set(fieldName, v)
|
|
|
|
return nil
|
|
}
|
|
|
|
var jsonArenaPool fastjson.ArenaPool
|