Skip to content

Commit

Permalink
Adds functionality to run oximeter standalone
Browse files Browse the repository at this point in the history
- Adds a "standalone" mode for the `oximeter-collector` crate, including
  the binary and main inner types. This runs in a slightly different
  mode, in which the ClickHouse database itself isn't strictly required.
  In this case, a task to simply print the results will be spawned in
  place of the normal results-sink task which inserts records into the
  database.
- Creates a tiny fake Nexus server, which includes only the API needed
  to register collectors and producers. This is started automatically
  when running `oximeter standalone`, and used to assign producers /
  collectors as the real Nexus does, but without a database. The
  assignments are only in memory.
- Adds internal `oximeter` API for listing / deleting a producer for
  each oximeter collector, and an `omdb` subcommand which exercises the
  listing.
  • Loading branch information
bnaecker committed Sep 18, 2023
1 parent 23eee1a commit 1dfccfd
Show file tree
Hide file tree
Showing 15 changed files with 1,202 additions and 111 deletions.
12 changes: 12 additions & 0 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion common/src/api/internal/nexus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub struct InstanceRuntimeState {

/// Information announced by a metric server, used so that clients can contact it and collect
/// available metric data from it.
#[derive(Debug, Clone, JsonSchema, Serialize, Deserialize)]
#[derive(Clone, Debug, Deserialize, JsonSchema, Serialize, PartialEq)]
pub struct ProducerEndpoint {
pub id: Uuid,
pub address: SocketAddr,
Expand Down
2 changes: 2 additions & 0 deletions omdb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ clap.workspace = true
diesel.workspace = true
dropshot.workspace = true
humantime.workspace = true
futures.workspace = true
nexus-client.workspace = true
nexus-db-model.workspace = true
nexus-db-queries.workspace = true
nexus-types.workspace = true
omicron-common.workspace = true
oximeter-client.workspace = true
# See omicron-rpaths for more about the "pq-sys" dependency.
pq-sys = "*"
serde.workspace = true
Expand Down
4 changes: 4 additions & 0 deletions omdb/src/bin/omdb/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use clap::Subcommand;

mod db;
mod nexus;
mod oximeter;

#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {
Expand All @@ -37,6 +38,7 @@ async fn main() -> Result<(), anyhow::Error> {
match args.command {
OmdbCommands::Nexus(nexus) => nexus.run_cmd(&log).await,
OmdbCommands::Db(db) => db.run_cmd(&log).await,
OmdbCommands::Oximeter(oximeter) => oximeter.run_cmd(&log).await,
}
}

Expand Down Expand Up @@ -67,6 +69,8 @@ enum OmdbCommands {
Db(db::DbArgs),
/// Debug a specific Nexus instance
Nexus(nexus::NexusArgs),
/// Query oximeter collector state
Oximeter(oximeter::OximeterArgs),
}

fn parse_dropshot_log_level(
Expand Down
92 changes: 92 additions & 0 deletions omdb/src/bin/omdb/oximeter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! omdb commands that query oximeter
use anyhow::Context;
use clap::Args;
use clap::Subcommand;
use futures::TryStreamExt;
use oximeter_client::types::ProducerEndpoint;
use oximeter_client::Client;
use slog::Logger;
use std::net::SocketAddr;
use std::time::Duration;
use tabled::Table;
use tabled::Tabled;
use uuid::Uuid;

#[derive(Debug, Args)]
pub struct OximeterArgs {
/// URL of the oximeter collector to query
oximeter_url: String,

#[command(subcommand)]
command: OximeterCommands,
}

/// Subcommands that query oximeter collector state
#[derive(Debug, Subcommand)]
enum OximeterCommands {
/// List the producers the collector is assigned to poll
ListProducers,
}

impl OximeterArgs {
fn client(&self, log: &Logger) -> Client {
Client::new(
&self.oximeter_url,
log.new(slog::o!("component" => "oximeter-client")),
)
}

pub async fn run_cmd(&self, log: &Logger) -> anyhow::Result<()> {
let client = self.client(log);
match self.command {
OximeterCommands::ListProducers => {
self.list_producers(client).await
}
}
}

async fn list_producers(&self, client: Client) -> anyhow::Result<()> {
let info = client
.collector_info()
.await
.context("failed to fetch collector info")?;
let producers: Vec<Producer> = client
.producers_list_stream(None)
.map_ok(Producer::from)
.try_collect()
.await
.context("failed to list producers")?;
let table = Table::new(producers)
.with(tabled::settings::Style::empty())
.with(tabled::settings::Padding::new(0, 1, 0, 0))
.to_string();
println!("Collector ID: {}\n", info.id);
println!("{table}");
Ok(())
}
}

#[derive(Tabled)]
struct Producer {
id: Uuid,
address: SocketAddr,
base_route: String,
interval: String,
}

impl From<ProducerEndpoint> for Producer {
fn from(p: ProducerEndpoint) -> Self {
let interval = Duration::new(p.interval.secs, p.interval.nanos);
Self {
id: p.id,
address: p.address.parse().unwrap(),
base_route: p.base_route,
interval: humantime::format_duration(interval).to_string(),
}
}
}
156 changes: 156 additions & 0 deletions openapi/oximeter.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,102 @@
"version": "0.0.1"
},
"paths": {
"/info": {
"get": {
"operationId": "collector_info",
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CollectorInfo"
}
}
}
},
"4XX": {
"$ref": "#/components/responses/Error"
},
"5XX": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/metrics/producers": {
"post": {
"operationId": "producers_post_standalone",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProducerEndpoint"
}
}
},
"required": true
},
"responses": {
"204": {
"description": "resource updated"
},
"4XX": {
"$ref": "#/components/responses/Error"
},
"5XX": {
"$ref": "#/components/responses/Error"
}
}
}
},
"/producers": {
"get": {
"operationId": "producers_list",
"parameters": [
{
"in": "query",
"name": "limit",
"description": "Maximum number of items returned by a single call",
"schema": {
"nullable": true,
"type": "integer",
"format": "uint32",
"minimum": 1
}
},
{
"in": "query",
"name": "page_token",
"description": "Token returned by previous call to retrieve the subsequent page",
"schema": {
"nullable": true,
"type": "string"
}
}
],
"responses": {
"200": {
"description": "successful operation",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProducerEndpointResultsPage"
}
}
}
},
"4XX": {
"$ref": "#/components/responses/Error"
},
"5XX": {
"$ref": "#/components/responses/Error"
}
},
"x-dropshot-pagination": {
"required": []
}
},
"post": {
"operationId": "producers_post",
"requestBody": {
Expand All @@ -35,6 +130,33 @@
}
}
}
},
"/producers/{producer_id}": {
"delete": {
"operationId": "producer_delete",
"parameters": [
{
"in": "path",
"name": "producer_id",
"required": true,
"schema": {
"type": "string",
"format": "uuid"
}
}
],
"responses": {
"204": {
"description": "successful deletion"
},
"4XX": {
"$ref": "#/components/responses/Error"
},
"5XX": {
"$ref": "#/components/responses/Error"
}
}
}
}
},
"components": {
Expand All @@ -51,6 +173,19 @@
}
},
"schemas": {
"CollectorInfo": {
"type": "object",
"properties": {
"id": {
"description": "The collector's UUID.",
"type": "string",
"format": "uuid"
}
},
"required": [
"id"
]
},
"Duration": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -113,6 +248,27 @@
"id",
"interval"
]
},
"ProducerEndpointResultsPage": {
"description": "A single page of results",
"type": "object",
"properties": {
"items": {
"description": "list of items on this page of results",
"type": "array",
"items": {
"$ref": "#/components/schemas/ProducerEndpoint"
}
},
"next_page": {
"nullable": true,
"description": "token used to fetch the next page of results (if any)",
"type": "string"
}
},
"required": [
"items"
]
}
}
}
Expand Down
1 change: 1 addition & 0 deletions oximeter-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ license = "MPL-2.0"

[dependencies]
chrono.workspace = true
futures.workspace = true
omicron-common.workspace = true
progenitor.workspace = true
reqwest = { workspace = true, features = ["json", "rustls-tls", "stream"] }
Expand Down
Loading

0 comments on commit 1dfccfd

Please sign in to comment.