Skip to content

Commit

Permalink
core: Serialize floating values locale-independent
Browse files Browse the repository at this point in the history
Closes: #281
  • Loading branch information
gjasny committed Jun 30, 2019
1 parent fb64de9 commit 1b1a99d
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 41 deletions.
75 changes: 34 additions & 41 deletions core/src/text_serializer.cc
Expand Up @@ -2,66 +2,56 @@

#include <cmath>
#include <limits>
#include <locale>
#include <ostream>

namespace prometheus {

namespace {

// Write a double as a string, with proper formatting for infinity and NaN
std::string ToString(double v) {
if (std::isnan(v)) {
return "Nan";
void WriteValue(std::ostream& out, double value) {
if (std::isnan(value)) {
out << "Nan";
} else if (std::isinf(value)) {
out << (value < 0 ? "-Inf" : "+Inf");
} else {
auto saved_flags = out.setf(std::ios::fixed, std::ios::floatfield);
out << value;
out.setf(saved_flags, std::ios::floatfield);
}
if (std::isinf(v)) {
return (v < 0 ? "-Inf" : "+Inf");
}
return std::to_string(v);
}

const std::string& EscapeLabelValue(const std::string& value,
std::string* tmp) {
bool copy = false;
for (size_t i = 0; i < value.size(); ++i) {
auto c = value[i];
void WriteValue(std::ostream& out, const std::string& value) {
for (auto c : value) {
if (c == '\\' || c == '"' || c == '\n') {
if (!copy) {
tmp->reserve(value.size() + 1);
tmp->assign(value, 0, i);
copy = true;
}
if (c == '\\') {
tmp->append("\\\\");
} else if (c == '"') {
tmp->append("\\\"");
} else {
tmp->append("\\\n");
}
} else if (copy) {
tmp->push_back(c);
out << "\\";
}
out << c;
}
return copy ? *tmp : value;
}

// Write a line header: metric name and labels
template <typename T = std::string>
void WriteHead(std::ostream& out, const MetricFamily& family,
const ClientMetric& metric, const std::string& suffix = "",
const std::string& extraLabelName = "",
const std::string& extraLabelValue = "") {
const T& extraLabelValue = T()) {
out << family.name << suffix;
if (!metric.label.empty() || !extraLabelName.empty()) {
out << "{";
const char* prefix = "";
std::string tmp;

for (auto& lp : metric.label) {
out << prefix << lp.name << "=\"" << EscapeLabelValue(lp.value, &tmp)
<< "\"";
out << prefix << lp.name << "=\"";
WriteValue(out, lp.value);
out << "\"";
prefix = ",";
}
if (!extraLabelName.empty()) {
out << prefix << extraLabelName << "=\""
<< EscapeLabelValue(extraLabelValue, &tmp) << "\"";
out << prefix << extraLabelName << "=\"";
WriteValue(out, extraLabelValue);
out << "\"";
}
out << "}";
}
Expand All @@ -79,14 +69,14 @@ void WriteTail(std::ostream& out, const ClientMetric& metric) {
void SerializeCounter(std::ostream& out, const MetricFamily& family,
const ClientMetric& metric) {
WriteHead(out, family, metric);
out << ToString(metric.counter.value);
WriteValue(out, metric.counter.value);
WriteTail(out, metric);
}

void SerializeGauge(std::ostream& out, const MetricFamily& family,
const ClientMetric& metric) {
WriteHead(out, family, metric);
out << ToString(metric.gauge.value);
WriteValue(out, metric.gauge.value);
WriteTail(out, metric);
}

Expand All @@ -98,20 +88,20 @@ void SerializeSummary(std::ostream& out, const MetricFamily& family,
WriteTail(out, metric);

WriteHead(out, family, metric, "_sum");
out << ToString(sum.sample_sum);
WriteValue(out, sum.sample_sum);
WriteTail(out, metric);

for (auto& q : sum.quantile) {
WriteHead(out, family, metric, "", "quantile", ToString(q.quantile));
out << ToString(q.value);
WriteHead(out, family, metric, "", "quantile", q.quantile);
WriteValue(out, q.value);
WriteTail(out, metric);
}
}

void SerializeUntyped(std::ostream& out, const MetricFamily& family,
const ClientMetric& metric) {
WriteHead(out, family, metric);
out << ToString(metric.untyped.value);
WriteValue(out, metric.untyped.value);
WriteTail(out, metric);
}

Expand All @@ -123,12 +113,12 @@ void SerializeHistogram(std::ostream& out, const MetricFamily& family,
WriteTail(out, metric);

WriteHead(out, family, metric, "_sum");
out << ToString(hist.sample_sum);
WriteValue(out, hist.sample_sum);
WriteTail(out, metric);

double last = -std::numeric_limits<double>::infinity();
for (auto& b : hist.bucket) {
WriteHead(out, family, metric, "_bucket", "le", ToString(b.upper_bound));
WriteHead(out, family, metric, "_bucket", "le", b.upper_bound);
last = b.upper_bound;
out << b.cumulative_count;
WriteTail(out, metric);
Expand Down Expand Up @@ -184,8 +174,11 @@ void SerializeFamily(std::ostream& out, const MetricFamily& family) {

void TextSerializer::Serialize(std::ostream& out,
const std::vector<MetricFamily>& metrics) const {
std::locale saved_locale = out.getloc();
out.imbue(std::locale::classic());
for (auto& family : metrics) {
SerializeFamily(out, family);
}
out.imbue(saved_locale);
}
} // namespace prometheus
1 change: 1 addition & 0 deletions core/tests/CMakeLists.txt
Expand Up @@ -7,6 +7,7 @@ add_executable(prometheus_test
gauge_test.cc
histogram_test.cc
registry_test.cc
serializer_test.cc
summary_test.cc
utils_test.cc
)
Expand Down
60 changes: 60 additions & 0 deletions core/tests/serializer_test.cc
@@ -0,0 +1,60 @@
#include "prometheus/counter.h"
#include "prometheus/family.h"
#include "prometheus/text_serializer.h"

#include <gmock/gmock.h>
#include <locale>
#include <sstream>

namespace prometheus {
namespace {

class SerializerTest : public testing::Test {
public:
void SetUp() override {
Family<Counter> family{"requests_total", "", {}};
auto& counter = family.Add({});
counter.Increment();

collected = family.Collect();
}

std::vector<MetricFamily> collected;
TextSerializer textSerializer;
};

TEST_F(SerializerTest, shouldSerializeLocaleIndependent) {
// save and change locale
const std::locale oldLocale = std::locale::classic();
std::locale::global(std::locale("de_DE.UTF-8"));

const auto serialized = textSerializer.Serialize(collected);
EXPECT_THAT(serialized, testing::HasSubstr("1.0"));

// restore locale
std::locale::global(oldLocale);
}

TEST_F(SerializerTest, shouldRestoreStreamState) {
std::ostringstream os;

// save stream state
auto saved_flags = os.flags();
auto saved_precision = os.precision();
auto saved_width = os.width();
auto saved_fill = os.fill();
auto saved_locale = os.getloc();

// serialize
textSerializer.Serialize(os, collected);

// check for expected flags
EXPECT_EQ(os.flags(), saved_flags);
EXPECT_EQ(os.precision(), saved_precision);
EXPECT_EQ(os.width(), saved_width);
EXPECT_EQ(os.fill(), saved_fill);
EXPECT_EQ(os.getloc(), saved_locale);
}

} // namespace
} // namespace prometheus

0 comments on commit 1b1a99d

Please sign in to comment.