Skip to content

Commit

Permalink
Define timeseries schema in TOML (#5889)
Browse files Browse the repository at this point in the history
This is the first commit in a thread of work to support updates to
timeseries schema. In our first step, we're moving the definition of
timeseries from the Rust structs we use today, to TOML text files. Each
file describes one target and all the metrics associated with it, from
which we generate exactly the same Rust code that it replaces.

This opens the door to a lot of downstream work, including well-defined
updates to schema; detection of conflicting schema; improved metadata
for timeseries; and generation of documentation. As an example, this
moves exactly one (1) timeseries into the new format, the physical
datalink statistics tracked through illumos kstats.

- Move the `oximeter` crate into a private implementation crate, and
re-export it at its original path
- Add intermediate representation of timeseries schema, and code for
deserializing TOML timeseries definitions, checking them for conflicts,
and handling updates. Note that **no actual updates** are currently
supported. The ingesting code includes a check that version numbers are
exactly 1, except in tests. We include tests that check updates in a
number of ways, but until the remainder of the work is done, we limit
timeseries in the wild to their current version of 1.
- Add a macro for consuming timeseries definitions in TOML, and
generating the equivalent Rust code. Developers should use this new
`oximeter::use_timeseries!()` proc macro to create new timeseries.
- Add an integration test in Nexus that pulls in timeseries schema
ingested with with `oximeter::use_timeseries!()`, and checks them all
for conflicts. As developers add new timeseries in TOML format, they
must be added here as well to ensure we detect problems at CI time.
- Updates `kstat-rs` dep to simplify platform-specific code. This is
part of this commit because we need the definitions of the
physical-data-link timeseries for our integration test, but those were
previously only compiled on illumos platforms. They're now included
everywhere, while the tests remain illumos-only.
  • Loading branch information
bnaecker authored Jun 25, 2024
1 parent 86e1710 commit fd5f1f3
Show file tree
Hide file tree
Showing 41 changed files with 3,264 additions and 547 deletions.
224 changes: 130 additions & 94 deletions Cargo.lock

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,12 @@ members = [
"nexus/types",
"oximeter/collector",
"oximeter/db",
"oximeter/impl",
"oximeter/instruments",
"oximeter/oximeter-macro-impl",
"oximeter/oximeter",
"oximeter/producer",
"oximeter/timeseries-macro",
"package",
"passwords",
"rpaths",
Expand Down Expand Up @@ -149,10 +151,12 @@ default-members = [
"nexus/types",
"oximeter/collector",
"oximeter/db",
"oximeter/impl",
"oximeter/instruments",
"oximeter/oximeter-macro-impl",
"oximeter/oximeter",
"oximeter/producer",
"oximeter/timeseries-macro",
"package",
"passwords",
"rpaths",
Expand Down Expand Up @@ -320,7 +324,7 @@ internet-checksum = "0.2"
ipnetwork = { version = "0.20", features = ["schemars"] }
ispf = { git = "https://github.com/oxidecomputer/ispf" }
key-manager = { path = "key-manager" }
kstat-rs = "0.2.3"
kstat-rs = "0.2.4"
libc = "0.2.155"
libfalcon = { git = "https://github.com/oxidecomputer/falcon", rev = "e69694a1f7cc9fe31fab27f321017280531fb5f7" }
libnvme = { git = "https://github.com/oxidecomputer/libnvme", rev = "6fffcc81d2c423ed2d2e6c5c2827485554c4ecbe" }
Expand Down Expand Up @@ -382,9 +386,11 @@ oximeter = { path = "oximeter/oximeter" }
oximeter-client = { path = "clients/oximeter-client" }
oximeter-db = { path = "oximeter/db/" }
oximeter-collector = { path = "oximeter/collector" }
oximeter-impl = { path = "oximeter/impl" }
oximeter-instruments = { path = "oximeter/instruments" }
oximeter-macro-impl = { path = "oximeter/oximeter-macro-impl" }
oximeter-producer = { path = "oximeter/producer" }
oximeter-timeseries-macro = { path = "oximeter/timeseries-macro" }
p256 = "0.13"
parse-display = "0.9.0"
partial-io = { version = "0.5.4", features = ["proptest1", "tokio1"] }
Expand Down
2 changes: 1 addition & 1 deletion nexus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ rcgen.workspace = true
regex.workspace = true
similar-asserts.workspace = true
sp-sim.workspace = true
rustls = { workspace = true }
rustls.workspace = true
subprocess.workspace = true
term.workspace = true
trust-dns-resolver.workspace = true
Expand Down
10 changes: 10 additions & 0 deletions openapi/bootstrap-agent.json
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,7 @@
"checker": {
"nullable": true,
"description": "Checker to apply to incoming messages.",
"default": null,
"type": "string"
},
"originate": {
Expand All @@ -340,6 +341,7 @@
"shaper": {
"nullable": true,
"description": "Shaper to apply to outgoing messages.",
"default": null,
"type": "string"
}
},
Expand Down Expand Up @@ -437,25 +439,29 @@
"local_pref": {
"nullable": true,
"description": "Apply a local preference to routes received from this peer.",
"default": null,
"type": "integer",
"format": "uint32",
"minimum": 0
},
"md5_auth_key": {
"nullable": true,
"description": "Use the given key for TCP-MD5 authentication with the peer.",
"default": null,
"type": "string"
},
"min_ttl": {
"nullable": true,
"description": "Require messages from a peer have a minimum IP time to live field.",
"default": null,
"type": "integer",
"format": "uint8",
"minimum": 0
},
"multi_exit_discriminator": {
"nullable": true,
"description": "Apply the provided multi-exit discriminator (MED) updates sent to the peer.",
"default": null,
"type": "integer",
"format": "uint32",
"minimum": 0
Expand All @@ -467,13 +473,15 @@
"remote_asn": {
"nullable": true,
"description": "Require that a peer has a specified ASN.",
"default": null,
"type": "integer",
"format": "uint32",
"minimum": 0
},
"vlan_id": {
"nullable": true,
"description": "Associate a VLAN ID with a BGP peer session.",
"default": null,
"type": "integer",
"format": "uint16",
"minimum": 0
Expand Down Expand Up @@ -1192,6 +1200,7 @@
"vlan_id": {
"nullable": true,
"description": "The VLAN id associated with this route.",
"default": null,
"type": "integer",
"format": "uint16",
"minimum": 0
Expand Down Expand Up @@ -1234,6 +1243,7 @@
"vlan_id": {
"nullable": true,
"description": "The VLAN id (if any) associated with this address.",
"default": null,
"type": "integer",
"format": "uint16",
"minimum": 0
Expand Down
10 changes: 10 additions & 0 deletions openapi/nexus-internal.json
Original file line number Diff line number Diff line change
Expand Up @@ -1567,6 +1567,7 @@
"checker": {
"nullable": true,
"description": "Checker to apply to incoming messages.",
"default": null,
"type": "string"
},
"originate": {
Expand All @@ -1579,6 +1580,7 @@
"shaper": {
"nullable": true,
"description": "Shaper to apply to outgoing messages.",
"default": null,
"type": "string"
}
},
Expand Down Expand Up @@ -1676,25 +1678,29 @@
"local_pref": {
"nullable": true,
"description": "Apply a local preference to routes received from this peer.",
"default": null,
"type": "integer",
"format": "uint32",
"minimum": 0
},
"md5_auth_key": {
"nullable": true,
"description": "Use the given key for TCP-MD5 authentication with the peer.",
"default": null,
"type": "string"
},
"min_ttl": {
"nullable": true,
"description": "Require messages from a peer have a minimum IP time to live field.",
"default": null,
"type": "integer",
"format": "uint8",
"minimum": 0
},
"multi_exit_discriminator": {
"nullable": true,
"description": "Apply the provided multi-exit discriminator (MED) updates sent to the peer.",
"default": null,
"type": "integer",
"format": "uint32",
"minimum": 0
Expand All @@ -1706,13 +1712,15 @@
"remote_asn": {
"nullable": true,
"description": "Require that a peer has a specified ASN.",
"default": null,
"type": "integer",
"format": "uint32",
"minimum": 0
},
"vlan_id": {
"nullable": true,
"description": "Associate a VLAN ID with a BGP peer session.",
"default": null,
"type": "integer",
"format": "uint16",
"minimum": 0
Expand Down Expand Up @@ -4345,6 +4353,7 @@
"vlan_id": {
"nullable": true,
"description": "The VLAN id associated with this route.",
"default": null,
"type": "integer",
"format": "uint16",
"minimum": 0
Expand Down Expand Up @@ -5003,6 +5012,7 @@
"vlan_id": {
"nullable": true,
"description": "The VLAN id (if any) associated with this address.",
"default": null,
"type": "integer",
"format": "uint16",
"minimum": 0
Expand Down
82 changes: 81 additions & 1 deletion openapi/nexus.json
Original file line number Diff line number Diff line change
Expand Up @@ -9344,6 +9344,39 @@
}
]
},
"AuthzScope": {
"description": "Authorization scope for a timeseries.\n\nThis describes the level at which a user must be authorized to read data from a timeseries. For example, fleet-scoping means the data is only visible to an operator or fleet reader. Project-scoped, on the other hand, indicates that a user will see data limited to the projects on which they have read permissions.",
"oneOf": [
{
"description": "Timeseries data is limited to fleet readers.",
"type": "string",
"enum": [
"fleet"
]
},
{
"description": "Timeseries data is limited to the authorized silo for a user.",
"type": "string",
"enum": [
"silo"
]
},
{
"description": "Timeseries data is limited to the authorized projects for a user.",
"type": "string",
"enum": [
"project"
]
},
{
"description": "The timeseries is viewable to all without limitation.",
"type": "string",
"enum": [
"viewable_to_all"
]
}
]
},
"Baseboard": {
"description": "Properties that uniquely identify an Oxide hardware component",
"type": "object",
Expand Down Expand Up @@ -12646,6 +12679,9 @@
"description": "The name and type information for a field of a timeseries schema.",
"type": "object",
"properties": {
"description": {
"type": "string"
},
"field_type": {
"$ref": "#/components/schemas/FieldType"
},
Expand All @@ -12657,6 +12693,7 @@
}
},
"required": [
"description",
"field_type",
"name",
"source"
Expand Down Expand Up @@ -16686,6 +16723,7 @@
"signing_keypair": {
"nullable": true,
"description": "request signing key pair",
"default": null,
"allOf": [
{
"$ref": "#/components/schemas/DerEncodedKeyPair"
Expand Down Expand Up @@ -18546,6 +18584,22 @@
"points"
]
},
"TimeseriesDescription": {
"description": "Text descriptions for the target and metric of a timeseries.",
"type": "object",
"properties": {
"metric": {
"type": "string"
},
"target": {
"type": "string"
}
},
"required": [
"metric",
"target"
]
},
"TimeseriesName": {
"title": "The name of a timeseries",
"description": "Names are constructed by concatenating the target and metric names with ':'. Target and metric names must be lowercase alphanumeric characters with '_' separating words.",
Expand All @@ -18569,13 +18623,19 @@
"description": "The schema for a timeseries.\n\nThis includes the name of the timeseries, as well as the datum type of its metric and the schema for each field.",
"type": "object",
"properties": {
"authz_scope": {
"$ref": "#/components/schemas/AuthzScope"
},
"created": {
"type": "string",
"format": "date-time"
},
"datum_type": {
"$ref": "#/components/schemas/DatumType"
},
"description": {
"$ref": "#/components/schemas/TimeseriesDescription"
},
"field_schema": {
"type": "array",
"items": {
Expand All @@ -18585,13 +18645,25 @@
},
"timeseries_name": {
"$ref": "#/components/schemas/TimeseriesName"
},
"units": {
"$ref": "#/components/schemas/Units"
},
"version": {
"type": "integer",
"format": "uint8",
"minimum": 1
}
},
"required": [
"authz_scope",
"created",
"datum_type",
"description",
"field_schema",
"timeseries_name"
"timeseries_name",
"units",
"version"
]
},
"TimeseriesSchemaResultsPage": {
Expand Down Expand Up @@ -18675,6 +18747,14 @@
"items"
]
},
"Units": {
"description": "Measurement units for timeseries samples.",
"type": "string",
"enum": [
"count",
"bytes"
]
},
"User": {
"description": "View of a User",
"type": "object",
Expand Down
Loading

0 comments on commit fd5f1f3

Please sign in to comment.