metrics: Add support for labels

Given that label values may be dynamic, any metrics callsite with labels
can't be stored statically. Instead, we contruct a fresh metrics Key each
time we hit a metric with labels (if a metrics recorder is installed).
This commit is contained in:
Jack Grigg 2020-12-24 16:33:33 +00:00
parent 7a96af8260
commit 92e75de46f
2 changed files with 235 additions and 38 deletions

View File

@ -5,6 +5,9 @@
#ifndef ZCASH_RUST_INCLUDE_RUST_METRICS_H
#define ZCASH_RUST_INCLUDE_RUST_METRICS_H
#include "rust/helpers.h"
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
@ -22,6 +25,9 @@ bool metrics_run(const char* listen_address);
struct MetricsCallsite;
typedef struct MetricsCallsite MetricsCallsite;
struct MetricsKey;
typedef struct MetricsKey MetricsKey;
/// Creates a metrics callsite.
///
/// You should usually call one of the helper macros such as `MetricsCounter`
@ -32,36 +38,85 @@ typedef struct MetricsCallsite MetricsCallsite;
/// UTF-8.
MetricsCallsite* metrics_callsite(const char* name);
/// Creates a metrics key.
///
/// This API supports labels that may not have static values, and is intended
/// to be called for each metrics callsite invocation. As such, it returns null
/// if a metrics recorder is not installed, to save on construction costs.
///
/// You should usually call one of the helper macros such as `MetricsCounter`
/// instead of calling this directly.
///
/// API requirements:
/// - label_names and label_values, if not null, MUST be the same length.
/// - All string arguments MUST be valid UTF-8.
MetricsKey* metrics_key(
const char* name,
const char* const* label_names,
const char* const* label_values,
size_t labels_len);
/// Increments a counter.
///
/// Counters represent a single monotonic value, which means the value can only
/// be incremented, not decremented, and always starts out with an initial value
/// of zero.
void metrics_increment_counter(const MetricsCallsite* callsite, uint64_t value);
void metrics_static_increment_counter(const MetricsCallsite* callsite, uint64_t value);
/// Increments a counter.
///
/// Counters represent a single monotonic value, which means the value can only
/// be incremented, not decremented, and always starts out with an initial value
/// of zero.
void metrics_increment_counter(MetricsKey* key, uint64_t value);
/// Updates a gauge.
///
/// Gauges represent a single value that can go up or down over time, and always
/// starts out with an initial value of zero.
void metrics_update_gauge(const MetricsCallsite* callsite, double value);
void metrics_static_update_gauge(const MetricsCallsite* callsite, double value);
/// Updates a gauge.
///
/// Gauges represent a single value that can go up or down over time, and always
/// starts out with an initial value of zero.
void metrics_update_gauge(MetricsKey* callsite, double value);
/// Increments a gauge.
///
/// Gauges represent a single value that can go up or down over time, and always
/// starts out with an initial value of zero.
void metrics_increment_gauge(const MetricsCallsite* callsite, double value);
void metrics_static_increment_gauge(const MetricsCallsite* callsite, double value);
/// Increments a gauge.
///
/// Gauges represent a single value that can go up or down over time, and always
/// starts out with an initial value of zero.
void metrics_increment_gauge(MetricsKey* callsite, double value);
/// Decrements a gauge.
///
/// Gauges represent a single value that can go up or down over time, and always
/// starts out with an initial value of zero.
void metrics_decrement_gauge(const MetricsCallsite* callsite, double value);
void metrics_static_decrement_gauge(const MetricsCallsite* callsite, double value);
/// Decrements a gauge.
///
/// Gauges represent a single value that can go up or down over time, and always
/// starts out with an initial value of zero.
void metrics_decrement_gauge(MetricsKey* callsite, double value);
/// Records a histogram.
///
/// Histograms measure the distribution of values for a given set of
/// measurements, and start with no initial values.
void metrics_record_histogram(const MetricsCallsite* callsite, double value);
void metrics_static_record_histogram(const MetricsCallsite* callsite, double value);
/// Records a histogram.
///
/// Histograms measure the distribution of values for a given set of
/// measurements, and start with no initial values.
void metrics_record_histogram(MetricsKey* callsite, double value);
#ifdef __cplusplus
}
@ -90,6 +145,14 @@ void metrics_record_histogram(const MetricsCallsite* callsite, double value);
metrics_callsite(name)
#endif
// Constructs a metrics key.
#define M_KEY(name, labels, values) \
metrics_key( \
name, \
labels, \
values, \
T_ARRLEN(labels))
//
// Metrics
//
@ -100,11 +163,19 @@ void metrics_record_histogram(const MetricsCallsite* callsite, double value);
/// be incremented, not decremented, and always starts out with an initial value
/// of zero.
///
/// name MUST be a static constant, and MUST be a valid UTF-8 string.
#define MetricsCounter(name, value) \
do { \
static MetricsCallsite* CALLSITE = M_CALLSITE(name); \
metrics_increment_counter(CALLSITE, value); \
/// name MUST be a static constant, and all strings MUST be valid UTF-8.
#define MetricsCounter(name, value, ...) \
do { \
IFE(__VA_ARGS__) \
(static MetricsCallsite* CALLSITE = M_CALLSITE(name); \
metrics_static_increment_counter(CALLSITE, value);) \
IFN(__VA_ARGS__)(const char* M_LABELS[] = \
{T_FIELD_NAMES(__VA_ARGS__)}; \
const char* M_VALUES[] = \
{T_FIELD_VALUES(__VA_ARGS__)}; \
MetricsKey* KEY = \
M_KEY(name, M_LABELS, M_VALUES); \
metrics_increment_counter(KEY, value);) \
} while (0)
/// Increments a counter by one.
@ -113,19 +184,27 @@ void metrics_record_histogram(const MetricsCallsite* callsite, double value);
/// be incremented, not decremented, and always starts out with an initial value
/// of zero.
///
/// name MUST be a static constant, and MUST be a valid UTF-8 string.
#define MetricsIncrementCounter(name) MetricsCounter(name, 1)
/// name MUST be a static constant, and all strings MUST be valid UTF-8.
#define MetricsIncrementCounter(name, ...) MetricsCounter(name, 1, __VA_ARGS__)
/// Updates a gauge.
///
/// Gauges represent a single value that can go up or down over time, and always
/// starts out with an initial value of zero.
///
/// name MUST be a static constant, and MUST be a valid UTF-8 string.
#define MetricsGauge(name, value) \
do { \
static MetricsCallsite* CALLSITE = M_CALLSITE(name); \
metrics_update_gauge(CALLSITE, value); \
/// name MUST be a static constant, and all strings MUST be valid UTF-8.
#define MetricsGauge(name, value, ...) \
do { \
IFE(__VA_ARGS__) \
(static MetricsCallsite* CALLSITE = M_CALLSITE(name); \
metrics_static_update_gauge(CALLSITE, value);) \
IFN(__VA_ARGS__)(const char* M_LABELS[] = \
{T_FIELD_NAMES(__VA_ARGS__)}; \
const char* M_VALUES[] = \
{T_FIELD_VALUES(__VA_ARGS__)}; \
MetricsKey* KEY = \
M_KEY(name, M_LABELS, M_VALUES); \
metrics_update_gauge(KEY, value);) \
} while (0)
/// Increments a gauge.
@ -133,11 +212,19 @@ void metrics_record_histogram(const MetricsCallsite* callsite, double value);
/// Gauges represent a single value that can go up or down over time, and always
/// starts out with an initial value of zero.
///
/// name MUST be a static constant, and MUST be a valid UTF-8 string.
#define MetricsIncrementGauge(name, value) \
do { \
static MetricsCallsite* CALLSITE = M_CALLSITE(name); \
metrics_increment_gauge(CALLSITE, value); \
/// name MUST be a static constant, and all strings MUST be valid UTF-8.
#define MetricsIncrementGauge(name, value, ...) \
do { \
IFE(__VA_ARGS__) \
(static MetricsCallsite* CALLSITE = M_CALLSITE(name); \
metrics_static_increment_gauge(CALLSITE, value);) \
IFN(__VA_ARGS__)(const char* M_LABELS[] = \
{T_FIELD_NAMES(__VA_ARGS__)}; \
const char* M_VALUES[] = \
{T_FIELD_VALUES(__VA_ARGS__)}; \
MetricsKey* KEY = \
M_KEY(name, M_LABELS, M_VALUES); \
metrics_increment_gauge(KEY, value);) \
} while (0)
/// Decrements a gauge.
@ -145,11 +232,19 @@ void metrics_record_histogram(const MetricsCallsite* callsite, double value);
/// Gauges represent a single value that can go up or down over time, and always
/// starts out with an initial value of zero.
///
/// name MUST be a static constant, and MUST be a valid UTF-8 string.
#define MetricsDecrementGauge(name, value) \
do { \
static MetricsCallsite* CALLSITE = M_CALLSITE(name); \
metrics_decrement_gauge(CALLSITE, value); \
/// name MUST be a static constant, and all strings MUST be valid UTF-8.
#define MetricsDecrementGauge(name, value, ...) \
do { \
IFE(__VA_ARGS__) \
(static MetricsCallsite* CALLSITE = M_CALLSITE(name); \
metrics_static_decrement_gauge(CALLSITE, value);) \
IFN(__VA_ARGS__)(const char* M_LABELS[] = \
{T_FIELD_NAMES(__VA_ARGS__)}; \
const char* M_VALUES[] = \
{T_FIELD_VALUES(__VA_ARGS__)}; \
MetricsKey* KEY = \
M_KEY(name, M_LABELS, M_VALUES); \
metrics_decrement_gauge(KEY, value);) \
} while (0)
/// Records a histogram.
@ -157,11 +252,19 @@ void metrics_record_histogram(const MetricsCallsite* callsite, double value);
/// Histograms measure the distribution of values for a given set of
/// measurements, and start with no initial values.
///
/// name MUST be a static constant, and MUST be a valid UTF-8 string.
#define MetricsHistogram(name, value) \
do { \
static MetricsCallsite* CALLSITE = M_CALLSITE(name); \
metrics_record_histogram(CALLSITE, value); \
/// name MUST be a static constant, and all strings MUST be valid UTF-8.
#define MetricsHistogram(name, value, ...) \
do { \
IFE(__VA_ARGS__) \
(static MetricsCallsite* CALLSITE = M_CALLSITE(name); \
metrics_static_record_histogram(CALLSITE, value);) \
IFN(__VA_ARGS__)(const char* M_LABELS[] = \
{T_FIELD_NAMES(__VA_ARGS__)}; \
const char* M_VALUES[] = \
{T_FIELD_VALUES(__VA_ARGS__)}; \
MetricsKey* KEY = \
M_KEY(name, M_LABELS, M_VALUES); \
metrics_record_histogram(KEY, value);) \
} while (0)
#endif // ZCASH_RUST_INCLUDE_RUST_METRICS_H

View File

@ -1,8 +1,10 @@
use libc::{c_char, c_double};
use metrics::{try_recorder, GaugeValue, Key, KeyData};
use metrics::{try_recorder, GaugeValue, Key, KeyData, Label};
use metrics_exporter_prometheus::PrometheusBuilder;
use std::ffi::CStr;
use std::net::SocketAddr;
use std::ptr;
use std::slice;
use tracing::error;
#[no_mangle]
@ -41,8 +43,50 @@ pub extern "C" fn metrics_callsite(name: *const c_char) -> *mut FfiCallsite {
}))
}
pub struct FfiKey {
inner: Key,
}
#[no_mangle]
pub extern "C" fn metrics_increment_counter(callsite: *const FfiCallsite, value: u64) {
pub extern "C" fn metrics_key(
name: *const c_char,
label_names: *const *const c_char,
label_values: *const *const c_char,
labels_len: usize,
) -> *mut FfiKey {
if try_recorder().is_none() {
// No recorder is currently installed, so don't genenerate a key. We check for
// null inside each API that consumes an FfiKey, just in case a recorder was
// installed in a racy way.
ptr::null_mut()
} else {
let name = unsafe { CStr::from_ptr(name) }.to_str().unwrap();
let labels = unsafe { slice::from_raw_parts(label_names, labels_len) };
let values = unsafe { slice::from_raw_parts(label_values, labels_len) };
let stringify = |s: &[_]| {
s.iter()
.map(|&p| unsafe { CStr::from_ptr(p) })
.map(|cs| cs.to_string_lossy().into_owned())
.collect::<Vec<_>>()
};
let labels = stringify(labels);
let values = stringify(values);
let labels: Vec<_> = labels
.into_iter()
.zip(values.into_iter())
.map(|(name, value)| Label::new(name, value))
.collect();
Box::into_raw(Box::new(FfiKey {
inner: Key::Owned(KeyData::from_parts(name, labels)),
}))
}
}
#[no_mangle]
pub extern "C" fn metrics_static_increment_counter(callsite: *const FfiCallsite, value: u64) {
if let Some(recorder) = try_recorder() {
let callsite = unsafe { callsite.as_ref().unwrap() };
recorder.increment_counter(Key::Borrowed(&callsite.key_data), value);
@ -50,7 +94,17 @@ pub extern "C" fn metrics_increment_counter(callsite: *const FfiCallsite, value:
}
#[no_mangle]
pub extern "C" fn metrics_update_gauge(callsite: *const FfiCallsite, value: c_double) {
pub extern "C" fn metrics_increment_counter(key: *mut FfiKey, value: u64) {
if let Some(recorder) = try_recorder() {
if !key.is_null() {
let key = unsafe { Box::from_raw(key) };
recorder.increment_counter(key.inner, value);
}
}
}
#[no_mangle]
pub extern "C" fn metrics_static_update_gauge(callsite: *const FfiCallsite, value: c_double) {
if let Some(recorder) = try_recorder() {
let callsite = unsafe { callsite.as_ref().unwrap() };
recorder.update_gauge(
@ -61,7 +115,17 @@ pub extern "C" fn metrics_update_gauge(callsite: *const FfiCallsite, value: c_do
}
#[no_mangle]
pub extern "C" fn metrics_increment_gauge(callsite: *const FfiCallsite, value: c_double) {
pub extern "C" fn metrics_update_gauge(key: *mut FfiKey, value: c_double) {
if let Some(recorder) = try_recorder() {
if !key.is_null() {
let key = unsafe { Box::from_raw(key) };
recorder.update_gauge(key.inner, GaugeValue::Absolute(value));
}
}
}
#[no_mangle]
pub extern "C" fn metrics_static_increment_gauge(callsite: *const FfiCallsite, value: c_double) {
if let Some(recorder) = try_recorder() {
let callsite = unsafe { callsite.as_ref().unwrap() };
recorder.update_gauge(
@ -72,7 +136,17 @@ pub extern "C" fn metrics_increment_gauge(callsite: *const FfiCallsite, value: c
}
#[no_mangle]
pub extern "C" fn metrics_decrement_gauge(callsite: *const FfiCallsite, value: c_double) {
pub extern "C" fn metrics_increment_gauge(key: *mut FfiKey, value: c_double) {
if let Some(recorder) = try_recorder() {
if !key.is_null() {
let key = unsafe { Box::from_raw(key) };
recorder.update_gauge(key.inner, GaugeValue::Increment(value));
}
}
}
#[no_mangle]
pub extern "C" fn metrics_static_decrement_gauge(callsite: *const FfiCallsite, value: c_double) {
if let Some(recorder) = try_recorder() {
let callsite = unsafe { callsite.as_ref().unwrap() };
recorder.update_gauge(
@ -83,9 +157,29 @@ pub extern "C" fn metrics_decrement_gauge(callsite: *const FfiCallsite, value: c
}
#[no_mangle]
pub extern "C" fn metrics_record_histogram(callsite: *const FfiCallsite, value: c_double) {
pub extern "C" fn metrics_decrement_gauge(key: *mut FfiKey, value: c_double) {
if let Some(recorder) = try_recorder() {
if !key.is_null() {
let key = unsafe { Box::from_raw(key) };
recorder.update_gauge(key.inner, GaugeValue::Decrement(value));
}
}
}
#[no_mangle]
pub extern "C" fn metrics_static_record_histogram(callsite: *const FfiCallsite, value: c_double) {
if let Some(recorder) = try_recorder() {
let callsite = unsafe { callsite.as_ref().unwrap() };
recorder.record_histogram(Key::Borrowed(&callsite.key_data), value);
}
}
#[no_mangle]
pub extern "C" fn metrics_record_histogram(key: *mut FfiKey, value: c_double) {
if let Some(recorder) = try_recorder() {
if !key.is_null() {
let key = unsafe { Box::from_raw(key) };
recorder.record_histogram(key.inner, value);
}
}
}