Skip to content

Commit

Permalink
Add API for fetching details about an oximeter producer (oxidecompute…
Browse files Browse the repository at this point in the history
…r#7139)

- Add `producer_details` API to `oximeter` collector, which returns
information about registration time, update time, and collection
summaries.
- Update producer details during collections themselves
- Add `omdb oximeter producer-details` subcommand for printing
- Closes oxidecomputer#7125
  • Loading branch information
bnaecker authored Dec 4, 2024
1 parent 564e9b8 commit 3b77deb
Show file tree
Hide file tree
Showing 8 changed files with 945 additions and 33 deletions.
187 changes: 185 additions & 2 deletions dev-tools/omdb/src/bin/omdb/oximeter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
use crate::helpers::CONNECTION_OPTIONS_HEADING;
use crate::Omdb;
use anyhow::Context;
use chrono::SecondsFormat;
use clap::Args;
use clap::Subcommand;
use futures::TryStreamExt;
use internal_dns_types::names::ServiceName;
use oximeter_client::types::FailedCollection;
use oximeter_client::types::ProducerDetails;
use oximeter_client::types::ProducerEndpoint;
use oximeter_client::types::SuccessfulCollection;
use oximeter_client::Client;
use slog::Logger;
use std::net::SocketAddr;
Expand Down Expand Up @@ -41,6 +45,11 @@ pub struct OximeterArgs {
enum OximeterCommands {
/// List the producers the collector is assigned to poll.
ListProducers,
/// Fetch details about a single assigned producer.
ProducerDetails {
/// The ID of the producer to fetch.
producer_id: Uuid,
},
}

impl OximeterArgs {
Expand Down Expand Up @@ -81,9 +90,26 @@ impl OximeterArgs {
OximeterCommands::ListProducers => {
self.list_producers(client).await
}
OximeterCommands::ProducerDetails { producer_id } => {
self.producer_details(client, producer_id).await
}
}
}

async fn producer_details(
&self,
client: Client,
producer_id: Uuid,
) -> anyhow::Result<()> {
let details = client
.producer_details(&producer_id)
.await
.context("failed to fetch producer details")?
.into_inner();
print_producer_details(details);
Ok(())
}

async fn list_producers(&self, client: Client) -> anyhow::Result<()> {
let info = client
.collector_info()
Expand Down Expand Up @@ -120,11 +146,168 @@ struct Producer {

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(),
interval: humantime::format_duration(interval).to_string(),
interval: duration_to_humantime(&p.interval),
}
}
}

fn duration_to_humantime(d: &oximeter_client::types::Duration) -> String {
let interval = Duration::new(d.secs, d.nanos);
humantime::format_duration(interval).to_string()
}

const WIDTH: usize = 12;

fn print_producer_details(details: ProducerDetails) {
println!();
println!("{:>WIDTH$}: {}", "ID", details.id);
println!("{:>WIDTH$}: {}", "Address", details.address);
println!(
"{:>WIDTH$}: {}",
"Registered",
details.registered.to_rfc3339_opts(SecondsFormat::Millis, true)
);
println!(
"{:>WIDTH$}: {}",
"Updated",
details.updated.to_rfc3339_opts(SecondsFormat::Millis, true)
);
println!(
"{:>WIDTH$}: {}",
"Interval",
duration_to_humantime(&details.interval)
);
println!("{:>WIDTH$}: {}", "Successes", details.n_collections);
println!("{:>WIDTH$}: {}", "Failures", details.n_failures);
println!();
print_last_success(details.last_success.as_ref());
println!();
print_last_failure(details.last_failure.as_ref());
}

fn print_last_success(maybe_success: Option<&SuccessfulCollection>) {
print!("{:>WIDTH$}: ", "Last success");
match maybe_success {
None => println!("None"),
Some(success) => {
println!();
println!(
"{:>WIDTH$}: {}",
"Started at",
success.started_at.to_rfc3339_opts(SecondsFormat::Millis, true)
);
println!(
"{:>WIDTH$}: {:?}",
"Queued for",
Duration::new(
success.time_queued.secs,
success.time_queued.nanos
)
);
println!(
"{:>WIDTH$}: {:?}",
"Duration",
Duration::new(
success.time_collecting.secs,
success.time_collecting.nanos
)
);
println!("{:>WIDTH$}: {}", "Samples", success.n_samples);
}
}
}

fn print_last_failure(maybe_failure: Option<&FailedCollection>) {
print!("{:>WIDTH$}: ", "Last failure");
match maybe_failure {
None => println!("None"),
Some(failure) => {
println!();
println!(
"{:>WIDTH$}: {}",
"Started at",
failure.started_at.to_rfc3339_opts(SecondsFormat::Millis, true)
);
println!(
"{:>WIDTH$}: {:?}",
"Queued for",
Duration::new(
failure.time_queued.secs,
failure.time_queued.nanos
)
);
println!(
"{:>WIDTH$}: {:?}",
"Duration",
Duration::new(
failure.time_collecting.secs,
failure.time_collecting.nanos
)
);
println!("{:>WIDTH$}: {}", "Reason", failure.reason);
}
}
}

#[cfg(test)]
mod tests {
use super::print_producer_details;
use chrono::Utc;
use oximeter_client::types::FailedCollection;
use oximeter_client::types::ProducerDetails;
use oximeter_client::types::SuccessfulCollection;
use std::time::Duration;
use uuid::Uuid;

#[test]
fn test_print_producer_details_success_only() {
let now = Utc::now();
let details = ProducerDetails {
id: Uuid::new_v4(),
address: "[::1]:12345".parse().unwrap(),
interval: Duration::from_secs(10).into(),
last_success: Some(SuccessfulCollection {
n_samples: 100,
started_at: now,
time_collecting: Duration::from_millis(100).into(),
time_queued: Duration::from_millis(10).into(),
}),
last_failure: None,
n_collections: 1,
n_failures: 0,
registered: now,
updated: now,
};
print_producer_details(details);
}

#[test]
fn test_print_producer_details_with_failure() {
let now = Utc::now();
let details = ProducerDetails {
id: Uuid::new_v4(),
interval: Duration::from_secs(10).into(),
address: "[::1]:12345".parse().unwrap(),
last_success: Some(SuccessfulCollection {
n_samples: 100,
started_at: now,
time_collecting: Duration::from_millis(100).into(),
time_queued: Duration::from_millis(10).into(),
}),
last_failure: Some(FailedCollection {
started_at: now,
time_collecting: Duration::from_millis(100).into(),
time_queued: Duration::from_millis(10).into(),
reason: String::from("unreachable"),
}),
n_collections: 1,
n_failures: 1,
registered: now,
updated: now,
};
print_producer_details(details);
}
}
5 changes: 3 additions & 2 deletions dev-tools/omdb/tests/usage_errors.out
Original file line number Diff line number Diff line change
Expand Up @@ -761,8 +761,9 @@ Query oximeter collector state
Usage: omdb oximeter [OPTIONS] <COMMAND>

Commands:
list-producers List the producers the collector is assigned to poll
help Print this message or the help of the given subcommand(s)
list-producers List the producers the collector is assigned to poll
producer-details Fetch details about a single assigned producer
help Print this message or the help of the given subcommand(s)

Options:
--log-level <LOG_LEVEL> log level filter [env: LOG_LEVEL=] [default: warn]
Expand Down
Loading

0 comments on commit 3b77deb

Please sign in to comment.