summaryrefslogtreecommitdiffstats
path: root/library/cpp/monlib
diff options
context:
space:
mode:
authorkgershov <[email protected]>2026-04-06 16:46:20 +0300
committerkgershov <[email protected]>2026-04-06 17:08:32 +0300
commitee9a12d94efbb3b7bb3e5395fa0b01f724c4d215 (patch)
tree7468ea854b171ac0cc66f3b3c09bea56c0d81eda /library/cpp/monlib
parentf86fc7fae5939cfb2fc83a842cd8043cffcbb9a3 (diff)
[library/cpp/monlib] add v3 spack encode/decode
commit_hash:91ce0e2023d29ef78fadeaded314ea236f316b8e
Diffstat (limited to 'library/cpp/monlib')
-rw-r--r--library/cpp/monlib/encode/buffered/string_pool.h7
-rw-r--r--library/cpp/monlib/encode/spack/spack_v1.h10
-rw-r--r--library/cpp/monlib/encode/spack/spack_v1_decoder.cpp69
-rw-r--r--library/cpp/monlib/encode/spack/spack_v1_encoder.cpp48
-rw-r--r--library/cpp/monlib/encode/spack/spack_v1_ut.cpp412
5 files changed, 520 insertions, 26 deletions
diff --git a/library/cpp/monlib/encode/buffered/string_pool.h b/library/cpp/monlib/encode/buffered/string_pool.h
index b7ab01d2a11..4c41b82258d 100644
--- a/library/cpp/monlib/encode/buffered/string_pool.h
+++ b/library/cpp/monlib/encode/buffered/string_pool.h
@@ -78,6 +78,13 @@ namespace NMonitoring {
InitIndex(data, size);
}
+ TStringPool(const char* data, const TVector<std::pair<ui32, ui32>>& segments) {
+ Index_.reserve(segments.size());
+ for (const auto& [offset, length] : segments) {
+ Index_.emplace_back(data + offset, length);
+ }
+ }
+
TStringBuf Get(ui32 i) const {
return Index_.at(i);
}
diff --git a/library/cpp/monlib/encode/spack/spack_v1.h b/library/cpp/monlib/encode/spack/spack_v1.h
index cf1c9417b90..ce516626e31 100644
--- a/library/cpp/monlib/encode/spack/spack_v1.h
+++ b/library/cpp/monlib/encode/spack/spack_v1.h
@@ -92,7 +92,8 @@ namespace NMonitoring {
enum ESpackV1Version: ui16 {
SV1_00 = 0x0100,
SV1_01 = 0x0101,
- SV1_02 = 0x0102
+ SV1_02 = 0x0102,
+ SV1_03 = 0x0103
};
IMetricEncoderPtr EncoderSpackV1(
@@ -110,6 +111,13 @@ namespace NMonitoring {
TStringBuf metricNameLabel = "name"
);
+ IMetricEncoderPtr EncoderSpackV13(
+ IOutputStream* out,
+ ETimePrecision timePrecision,
+ ECompression compression,
+ EMetricsMergingMode mergingMode = EMetricsMergingMode::DEFAULT
+ );
+
void DecodeSpackV1(IInputStream* in, IMetricConsumer* c, TStringBuf metricNameLabel = "name");
}
diff --git a/library/cpp/monlib/encode/spack/spack_v1_decoder.cpp b/library/cpp/monlib/encode/spack/spack_v1_decoder.cpp
index a236bb9a509..7d04aa0d555 100644
--- a/library/cpp/monlib/encode/spack/spack_v1_decoder.cpp
+++ b/library/cpp/monlib/encode/spack/spack_v1_decoder.cpp
@@ -62,22 +62,48 @@ namespace NMonitoring {
TimePrecision_ = DecodeTimePrecision(Header_.TimePrecision);
- const ui64 labelSizeTotal = ui64(Header_.LabelNamesSize) + Header_.LabelValuesSize;
+ // (2) read string pools
+ TVector<char> namesBuf;
+ TVector<char> valuesBuf;
+
+ if (Header_.Version == SV1_03) {
+ auto namesResult = ReadLengthDelimitedStringPool(Header_.LabelNamesSize);
+ namesBuf = std::move(namesResult.first);
+ auto valuesResult = ReadLengthDelimitedStringPool(Header_.LabelValuesSize);
+ valuesBuf = std::move(valuesResult.first);
+
+ TStringPool labelNames(namesBuf.data(), namesResult.second);
+ TStringPool labelValues(valuesBuf.data(), valuesResult.second);
+
+ DecodeBody(labelNames, labelValues, c);
+ return;
+ }
+ const ui64 labelSizeTotal = ui64(Header_.LabelNamesSize) + Header_.LabelValuesSize;
DECODE_ENSURE(labelSizeTotal <= LABEL_SIZE_LIMIT, "Label names & values size of " << HumanReadableSize(labelSizeTotal, SF_BYTES)
<< " exceeds the limit which is " << HumanReadableSize(LABEL_SIZE_LIMIT, SF_BYTES));
- // (2) read string pools
- TVector<char> namesBuf(Header_.LabelNamesSize);
+ namesBuf.resize(Header_.LabelNamesSize);
readBytes = In_->Load(namesBuf.data(), namesBuf.size());
DECODE_ENSURE(readBytes == Header_.LabelNamesSize, "not enough data to read label names pool");
- TStringPool labelNames(namesBuf.data(), namesBuf.size());
+ {
+ TStringPool labelNames(namesBuf.data(), namesBuf.size());
+
+ valuesBuf.resize(Header_.LabelValuesSize);
+ readBytes = In_->Load(valuesBuf.data(), valuesBuf.size());
+ DECODE_ENSURE(readBytes == Header_.LabelValuesSize, "not enough data to read label values pool");
+ TStringPool labelValues(valuesBuf.data(), valuesBuf.size());
- TVector<char> valuesBuf(Header_.LabelValuesSize);
- readBytes = In_->Load(valuesBuf.data(), valuesBuf.size());
- DECODE_ENSURE(readBytes == Header_.LabelValuesSize, "not enough data to read label values pool");
- TStringPool labelValues(valuesBuf.data(), valuesBuf.size());
+ DecodeBody(labelNames, labelValues, c);
+ }
+ }
+ private:
+ void DecodeBody(
+ const TStringPool& labelNames,
+ const TStringPool& labelValues,
+ IMetricConsumer* c)
+ {
// (3) read common time
c->OnCommonTime(ReadTime());
@@ -93,7 +119,6 @@ namespace NMonitoring {
c->OnStreamEnd();
}
- private:
void ReadMetrics(
const TStringPool& labelNames,
const TStringPool& labelValues,
@@ -111,15 +136,15 @@ namespace NMonitoring {
c->OnMemOnly(ReadFixed<ui8>() & 0x01);
auto metricNameValueIndex = std::numeric_limits<ui32>::max();
- if (Header_.Version >= SV1_02) {
+ if (Header_.Version == SV1_02) {
metricNameValueIndex = ReadVarint();
}
// (5.3) labels
ui32 labelsCount = ReadVarint();
- DECODE_ENSURE(Header_.Version >= SV1_02 || labelsCount > 0, "metric #" << i << " has no labels");
+ DECODE_ENSURE(Header_.Version == SV1_02 || labelsCount > 0, "metric #" << i << " has no labels");
c->OnLabelsBegin();
- if (Header_.Version >= SV1_02) {
+ if (Header_.Version == SV1_02) {
c->OnLabel(MetricNameLabel_, labelValues.Get(metricNameValueIndex));
}
ReadLabels(labelNames, labelValues, labelsCount, c);
@@ -273,6 +298,26 @@ namespace NMonitoring {
return ReadVarUInt32(In_);
}
+ std::pair<TVector<char>, TVector<std::pair<ui32, ui32>>> ReadLengthDelimitedStringPool(ui32 count) {
+ TVector<char> storage;
+ TVector<std::pair<ui32, ui32>> segments;
+ segments.reserve(count);
+
+ for (ui32 i = 0; i < count; i++) {
+ ui32 len = ReadVarint();
+ ui32 offset = static_cast<ui32>(storage.size());
+ storage.resize(offset + len);
+ if (len > 0) {
+ size_t readBytes = In_->Load(storage.data() + offset, len);
+ DECODE_ENSURE(readBytes == len, "not enough data to read string #" << i
+ << " (expected " << len << " bytes, got " << readBytes << ")");
+ }
+ segments.emplace_back(offset, len);
+ }
+
+ return {std::move(storage), std::move(segments)};
+ }
+
private:
IInputStream* In_;
TString MetricNameLabel_;
diff --git a/library/cpp/monlib/encode/spack/spack_v1_encoder.cpp b/library/cpp/monlib/encode/spack/spack_v1_encoder.cpp
index 70c5bba5512..ac9934b0593 100644
--- a/library/cpp/monlib/encode/spack/spack_v1_encoder.cpp
+++ b/library/cpp/monlib/encode/spack/spack_v1_encoder.cpp
@@ -31,7 +31,7 @@ namespace NMonitoring {
, TimePrecision_(timePrecision)
, Compression_(compression)
, Version_(version)
- , MetricName_(Version_ >= SV1_02 ? LabelNamesPool_.PutIfAbsent(metricNameLabel) : nullptr)
+ , MetricName_(Version_ == SV1_02 ? LabelNamesPool_.PutIfAbsent(metricNameLabel) : nullptr)
{
MetricsMergingMode_ = mergingMode;
@@ -96,10 +96,15 @@ namespace NMonitoring {
header.Version = Version_;
header.TimePrecision = EncodeTimePrecision(TimePrecision_);
header.Compression = EncodeCompression(Compression_);
- header.LabelNamesSize = static_cast<ui32>(
- LabelNamesPool_.BytesSize() + LabelNamesPool_.Count());
- header.LabelValuesSize = static_cast<ui32>(
- LabelValuesPool_.BytesSize() + LabelValuesPool_.Count());
+ if (Version_ == SV1_03) {
+ header.LabelNamesSize = static_cast<ui32>(LabelNamesPool_.Count());
+ header.LabelValuesSize = static_cast<ui32>(LabelValuesPool_.Count());
+ } else {
+ header.LabelNamesSize = static_cast<ui32>(
+ LabelNamesPool_.BytesSize() + LabelNamesPool_.Count());
+ header.LabelValuesSize = static_cast<ui32>(
+ LabelValuesPool_.BytesSize() + LabelValuesPool_.Count());
+ }
header.MetricCount = Metrics_.size();
header.PointsCount = pointsCount;
Out_->Write(&header, sizeof(header));
@@ -111,13 +116,21 @@ namespace NMonitoring {
}
// (2) write string pools
- auto strPoolWrite = [this](TStringBuf str, ui32, ui32) {
- Out_->Write(str);
- Out_->Write('\0');
- };
-
- LabelNamesPool_.ForEach(strPoolWrite);
- LabelValuesPool_.ForEach(strPoolWrite);
+ if (Version_ == SV1_03) {
+ auto strPoolWrite = [this](TStringBuf str, ui32, ui32) {
+ WriteVarUInt32(Out_, static_cast<ui32>(str.size()));
+ Out_->Write(str);
+ };
+ LabelNamesPool_.ForEach(strPoolWrite);
+ LabelValuesPool_.ForEach(strPoolWrite);
+ } else {
+ auto strPoolWrite = [this](TStringBuf str, ui32, ui32) {
+ Out_->Write(str);
+ Out_->Write('\0');
+ };
+ LabelNamesPool_.ForEach(strPoolWrite);
+ LabelValuesPool_.ForEach(strPoolWrite);
+ }
// (3) write common time
WriteTime(CommonTime_);
@@ -137,7 +150,7 @@ namespace NMonitoring {
Out_->Write(&flagsByte, sizeof(flagsByte));
// v1.2 format addition — metric name
- if (Version_ >= SV1_02) {
+ if (Version_ == SV1_02) {
const auto it = FindIf(metric.Labels, [&](const auto& l) {
return l.Key == MetricName_;
});
@@ -319,4 +332,13 @@ namespace NMonitoring {
Y_ENSURE(!metricNameLabel.empty(), "metricNameLabel can't be empty");
return MakeHolder<TEncoderSpackV1>(out, timePrecision, compression, mergingMode, SV1_02, metricNameLabel);
}
+
+ IMetricEncoderPtr EncoderSpackV13(
+ IOutputStream* out,
+ ETimePrecision timePrecision,
+ ECompression compression,
+ EMetricsMergingMode mergingMode
+ ) {
+ return MakeHolder<TEncoderSpackV1>(out, timePrecision, compression, mergingMode, SV1_03, "");
+ }
}
diff --git a/library/cpp/monlib/encode/spack/spack_v1_ut.cpp b/library/cpp/monlib/encode/spack/spack_v1_ut.cpp
index bbc3b8712b9..2a75f441f2d 100644
--- a/library/cpp/monlib/encode/spack/spack_v1_ut.cpp
+++ b/library/cpp/monlib/encode/spack/spack_v1_ut.cpp
@@ -2,6 +2,7 @@
#include <library/cpp/monlib/encode/protobuf/protobuf.h>
#include <library/cpp/monlib/metrics/labels.h>
+#include <library/cpp/monlib/metrics/histogram_snapshot.h>
#include <library/cpp/monlib/metrics/metric.h>
#include <library/cpp/testing/unittest/registar.h>
@@ -811,6 +812,417 @@ Y_UNIT_TEST_SUITE(TSpackTest) {
}
}
+ Y_UNIT_TEST(SimpleV13) {
+ ui8 expectedSerialized[] = {
+ // header
+ 0x53, 0x50, // magic "SP" (fixed ui16)
+ // minor, major
+ 0x03, 0x01, // version (fixed ui16)
+ 0x18, 0x00, // header size (fixed ui16)
+ 0x00, // time precision (fixed ui8)
+ 0x00, // compression algorithm (fixed ui8)
+ 0x02, 0x00, 0x00, 0x00, // label names count (fixed ui32) -- 2 names: "project", "name"
+ 0x03, 0x00, 0x00, 0x00, // label values count (fixed ui32) -- 3 values: "solomon", "temperature", "speed"
+ 0x02, 0x00, 0x00, 0x00, // metric count (fixed ui32)
+ 0x02, 0x00, 0x00, 0x00, // points count (fixed ui32)
+
+ // string pools (length-delimited)
+ // label names (sorted by frequency: "name" appears 2x, "project" 1x)
+ 0x04, 0x6e, 0x61, 0x6d, 0x65, // varint(4) + "name"
+ 0x07, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, // varint(7) + "project"
+ // label values (sorted by frequency: all appear 1x, insertion order preserved)
+ 0x07, 0x73, 0x6f, 0x6c, 0x6f, 0x6d, 0x6f, 0x6e, // varint(7) + "solomon"
+ 0x0b, 0x74, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, // varint(11) + "temperature"
+ 0x74, 0x75, 0x72, 0x65,
+ 0x05, 0x73, 0x70, 0x65, 0x65, 0x64, // varint(5) + "speed"
+
+ // common time
+ 0x00, 0x2f, 0x68, 0x59, // common time in seconds (fixed ui32)
+
+ // common labels
+ 0x01, // common labels count (varint)
+ 0x01, // label name index (project) (varint)
+ 0x00, // label value index (solomon) (varint)
+
+ // metric 1: COUNTER, ONE_WITHOUT_TS
+ 0x09, // types (COUNTER | ONE_WITHOUT_TS) (fixed ui8)
+ 0x00, // flags (fixed ui8)
+ // no metric name index (v1_3 does not have it)
+ 0x01, // metric labels count (varint)
+ 0x00, // label name index (name) (varint)
+ 0x01, // label value index (temperature) (varint)
+ 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // value 17 (fixed ui64)
+
+ // metric 2: GAUGE, ONE_WITH_TS
+ 0x06, // types (GAUGE | ONE_WITH_TS) (fixed ui8)
+ 0x00, // flags (fixed ui8)
+ 0x01, // metric labels count (varint)
+ 0x00, // label name index (name) (varint)
+ 0x02, // label value index (speed) (varint)
+ 0x0b, 0x63, 0xfe, 0x59, // time in seconds (fixed ui32)
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x40, // value 42.0 (double IEEE754)
+ };
+
+ // encode
+ {
+ TBuffer actualSerialized;
+ {
+ TBufferOutput out(actualSerialized);
+ auto e = EncoderSpackV13(
+ &out,
+ ETimePrecision::SECONDS,
+ ECompression::IDENTITY);
+
+ e->OnStreamBegin();
+ e->OnCommonTime(TInstant::Seconds(1500000000));
+ {
+ e->OnLabelsBegin();
+ e->OnLabel("project", "solomon");
+ e->OnLabelsEnd();
+ }
+
+ {
+ e->OnMetricBegin(EMetricType::COUNTER);
+ {
+ e->OnLabelsBegin();
+ e->OnLabel("name", "temperature");
+ e->OnLabelsEnd();
+ }
+ e->OnUint64(TInstant::Zero(), 17);
+ e->OnMetricEnd();
+ }
+
+ {
+ e->OnMetricBegin(EMetricType::GAUGE);
+ {
+ e->OnLabelsBegin();
+ e->OnLabel("name", "speed");
+ e->OnLabelsEnd();
+ }
+ e->OnDouble(now, 42);
+ e->OnMetricEnd();
+ }
+
+ e->OnStreamEnd();
+ e->Close();
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(actualSerialized.Size(), Y_ARRAY_SIZE(expectedSerialized));
+ UNIT_ASSERT_BINARY_EQUALS(actualSerialized.Data(), expectedSerialized);
+ }
+
+ // decode
+ {
+ NProto::TMultiSamplesList samples;
+ {
+ auto input = TMemoryInput(expectedSerialized, Y_ARRAY_SIZE(expectedSerialized));
+ auto encoder = EncoderProtobuf(&samples);
+ DecodeSpackV1(&input, encoder.Get());
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(TInstant::MilliSeconds(samples.GetCommonTime()),
+ TInstant::Seconds(1500000000));
+
+ UNIT_ASSERT_VALUES_EQUAL(samples.CommonLabelsSize(), 1);
+ AssertLabelEqual(samples.GetCommonLabels(0), "project", "solomon");
+
+ UNIT_ASSERT_VALUES_EQUAL(samples.SamplesSize(), 2);
+ {
+ const auto& s = samples.GetSamples(0);
+ UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::COUNTER);
+ UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
+ AssertLabelEqual(s.GetLabels(0), "name", "temperature");
+ UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
+ AssertPointEqual(s.GetPoints(0), TInstant::Zero(), ui64(17));
+ }
+ {
+ const auto& s = samples.GetSamples(1);
+ UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE);
+ UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
+ AssertLabelEqual(s.GetLabels(0), "name", "speed");
+ UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
+ AssertPointEqual(s.GetPoints(0), now, double(42));
+ }
+ }
+ }
+
+ // Ported from Java MetricSpackEncoderTest.doEncodeDecode
+ void V13EncodeDecodeImpl(ETimePrecision timePrecision, ECompression compression) {
+ const TInstant commonTime = TInstant::Seconds(1500000000);
+ const TInstant alignedNow = (timePrecision == ETimePrecision::SECONDS)
+ ? TInstant::Seconds(now.Seconds())
+ : now;
+
+ // label value with embedded null bytes (v1_3 feature)
+ TStringBuilder nullLabelBuilder;
+ nullLabelBuilder << "thįs";
+ nullLabelBuilder << '\0';
+ nullLabelBuilder << "īs";
+ nullLabelBuilder << '\0';
+ nullLabelBuilder << "fïne";
+
+ TString nullLabel = nullLabelBuilder;
+ UNIT_ASSERT_VALUES_EQUAL(nullLabel.size(), 15u);
+
+ TBuffer buffer;
+ {
+ TBufferOutput out(buffer);
+ auto e = EncoderSpackV13(&out, timePrecision, compression);
+
+ e->OnStreamBegin();
+ e->OnCommonTime(commonTime);
+ {
+ e->OnLabelsBegin();
+ e->OnLabel("project", "solomon");
+ e->OnLabel("cluster", "production");
+ e->OnLabel("service", "stockpile");
+ e->OnLabelsEnd();
+ }
+ { // metric #1 (no values)
+ e->OnMetricBegin(EMetricType::GAUGE);
+ {
+ e->OnLabelsBegin();
+ e->OnLabel("sensor", "q1");
+ e->OnLabelsEnd();
+ }
+ e->OnMetricEnd();
+ }
+ { // metric #2 (one value without ts)
+ e->OnMetricBegin(EMetricType::COUNTER);
+ {
+ e->OnLabelsBegin();
+ e->OnLabel("sensor", "q2");
+ e->OnLabelsEnd();
+ }
+ e->OnUint64(TInstant::Zero(), 42);
+ e->OnMetricEnd();
+ }
+ { // metric #3 (one value with ts)
+ e->OnMetricBegin(EMetricType::GAUGE);
+ {
+ e->OnLabelsBegin();
+ e->OnLabel("sensor", "q3");
+ e->OnLabelsEnd();
+ }
+ e->OnDouble(alignedNow + TDuration::Seconds(5), 3.14159);
+ e->OnMetricEnd();
+ }
+ { // metric #4 (many values with ts)
+ e->OnMetricBegin(EMetricType::COUNTER);
+ {
+ e->OnLabelsBegin();
+ e->OnLabel("sensor", "q4");
+ e->OnLabelsEnd();
+ }
+ e->OnUint64(alignedNow + TDuration::Seconds(5), 43);
+ e->OnUint64(alignedNow + TDuration::Seconds(10), 44);
+ e->OnUint64(alignedNow + TDuration::Seconds(15), 45);
+ e->OnMetricEnd();
+ }
+ { // metric #5 (igauge)
+ e->OnMetricBegin(EMetricType::IGAUGE);
+ {
+ e->OnLabelsBegin();
+ e->OnLabel("sensor", "q5");
+ e->OnLabelsEnd();
+ }
+ e->OnInt64(alignedNow, 46);
+ e->OnMetricEnd();
+ }
+ { // metric #6 (hist_rate)
+ e->OnMetricBegin(EMetricType::HIST_RATE);
+ {
+ e->OnLabelsBegin();
+ e->OnLabel("sensor", "q6");
+ e->OnLabelsEnd();
+ }
+ auto h = ExplicitHistogramSnapshot({10, 20, 30, Max<double>()}, {0, 1, 0, 100});
+ e->OnHistogram(alignedNow, h);
+ e->OnMetricEnd();
+ }
+ { // metric #7 (hist, many values)
+ e->OnMetricBegin(EMetricType::HIST);
+ {
+ e->OnLabelsBegin();
+ e->OnLabel("sensor", "q7");
+ e->OnLabelsEnd();
+ }
+ {
+ auto h = ExplicitHistogramSnapshot({10, 20, 30, Max<double>()}, {0, 1, 0, 13});
+ e->OnHistogram(alignedNow + TDuration::Seconds(5), h);
+ }
+ {
+ auto h = ExplicitHistogramSnapshot({10, 20, 30, Max<double>()}, {1, 0, 2, 42});
+ e->OnHistogram(alignedNow + TDuration::Seconds(10), h);
+ }
+ e->OnMetricEnd();
+ }
+ { // metric #8 (counter with embedded null bytes in label)
+ e->OnMetricBegin(EMetricType::COUNTER);
+ {
+ e->OnLabelsBegin();
+ e->OnLabel("sensor", nullLabel);
+ e->OnLabelsEnd();
+ }
+ e->OnUint64(TInstant::Zero(), 1234);
+ e->OnMetricEnd();
+ }
+ { // metric #9 (mem-only)
+ e->OnMetricBegin(EMetricType::COUNTER);
+ {
+ e->OnLabelsBegin();
+ e->OnLabel("sensor", "aggregate");
+ e->OnLabelsEnd();
+ }
+ e->OnUint64(TInstant::Zero(), 42);
+ e->OnMemOnly(true);
+ e->OnMetricEnd();
+ }
+ e->OnStreamEnd();
+ e->Close();
+ }
+
+ // verify header
+ auto* header = reinterpret_cast<const TSpackHeader*>(buffer.Data());
+ UNIT_ASSERT_VALUES_EQUAL(header->Version, static_cast<ui16>(SV1_03));
+ UNIT_ASSERT_VALUES_EQUAL(DecodeCompression(header->Compression), compression);
+ UNIT_ASSERT_VALUES_EQUAL(header->MetricCount, 9u);
+ UNIT_ASSERT_VALUES_EQUAL(header->PointsCount, 11u);
+
+ // decode and verify
+ NProto::TMultiSamplesList samples;
+ {
+ IMetricEncoderPtr e = EncoderProtobuf(&samples);
+ TBufferInput in(buffer);
+ DecodeSpackV1(&in, e.Get());
+ }
+
+ UNIT_ASSERT_VALUES_EQUAL(
+ TInstant::MilliSeconds(samples.GetCommonTime()),
+ commonTime);
+
+ UNIT_ASSERT_VALUES_EQUAL(samples.CommonLabelsSize(), 3);
+ AssertLabelEqual(samples.GetCommonLabels(0), "project", "solomon");
+ AssertLabelEqual(samples.GetCommonLabels(1), "cluster", "production");
+ AssertLabelEqual(samples.GetCommonLabels(2), "service", "stockpile");
+
+ UNIT_ASSERT_VALUES_EQUAL(samples.SamplesSize(), 9);
+ { // #1 no values
+ const auto& s = samples.GetSamples(0);
+ UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE);
+ UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
+ AssertLabelEqual(s.GetLabels(0), "sensor", "q1");
+ UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 0);
+ }
+ { // #2 one value without ts
+ const auto& s = samples.GetSamples(1);
+ UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::COUNTER);
+ UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
+ AssertLabelEqual(s.GetLabels(0), "sensor", "q2");
+ UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
+ AssertPointEqual(s.GetPoints(0), TInstant::Zero(), ui64(42));
+ }
+ { // #3 one value with ts
+ const auto& s = samples.GetSamples(2);
+ UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE);
+ UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
+ AssertLabelEqual(s.GetLabels(0), "sensor", "q3");
+ UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
+ AssertPointEqual(s.GetPoints(0), alignedNow + TDuration::Seconds(5), 3.14159);
+ }
+ { // #4 many values with ts
+ const auto& s = samples.GetSamples(3);
+ UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::COUNTER);
+ UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
+ AssertLabelEqual(s.GetLabels(0), "sensor", "q4");
+ UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 3);
+ AssertPointEqual(s.GetPoints(0), alignedNow + TDuration::Seconds(5), ui64(43));
+ AssertPointEqual(s.GetPoints(1), alignedNow + TDuration::Seconds(10), ui64(44));
+ AssertPointEqual(s.GetPoints(2), alignedNow + TDuration::Seconds(15), ui64(45));
+ }
+ { // #5 igauge
+ const auto& s = samples.GetSamples(4);
+ UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::IGAUGE);
+ UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
+ AssertLabelEqual(s.GetLabels(0), "sensor", "q5");
+ UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
+ AssertPointEqual(s.GetPoints(0), alignedNow, i64(46));
+ }
+ { // #6 hist_rate
+ const auto& s = samples.GetSamples(5);
+ UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::HIST_RATE);
+ UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
+ AssertLabelEqual(s.GetLabels(0), "sensor", "q6");
+ UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
+
+ const auto& h = s.GetPoints(0).GetHistogram();
+ UNIT_ASSERT_VALUES_EQUAL(h.BoundsSize(), 4u);
+ UNIT_ASSERT_VALUES_EQUAL(h.ValuesSize(), 4u);
+ }
+ { // #7 hist, many
+ const auto& s = samples.GetSamples(6);
+ UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::HISTOGRAM);
+ UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
+ AssertLabelEqual(s.GetLabels(0), "sensor", "q7");
+ UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 2);
+ }
+ { // #8 embedded null bytes in label
+ const auto& s = samples.GetSamples(7);
+ UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::COUNTER);
+ UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
+ UNIT_ASSERT_STRINGS_EQUAL(s.GetLabels(0).GetName(), "sensor");
+ UNIT_ASSERT_VALUES_EQUAL(s.GetLabels(0).GetValue().size(), nullLabel.size());
+ UNIT_ASSERT_VALUES_EQUAL(s.GetLabels(0).GetValue(), nullLabel);
+ UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
+ AssertPointEqual(s.GetPoints(0), TInstant::Zero(), ui64(1234));
+ }
+ { // #9 mem-only
+ const auto& s = samples.GetSamples(8);
+ UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::COUNTER);
+ UNIT_ASSERT_EQUAL(s.GetIsMemOnly(), true);
+ UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
+ AssertLabelEqual(s.GetLabels(0), "sensor", "aggregate");
+ UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
+ AssertPointEqual(s.GetPoints(0), TInstant::Zero(), ui64(42));
+ }
+ }
+
+ Y_UNIT_TEST(V13EncodeDecodeSeconds) {
+ V13EncodeDecodeImpl(ETimePrecision::SECONDS, ECompression::IDENTITY);
+ }
+
+ Y_UNIT_TEST(V13EncodeDecodeMillis) {
+ V13EncodeDecodeImpl(ETimePrecision::MILLIS, ECompression::IDENTITY);
+ }
+
+ Y_UNIT_TEST(V13EncodeDecodeZlib) {
+ V13EncodeDecodeImpl(ETimePrecision::SECONDS, ECompression::ZLIB);
+ }
+
+ Y_UNIT_TEST(V13EncodeDecodeZstd) {
+ V13EncodeDecodeImpl(ETimePrecision::SECONDS, ECompression::ZSTD);
+ }
+
+ Y_UNIT_TEST(V13EncodeDecodeLz4) {
+ V13EncodeDecodeImpl(ETimePrecision::SECONDS, ECompression::LZ4);
+ }
+
+ Y_UNIT_TEST(V13EmptyStream) {
+ TBuffer buffer;
+ {
+ TBufferOutput out(buffer);
+ auto e = EncoderSpackV13(&out, ETimePrecision::SECONDS, ECompression::IDENTITY);
+ e->OnStreamBegin();
+ e->OnStreamEnd();
+ e->Close();
+ }
+
+ auto* header = reinterpret_cast<const TSpackHeader*>(buffer.Data());
+ UNIT_ASSERT_VALUES_EQUAL(header->Version, static_cast<ui16>(SV1_03));
+ UNIT_ASSERT_VALUES_EQUAL(header->MetricCount, 0u);
+ UNIT_ASSERT_VALUES_EQUAL(header->PointsCount, 0u);
+ }
+
Y_UNIT_TEST(V12MissingNameForOneMetric) {
TBuffer b;
TBufferOutput out(b);