--- /dev/null
+#include "prometheus/text_serializer.h"
+
+#include <cmath>
+#include <limits>
+#include <locale>
+#include <ostream>
+#include <string>
+
+#include "prometheus/client_metric.h"
+#include "prometheus/metric_family.h"
+#include "prometheus/metric_type.h"
+
+namespace prometheus {
+
+namespace {
+
+// Write a double as a string, with proper formatting for infinity and 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 {
+ out << value;
+ }
+}
+
+void WriteValue(std::ostream& out, const std::string& value) {
+ for (auto c : value) {
+ switch (c) {
+ case '\n':
+ out << '\\' << 'n';
+ break;
+
+ case '\\':
+ out << '\\' << c;
+ break;
+
+ case '"':
+ out << '\\' << c;
+ break;
+
+ default:
+ out << c;
+ break;
+ }
+ }
+}
+
+// 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 T& extraLabelValue = T()) {
+ out << family.name << suffix;
+ if (!metric.label.empty() || !extraLabelName.empty()) {
+ out << "{";
+ const char* prefix = "";
+
+ for (auto& lp : metric.label) {
+ out << prefix << lp.name << "=\"";
+ WriteValue(out, lp.value);
+ out << "\"";
+ prefix = ",";
+ }
+ if (!extraLabelName.empty()) {
+ out << prefix << extraLabelName << "=\"";
+ WriteValue(out, extraLabelValue);
+ out << "\"";
+ }
+ out << "}";
+ }
+ out << " ";
+}
+
+// Write a line trailer: timestamp
+void WriteTail(std::ostream& out, const ClientMetric& metric) {
+ if (metric.timestamp_ms != 0) {
+ out << " " << metric.timestamp_ms;
+ }
+ out << "\n";
+}
+
+void SerializeCounter(std::ostream& out, const MetricFamily& family,
+ const ClientMetric& metric) {
+ WriteHead(out, family, metric);
+ WriteValue(out, metric.counter.value);
+ WriteTail(out, metric);
+}
+
+void SerializeGauge(std::ostream& out, const MetricFamily& family,
+ const ClientMetric& metric) {
+ WriteHead(out, family, metric);
+ WriteValue(out, metric.gauge.value);
+ WriteTail(out, metric);
+}
+
+void SerializeSummary(std::ostream& out, const MetricFamily& family,
+ const ClientMetric& metric) {
+ auto& sum = metric.summary;
+ WriteHead(out, family, metric, "_count");
+ out << sum.sample_count;
+ WriteTail(out, metric);
+
+ WriteHead(out, family, metric, "_sum");
+ WriteValue(out, sum.sample_sum);
+ WriteTail(out, metric);
+
+ for (auto& q : sum.quantile) {
+ 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);
+ WriteValue(out, metric.untyped.value);
+ WriteTail(out, metric);
+}
+
+void SerializeHistogram(std::ostream& out, const MetricFamily& family,
+ const ClientMetric& metric) {
+ auto& hist = metric.histogram;
+ WriteHead(out, family, metric, "_count");
+ out << hist.sample_count;
+ WriteTail(out, metric);
+
+ WriteHead(out, family, metric, "_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", b.upper_bound);
+ last = b.upper_bound;
+ out << b.cumulative_count;
+ WriteTail(out, metric);
+ }
+
+ if (last != std::numeric_limits<double>::infinity()) {
+ WriteHead(out, family, metric, "_bucket", "le", "+Inf");
+ out << hist.sample_count;
+ WriteTail(out, metric);
+ }
+}
+
+void SerializeFamily(std::ostream& out, const MetricFamily& family) {
+ if (!family.help.empty()) {
+ out << "# HELP " << family.name << " " << family.help << "\n";
+ }
+ switch (family.type) {
+ case MetricType::Counter:
+ out << "# TYPE " << family.name << " counter\n";
+ for (auto& metric : family.metric) {
+ SerializeCounter(out, family, metric);
+ }
+ break;
+ case MetricType::Gauge:
+ out << "# TYPE " << family.name << " gauge\n";
+ for (auto& metric : family.metric) {
+ SerializeGauge(out, family, metric);
+ }
+ break;
+ case MetricType::Summary:
+ out << "# TYPE " << family.name << " summary\n";
+ for (auto& metric : family.metric) {
+ SerializeSummary(out, family, metric);
+ }
+ break;
+ case MetricType::Untyped:
+ out << "# TYPE " << family.name << " untyped\n";
+ for (auto& metric : family.metric) {
+ SerializeUntyped(out, family, metric);
+ }
+ break;
+ case MetricType::Histogram:
+ out << "# TYPE " << family.name << " histogram\n";
+ for (auto& metric : family.metric) {
+ SerializeHistogram(out, family, metric);
+ }
+ break;
+ }
+}
+} // namespace
+
+void TextSerializer::Serialize(std::ostream& out,
+ const std::vector<MetricFamily>& metrics) const {
+ auto saved_locale = out.getloc();
+ auto saved_precision = out.precision();
+
+ out.imbue(std::locale::classic());
+ out.precision(std::numeric_limits<double>::max_digits10 - 1);
+
+ for (auto& family : metrics) {
+ SerializeFamily(out, family);
+ }
+
+ out.imbue(saved_locale);
+ out.precision(saved_precision);
+}
+} // namespace prometheus