Skip to content
Merged
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
128 changes: 101 additions & 27 deletions include/OtelMetrics.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,99 @@

namespace OTel {

// — a single, shared ResourceConfig for all metrics —
static inline OTelResourceConfig& defaultMetricResource() {
static OTelResourceConfig rc;
return rc; // <-- returns a reference, not a temporary
}

struct Metrics {
/// Call once in setup() to stamp service attributes on all metrics
static void begin(const String& serviceName,
const String& serviceNamespace,
const String& instanceId,
const String& version = "") {
auto& rc = defaultMetricResource();
rc.setAttribute("service.name", serviceName);
rc.setAttribute("service.namespace", serviceNamespace);
rc.setAttribute("service.instance.id", instanceId);
if (!version.isEmpty()) {
rc.setAttribute("service.version", version);
}
}
};

class OTelMetricBase {
protected:
String name;
String unit;
OTelResourceConfig config;
OTelResourceConfig& config = OTel::defaultMetricResource();
//OTelResourceConfig config;

public:
OTelMetricBase(String metricName, String metricUnit = "")
: name(metricName), unit(metricUnit), config(getDefaultResource()) {}

void setAttribute(const String &key, const String &value) {
config.setAttribute(key, value);
}
// ctor must initialize config from the ref‑returning function
OTelMetricBase(const String& metricName,
const String& metricUnit)
: name(metricName),
unit(metricUnit),
config(OTel::defaultMetricResource()) // <-- now an lvalue reference
{}
};

#include <ArduinoJson.h>
#include "OtelDefaults.h" // provides OTelResourceConfig
#include "OtelSender.h" // provides OTelSender::sendJson()

class OTelGauge : public OTelMetricBase {
public:
using OTelMetricBase::OTelMetricBase;

void set(float value) {
// elastic, heap‑backed document—no need to pick static vs dynamic.
JsonDocument doc;

JsonObject resourceMetric = doc["resourceMetrics"].add<JsonObject>();
// ── resourceMetrics → [ { … } ]
JsonArray resourceMetrics = doc["resourceMetrics"].to<JsonArray>();
JsonObject resourceMetric = resourceMetrics.add<JsonObject>();

// └─ resource
JsonObject resource = resourceMetric["resource"].to<JsonObject>();
config.addResourceAttributes(resource);

JsonObject scopeMetric = resourceMetric["scopeMetrics"].add<JsonObject>();
// ── scopeMetrics → [ { … } ]
JsonArray scopeMetrics = resourceMetric["scopeMetrics"].to<JsonArray>();
JsonObject scopeMetric = scopeMetrics.add<JsonObject>();

// └─ scope
JsonObject scope = scopeMetric["scope"].to<JsonObject>();
scope["name"] = "otel-embedded";
scope["name"] = "otel-embedded";
scope["version"] = "0.1.0";

JsonObject metric = scopeMetric["metrics"].add<JsonObject>();
// └─ metrics → [ { … } ]
JsonArray metrics = scopeMetric["metrics"].to<JsonArray>();
JsonObject metric = metrics.add<JsonObject>();
metric["name"] = name;
metric["unit"] = unit;
metric["type"] = "gauge";

JsonObject gauge = metric["gauge"].to<JsonObject>();
JsonObject dp = gauge["dataPoints"].add<JsonObject>();
// └─ gauge.dataPoints → [ { … } ]
JsonObject gauge = metric["gauge"].to<JsonObject>();
JsonArray dataPoints = gauge["dataPoints"].to<JsonArray>();
JsonObject dp = dataPoints.add<JsonObject>();

// ├─ attach the same resource labels on each point
config.addResourceAttributes(dp);

// ├─ timestamp & value
dp["timeUnixNano"] = nowUnixNano();
dp["asDouble"] = value;
dp["asDouble"] = value;

// send it off
OTelSender::sendJson("/v1/metrics", doc);
}
};


class OTelCounter : public OTelMetricBase {
private:
float count = 0;
Expand Down Expand Up @@ -97,51 +144,78 @@ class OTelHistogram : public OTelMetricBase {
using OTelMetricBase::OTelMetricBase;

void record(double value) {
// no size parameters, use the new JsonDocument
// 1) Build the top‐level JSON document
JsonDocument doc;

// resourceMetrics → array
// ── resourceMetrics → [ { … } ]
JsonArray resourceMetrics = doc["resourceMetrics"].to<JsonArray>();
JsonObject resourceMetric = resourceMetrics.add<JsonObject>();

// resource + attributes
// └─ resource
JsonObject resource = resourceMetric["resource"].to<JsonObject>();
config.addResourceAttributes(resource);

// scopeMetrics → array
// ── scopeMetrics → [ { … } ]
JsonArray scopeMetrics = resourceMetric["scopeMetrics"].to<JsonArray>();
JsonObject scopeMetric = scopeMetrics.add<JsonObject>();

// scope
// └─ scope
JsonObject scope = scopeMetric["scope"].to<JsonObject>();
scope["name"] = "otel-embedded";
scope["version"] = "0.1.0";

// metrics → array
// └─ metrics → [ { … } ]
JsonArray metrics = scopeMetric["metrics"].to<JsonArray>();
JsonObject metric = metrics.add<JsonObject>();
metric["name"] = name;
metric["unit"] = unit;
metric["type"] = "histogram";

// histogram object
// └─ histogram
JsonObject histogram = metric["histogram"].to<JsonObject>();
histogram["aggregationTemporality"] = 2;
histogram["aggregationTemporality"] = 2; // DELTA = 2

// dataPoints → array
// └─ dataPoints → [ { … } ]
JsonArray dataPoints = histogram["dataPoints"].to<JsonArray>();
JsonObject dp = dataPoints.add<JsonObject>();

// attach resource attrs, timestamp, and value
// ├─ shared resource labels
config.addResourceAttributes(dp);
dp["timeUnixNano"] = nowUnixNano();
dp["asDouble"] = value;

// ship it off
// ├─ timestamp, count, sum
dp["timeUnixNano"] = nowUnixNano();
dp["count"] = 1;
dp["sum"] = value;

// ├─ explicitBounds (define your buckets here)
static const double explicitBounds[] = {100.0, 200.0, 500.0, 1000.0};
static const size_t B = sizeof(explicitBounds) / sizeof(*explicitBounds);
JsonArray boundsArr = dp["explicitBounds"].to<JsonArray>();
for (size_t i = 0; i < B; ++i) {
boundsArr.add(explicitBounds[i]);
}

// └─ bucketCounts (one slot per boundary + underflow/overflow)
JsonArray countsArr = dp["bucketCounts"].to<JsonArray>();
// find which bucket this value falls into
size_t bucketIdx = B; // overflow by default
for (size_t i = 0; i < B; ++i) {
if (value <= explicitBounds[i]) {
bucketIdx = i;
break;
}
}
// emit counts: exactly one '1' in the right bucket, zero elsewhere
for (size_t i = 0; i <= B; ++i) {
countsArr.add(i == bucketIdx ? 1 : 0);
}

// 2) Send it
OTelSender::sendJson("/v1/metrics", doc);
}
};


} // namespace OTel

#endif
Expand Down