#include "json.h"
#include <library/cpp/monlib/encode/protobuf/protobuf.h>
#include <library/cpp/monlib/metrics/labels.h>
#include <library/cpp/json/json_reader.h>
#include <library/cpp/resource/resource.h>
#include <library/cpp/testing/unittest/registar.h>
#include <util/stream/str.h>
#include <util/string/builder.h>
#include <limits>
using namespace NMonitoring;
namespace NMonitoring {
bool operator<(const TLabel& lhs, const TLabel& rhs) {
return lhs.Name() < rhs.Name() ||
(lhs.Name() == rhs.Name() && lhs.Value() < rhs.Value());
}
}
namespace {
void AssertLabels(const NProto::TMultiSample& actual, const TLabels& expected) {
UNIT_ASSERT_EQUAL(actual.LabelsSize(), expected.Size());
TSet<TLabel> actualSet;
TSet<TLabel> expectedSet;
Transform(expected.begin(), expected.end(), std::inserter(expectedSet, expectedSet.end()), [] (auto&& l) {
return TLabel{l.Name(), l.Value()};
});
const auto& l = actual.GetLabels();
Transform(std::begin(l), std::end(l), std::inserter(actualSet, std::begin(actualSet)),
[](auto&& elem) -> TLabel {
return {elem.GetName(), elem.GetValue()};
});
TVector<TLabel> diff;
SetSymmetricDifference(std::begin(expectedSet), std::end(expectedSet),
std::begin(actualSet), std::end(actualSet), std::back_inserter(diff));
if (diff.size() > 0) {
for (auto&& l : diff) {
Cerr << l << Endl;
}
UNIT_FAIL("Labels don't match");
}
}
void AssertLabelEqual(const NProto::TLabel& l, TStringBuf name, TStringBuf value) {
UNIT_ASSERT_STRINGS_EQUAL(l.GetName(), name);
UNIT_ASSERT_STRINGS_EQUAL(l.GetValue(), value);
}
void AssertPointEqual(const NProto::TPoint& p, TInstant time, double value) {
UNIT_ASSERT_VALUES_EQUAL(p.GetTime(), time.MilliSeconds());
UNIT_ASSERT_EQUAL(p.GetValueCase(), NProto::TPoint::kFloat64);
UNIT_ASSERT_DOUBLES_EQUAL(p.GetFloat64(), value, std::numeric_limits<double>::epsilon());
}
void AssertPointEqualNan(const NProto::TPoint& p, TInstant time) {
UNIT_ASSERT_VALUES_EQUAL(p.GetTime(), time.MilliSeconds());
UNIT_ASSERT_EQUAL(p.GetValueCase(), NProto::TPoint::kFloat64);
UNIT_ASSERT(std::isnan(p.GetFloat64()));
}
void AssertPointEqualInf(const NProto::TPoint& p, TInstant time, int sign) {
UNIT_ASSERT_VALUES_EQUAL(p.GetTime(), time.MilliSeconds());
UNIT_ASSERT_EQUAL(p.GetValueCase(), NProto::TPoint::kFloat64);
UNIT_ASSERT(std::isinf(p.GetFloat64()));
if (sign < 0) {
UNIT_ASSERT(p.GetFloat64() < 0);
}
}
void AssertPointEqual(const NProto::TPoint& p, TInstant time, ui64 value) {
UNIT_ASSERT_VALUES_EQUAL(p.GetTime(), time.MilliSeconds());
UNIT_ASSERT_EQUAL(p.GetValueCase(), NProto::TPoint::kUint64);
UNIT_ASSERT_VALUES_EQUAL(p.GetUint64(), value);
}
void AssertPointEqual(const NProto::TPoint& p, TInstant time, i64 value) {
UNIT_ASSERT_VALUES_EQUAL(p.GetTime(), time.MilliSeconds());
UNIT_ASSERT_EQUAL(p.GetValueCase(), NProto::TPoint::kInt64);
UNIT_ASSERT_VALUES_EQUAL(p.GetInt64(), value);
}
void AssertPointEqual(const NProto::TPoint& p, TInstant time, const IHistogramSnapshot& expected) {
UNIT_ASSERT_VALUES_EQUAL(p.GetTime(), time.MilliSeconds());
UNIT_ASSERT_EQUAL(p.GetValueCase(), NProto::TPoint::kHistogram);
const NProto::THistogram& h = p.GetHistogram();
UNIT_ASSERT_VALUES_EQUAL(h.BoundsSize(), expected.Count());
UNIT_ASSERT_VALUES_EQUAL(h.ValuesSize(), expected.Count());
for (size_t i = 0; i < h.BoundsSize(); i++) {
UNIT_ASSERT_DOUBLES_EQUAL(h.GetBounds(i), expected.UpperBound(i), Min<double>());
UNIT_ASSERT_VALUES_EQUAL(h.GetValues(i), expected.Value(i));
}
}
void AssertPointEqual(const NProto::TPoint& p, TInstant time, const TLogHistogramSnapshot& expected) {
UNIT_ASSERT_VALUES_EQUAL(p.GetTime(), time.MilliSeconds());
UNIT_ASSERT_EQUAL(p.GetValueCase(), NProto::TPoint::kLogHistogram);
const double eps = 1e-10;
const NProto::TLogHistogram& h = p.GetLogHistogram();
UNIT_ASSERT_DOUBLES_EQUAL(h.GetBase(), expected.Base(), eps);
UNIT_ASSERT_VALUES_EQUAL(h.GetZerosCount(), expected.ZerosCount());
UNIT_ASSERT_VALUES_EQUAL(h.GetStartPower(), expected.StartPower());
UNIT_ASSERT_VALUES_EQUAL(h.BucketsSize(), expected.Count());
for (size_t i = 0; i < expected.Count(); ++i) {
UNIT_ASSERT_DOUBLES_EQUAL(h.GetBuckets(i), expected.Bucket(i), eps);
}
}
void AssertPointEqual(const NProto::TPoint& p, TInstant time, const ISummaryDoubleSnapshot& expected) {
UNIT_ASSERT_VALUES_EQUAL(p.GetTime(), time.MilliSeconds());
UNIT_ASSERT_EQUAL(p.GetValueCase(), NProto::TPoint::kSummaryDouble);
auto actual = p.GetSummaryDouble();
const double eps = 1e-10;
UNIT_ASSERT_DOUBLES_EQUAL(actual.GetSum(), expected.GetSum(), eps);
UNIT_ASSERT_DOUBLES_EQUAL(actual.GetMin(), expected.GetMin(), eps);
UNIT_ASSERT_DOUBLES_EQUAL(actual.GetMax(), expected.GetMax(), eps);
UNIT_ASSERT_DOUBLES_EQUAL(actual.GetLast(), expected.GetLast(), eps);
UNIT_ASSERT_VALUES_EQUAL(actual.GetCount(), expected.GetCount());
}
} // namespace
Y_UNIT_TEST_SUITE(TJsonTest) {
const TInstant now = TInstant::ParseIso8601Deprecated("2017-11-05T01:02:03Z");
Y_UNIT_TEST(Encode) {
auto check = [](bool cloud, bool buffered, TStringBuf expectedResourceKey) {
TString json;
TStringOutput out(json);
auto e = cloud
? (buffered ? BufferedEncoderCloudJson(&out, 2, "metric") : EncoderCloudJson(&out, 2, "metric"))
: (buffered ? BufferedEncoderJson(&out, 2) : EncoderJson(&out, 2));
e->OnStreamBegin();
{ // common time
e->OnCommonTime(TInstant::Seconds(1500000000));
}
{ // common labels
e->OnLabelsBegin();
e->OnLabel("project", "solomon");
e->OnLabelsEnd();
}
{ // metric #1
e->OnMetricBegin(EMetricType::COUNTER);
{
e->OnLabelsBegin();
e->OnLabel("metric", "single");
e->OnLabel("labels", "l1");
e->OnLabelsEnd();
}
e->OnUint64(now, 17);
e->OnMetricEnd();
}
{ // metric #2
e->OnMetricBegin(EMetricType::RATE);
{
e->OnLabelsBegin();
e->OnLabel("metric", "single");
e->OnLabel("labels", "l2");
e->OnLabelsEnd();
}
e->OnUint64(now, 17);
e->OnMetricEnd();
}
{ // metric #3
e->OnMetricBegin(EMetricType::GAUGE);
e->OnDouble(now, 3.14);
{
e->OnLabelsBegin();
e->OnLabel("metric", "single");
e->OnLabel("labels", "l3");
e->OnLabelsEnd();
}
e->OnMetricEnd();
}
{ // metric #4
e->OnMetricBegin(EMetricType::IGAUGE);
e->OnInt64(now, 42);
{
e->OnLabelsBegin();
e->OnLabel("metric", "single_igauge");
e->OnLabel("labels", "l4");
e->OnLabelsEnd();
}
e->OnMetricEnd();
}
{ // metric #5
e->OnMetricBegin(EMetricType::GAUGE);
{
e->OnLabelsBegin();
e->OnLabel("metric", "multiple");
e->OnLabel("labels", "l5");
e->OnLabelsEnd();
}
e->OnDouble(now, std::numeric_limits<double>::quiet_NaN());
e->OnDouble(now + TDuration::Seconds(15), std::numeric_limits<double>::infinity());
e->OnDouble(now + TDuration::Seconds(30), -std::numeric_limits<double>::infinity());
e->OnMetricEnd();
}
{ // metric #6
e->OnMetricBegin(EMetricType::COUNTER);
e->OnUint64(now, 1337);
e->OnUint64(now + TDuration::Seconds(15), 1338);
{
e->OnLabelsBegin();
e->OnLabel("metric", "multiple");
e->OnLabel("labels", "l6");
e->OnLabelsEnd();
}
e->OnMetricEnd();
}
e->OnStreamEnd();
e->Close();
json += "\n";
auto parseJson = [] (auto buf) {
NJson::TJsonValue value;
NJson::ReadJsonTree(buf, &value, true);
return value;
};
const auto expectedJson = NResource::Find(expectedResourceKey);
UNIT_ASSERT_EQUAL(parseJson(json), parseJson(expectedJson));
};
check(false, false, "/expected.json");
check(false, true, "/expected_buffered.json");
check(true, false, "/expected_cloud.json");
check(true, true, "/expected_cloud_buffered.json");
}
TLogHistogramSnapshotPtr TestLogHistogram(ui32 v = 1) {
TVector<double> buckets{0.5 * v, 0.25 * v, 0.25 * v, 0.5 * v};
return MakeIntrusive<TLogHistogramSnapshot>(1.5, 1u, 0, std::move(buckets));
}
Y_UNIT_TEST(HistogramAndSummaryMetricTypesAreNotSupportedByCloudJson) {
const TInstant now = TInstant::ParseIso8601Deprecated("2017-11-05T01:02:03Z");
auto emit = [&](IMetricEncoder* encoder, EMetricType metricType) {
encoder->OnStreamBegin();
{
encoder->OnMetricBegin(metricType);
{
encoder->OnLabelsBegin();
encoder->OnLabel("name", "m");
encoder->OnLabelsEnd();
}
switch (metricType) {
case EMetricType::HIST: {
auto histogram = ExponentialHistogram(6, 2);
encoder->OnHistogram(now, histogram->Snapshot());
break;
}
case EMetricType::LOGHIST: {
auto histogram = TestLogHistogram();
encoder->OnLogHistogram(now, histogram);
break;
}
case EMetricType::DSUMMARY: {
auto summary = MakeIntrusive<TSummaryDoubleSnapshot>(10., -0.5, 0.5, 0.3, 30u);
encoder->OnSummaryDouble(now, summary);
break;
}
default:
Y_FAIL("unexpected metric type [%s]", ToString(metricType).c_str());
}
encoder->OnMetricEnd();
}
encoder->OnStreamEnd();
encoder->Close();
};
auto doTest = [&](bool buffered, EMetricType metricType) {
TString json;
TStringOutput out(json);
auto encoder = buffered ? BufferedEncoderCloudJson(&out, 2) : EncoderCloudJson(&out, 2);
const TString expectedMessage = TStringBuilder()
<< "metric type '" << metricType << "' is not supported by cloud json format";
UNIT_ASSERT_EXCEPTION_CONTAINS_C(emit(encoder.Get(), metricType), yexception, expectedMessage,
TString("buffered: ") + ToString(buffered));
};
doTest(false, EMetricType::HIST);
doTest(false, EMetricType::LOGHIST);
doTest(false, EMetricType::DSUMMARY);
doTest(true, EMetricType::HIST);
doTest(true, EMetricType::LOGHIST);
doTest(true, EMetricType::DSUMMARY);
}
Y_UNIT_TEST(MetricsWithDifferentLabelOrderGetMerged) {
TString json;
TStringOutput out(json);
auto e = BufferedEncoderJson(&out, 2);
e->OnStreamBegin();
{
e->OnMetricBegin(EMetricType::RATE);
{
e->OnLabelsBegin();
e->OnLabel("metric", "hello");
e->OnLabel("label", "world");
e->OnLabelsEnd();
}
e->OnUint64(TInstant::Zero(), 0);
e->OnMetricEnd();
}
{
e->OnMetricBegin(EMetricType::RATE);
{
e->OnLabelsBegin();
e->OnLabel("label", "world");
e->OnLabel("metric", "hello");
e->OnLabelsEnd();
}
e->OnUint64(TInstant::Zero(), 1);
e->OnMetricEnd();
}
e->OnStreamEnd();
e->Close();
json += "\n";
TString expectedJson = NResource::Find("/merged.json");
// we cannot be sure regarding the label order in the result,
// so we'll have to parse the expected value and then compare it with actual
NProto::TMultiSamplesList samples;
IMetricEncoderPtr d = EncoderProtobuf(&samples);
DecodeJson(expectedJson, d.Get());
UNIT_ASSERT_VALUES_EQUAL(samples.SamplesSize(), 1);
{
const NProto::TMultiSample& s = samples.GetSamples(0);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::RATE);
AssertLabels(s, TLabels{{"metric", "hello"}, {"label", "world"}});
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
AssertPointEqual(s.GetPoints(0), TInstant::Zero(), ui64(1));
}
}
Y_UNIT_TEST(Decode1) {
NProto::TMultiSamplesList samples;
{
IMetricEncoderPtr e = EncoderProtobuf(&samples);
TString testJson = NResource::Find("/expected.json");
DecodeJson(testJson, e.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(), 6);
{
const NProto::TMultiSample& s = samples.GetSamples(0);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::COUNTER);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 2);
AssertLabelEqual(s.GetLabels(0), "metric", "single");
AssertLabelEqual(s.GetLabels(1), "labels", "l1");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
AssertPointEqual(s.GetPoints(0), now, ui64(17));
}
{
const NProto::TMultiSample& s = samples.GetSamples(1);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::RATE);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 2);
AssertLabelEqual(s.GetLabels(0), "metric", "single");
AssertLabelEqual(s.GetLabels(1), "labels", "l2");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
AssertPointEqual(s.GetPoints(0), now, ui64(17));
}
{
const NProto::TMultiSample& s = samples.GetSamples(2);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 2);
AssertLabelEqual(s.GetLabels(0), "metric", "single");
AssertLabelEqual(s.GetLabels(1), "labels", "l3");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
AssertPointEqual(s.GetPoints(0), now, 3.14);
}
{
const NProto::TMultiSample& s = samples.GetSamples(3);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::IGAUGE);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 2);
AssertLabelEqual(s.GetLabels(0), "metric", "single_igauge");
AssertLabelEqual(s.GetLabels(1), "labels", "l4");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
AssertPointEqual(s.GetPoints(0), now, i64(42));
}
{
const NProto::TMultiSample& s = samples.GetSamples(4);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 2);
AssertLabelEqual(s.GetLabels(0), "metric", "multiple");
AssertLabelEqual(s.GetLabels(1), "labels", "l5");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 3);
AssertPointEqualNan(s.GetPoints(0), now);
AssertPointEqualInf(s.GetPoints(1), now + TDuration::Seconds(15), 1);
AssertPointEqualInf(s.GetPoints(2), now + TDuration::Seconds(30), -11);
}
{
const NProto::TMultiSample& s = samples.GetSamples(5);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::COUNTER);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 2);
AssertLabelEqual(s.GetLabels(0), "metric", "multiple");
AssertLabelEqual(s.GetLabels(1), "labels", "l6");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 2);
AssertPointEqual(s.GetPoints(0), now, ui64(1337));
AssertPointEqual(s.GetPoints(1), now + TDuration::Seconds(15), ui64(1338));
}
}
Y_UNIT_TEST(DecodeMetrics) {
NProto::TMultiSamplesList samples;
{
IMetricEncoderPtr e = EncoderProtobuf(&samples);
TString metricsJson = NResource::Find("/metrics.json");
DecodeJson(metricsJson, e.Get());
}
UNIT_ASSERT_VALUES_EQUAL(
TInstant::MilliSeconds(samples.GetCommonTime()),
TInstant::ParseIso8601Deprecated("2017-08-27T12:34:56Z"));
UNIT_ASSERT_VALUES_EQUAL(samples.CommonLabelsSize(), 3);
AssertLabelEqual(samples.GetCommonLabels(0), "project", "solomon");
AssertLabelEqual(samples.GetCommonLabels(1), "cluster", "man");
AssertLabelEqual(samples.GetCommonLabels(2), "service", "stockpile");
UNIT_ASSERT_VALUES_EQUAL(samples.SamplesSize(), 4);
{
const NProto::TMultiSample& s = samples.GetSamples(0);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "metric", "Memory");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
AssertPointEqual(s.GetPoints(0), TInstant::Zero(), 10.0);
}
{
const NProto::TMultiSample& s = samples.GetSamples(1);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::RATE);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "metric", "UserTime");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
AssertPointEqual(s.GetPoints(0), TInstant::Zero(), ui64(1));
}
{
const NProto::TMultiSample& s = samples.GetSamples(2);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 2);
AssertLabelEqual(s.GetLabels(0), "export", "Oxygen");
AssertLabelEqual(s.GetLabels(1), "metric", "QueueSize");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
auto ts = TInstant::ParseIso8601Deprecated("2017-11-05T12:34:56.000Z");
AssertPointEqual(s.GetPoints(0), ts, 3.14159);
}
{
const NProto::TMultiSample& s = samples.GetSamples(3);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "metric", "Writes");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 2);
auto ts1 = TInstant::ParseIso8601Deprecated("2017-08-28T12:32:11Z");
AssertPointEqual(s.GetPoints(0), ts1, -10.0);
auto ts2 = TInstant::Seconds(1503923187);
AssertPointEqual(s.GetPoints(1), ts2, 20.0);
}
}
Y_UNIT_TEST(DecodeSensors) {
NProto::TMultiSamplesList samples;
{
IMetricEncoderPtr e = EncoderProtobuf(&samples);
TString sensorsJson = NResource::Find("/sensors.json");
DecodeJson(sensorsJson, e.Get());
}
UNIT_ASSERT_VALUES_EQUAL(
TInstant::MilliSeconds(samples.GetCommonTime()),
TInstant::ParseIso8601Deprecated("2017-08-27T12:34:56Z"));
UNIT_ASSERT_VALUES_EQUAL(samples.CommonLabelsSize(), 3);
AssertLabelEqual(samples.GetCommonLabels(0), "project", "solomon");
AssertLabelEqual(samples.GetCommonLabels(1), "cluster", "man");
AssertLabelEqual(samples.GetCommonLabels(2), "service", "stockpile");
UNIT_ASSERT_VALUES_EQUAL(samples.SamplesSize(), 4);
{
const NProto::TMultiSample& s = samples.GetSamples(0);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "metric", "Memory");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
AssertPointEqual(s.GetPoints(0), TInstant::Zero(), 10.0);
}
{
const NProto::TMultiSample& s = samples.GetSamples(1);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::RATE);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "metric", "UserTime");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
AssertPointEqual(s.GetPoints(0), TInstant::Zero(), ui64(1));
}
{
const NProto::TMultiSample& s = samples.GetSamples(2);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 2);
AssertLabelEqual(s.GetLabels(0), "export", "Oxygen");
AssertLabelEqual(s.GetLabels(1), "metric", "QueueSize");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
auto ts = TInstant::ParseIso8601Deprecated("2017-11-05T12:34:56.000Z");
AssertPointEqual(s.GetPoints(0), ts, 3.14159);
}
{
const NProto::TMultiSample& s = samples.GetSamples(3);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::GAUGE);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "metric", "Writes");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 2);
auto ts1 = TInstant::ParseIso8601Deprecated("2017-08-28T12:32:11Z");
AssertPointEqual(s.GetPoints(0), ts1, -10.0);
auto ts2 = TInstant::Seconds(1503923187);
AssertPointEqual(s.GetPoints(1), ts2, 20.0);
}
}
Y_UNIT_TEST(DecodeToEncoder) {
auto testJson = NResource::Find("/test_decode_to_encode.json");
TStringStream Stream_;
auto encoder = BufferedEncoderJson(&Stream_, 4);
DecodeJson(testJson, encoder.Get());
encoder->Close();
auto val1 = NJson::ReadJsonFastTree(testJson, true);
auto val2 = NJson::ReadJsonFastTree(Stream_.Str(), true);
UNIT_ASSERT_VALUES_EQUAL(val1, val2);
}
void WriteEmptySeries(const IMetricEncoderPtr& e) {
e->OnStreamBegin();
{
e->OnMetricBegin(EMetricType::COUNTER);
{
e->OnLabelsBegin();
e->OnLabel("foo", "bar");
e->OnLabelsEnd();
}
e->OnMetricEnd();
}
e->OnStreamEnd();
e->Close();
}
Y_UNIT_TEST(EncodeEmptySeries) {
TString json;
TStringOutput out(json);
auto e = EncoderJson(&out, 2);
WriteEmptySeries(e);
json += "\n";
TString expectedJson = NResource::Find("/empty_series.json");
UNIT_ASSERT_NO_DIFF(json, expectedJson);
}
void WriteEmptyLabels(IMetricEncoderPtr& e) {
e->OnStreamBegin();
e->OnMetricBegin(EMetricType::COUNTER);
e->OnLabelsBegin();
UNIT_ASSERT_EXCEPTION(e->OnLabelsEnd(), yexception);
}
Y_UNIT_TEST(LabelsCannotBeEmpty) {
TString json;
TStringOutput out(json);
auto e = EncoderJson(&out, 2);
WriteEmptyLabels(e);
}
Y_UNIT_TEST(LabelsCannotBeEmptyBuffered) {
TString json;
TStringOutput out(json);
auto e = BufferedEncoderJson(&out, 2);
WriteEmptyLabels(e);
}
Y_UNIT_TEST(EncodeEmptySeriesBuffered) {
TString json;
TStringOutput out(json);
auto e = BufferedEncoderJson(&out, 2);
WriteEmptySeries(e);
json += "\n";
TString expectedJson = NResource::Find("/empty_series.json");
UNIT_ASSERT_NO_DIFF(json, expectedJson);
}
Y_UNIT_TEST(BufferedEncoderMergesMetrics) {
TString json;
TStringOutput out(json);
auto e = BufferedEncoderJson(&out, 2);
auto ts = 1;
auto writeMetric = [&] (const TString& val) {
e->OnMetricBegin(EMetricType::COUNTER);
e->OnLabelsBegin();
e->OnLabel("foo", val);
e->OnLabelsEnd();
e->OnUint64(TInstant::Seconds(ts++), 42);
e->OnMetricEnd();
};
e->OnStreamBegin();
writeMetric("bar");
writeMetric("bar");
writeMetric("baz");
writeMetric("bar");
e->OnStreamEnd();
e->Close();
json += "\n";
TString expectedJson = NResource::Find("/buffered_test.json");
UNIT_ASSERT_NO_DIFF(json, expectedJson);
}
Y_UNIT_TEST(JsonEncoderDisallowsValuesInTimeseriesWithoutTs) {
TStringStream out;
auto e = EncoderJson(&out);
auto writePreamble = [&] {
e->OnStreamBegin();
e->OnMetricBegin(EMetricType::COUNTER);
e->OnLabelsBegin();
e->OnLabel("foo", "bar");
e->OnLabelsEnd();
};
// writing two values for a metric in a row will trigger
// timeseries object construction
writePreamble();
e->OnUint64(TInstant::Zero(), 42);
UNIT_ASSERT_EXCEPTION(e->OnUint64(TInstant::Zero(), 42), yexception);
e = EncoderJson(&out);
writePreamble();
e->OnUint64(TInstant::Zero(), 42);
UNIT_ASSERT_EXCEPTION(e->OnUint64(TInstant::Now(), 42), yexception);
e = EncoderJson(&out);
writePreamble();
e->OnUint64(TInstant::Now(), 42);
UNIT_ASSERT_EXCEPTION(e->OnUint64(TInstant::Zero(), 42), yexception);
}
Y_UNIT_TEST(BufferedJsonEncoderMergesTimeseriesWithoutTs) {
TStringStream out;
{
auto e = BufferedEncoderJson(&out, 2);
e->OnStreamBegin();
e->OnMetricBegin(EMetricType::COUNTER);
e->OnLabelsBegin();
e->OnLabel("foo", "bar");
e->OnLabelsEnd();
// in buffered mode we are able to find values with same (in this case zero)
// timestamp and discard duplicates
e->OnUint64(TInstant::Zero(), 42);
e->OnUint64(TInstant::Zero(), 43);
e->OnUint64(TInstant::Zero(), 44);
e->OnUint64(TInstant::Zero(), 45);
e->OnMetricEnd();
e->OnStreamEnd();
}
out << "\n";
UNIT_ASSERT_NO_DIFF(out.Str(), NResource::Find("/buffered_ts_merge.json"));
}
template <typename TFactory, typename TConsumer>
TString EncodeToString(TFactory factory, TConsumer consumer) {
TStringStream out;
{
IMetricEncoderPtr e = factory(&out, 2);
consumer(e.Get());
}
out << '\n';
return out.Str();
}
Y_UNIT_TEST(SummaryValueEncode) {
auto writeDocument = [](IMetricEncoder* e) {
e->OnStreamBegin();
{
e->OnMetricBegin(EMetricType::DSUMMARY);
{
e->OnLabelsBegin();
e->OnLabel("metric", "temperature");
e->OnLabelsEnd();
}
e->OnSummaryDouble(now, MakeIntrusive<TSummaryDoubleSnapshot>(10., -0.5, 0.5, 0.3, 30u));
e->OnMetricEnd();
}
e->OnStreamEnd();
};
TString result1 = EncodeToString(EncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result1, NResource::Find("/summary_value.json"));
TString result2 = EncodeToString(BufferedEncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result2, NResource::Find("/summary_value.json"));
}
ISummaryDoubleSnapshotPtr TestInfSummary() {
return MakeIntrusive<TSummaryDoubleSnapshot>(
std::numeric_limits<double>::quiet_NaN(),
-std::numeric_limits<double>::infinity(),
std::numeric_limits<double>::infinity(),
0.3,
30u);
}
Y_UNIT_TEST(SummaryInfEncode) {
auto writeDocument = [](IMetricEncoder* e) {
e->OnStreamBegin();
{
e->OnMetricBegin(EMetricType::DSUMMARY);
{
e->OnLabelsBegin();
e->OnLabel("metric", "temperature");
e->OnLabelsEnd();
}
e->OnSummaryDouble(now, TestInfSummary());
e->OnMetricEnd();
}
e->OnStreamEnd();
};
TString result1 = EncodeToString(EncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result1, NResource::Find("/summary_inf.json"));
TString result2 = EncodeToString(BufferedEncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result2, NResource::Find("/summary_inf.json"));
}
Y_UNIT_TEST(SummaryInfDecode) {
NProto::TMultiSamplesList samples;
{
IMetricEncoderPtr e = EncoderProtobuf(&samples);
TString testJson = NResource::Find("/summary_inf.json");
DecodeJson(testJson, e.Get());
}
UNIT_ASSERT_VALUES_EQUAL(1, samples.SamplesSize());
const NProto::TMultiSample& s = samples.GetSamples(0);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::DSUMMARY);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "metric", "temperature");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
auto actual = s.GetPoints(0).GetSummaryDouble();
UNIT_ASSERT(std::isnan(actual.GetSum()));
UNIT_ASSERT(actual.GetMin() < 0);
UNIT_ASSERT(std::isinf(actual.GetMin()));
UNIT_ASSERT(actual.GetMax() > 0);
UNIT_ASSERT(std::isinf(actual.GetMax()));
}
Y_UNIT_TEST(SummaryValueDecode) {
NProto::TMultiSamplesList samples;
{
IMetricEncoderPtr e = EncoderProtobuf(&samples);
TString testJson = NResource::Find("/summary_value.json");
DecodeJson(testJson, e.Get());
}
UNIT_ASSERT_VALUES_EQUAL(1, samples.SamplesSize());
const NProto::TMultiSample& s = samples.GetSamples(0);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::DSUMMARY);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "metric", "temperature");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
auto snapshot = TSummaryDoubleSnapshot(10., -0.5, 0.5, 0.3, 30u);
AssertPointEqual(s.GetPoints(0), now, snapshot);
}
Y_UNIT_TEST(SummaryTimeSeriesEncode) {
auto writeDocument = [](IMetricEncoder* e) {
e->OnStreamBegin();
{
e->OnMetricBegin(EMetricType::DSUMMARY);
{
e->OnLabelsBegin();
e->OnLabel("metric", "temperature");
e->OnLabelsEnd();
}
TSummaryDoubleCollector summary;
summary.Collect(0.3);
summary.Collect(-0.5);
summary.Collect(1.);
e->OnSummaryDouble(now, summary.Snapshot());
summary.Collect(-1.5);
summary.Collect(0.01);
e->OnSummaryDouble(now + TDuration::Seconds(15), summary.Snapshot());
e->OnMetricEnd();
}
e->OnStreamEnd();
};
TString result1 = EncodeToString(EncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result1, NResource::Find("/summary_timeseries.json"));
TString result2 = EncodeToString(BufferedEncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result2, NResource::Find("/summary_timeseries.json"));
}
Y_UNIT_TEST(SummaryTimeSeriesDecode) {
NProto::TMultiSamplesList samples;
{
IMetricEncoderPtr e = EncoderProtobuf(&samples);
TString testJson = NResource::Find("/summary_timeseries.json");
DecodeJson(testJson, e.Get());
}
UNIT_ASSERT_VALUES_EQUAL(1, samples.SamplesSize());
const NProto::TMultiSample& s = samples.GetSamples(0);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::DSUMMARY);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "metric", "temperature");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 2);
TSummaryDoubleCollector summary;
summary.Collect(0.3);
summary.Collect(-0.5);
summary.Collect(1.);
AssertPointEqual(s.GetPoints(0), now, *summary.Snapshot());
summary.Collect(-1.5);
summary.Collect(0.01);
AssertPointEqual(s.GetPoints(1), now + TDuration::Seconds(15), *summary.Snapshot());
}
Y_UNIT_TEST(LogHistogramValueEncode) {
auto writeDocument = [](IMetricEncoder* e) {
e->OnStreamBegin();
{
e->OnMetricBegin(EMetricType::LOGHIST);
{
e->OnLabelsBegin();
e->OnLabel("metric", "ms");
e->OnLabelsEnd();
}
e->OnLogHistogram(now, TestLogHistogram());
e->OnMetricEnd();
}
e->OnStreamEnd();
};
TString result1 = EncodeToString(EncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result1, NResource::Find("/log_histogram_value.json"));
TString result2 = EncodeToString(BufferedEncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result2, NResource::Find("/log_histogram_value.json"));
}
Y_UNIT_TEST(LogHistogramValueDecode) {
NProto::TMultiSamplesList samples;
{
IMetricEncoderPtr e = EncoderProtobuf(&samples);
TString testJson = NResource::Find("/log_histogram_value.json");
DecodeJson(testJson, e.Get());
}
UNIT_ASSERT_VALUES_EQUAL(1, samples.SamplesSize());
const NProto::TMultiSample& s = samples.GetSamples(0);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::LOGHISTOGRAM);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "metric", "ms");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
auto snapshot = TestLogHistogram();
AssertPointEqual(s.GetPoints(0), now, *snapshot);
}
Y_UNIT_TEST(HistogramValueEncode) {
auto writeDocument = [](IMetricEncoder* e) {
e->OnStreamBegin();
{
e->OnMetricBegin(EMetricType::HIST);
{
e->OnLabelsBegin();
e->OnLabel("metric", "responseTimeMillis");
e->OnLabelsEnd();
}
// {1: 1, 2: 1, 4: 2, 8: 4, 16: 8, inf: 83}
auto h = ExponentialHistogram(6, 2);
for (i64 i = 1; i < 100; i++) {
h->Collect(i);
}
e->OnHistogram(now, h->Snapshot());
e->OnMetricEnd();
}
e->OnStreamEnd();
};
TString result1 = EncodeToString(EncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result1, NResource::Find("/histogram_value.json"));
TString result2 = EncodeToString(BufferedEncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result2, NResource::Find("/histogram_value.json"));
}
Y_UNIT_TEST(LogHistogramTimeSeriesEncode) {
auto writeDocument = [](IMetricEncoder* e) {
e->OnStreamBegin();
{
e->OnMetricBegin(EMetricType::LOGHIST);
{
e->OnLabelsBegin();
e->OnLabel("metric", "ms");
e->OnLabelsEnd();
}
e->OnLogHistogram(now, TestLogHistogram(1));;
e->OnLogHistogram(now + TDuration::Seconds(15), TestLogHistogram(2));
e->OnMetricEnd();
}
e->OnStreamEnd();
};
TString result1 = EncodeToString(EncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result1, NResource::Find("/log_histogram_timeseries.json"));
TString result2 = EncodeToString(BufferedEncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result2, NResource::Find("/log_histogram_timeseries.json"));
}
Y_UNIT_TEST(LogHistogramTimeSeriesDecode) {
NProto::TMultiSamplesList samples;
{
IMetricEncoderPtr e = EncoderProtobuf(&samples);
TString testJson = NResource::Find("/log_histogram_timeseries.json");
DecodeJson(testJson, e.Get());
}
UNIT_ASSERT_VALUES_EQUAL(1, samples.SamplesSize());
const NProto::TMultiSample& s = samples.GetSamples(0);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::LOGHISTOGRAM);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "metric", "ms");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 2);
auto logHist = TestLogHistogram(1);
AssertPointEqual(s.GetPoints(0), now, *logHist);
logHist = TestLogHistogram(2);
AssertPointEqual(s.GetPoints(1), now + TDuration::Seconds(15), *logHist);
}
void HistogramValueDecode(const TString& filePath) {
NProto::TMultiSamplesList samples;
{
IMetricEncoderPtr e = EncoderProtobuf(&samples);
TString testJson = NResource::Find(filePath);
DecodeJson(testJson, e.Get());
}
UNIT_ASSERT_VALUES_EQUAL(1, samples.SamplesSize());
const NProto::TMultiSample& s = samples.GetSamples(0);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::HISTOGRAM);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "metric", "responseTimeMillis");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 1);
auto h = ExponentialHistogram(6, 2);
for (i64 i = 1; i < 100; i++) {
h->Collect(i);
}
AssertPointEqual(s.GetPoints(0), now, *h->Snapshot());
}
Y_UNIT_TEST(HistogramValueDecode) {
HistogramValueDecode("/histogram_value.json");
HistogramValueDecode("/histogram_value_inf_before_bounds.json");
}
Y_UNIT_TEST(HistogramTimeSeriesEncode) {
auto writeDocument = [](IMetricEncoder* e) {
e->OnStreamBegin();
{
e->OnMetricBegin(EMetricType::HIST_RATE);
{
e->OnLabelsBegin();
e->OnLabel("metric", "responseTimeMillis");
e->OnLabelsEnd();
}
// {1: 1, 2: 1, 4: 2, 8: 4, 16: 8, inf: 83}
auto h = ExponentialHistogram(6, 2);
for (i64 i = 1; i < 100; i++) {
h->Collect(i);
}
e->OnHistogram(now, h->Snapshot());
// {1: 2, 2: 2, 4: 4, 8: 8, 16: 16, inf: 166}
for (i64 i = 1; i < 100; i++) {
h->Collect(i);
}
e->OnHistogram(now + TDuration::Seconds(15), h->Snapshot());
e->OnMetricEnd();
}
e->OnStreamEnd();
};
TString result1 = EncodeToString(EncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result1, NResource::Find("/histogram_timeseries.json"));
TString result2 = EncodeToString(BufferedEncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result2, NResource::Find("/histogram_timeseries.json"));
}
Y_UNIT_TEST(HistogramTimeSeriesDecode) {
NProto::TMultiSamplesList samples;
{
IMetricEncoderPtr e = EncoderProtobuf(&samples);
TString testJson = NResource::Find("/histogram_timeseries.json");
DecodeJson(testJson, e.Get());
}
UNIT_ASSERT_VALUES_EQUAL(1, samples.SamplesSize());
const NProto::TMultiSample& s = samples.GetSamples(0);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::HIST_RATE);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "metric", "responseTimeMillis");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 2);
auto h = ExponentialHistogram(6, 2);
for (i64 i = 1; i < 100; i++) {
h->Collect(i);
}
AssertPointEqual(s.GetPoints(0), now, *h->Snapshot());
for (i64 i = 1; i < 100; i++) {
h->Collect(i);
}
AssertPointEqual(s.GetPoints(1), now + TDuration::Seconds(15), *h->Snapshot());
}
Y_UNIT_TEST(IntGaugeEncode) {
auto writeDocument = [](IMetricEncoder* e) {
e->OnStreamBegin();
{
e->OnMetricBegin(EMetricType::IGAUGE);
{
e->OnLabelsBegin();
e->OnLabel("metric", "a");
e->OnLabelsEnd();
}
e->OnInt64(now, Min<i64>());
e->OnInt64(now + TDuration::Seconds(1), -1);
e->OnInt64(now + TDuration::Seconds(2), 0);
e->OnInt64(now + TDuration::Seconds(3), Max<i64>());
e->OnMetricEnd();
}
e->OnStreamEnd();
};
TString result1 = EncodeToString(EncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result1, NResource::Find("/int_gauge.json"));
TString result2 = EncodeToString(BufferedEncoderJson, writeDocument);
UNIT_ASSERT_NO_DIFF(result2, NResource::Find("/int_gauge.json"));
}
Y_UNIT_TEST(InconsistentMetricTypes) {
auto emitMetrics = [](IMetricEncoder& encoder, const TString& expectedError) {
encoder.OnMetricBegin(EMetricType::GAUGE);
{
encoder.OnLabelsBegin();
encoder.OnLabel("name", "m");
encoder.OnLabel("l1", "v1");
encoder.OnLabel("l2", "v2");
encoder.OnLabelsEnd();
}
encoder.OnDouble(now, 1.0);
encoder.OnMetricEnd();
encoder.OnMetricBegin(EMetricType::COUNTER);
{
encoder.OnLabelsBegin();
encoder.OnLabel("name", "m");
encoder.OnLabel("l1", "v1");
encoder.OnLabel("l2", "v2");
encoder.OnLabelsEnd();
}
encoder.OnUint64(now, 1);
UNIT_ASSERT_EXCEPTION_CONTAINS(encoder.OnMetricEnd(),
yexception,
expectedError);
};
{
TStringStream out;
auto encoder = BufferedEncoderJson(&out);
encoder->OnStreamBegin();
encoder->OnLabelsBegin();
encoder->OnLabel("c", "cv");
encoder->OnLabelsEnd();
emitMetrics(*encoder,
"Time series point type mismatch: expected DOUBLE but found UINT64, "
"labels '{c=cv, l1=v1, l2=v2, name=m}'");
}
{
TStringStream out;
auto encoder = BufferedEncoderJson(&out);
encoder->OnStreamBegin();
encoder->OnLabelsBegin();
encoder->OnLabel("l1", "v100");
encoder->OnLabelsEnd();
emitMetrics(*encoder,
"Time series point type mismatch: expected DOUBLE but found UINT64, "
"labels '{l1=v1, l2=v2, name=m}'");
}
}
Y_UNIT_TEST(IntGaugeDecode) {
NProto::TMultiSamplesList samples;
{
IMetricEncoderPtr e = EncoderProtobuf(&samples);
TString testJson = NResource::Find("/int_gauge.json");
DecodeJson(testJson, e.Get());
}
UNIT_ASSERT_VALUES_EQUAL(1, samples.SamplesSize());
const NProto::TMultiSample& s = samples.GetSamples(0);
UNIT_ASSERT_EQUAL(s.GetMetricType(), NProto::IGAUGE);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "metric", "a");
UNIT_ASSERT_VALUES_EQUAL(s.PointsSize(), 4);
AssertPointEqual(s.GetPoints(0), now, Min<i64>());
AssertPointEqual(s.GetPoints(1), now + TDuration::Seconds(1), i64(-1));
AssertPointEqual(s.GetPoints(2), now + TDuration::Seconds(2), i64(0));
AssertPointEqual(s.GetPoints(3), now + TDuration::Seconds(3), Max<i64>());
}
Y_UNIT_TEST(FuzzerRegression) {
NProto::TMultiSamplesList samples;
{
IMetricEncoderPtr e = EncoderProtobuf(&samples);
for (auto f : { "/hist_crash.json", "/crash.json" }) {
TString testJson = NResource::Find(f);
UNIT_ASSERT_EXCEPTION(DecodeJson(testJson, e.Get()), yexception);
}
}
}
Y_UNIT_TEST(LegacyNegativeRateThrows) {
const auto input = R"({
"sensors": [
{
"mode": "deriv",
"value": -1,
"labels": { "metric": "SystemTime" }
},
}
]}")";
NProto::TMultiSamplesList samples;
IMetricEncoderPtr e = EncoderProtobuf(&samples);
UNIT_ASSERT_EXCEPTION(DecodeJson(input, e.Get()), yexception);
}
Y_UNIT_TEST(DecodeNamedMetrics) {
NProto::TMultiSamplesList samples;
{
IMetricEncoderPtr e = EncoderProtobuf(&samples);
TString metricsJson = NResource::Find("/named_metrics.json");
DecodeJson(metricsJson, e.Get(), "sensor");
}
UNIT_ASSERT_VALUES_EQUAL(samples.SamplesSize(), 2);
{
const NProto::TMultiSample& s = samples.GetSamples(0);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 1);
AssertLabelEqual(s.GetLabels(0), "sensor", "Memory");
}
{
const NProto::TMultiSample& s = samples.GetSamples(1);
UNIT_ASSERT_VALUES_EQUAL(s.LabelsSize(), 2);
AssertLabelEqual(s.GetLabels(0), "sensor", "QueueSize");
AssertLabelEqual(s.GetLabels(1), "export", "Oxygen");
}
}
}