Skip to content

Commit

Permalink
Define timeseries schema in TOML
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.
  The actual file format also has version number associated with it,
  currently asserted to be 1, so that we can more easily update the
  entire format if needed.
- 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 `oximeter` that pulls in all the
  centrally-defined timeseries schema, and checks them all for
  conflicts. As developers add new timeseries in TOML format, they will
  be picked up automatically by this test, to detect problems at merge
  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 committed Jun 25, 2024
1 parent 86e1710 commit b1be993
Show file tree
Hide file tree
Showing 41 changed files with 3,249 additions and 544 deletions.
220 changes: 128 additions & 92 deletions Cargo.lock

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions 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 @@ -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 b1be993

Please sign in to comment.