x:Metric: say which node wrote each sample #133
@@ -104,6 +104,12 @@ impl StoredMetric {
|
|||||||
pub fn timestamp(&self) -> u64 {
|
pub fn timestamp(&self) -> u64 {
|
||||||
SnowflakeIdGenerator::to_timestamp(self.id)
|
SnowflakeIdGenerator::to_timestamp(self.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The node that wrote the sample. Histogram totals are per node, so a
|
||||||
|
/// reader diffs them per node.
|
||||||
|
pub fn node_id(&self) -> u64 {
|
||||||
|
SnowflakeIdGenerator::to_node_id(self.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What the node wrote last, so counters and histograms are written as
|
/// What the node wrote last, so counters and histograms are written as
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
//! `x:Metric/get` and `/query` over the stored history (monitoring spec,
|
//! `x:Metric/get` and `/query` over the stored history (monitoring spec,
|
||||||
//! "Interfaces"). Samples are server-level (MON-31) and read-only (MON-32).
|
//! "Interfaces"). Samples are server-level (MON-31) and read-only (MON-32).
|
||||||
//! A sample's `timestamp` comes from its id.
|
//! A sample's `timestamp` and `nodeId` come from its id.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::query::QueryResponseBuilder,
|
api::query::QueryResponseBuilder,
|
||||||
@@ -54,12 +54,16 @@ fn metric_type(metric: &Metric) -> MetricType {
|
|||||||
|
|
||||||
fn to_value(sample: StoredMetric) -> JmapValue<'static> {
|
fn to_value(sample: StoredMetric) -> JmapValue<'static> {
|
||||||
let timestamp = sample.timestamp();
|
let timestamp = sample.timestamp();
|
||||||
|
let node_id = sample.node_id();
|
||||||
let mut value = sample.metric.into_value();
|
let mut value = sample.metric.into_value();
|
||||||
if let JmapValue::Object(obj) = &mut value {
|
if let JmapValue::Object(obj) = &mut value {
|
||||||
obj.insert_unchecked(
|
obj.insert_unchecked(
|
||||||
Property::Timestamp,
|
Property::Timestamp,
|
||||||
JmapValue::Str(UTCDateTime::from_timestamp(timestamp as i64).to_string().into()),
|
JmapValue::Str(UTCDateTime::from_timestamp(timestamp as i64).to_string().into()),
|
||||||
);
|
);
|
||||||
|
// Histograms are running totals per node; without this a reader
|
||||||
|
// diffs one node's total against another's
|
||||||
|
obj.insert_unchecked(Property::NodeId, JmapValue::Number(node_id.into()));
|
||||||
}
|
}
|
||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,6 +108,12 @@ impl SnowflakeIdGenerator {
|
|||||||
(id >> (SEQUENCE_LEN + NODE_ID_LEN)) / 1000 + DEFAULT_EPOCH
|
(id >> (SEQUENCE_LEN + NODE_ID_LEN)) / 1000 + DEFAULT_EPOCH
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// inbuxa: the node that made the id, so per-node history (metric
|
||||||
|
// totals) can be told apart
|
||||||
|
pub fn to_node_id(id: u64) -> u64 {
|
||||||
|
id & NODE_ID_MASK
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn past_id(&self, period: Duration) -> Option<u64> {
|
pub fn past_id(&self, period: Duration) -> Option<u64> {
|
||||||
self.epoch.elapsed().ok().map(|elapsed| {
|
self.epoch.elapsed().ok().map(|elapsed| {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
|
||||||
|
*
|
||||||
|
* Modified by Coffey Labs in 2026 for INBUXA.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::utils::server::TestServer;
|
use crate::utils::server::TestServer;
|
||||||
@@ -53,6 +55,15 @@ pub async fn test(test: &TestServer) {
|
|||||||
);
|
);
|
||||||
assert_eq!(metrics.len(), metric_ids.len());
|
assert_eq!(metrics.len(), metric_ids.len());
|
||||||
|
|
||||||
|
// Every sample says which node wrote it, so histogram totals can be
|
||||||
|
// diffed per node
|
||||||
|
for metric in metrics {
|
||||||
|
assert!(
|
||||||
|
metric.get("nodeId").is_some_and(|v| v.is_u64()),
|
||||||
|
"Missing nodeId in {metric}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Fetch the last 48 hours of metrics
|
// Fetch the last 48 hours of metrics
|
||||||
let metric_ids = admin
|
let metric_ids = admin
|
||||||
.registry_query(
|
.registry_query(
|
||||||
|
|||||||
Reference in New Issue
Block a user