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
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ members = [
"foundations",
"foundations-macros",
"foundations-sentry",
"foundations-metrics-registry",
"examples",
"tools/gen-syscall-enum",
]
Expand Down Expand Up @@ -71,6 +72,10 @@ proc-macro2 = { version = "1.0.106", default-features = false }
prometheus = { version = "0.14.0", default-features = false }
prometheus-client = "0.18.1"
prometools = "0.2.3"
prost = "0.14.4"
prost-build = "0.14.4"
prost-types = "0.14.4"
protox = "0.9.1"
rand = "0.10.1"
percent-encoding = "2.3.2"
quote = "1.0.45"
Expand Down
19 changes: 19 additions & 0 deletions foundations-metrics-registry/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "foundations-metrics-registry"
version = "0.1.0"
edition = { workspace = true }
repository = { workspace = true }
authors = { workspace = true }
license = { workspace = true }

[lints]
workspace = true

[dependencies]
prost = { workspace = true }
prost-types = { workspace = true }

[dev-dependencies]
prost-build = { workspace = true }
protox = { workspace = true }
tempfile = { workspace = true }
156 changes: 156 additions & 0 deletions foundations-metrics-registry/proto/metrics.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// Copyright 2013 Prometheus Team
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto2";

package io.prometheus.client;
option java_package = "io.prometheus.client";
option go_package = "github.com/prometheus/client_model/go;io_prometheus_client";

import "google/protobuf/timestamp.proto";

message LabelPair {
optional string name = 1;
optional string value = 2;
}

enum MetricType {
// COUNTER must use the Metric field "counter".
COUNTER = 0;
// GAUGE must use the Metric field "gauge".
GAUGE = 1;
// SUMMARY must use the Metric field "summary".
SUMMARY = 2;
// UNTYPED must use the Metric field "untyped".
UNTYPED = 3;
// HISTOGRAM must use the Metric field "histogram".
HISTOGRAM = 4;
// GAUGE_HISTOGRAM must use the Metric field "histogram".
GAUGE_HISTOGRAM = 5;
}

message Gauge {
optional double value = 1;
}

message Counter {
optional double value = 1;
optional Exemplar exemplar = 2;

optional google.protobuf.Timestamp created_timestamp = 3;
}

message Quantile {
optional double quantile = 1;
optional double value = 2;
}

message Summary {
optional uint64 sample_count = 1;
optional double sample_sum = 2;
repeated Quantile quantile = 3;

optional google.protobuf.Timestamp created_timestamp = 4;
}

message Untyped {
optional double value = 1;
}

message Histogram {
optional uint64 sample_count = 1;
optional double sample_count_float = 4; // Overrides sample_count if > 0.
optional double sample_sum = 2;
// Buckets for the conventional histogram.
repeated Bucket bucket = 3; // Ordered in increasing order of upper_bound, +Inf bucket is optional.

optional google.protobuf.Timestamp created_timestamp = 15;

// Everything below here is for native histograms (formerly known as sparse histograms).

// schema defines the bucket schema. Currently, valid numbers are -4 <= n <= 8.
// They are all for base-2 bucket schemas, where 1 is a bucket boundary in each case, and
// then each power of two is divided into 2^n logarithmic buckets.
// Or in other words, each bucket boundary is the previous boundary times 2^(2^-n).
// In the future, more bucket schemas may be added using numbers < -4 or > 8.
optional sint32 schema = 5;
optional double zero_threshold = 6; // Breadth of the zero bucket.
optional uint64 zero_count = 7; // Count in zero bucket.
optional double zero_count_float = 8; // Overrides sb_zero_count if > 0.

// Negative buckets for the native histogram.
repeated BucketSpan negative_span = 9;
// Use either "negative_delta" or "negative_count", the former for
// regular histograms with integer counts, the latter for float
// histograms.
repeated sint64 negative_delta = 10; // Count delta of each bucket compared to previous one (or to zero for 1st bucket).
repeated double negative_count = 11; // Absolute count of each bucket.

// Positive buckets for the native histogram.
// Use a no-op span (offset 0, length 0) for a native histogram without any
// observations yet and with a zero_threshold of 0. Otherwise, it would be
// indistinguishable from a classic histogram.
repeated BucketSpan positive_span = 12;
// Use either "positive_delta" or "positive_count", the former for
// regular histograms with integer counts, the latter for float
// histograms.
repeated sint64 positive_delta = 13; // Count delta of each bucket compared to previous one (or to zero for 1st bucket).
repeated double positive_count = 14; // Absolute count of each bucket.

// Only used for native histograms. These exemplars MUST have a timestamp.
repeated Exemplar exemplars = 16;
}

// A Bucket of a conventional histogram, each of which is treated as
// an individual counter-like time series by Prometheus.
message Bucket {
optional uint64 cumulative_count = 1; // Cumulative in increasing order.
optional double cumulative_count_float = 4; // Overrides cumulative_count if > 0.
optional double upper_bound = 2; // Inclusive.
optional Exemplar exemplar = 3;
}

// A BucketSpan defines a number of consecutive buckets in a native
// histogram with their offset. Logically, it would be more
// straightforward to include the bucket counts in the Span. However,
// the protobuf representation is more compact in the way the data is
// structured here (with all the buckets in a single array separate
// from the Spans).
message BucketSpan {
optional sint32 offset = 1; // Gap to previous span, or starting point for 1st span (which can be negative).
optional uint32 length = 2; // Length of consecutive buckets.
}

message Exemplar {
repeated LabelPair label = 1;
optional double value = 2;
optional google.protobuf.Timestamp timestamp = 3; // OpenMetrics-style.
}

message Metric {
repeated LabelPair label = 1;
optional Gauge gauge = 2;
optional Counter counter = 3;
optional Summary summary = 4;
optional Untyped untyped = 5;
optional Histogram histogram = 7;
optional int64 timestamp_ms = 6;
}

message MetricFamily {
optional string name = 1;
optional string help = 2;
optional MetricType type = 3;
repeated Metric metric = 4;
optional string unit = 5;
}
22 changes: 22 additions & 0 deletions foundations-metrics-registry/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//! The stable core of the `foundations` metrics stack.
//!
//! `foundations-metrics-registry` holds the parts of the metrics stack that must
//! stay stable across `foundations` major versions. Today that is the [`proto`]
//! data model: the [`prometheus/client_model`] protobuf types that are the
//! canonical wire format for the protobuf `/metrics` endpoint.
//!
//! The crate is kept small and dependency-light on purpose. The metrics registry
//! is a process-global singleton, so when two `foundations` majors are linked
//! into the same binary they must resolve to the *same* version of this crate to
//! share one registry rather than splitting metrics between them. A minimal,
//! slow-moving crate is what keeps that shared version easy to hold still — the
//! one expected source-breaking change is a change to the protobuf data model.
//!
//! Everything that can evolve more freely — metric types, encoders, label
//! serialisation, and service-name handling — lives in the sibling
//! `foundations-metrics` crate, not here.
//!
//! [`prometheus/client_model`]: https://github.com/prometheus/client_model
#![warn(missing_docs)]

pub mod proto;
60 changes: 60 additions & 0 deletions foundations-metrics-registry/src/proto/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! The Prometheus protobuf data model (package `io.prometheus.client`).
//!
//! These types mirror [`prometheus/client_model`] and are the canonical wire
//! format for the protobuf `/metrics` endpoint. The generated Rust model is
//! checked in and verified against the vendored `proto/metrics.proto`.
//!
//! [`prometheus/client_model`]: https://github.com/prometheus/client_model

#[allow(missing_docs, unreachable_pub, clippy::all)]
mod model;

pub use model::{BucketSpan, Histogram, Metric, MetricFamily, MetricType};

#[cfg(test)]
mod tests {
use super::*;
use prost::Message;

#[test]
fn native_histogram_family_round_trips() {
let family = MetricFamily {
name: Some("requests_duration_seconds".to_owned()),
help: Some("Request duration.".to_owned()),
r#type: Some(MetricType::Histogram as i32),
unit: Some("seconds".to_owned()),
metric: vec![Metric {
histogram: Some(Histogram {
sample_count: Some(3),
sample_sum: Some(1.5),
schema: Some(2),
zero_threshold: Some(1e-9),
zero_count: Some(1),
positive_span: vec![BucketSpan {
offset: Some(0),
length: Some(2),
}],
positive_delta: vec![1, 0],
..Default::default()
}),
..Default::default()
}],
};

let decoded = MetricFamily::decode(family.encode_to_vec().as_slice())
.expect("protobuf roundtrip should succeed");

assert_eq!(decoded, family);
assert_eq!(decoded.r#type(), MetricType::Histogram);

let histogram = decoded.metric[0]
.histogram
.as_ref()
.expect("histogram present");

assert_eq!(histogram.schema, Some(2));
assert_eq!(histogram.zero_count, Some(1));
assert_eq!(histogram.positive_span.len(), 1);
assert_eq!(histogram.positive_delta, vec![1, 0]);
}
}
Loading
Loading