Skip to content

Commit

Permalink
feat: don't create temporary objects for serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
gjasny committed Dec 28, 2023
1 parent 4d2771f commit a6f70c7
Show file tree
Hide file tree
Showing 23 changed files with 437 additions and 183 deletions.
9 changes: 2 additions & 7 deletions core/include/prometheus/collectable.h
@@ -1,12 +1,7 @@
#pragma once

#include <vector>

#include "prometheus/detail/core_export.h"

namespace prometheus {
struct MetricFamily;
}
#include "prometheus/serializer.h"

namespace prometheus {

Expand All @@ -19,7 +14,7 @@ class PROMETHEUS_CPP_CORE_EXPORT Collectable {
virtual ~Collectable() = default;

/// \brief Returns a list of metrics and their samples.
virtual std::vector<MetricFamily> Collect() const = 0;
virtual void Collect(const Serializer& out) const = 0;
};

} // namespace prometheus
2 changes: 1 addition & 1 deletion core/include/prometheus/family.h
Expand Up @@ -140,7 +140,7 @@ class PROMETHEUS_CPP_CORE_EXPORT Family : public Collectable {
/// Collect is called by the Registry when collecting metrics.
///
/// \return Zero or more samples for each dimensional data.
std::vector<MetricFamily> Collect() const override;
void Collect(const Serializer& out) const override;

private:
std::unordered_map<Labels, std::unique_ptr<T>, detail::LabelHasher> metrics_;
Expand Down
50 changes: 50 additions & 0 deletions core/include/prometheus/iovector.h
@@ -0,0 +1,50 @@
#pragma once

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <numeric>
#include <vector>

#include "prometheus/detail/core_export.h"

namespace prometheus {

struct PROMETHEUS_CPP_CORE_EXPORT IOVector {
bool empty() const { return data.empty() || !size(); }

using ByteVector = std::vector<std::uint8_t>;

std::size_t size() const {
return std::accumulate(begin(data), end(data), std::size_t{0},
[](std::size_t size, const ByteVector& chunk) {
return size + chunk.size();
});
}

std::size_t copy(std::size_t offset, void* buffer,
std::size_t bufferSize) const {
std::size_t copied = 0;
for (const auto& chunk : data) {
if (offset >= chunk.size()) {
offset -= chunk.size();
continue;
}

auto chunkSize = std::min(chunk.size() - offset, bufferSize - copied);
std::copy_n(chunk.data() + offset, chunkSize,
reinterpret_cast<std::uint8_t*>(buffer) + copied);
copied += chunkSize;
offset = 0;

if (copied == bufferSize) {
break;
}
}
return copied;
}

std::vector<ByteVector> data;
};

} // namespace prometheus
1 change: 0 additions & 1 deletion core/include/prometheus/metric_family.h
Expand Up @@ -13,6 +13,5 @@ struct PROMETHEUS_CPP_CORE_EXPORT MetricFamily {
std::string name;
std::string help;
MetricType type = MetricType::Untyped;
std::vector<ClientMetric> metric;
};
} // namespace prometheus
2 changes: 1 addition & 1 deletion core/include/prometheus/registry.h
Expand Up @@ -79,7 +79,7 @@ class PROMETHEUS_CPP_CORE_EXPORT Registry : public Collectable {
/// function.
///
/// \return Zero or more metrics and their samples.
std::vector<MetricFamily> Collect() const override;
void Collect(const Serializer& out) const override;

/// \brief Removes a metrics family from the registry.
///
Expand Down
7 changes: 4 additions & 3 deletions core/include/prometheus/serializer.h
Expand Up @@ -12,9 +12,10 @@ namespace prometheus {
class PROMETHEUS_CPP_CORE_EXPORT Serializer {
public:
virtual ~Serializer() = default;
virtual std::string Serialize(const std::vector<MetricFamily>&) const;
virtual void Serialize(std::ostream& out,
const std::vector<MetricFamily>& metrics) const = 0;

virtual void Serialize(const MetricFamily& family) const = 0;
virtual void Serialize(const MetricFamily& family,
const ClientMetric& metric) const = 0;
};

} // namespace prometheus
18 changes: 13 additions & 5 deletions core/include/prometheus/text_serializer.h
@@ -1,19 +1,27 @@
#pragma once

#include <iosfwd>
#include <vector>
#include <cstddef>

#include "prometheus/detail/core_export.h"
#include "prometheus/iovector.h"
#include "prometheus/metric_family.h"
#include "prometheus/serializer.h"

namespace prometheus {

class PROMETHEUS_CPP_CORE_EXPORT TextSerializer : public Serializer {
public:
using Serializer::Serialize;
void Serialize(std::ostream& out,
const std::vector<MetricFamily>& metrics) const override;
TextSerializer(IOVector& ioVector);

void Serialize(const MetricFamily& family) const override;
void Serialize(const MetricFamily& family,
const ClientMetric& metric) const override;

private:
void Add(const std::ostringstream& stream) const;

IOVector& ioVector_;
static constexpr std::size_t chunkSize_ = 1 * 1024 * 1024;
};

} // namespace prometheus
12 changes: 7 additions & 5 deletions core/src/family.cc
Expand Up @@ -86,22 +86,24 @@ const Labels& Family<T>::GetConstantLabels() const {
}

template <typename T>
std::vector<MetricFamily> Family<T>::Collect() const {
void Family<T>::Collect(const Serializer& out) const {
std::lock_guard<std::mutex> lock{mutex_};

if (metrics_.empty()) {
return {};
return;
}

auto family = MetricFamily{};
family.name = name_;
family.help = help_;
family.type = T::metric_type;
family.metric.reserve(metrics_.size());

out.Serialize(family);

for (const auto& m : metrics_) {
family.metric.push_back(std::move(CollectMetric(m.first, m.second.get())));
auto&& metric = CollectMetric(m.first, m.second.get());
out.Serialize(family, metric);
}
return {family};
}

template <typename T>
Expand Down
21 changes: 8 additions & 13 deletions core/src/registry.cc
Expand Up @@ -16,11 +16,9 @@ namespace prometheus {

namespace {
template <typename T>
void CollectAll(std::vector<MetricFamily>& results, const T& families) {
void CollectAll(const Serializer& out, const T& families) {
for (auto&& collectable : families) {
auto metrics = collectable->Collect();
results.insert(results.end(), std::make_move_iterator(metrics.begin()),
std::make_move_iterator(metrics.end()));
collectable->Collect(out);
}
}

Expand All @@ -43,17 +41,14 @@ Registry::Registry(InsertBehavior insert_behavior)

Registry::~Registry() = default;

std::vector<MetricFamily> Registry::Collect() const {
void Registry::Collect(const Serializer& out) const {
std::lock_guard<std::mutex> lock{mutex_};
auto results = std::vector<MetricFamily>{};

CollectAll(results, counters_);
CollectAll(results, gauges_);
CollectAll(results, histograms_);
CollectAll(results, infos_);
CollectAll(results, summaries_);

return results;
CollectAll(out, counters_);
CollectAll(out, gauges_);
CollectAll(out, histograms_);
CollectAll(out, infos_);
CollectAll(out, summaries_);
}

template <>
Expand Down
6 changes: 0 additions & 6 deletions core/src/serializer.cc
Expand Up @@ -4,10 +4,4 @@

namespace prometheus {

std::string Serializer::Serialize(
const std::vector<MetricFamily>& metrics) const {
std::ostringstream ss;
Serialize(ss, metrics);
return ss.str();
}
} // namespace prometheus
83 changes: 55 additions & 28 deletions core/src/text_serializer.cc
Expand Up @@ -4,6 +4,7 @@
#include <limits>
#include <locale>
#include <ostream>
#include <sstream>
#include <string>

#include "prometheus/client_metric.h"
Expand Down Expand Up @@ -153,67 +154,93 @@ void SerializeHistogram(std::ostream& out, const MetricFamily& family,
WriteTail(out, metric);
}
}
} // namespace

TextSerializer::TextSerializer(IOVector& ioVector) : ioVector_(ioVector) {}

void TextSerializer::Serialize(const MetricFamily& family) const {
std::ostringstream out;

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;
// info is not handled by prometheus, we use gauge as workaround
// (https://github.com/OpenObservability/OpenMetrics/blob/98ae26c87b1c3bcf937909a880b32c8be643cc9b/specification/OpenMetrics.md#info-1)
case MetricType::Info:
out << "# TYPE " << family.name << " gauge\n";
for (auto& metric : family.metric) {
SerializeInfo(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;
}

Add(out);
}
} // namespace

void TextSerializer::Serialize(std::ostream& out,
const std::vector<MetricFamily>& metrics) const {
auto saved_locale = out.getloc();
auto saved_precision = out.precision();
void TextSerializer::Serialize(const MetricFamily& family,
const ClientMetric& metric) const {
std::ostringstream out;

out.imbue(std::locale::classic());
out.precision(std::numeric_limits<double>::max_digits10 - 1);

for (auto& family : metrics) {
SerializeFamily(out, family);
switch (family.type) {
case MetricType::Counter:
SerializeCounter(out, family, metric);
break;
case MetricType::Gauge:
SerializeGauge(out, family, metric);
break;
// info is not handled by prometheus, we use gauge as workaround
// (https://github.com/OpenObservability/OpenMetrics/blob/98ae26c87b1c3bcf937909a880b32c8be643cc9b/specification/OpenMetrics.md#info-1)
case MetricType::Info:
SerializeInfo(out, family, metric);
break;
case MetricType::Summary:
SerializeSummary(out, family, metric);
break;
case MetricType::Untyped:
SerializeUntyped(out, family, metric);
break;
case MetricType::Histogram:
SerializeHistogram(out, family, metric);
break;
}

out.imbue(saved_locale);
out.precision(saved_precision);
Add(out);
}

void TextSerializer::Add(const std::ostringstream& stream) const {
std::string str = stream.str();

std::size_t size = str.size();
std::size_t offset = 0;

while (size > 0) {
if (ioVector_.data.empty() || ioVector_.data.back().size() >= chunkSize_) {
ioVector_.data.emplace_back();
ioVector_.data.back().reserve(chunkSize_);
}
auto&& chunk = ioVector_.data.back();
std::size_t toAdd = std::min(size, chunkSize_ - chunk.size());
chunk.insert(chunk.end(), str.data() + offset, str.data() + offset + toAdd);

size -= toAdd;
offset += toAdd;
}
}

} // namespace prometheus

0 comments on commit a6f70c7

Please sign in to comment.