Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

core: Add option to merge families #286

Merged
merged 1 commit into from
Oct 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions core/include/prometheus/family.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ class PROMETHEUS_CPP_CORE_EXPORT Family : public Collectable {
/// if the given metric was not returned by Add().
void Remove(T* metric);

/// \brief Returns the name for this family.
///
/// \return The family name.
const std::string& GetName() const;

/// \brief Returns the constant labels for this family.
///
/// \return All constant labels as key-value pairs.
const std::map<std::string, std::string> GetConstantLabels() const;

/// \brief Returns the current value of each dimensional data.
///
/// Collect is called by the Registry when collecting metrics.
Expand Down
33 changes: 31 additions & 2 deletions core/include/prometheus/registry.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,27 @@ class Builder;
/// a data race.
class PROMETHEUS_CPP_CORE_EXPORT Registry : public Collectable {
public:
/// \brief How to deal with repeatedly added family names for a type.
///
/// Adding a family with the same name but different types is always an error
/// and will lead to an exception.
enum class InsertBehavior {
/// \brief If a family with the same name and labels already exists return
/// the existing one. If no family with that name exists create it.
/// Otherwise throw.
Merge,
/// \brief Throws if a family with the same name already exists.
Throw,
/// \brief Never merge and always create a new family. This violates the
/// prometheus specification but was the default behavior in earlier
/// versions
NonStandardAppend,
};

/// \brief name Create a new registry.
Registry();
///
/// \param insert_behavior How to handle families with the same name.
explicit Registry(InsertBehavior insert_behavior = InsertBehavior::Merge);

/// \brief name Destroys a registry.
~Registry();
Expand All @@ -58,11 +77,21 @@ class PROMETHEUS_CPP_CORE_EXPORT Registry : public Collectable {
template <typename T>
friend class detail::Builder;

template <typename T>
std::vector<std::unique_ptr<Family<T>>>& GetFamilies();

template <typename T>
bool NameExistsInOtherType(const std::string& name) const;

template <typename T>
Family<T>& Add(const std::string& name, const std::string& help,
const std::map<std::string, std::string>& labels);

std::vector<std::unique_ptr<Collectable>> collectables_;
const InsertBehavior insert_behavior_;
std::vector<std::unique_ptr<Family<Counter>>> counters_;
gjasny marked this conversation as resolved.
Show resolved Hide resolved
std::vector<std::unique_ptr<Family<Gauge>>> gauges_;
std::vector<std::unique_ptr<Family<Histogram>>> histograms_;
std::vector<std::unique_ptr<Family<Summary>>> summaries_;
std::mutex mutex_;
};

Expand Down
10 changes: 10 additions & 0 deletions core/src/family.cc
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ void Family<T>::Remove(T* metric) {
labels_reverse_lookup_.erase(metric);
}

template <typename T>
const std::string& Family<T>::GetName() const {
return name_;
}

template <typename T>
const std::map<std::string, std::string> Family<T>::GetConstantLabels() const {
return constant_labels_;
}

template <typename T>
std::vector<MetricFamily> Family<T>::Collect() {
std::lock_guard<std::mutex> lock{mutex_};
Expand Down
112 changes: 105 additions & 7 deletions core/src/registry.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,127 @@

namespace prometheus {

Registry::Registry() = default;
namespace {
template <typename T>
void CollectAll(std::vector<MetricFamily>& results, 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()));
}
}

bool FamilyNameExists(const std::string& /* name */) { return false; }

template <typename T, typename... Args>
bool FamilyNameExists(const std::string& name, const T& families,
Args&&... args) {
auto sameName = [&name](const typename T::value_type& entry) {
return name == entry->GetName();
};
auto exists = std::find_if(std::begin(families), std::end(families),
sameName) != std::end(families);
return exists || FamilyNameExists(name, args...);
}
} // namespace

Registry::Registry(InsertBehavior insert_behavior)
: insert_behavior_{insert_behavior} {}

Registry::~Registry() = default;

std::vector<MetricFamily> Registry::Collect() {
std::lock_guard<std::mutex> lock{mutex_};
auto results = std::vector<MetricFamily>{};
for (auto&& collectable : collectables_) {
auto metrics = collectable->Collect();
results.insert(results.end(), std::make_move_iterator(metrics.begin()),
std::make_move_iterator(metrics.end()));
}

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

return results;
}

template <>
std::vector<std::unique_ptr<Family<Counter>>>& Registry::GetFamilies() {
return counters_;
}

template <>
std::vector<std::unique_ptr<Family<Gauge>>>& Registry::GetFamilies() {
return gauges_;
}

template <>
std::vector<std::unique_ptr<Family<Histogram>>>& Registry::GetFamilies() {
return histograms_;
}

template <>
std::vector<std::unique_ptr<Family<Summary>>>& Registry::GetFamilies() {
return summaries_;
}

template <>
bool Registry::NameExistsInOtherType<Counter>(const std::string& name) const {
return FamilyNameExists(name, gauges_, histograms_, summaries_);
}

template <>
bool Registry::NameExistsInOtherType<Gauge>(const std::string& name) const {
return FamilyNameExists(name, counters_, histograms_, summaries_);
}

template <>
bool Registry::NameExistsInOtherType<Histogram>(const std::string& name) const {
return FamilyNameExists(name, counters_, gauges_, summaries_);
}

template <>
bool Registry::NameExistsInOtherType<Summary>(const std::string& name) const {
return FamilyNameExists(name, counters_, gauges_, histograms_);
}

template <typename T>
Family<T>& Registry::Add(const std::string& name, const std::string& help,
const std::map<std::string, std::string>& labels) {
std::lock_guard<std::mutex> lock{mutex_};

if (NameExistsInOtherType<T>(name)) {
throw std::invalid_argument(
"Family name already exists with different type");
}

auto& families = GetFamilies<T>();

if (insert_behavior_ == InsertBehavior::Merge) {
auto same_name_and_labels =
[&name, &labels](const std::unique_ptr<Family<T>>& family) {
return std::tie(name, labels) ==
std::tie(family->GetName(), family->GetConstantLabels());
};

auto it =
std::find_if(families.begin(), families.end(), same_name_and_labels);
if (it != families.end()) {
return **it;
}
}

if (insert_behavior_ != InsertBehavior::NonStandardAppend) {
auto same_name = [&name](const std::unique_ptr<Family<T>>& family) {
return name == family->GetName();
};

auto it = std::find_if(families.begin(), families.end(), same_name);
if (it != families.end()) {
throw std::invalid_argument("Family name already exists");
}
}

auto family = detail::make_unique<Family<T>>(name, help, labels);
auto& ref = *family;
collectables_.push_back(std::move(family));
families.push_back(std::move(family));
return ref;
}

Expand Down
101 changes: 99 additions & 2 deletions core/tests/registry_test.cc
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
#include "prometheus/registry.h"
#include "prometheus/counter.h"
#include "prometheus/histogram.h"
#include "prometheus/summary.h"

#include <vector>

#include <gmock/gmock.h>

#include "prometheus/collectable.h"

namespace prometheus {
namespace {

Expand Down Expand Up @@ -39,5 +38,103 @@ TEST(RegistryTest, build_histogram_family) {
ASSERT_EQ(collected.size(), 1U);
}

TEST(RegistryTest, reject_different_type_than_counter) {
const auto same_name = std::string{"same_name"};
Registry registry{};

EXPECT_NO_THROW(BuildCounter().Name(same_name).Register(registry));
EXPECT_ANY_THROW(BuildGauge().Name(same_name).Register(registry));
EXPECT_ANY_THROW(BuildHistogram().Name(same_name).Register(registry));
EXPECT_ANY_THROW(BuildSummary().Name(same_name).Register(registry));
}

TEST(RegistryTest, reject_different_type_than_gauge) {
const auto same_name = std::string{"same_name"};
Registry registry{};

EXPECT_NO_THROW(BuildGauge().Name(same_name).Register(registry));
EXPECT_ANY_THROW(BuildCounter().Name(same_name).Register(registry));
EXPECT_ANY_THROW(BuildHistogram().Name(same_name).Register(registry));
EXPECT_ANY_THROW(BuildSummary().Name(same_name).Register(registry));
}

TEST(RegistryTest, reject_different_type_than_histogram) {
const auto same_name = std::string{"same_name"};
Registry registry{};

EXPECT_NO_THROW(BuildHistogram().Name(same_name).Register(registry));
EXPECT_ANY_THROW(BuildCounter().Name(same_name).Register(registry));
EXPECT_ANY_THROW(BuildGauge().Name(same_name).Register(registry));
EXPECT_ANY_THROW(BuildSummary().Name(same_name).Register(registry));
}

TEST(RegistryTest, reject_different_type_than_summary) {
const auto same_name = std::string{"same_name"};
Registry registry{};

EXPECT_NO_THROW(BuildSummary().Name(same_name).Register(registry));
EXPECT_ANY_THROW(BuildCounter().Name(same_name).Register(registry));
EXPECT_ANY_THROW(BuildGauge().Name(same_name).Register(registry));
EXPECT_ANY_THROW(BuildHistogram().Name(same_name).Register(registry));
}

TEST(RegistryTest, append_same_families) {
Registry registry{Registry::InsertBehavior::NonStandardAppend};

std::size_t loops = 4;

while (loops-- > 0) {
BuildCounter()
.Name("counter")
.Help("Test Counter")
.Register(registry)
.Add({{"name", "test_counter"}});
}

auto collected = registry.Collect();
EXPECT_EQ(4U, collected.size());
}

TEST(RegistryTest, throw_for_same_family_name) {
const auto same_name = std::string{"same_name"};
Registry registry{Registry::InsertBehavior::Throw};

EXPECT_NO_THROW(BuildCounter().Name(same_name).Register(registry));
EXPECT_ANY_THROW(BuildCounter().Name(same_name).Register(registry));
}

TEST(RegistryTest, merge_same_families) {
Registry registry{Registry::InsertBehavior::Merge};

std::size_t loops = 4;

while (loops-- > 0) {
BuildCounter()
.Name("counter")
.Help("Test Counter")
.Register(registry)
.Add({{"name", "test_counter"}});
}

auto collected = registry.Collect();
EXPECT_EQ(1U, collected.size());
}

TEST(RegistryTest, do_not_merge_families_with_different_labels) {
Registry registry{Registry::InsertBehavior::Merge};

EXPECT_NO_THROW(BuildCounter()
.Name("counter")
.Help("Test Counter")
.Labels({{"a", "A"}})
.Register(registry));

EXPECT_ANY_THROW(BuildCounter()
.Name("counter")
.Help("Test Counter")
.Labels({{"b", "B"}})
.Register(registry));
}

} // namespace
} // namespace prometheus