Skip to content

Commit

Permalink
playing around
Browse files Browse the repository at this point in the history
  • Loading branch information
bnaecker committed May 29, 2024
1 parent 1fe55e9 commit 37b9f3e
Show file tree
Hide file tree
Showing 7 changed files with 877 additions and 2 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions oximeter/oximeter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ license = "MPL-2.0"
workspace = true

[dependencies]
anyhow.workspace = true
bytes = { workspace = true, features = [ "serde" ] }
chrono.workspace = true
heck.workspace = true
num.workspace = true
omicron-common.workspace = true
oximeter-macro-impl.workspace = true
Expand All @@ -22,9 +24,11 @@ strum.workspace = true
thiserror.workspace = true
uuid.workspace = true
omicron-workspace-hack.workspace = true
ron = "0.8.1"

[dev-dependencies]
approx.workspace = true
rstest.workspace = true
serde_json.workspace = true
toml.workspace = true
trybuild.workspace = true
61 changes: 61 additions & 0 deletions oximeter/oximeter/schema/physical_data_link_bytes_received.ron
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
[
(
name: "physical_data_link",
description: "A physical link",
versions: {
1: (
)
},
embed_fields: [],
fields_by_version: {
0: {
"sled_model": string,
"sled_revision": u32,
"sled_serial": string,
"link_name": string,
},
}
metrics: [
(
name: "bytes_received",
description: "count of bytes received",
fields_by_version: {0: {}},
datum_type: cumulative_u64,
units: bytes,
),
(
name: "bytes_sent",
description: "count of bytes sent",
fields_by_version: {0: {}},
datum_type: cumulative_u64,
units: bytes,
)
]
),
(
name: "http_service",
description: "An Oxide HTTP service",
embed_fields: [],
fields: {
0: {
"name": string,
"id": uuid,
},
},
metrics: [
(
name: "request_latency_histogram",
description: "Distribution of request latencies",
fields: {
0: {
"route": string,
"method": string,
"status_code": u16,
},
}
datum_type: histogram_f64,
units: seconds,
)
]
)
]
198 changes: 198 additions & 0 deletions oximeter/oximeter/src/foo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
use oximeter::Datum;
use oximeter::DatumType;
use oximeter::FieldType;
use oximeter::FieldValue;
use std::collections::BTreeMap;

#[derive(Clone, Debug)]
struct TimeseriesSchema {
target: TargetSchema,
metric: MetricSchema,
}

impl TimeseriesSchema {
fn new(target: TargetSchema, metric: MetricSchema) -> Result<Self, ()> {
for tf in target.fields.keys() {
if metric.fields.contains_key(tf) {
return Err(());
}
}
Ok(Self { target, metric })
}
}

#[derive(Clone, Debug)]
struct FieldBuilder {
name: String,
fields: BTreeMap<String, FieldType>,
}

impl FieldBuilder {
fn new(name: &str) -> Result<Self, ()> {
// validate name
Ok(Self { name: name.to_string(), fields: BTreeMap::new() })
}

fn field(mut self, field_name: &str, field_type: FieldType) -> Self {
let _ = self.fields.insert(field_name.to_string(), field_type);
self
}

fn build_target(self) -> TargetSchema {
TargetSchema { name: self.name, fields: self.fields }
}

fn build_metric(self, datum_type: DatumType) -> MetricSchema {
MetricSchema { name: self.name, fields: self.fields, datum_type }
}
}

#[derive(Clone, Debug)]
struct TargetSchema {
name: String,
fields: BTreeMap<String, FieldType>,
}

#[derive(Clone, Debug)]
struct MetricSchema {
name: String,
fields: BTreeMap<String, FieldType>,
datum_type: DatumType,
}

#[derive(Clone, Debug, Default)]
struct Target {
name: String,
fields: BTreeMap<String, FieldValue>,
}

#[derive(Clone, Debug)]
struct Metric {
name: String,
fields: BTreeMap<String, FieldValue>,
datum_type: DatumType,
datum: Option<Datum>,
}

#[derive(Clone, Debug)]
struct Timeseries {
target: Target,
metric: Metric,
}

#[derive(Clone, Debug)]
struct TimeseriesBuilder<'a> {
schema: &'a TimeseriesSchema,
target_fields: BTreeMap<String, FieldValue>,
metric_fields: BTreeMap<String, FieldValue>,
}

impl<'a> TimeseriesBuilder<'a> {
fn field<V: Into<FieldValue>>(
mut self,
field_name: &str,
field_value: V,
) -> Result<Self, ()> {
// Find the field in either the target or metric fields.
let (map, ty) = {
if let Some(ty) = self.schema.target.fields.get(field_name) {
(&mut self.target_fields, ty)
} else if let Some(ty) = self.schema.metric.fields.get(field_name) {
(&mut self.metric_fields, ty)
} else {
return Err(());
}
};
let field_value = field_value.into();
if field_value.field_type() != *ty {
return Err(());
}
map.insert(field_name.to_string(), field_value);
Ok(self)
}

fn build(self) -> Result<Timeseries, ()> {
for tf in self.schema.target.fields.keys() {
if !self.target_fields.contains_key(tf) {
return Err(());
}
}
for tf in self.schema.metric.fields.keys() {
if !self.metric_fields.contains_key(tf) {
return Err(());
}
}
let target = Target {
name: self.schema.target.name.clone(),
fields: self.target_fields,
};
let metric = Metric {
name: self.schema.metric.name.clone(),
fields: self.metric_fields,
datum_type: self.schema.metric.datum_type,
datum: None,
};
Ok(Timeseries { target, metric })
}
}

impl TimeseriesSchema {
pub fn builder(&self) -> TimeseriesBuilder {
TimeseriesBuilder {
schema: self,
target_fields: BTreeMap::new(),
metric_fields: BTreeMap::new(),
}
}
}

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

#[test]
fn test_foo() {
FieldBuilder::new("oxide.rack.switch.transceiver")
.field("part", FieldType::String)
.field("vendor", FieldType::String)
.field("serial", FieldType::String)
.build_metric("temperature", DatumType::F64)
.unwrap();
let target = FieldBuilder::new("target")
.unwrap()
.field("first", FieldType::U8)
.field("second", FieldType::IpAddr)
.build_target();

let metric = FieldBuilder::new("metric")
.unwrap()
.field("third", FieldType::U8)
.field("fourth", FieldType::IpAddr)
.build_metric(DatumType::I32);

let timeseries_schema = TimeseriesSchema::new(target, metric).unwrap();
let mut timeseries = timeseries_schema
.builder()
.field::<u8>("first", 0)
.unwrap()
.field(
"second",
std::net::IpAddr::from(std::net::Ipv4Addr::LOCALHOST),
)
.unwrap()
.field("third", 1u8)
.unwrap()
.field(
"fourth",
std::net::IpAddr::from(std::net::Ipv4Addr::LOCALHOST),
)
.unwrap()
.build()
.unwrap();

timeseries.metric.datum.replace(0i64.into());

println!("{:#?}", timeseries_schema);
println!("{:#?}", timeseries);
}
}
1 change: 1 addition & 0 deletions oximeter/oximeter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ extern crate self as oximeter;

pub mod histogram;
pub mod schema;
pub mod schemagen;
pub mod test_util;
pub mod traits;
pub mod types;
Expand Down
34 changes: 32 additions & 2 deletions oximeter/oximeter/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,23 @@ pub struct TimeseriesSchema {
pub timeseries_name: TimeseriesName,
pub field_schema: BTreeSet<FieldSchema>,
pub datum_type: DatumType,
#[serde(default = "crate::schemagen::Units::none")]
pub units: crate::schemagen::Units,
#[serde(default = "default_version")]
pub version: u8,
pub created: DateTime<Utc>,
}

const fn default_version() -> u8 {
1
}

// TODO(ben) Don't need to do this conversion at all.
//
// Instead, the plan will be to read these schema at startup, generate a sample
// which refers to the target / metric names (as it currently does) and a
// version. Then we'll lookup the schema in the static schema library, no
// conversion needed (or the "conversion" will just be a lookup).
impl From<&Sample> for TimeseriesSchema {
fn from(sample: &Sample) -> Self {
let timeseries_name = sample.timeseries_name.parse().unwrap();
Expand All @@ -186,7 +200,14 @@ impl From<&Sample> for TimeseriesSchema {
field_schema.insert(schema);
}
let datum_type = sample.measurement.datum_type();
Self { timeseries_name, field_schema, datum_type, created: Utc::now() }
Self {
timeseries_name,
field_schema,
datum_type,
units: crate::schemagen::Units::None,
version: default_version(),
created: Utc::now(),
}
}
}

Expand Down Expand Up @@ -218,7 +239,14 @@ impl TimeseriesSchema {
field_schema.insert(schema);
}
let datum_type = metric.datum_type();
Self { timeseries_name, field_schema, datum_type, created: Utc::now() }
Self {
timeseries_name,
field_schema,
datum_type,
units: crate::schemagen::Units::None,
version: default_version(),
created: Utc::now(),
}
}

/// Construct a timeseries schema from a sample
Expand Down Expand Up @@ -600,6 +628,8 @@ mod tests {
timeseries_name,
field_schema,
datum_type,
units: crate::schemagen::Units::None,
version: 1,
created: Utc::now(),
};

Expand Down
Loading

0 comments on commit 37b9f3e

Please sign in to comment.