Skip to content
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
6 changes: 6 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
build:asan --strip=never
build:asan --copt -fsanitize=address
build:asan --copt -O0
build:asan --copt -fno-omit-frame-pointer
build:asan --copt -g
build:asan --linkopt -fsanitize=address
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ offer the possibility for metrics to collected by Prometheus, but
other push/pull collections can be added as plugins.

## Project Status
Pre-alpha
Alpha

* parts of the library are instrumented by itself (bytes scraped, number of scrapes)
* there is a working [example](tests/sample_server.cc) that prometheus successfully scrapes
* gauge and counter metrics are implemented, histograms and summaries aren't
* thread safety is missing in registries and metric families (you'd have to lock access yourself for now)

## License
MIT
7 changes: 5 additions & 2 deletions lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ cc_library(
name = "prometheus-cpp",
srcs = ["counter.cc",
"gauge.cc",
"exposer.cc"],
"exposer.cc",
"registry.cc"],
hdrs = ["counter.h",
"gauge.h",
"exposer.h",
"metric.h",
"collectable.h",
"family.h"],
"family.h",
"registry.h"],
visibility = ["//visibility:public"],
deps = ["@protobuf//:protobuf",
"@prometheus_client_model//:prometheus_client_model",
"@civetweb//:civetweb",
],
linkstatic = 1,
)
96 changes: 58 additions & 38 deletions lib/exposer.cc
Original file line number Diff line number Diff line change
@@ -1,61 +1,81 @@
#include <chrono>
#include <sstream>
#include <string>
#include <thread>
#include <sstream>

#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>

#include "exposer.h"

#include "cpp/metrics.pb.h"

namespace prometheus {
MetricsHandler::MetricsHandler(
const std::vector<std::weak_ptr<Collectable>>& collectables,
Registry& registry)
: collectables_(collectables),
bytesTransferedFamily_(registry.add_counter(
"exposer_bytes_transfered", "bytesTransferred to metrics services",
{{"component", "exposer"}})),
bytesTransfered_(bytesTransferedFamily_->add({})),
numScrapesFamily_(registry.add_counter(
"exposer_total_scrapes", "Number of times metrics were scraped",
{{"component", "exposer"}})),
numScrapes_(numScrapesFamily_->add({})) {}

bool MetricsHandler::handleGet(CivetServer* server,
struct mg_connection* conn) {
using namespace io::prometheus::client;

std::ostringstream ss;
for (auto&& wcollectable : collectables_) {
auto collectable = wcollectable.lock();
if (!collectable) {
continue;
}

class MetricsHandler : public CivetHandler {
public:
bool handleGet(CivetServer* server, struct mg_connection* conn) {
using namespace io::prometheus::client;

MetricFamily message;
message.set_name("Foo");
message.set_help("Foo help");
message.set_type(MetricType::COUNTER);
auto metric1 = message.add_metric();
auto counter = metric1->mutable_counter();
counter->set_value(1337.0);

std::ostringstream ss;
{
for (auto&& metricFamily : collectable->collect()) {
{
google::protobuf::io::OstreamOutputStream rawOutput{&ss};
google::protobuf::io::CodedOutputStream output(&rawOutput);

// Write the size.
const int size = message.ByteSize();
const int size = metricFamily.ByteSize();
output.WriteVarint32(size);
}
}

auto buf = ss.str();
message.AppendToString(&buf);
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: "
"application/vnd.google.protobuf; "
"proto=io.prometheus.client.MetricFamily; "
"encoding=delimited\r\n"
"Content-Length: ");
mg_printf(conn, "%lu\r\n\r\n", buf.size());
mg_write(conn, buf.data(), buf.size());
return true;
auto buffer = std::string{};
metricFamily.SerializeToString(&buffer);
ss << buffer;
}
}
};

auto body = ss.str();
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: "
"application/vnd.google.protobuf; "
"proto=io.prometheus.client.MetricFamily; "
"encoding=delimited\r\n"
"Content-Length: ");
mg_printf(conn, "%lu\r\n\r\n", body.size());
mg_write(conn, body.data(), body.size());
bytesTransfered_->inc(body.size());
numScrapes_->inc();
return true;
}

Exposer::Exposer(std::uint16_t port)
: server_({"listening_ports", std::to_string(port)}) {
MetricsHandler handler;
server_.addHandler("/metrics", &handler);
std::this_thread::sleep_for(std::chrono::seconds(60000));
: server_({"listening_ports", std::to_string(port)}),
exposerRegistry_(
std::make_shared<Registry>(std::map<std::string, std::string>{})),
metricsHandler_(collectables_, *exposerRegistry_) {
registerCollectable(exposerRegistry_);
server_.addHandler("/metrics", &metricsHandler_);
}

void Exposer::run() {}
void Exposer::registerCollectable(
const std::weak_ptr<Collectable>& collectable) {
collectables_.push_back(collectable);
}
}
30 changes: 25 additions & 5 deletions lib/exposer.h
Original file line number Diff line number Diff line change
@@ -1,17 +1,37 @@
#pragma once

#include <atomic>
#include <cstdint>
#include <memory>

#include "CivetServer.h"
#include "registry.h"

namespace prometheus {

class Exposer {
class MetricsHandler : public CivetHandler {
public:
Exposer(std::uint16_t port);
void run();
private:
CivetServer server_;
MetricsHandler(const std::vector<std::weak_ptr<Collectable>>& collectables,
Registry& registry);

bool handleGet(CivetServer* server, struct mg_connection* conn);

const std::vector<std::weak_ptr<Collectable>>& collectables_;
Family<Counter>* bytesTransferedFamily_;
Counter* bytesTransfered_;
Family<Counter>* numScrapesFamily_;
Counter* numScrapes_;
};

class Exposer {
public:
Exposer(std::uint16_t port);
void registerCollectable(const std::weak_ptr<Collectable>& collectable);

private:
CivetServer server_;
std::vector<std::weak_ptr<Collectable>> collectables_;
std::shared_ptr<Registry> exposerRegistry_;
MetricsHandler metricsHandler_;
};
}
2 changes: 2 additions & 0 deletions lib/family.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
#include <string>
#include <unordered_map>

#include "counter.h"
#include "collectable.h"
#include "gauge.h"
#include "metric.h"

namespace prometheus {
Expand Down
13 changes: 10 additions & 3 deletions lib/gauge.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ void Gauge::dec(double value) {
void Gauge::set(double value) { value_.store(value); }

void Gauge::change(double value) {
auto current = value_.load();
while (!value_.compare_exchange_weak(current, current + value))
;
auto current = value_.load();
while (!value_.compare_exchange_weak(current, current + value))
;
}

void Gauge::set_to_current_time() {
Expand All @@ -38,4 +38,11 @@ void Gauge::set_to_current_time() {
}

double Gauge::value() const { return value_; }

io::prometheus::client::Metric Gauge::collect() {
io::prometheus::client::Metric metric;
auto gauge = metric.mutable_gauge();
gauge->set_value(value());
return metric;
}
}
10 changes: 9 additions & 1 deletion lib/gauge.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@

#include <atomic>

#include "cpp/metrics.pb.h"
#include "metric.h"

namespace prometheus {

class Gauge {
class Gauge : public Metric {
public:
static const io::prometheus::client::MetricType metric_type =
io::prometheus::client::GAUGE;

Gauge();
Gauge(double);
void inc();
Expand All @@ -16,6 +22,8 @@ class Gauge {
void set_to_current_time();
double value() const;

io::prometheus::client::Metric collect();

private:
void change(double);
std::atomic<double> value_;
Expand Down
43 changes: 43 additions & 0 deletions lib/registry.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include "registry.h"

namespace prometheus {

Registry::Registry(const std::map<std::string, std::string>& constLabels)
: constLabels_(constLabels) {}

Family<Counter>* Registry::add_counter(
const std::string& name, const std::string& help,
const std::map<std::string, std::string>& labels) {
auto counterFamily = new Family<Counter>(name, help, labels);
collectables_.push_back(std::unique_ptr<Collectable>{counterFamily});
return counterFamily;
}

Family<Gauge>* Registry::add_gauge(
const std::string& name, const std::string& help,
const std::map<std::string, std::string>& labels) {
auto gaugeFamily = new Family<Gauge>(name, help, labels);
collectables_.push_back(std::unique_ptr<Collectable>{gaugeFamily});
return gaugeFamily;
}

std::vector<io::prometheus::client::MetricFamily> Registry::collect() {
auto results = std::vector<io::prometheus::client::MetricFamily>{};
for(auto&& collectable : collectables_) {
auto metrics = collectable->collect();
results.insert(results.end(), metrics.begin(), metrics.end());
}

for (auto&& metricFamily : results) {
for (auto&& metric : *metricFamily.mutable_metric()) {
for (auto&& constLabelPair : constLabels_) {
auto label = metric.add_label();
label->set_name(constLabelPair.first);
label->set_value(constLabelPair.second);
}
}
}

return results;
}
}
31 changes: 31 additions & 0 deletions lib/registry.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include <map>

#include "collectable.h"
#include "cpp/metrics.pb.h"
#include "family.h"

namespace prometheus {

class Counter;
class Gauge;

class Registry : public Collectable {
public:
Registry() = default;
Registry(const std::map<std::string, std::string>& constLabels);
Family<Counter>* add_counter(
const std::string& name, const std::string& help,
const std::map<std::string, std::string>& labels);
Family<Gauge>* add_gauge(const std::string& name, const std::string& help,
const std::map<std::string, std::string>& labels);

// collectable
std::vector<io::prometheus::client::MetricFamily> collect() override;

private:
std::vector<std::unique_ptr<Collectable>> collectables_;
std::map<std::string, std::string> constLabels_;
};
}
3 changes: 2 additions & 1 deletion tests/BUILD
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
cc_test(
name = "prometheus_test",
srcs = ["counter_test.cc", "gauge_test.cc", "mock_metric.h", "family_test.cc"],
srcs = ["counter_test.cc", "gauge_test.cc", "mock_metric.h", "family_test.cc", "registry_test.cc"],
copts = ["-Iexternal/googletest/include"],
deps = ["@googletest//:main",
"//lib:prometheus-cpp"],
linkstatic = 1,
)

cc_binary(
Expand Down
32 changes: 32 additions & 0 deletions tests/registry_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include <vector>

#include <gmock/gmock.h>

#include "lib/registry.h"
#include "lib/collectable.h"

using namespace testing;
using namespace prometheus;

class MockCollectable : public Collectable {
public:
MOCK_METHOD0(collect, std::vector<io::prometheus::client::MetricFamily>());
};

class RegistryTest : public Test {};

TEST_F(RegistryTest, collectsSingleMetricFamily) {
auto registry = Registry{};
auto counterFamily = registry.add_counter("test", "a test", {});
counterFamily->add({{"name", "counter1"}});
counterFamily->add({{"name", "counter2"}});
auto collected = registry.collect();
ASSERT_EQ(collected.size(), 1);
EXPECT_EQ(collected[0].name(), "test");
EXPECT_EQ(collected[0].help(), "a test");
ASSERT_EQ(collected[0].metric_size(), 2);
ASSERT_EQ(collected[0].metric(0).label_size(), 1);
EXPECT_EQ(collected[0].metric(0).label(0).name(), "name");
ASSERT_EQ(collected[0].metric(1).label_size(), 1);
EXPECT_EQ(collected[0].metric(1).label(0).name(), "name");
}
Loading