use crate::config::Config;
use crate::error::Result;
use crate::output::{json, pretty};
use otelite_client::models::MetricResponse;
use otelite_client::ApiClient;
#[allow(clippy::too_many_arguments)]
pub async fn handle_list(
client: &ApiClient,
config: &Config,
limit: Option<u32>,
name: Option<String>,
labels: Vec<String>,
since: Option<String>,
query: Option<String>,
) -> Result<()> {
let mut params = Vec::new();
if let Some(limit) = limit {
params.push(("limit", limit.to_string()));
}
if let Some(name) = name {
params.push(("name", name));
}
if let Some(since) = since {
params.push(("since", since));
}
for label in labels {
params.push(("label", label));
}
if let Some(query_str) = query {
let predicates = otelite_core::query::parse_query(&query_str)
.map_err(|e| crate::error::Error::InvalidArgument(format!("Invalid query: {}", e)))?;
for predicate in predicates {
let param_value = format!(
"{} {} {}",
predicate.field, predicate.operator, predicate.value
);
params.push(("query", param_value));
}
}
let metrics = client.fetch_metrics(params).await?;
match config.format {
crate::config::OutputFormat::Pretty => {
pretty::print_metrics_table(&metrics, config)?;
},
crate::config::OutputFormat::Json => {
json::print_metrics_json(&metrics)?;
},
crate::config::OutputFormat::JsonCompact => {
json::print_metrics_json_compact(&metrics)?;
},
}
Ok(())
}
pub async fn handle_show(
client: &ApiClient,
config: &Config,
name: &str,
labels: Vec<String>,
since: Option<String>,
) -> Result<()> {
let mut params = Vec::new();
if let Some(since) = since {
params.push(("since", since));
}
for label in labels {
params.push(("label", label));
}
let metrics = client.fetch_metric_by_name(name, params).await?;
match config.format {
crate::config::OutputFormat::Pretty => {
if metrics.is_empty() {
println!("No metrics found with name '{}'", name);
} else if metrics.len() == 1 {
pretty::print_metric_details(&metrics[0], config)?;
} else {
pretty::print_metrics_table(&metrics, config)?;
}
},
crate::config::OutputFormat::Json => {
if metrics.len() == 1 {
json::print_metric_json(&metrics[0])?;
} else {
json::print_metrics_json(&metrics)?;
}
},
crate::config::OutputFormat::JsonCompact => {
if metrics.len() == 1 {
json::print_metric_json_compact(&metrics[0])?;
} else {
json::print_metrics_json_compact(&metrics)?;
}
},
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub async fn handle_export(
client: &ApiClient,
_config: &Config,
format: &str,
name: Option<String>,
since: Option<String>,
output: Option<String>,
) -> Result<()> {
let mut params = vec![("format", format.to_string())];
if let Some(name) = name {
params.push(("name", name));
}
if let Some(since) = since {
params.push(("since", since));
}
let data = client.export_metrics(params).await?;
if let Some(output_path) = output {
std::fs::write(&output_path, &data)?;
let count = data.matches("\"name\"").count();
eprintln!("✓ Exported {} metrics to {}", count, output_path);
} else {
print!("{}", data);
}
Ok(())
}
pub fn filter_by_labels(
metrics: Vec<MetricResponse>,
label_filters: &[String],
) -> Vec<MetricResponse> {
if label_filters.is_empty() {
return metrics;
}
let filters: Vec<(&str, &str)> = label_filters
.iter()
.filter_map(|filter| {
let parts: Vec<&str> = filter.splitn(2, '=').collect();
if parts.len() == 2 {
Some((parts[0], parts[1]))
} else {
None
}
})
.collect();
metrics
.into_iter()
.filter(|metric| {
filters.iter().all(|(key, value)| {
metric
.attributes
.get(*key)
.map(|v| v == value)
.unwrap_or(false)
})
})
.collect()
}
pub fn filter_by_name(metrics: Vec<MetricResponse>, name_pattern: &str) -> Vec<MetricResponse> {
metrics
.into_iter()
.filter(|metric| metric.name.contains(name_pattern))
.collect()
}
pub fn filter_by_type(metrics: Vec<MetricResponse>, metric_type: &str) -> Vec<MetricResponse> {
metrics
.into_iter()
.filter(|metric| metric.metric_type.eq_ignore_ascii_case(metric_type))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use otelite_client::models::{HistogramValue, MetricResponse, MetricValue};
use std::collections::HashMap;
#[test]
fn test_filter_by_labels_single_label() {
let metrics = vec![
MetricResponse {
name: "http_requests_total".to_string(),
description: None,
unit: None,
metric_type: "counter".to_string(),
value: MetricValue::Counter(100),
timestamp: 1234567890000000000,
attributes: HashMap::from([("method".to_string(), "GET".to_string())]),
resource: None,
},
MetricResponse {
name: "http_requests_total".to_string(),
description: None,
unit: None,
metric_type: "counter".to_string(),
value: MetricValue::Counter(50),
timestamp: 1234567890000000000,
attributes: HashMap::from([("method".to_string(), "POST".to_string())]),
resource: None,
},
MetricResponse {
name: "http_requests_total".to_string(),
description: None,
unit: None,
metric_type: "counter".to_string(),
value: MetricValue::Counter(25),
timestamp: 1234567890000000000,
attributes: HashMap::from([("method".to_string(), "DELETE".to_string())]),
resource: None,
},
];
let filters = vec!["method=GET".to_string()];
let filtered = filter_by_labels(metrics, &filters);
assert_eq!(filtered.len(), 1);
if let MetricValue::Counter(val) = filtered[0].value {
assert_eq!(val, 100);
}
}
#[test]
fn test_filter_by_labels_multiple_labels() {
let metrics = vec![
MetricResponse {
name: "http_requests_total".to_string(),
description: None,
unit: None,
metric_type: "counter".to_string(),
value: MetricValue::Counter(100),
timestamp: 1234567890000000000,
attributes: HashMap::from([
("method".to_string(), "GET".to_string()),
("status".to_string(), "200".to_string()),
]),
resource: None,
},
MetricResponse {
name: "http_requests_total".to_string(),
description: None,
unit: None,
metric_type: "counter".to_string(),
value: MetricValue::Counter(50),
timestamp: 1234567890000000000,
attributes: HashMap::from([
("method".to_string(), "GET".to_string()),
("status".to_string(), "404".to_string()),
]),
resource: None,
},
MetricResponse {
name: "http_requests_total".to_string(),
description: None,
unit: None,
metric_type: "counter".to_string(),
value: MetricValue::Counter(25),
timestamp: 1234567890000000000,
attributes: HashMap::from([
("method".to_string(), "POST".to_string()),
("status".to_string(), "200".to_string()),
]),
resource: None,
},
];
let filters = vec!["method=GET".to_string(), "status=200".to_string()];
let filtered = filter_by_labels(metrics, &filters);
assert_eq!(filtered.len(), 1);
if let MetricValue::Counter(val) = filtered[0].value {
assert_eq!(val, 100);
}
}
#[test]
fn test_filter_by_labels_no_match() {
let metrics = vec![MetricResponse {
name: "http_requests_total".to_string(),
description: None,
unit: None,
metric_type: "counter".to_string(),
value: MetricValue::Counter(100),
timestamp: 1234567890000000000,
attributes: HashMap::from([("method".to_string(), "GET".to_string())]),
resource: None,
}];
let filters = vec!["method=POST".to_string()];
let filtered = filter_by_labels(metrics, &filters);
assert_eq!(filtered.len(), 0);
}
#[test]
fn test_filter_by_labels_empty_filters() {
let metrics = vec![
MetricResponse {
name: "metric1".to_string(),
description: None,
unit: None,
metric_type: "counter".to_string(),
value: MetricValue::Counter(100),
timestamp: 1234567890000000000,
attributes: HashMap::new(),
resource: None,
},
MetricResponse {
name: "metric2".to_string(),
description: None,
unit: None,
metric_type: "gauge".to_string(),
value: MetricValue::Gauge(50.0),
timestamp: 1234567890000000000,
attributes: HashMap::new(),
resource: None,
},
];
let filters: Vec<String> = vec![];
let filtered = filter_by_labels(metrics.clone(), &filters);
assert_eq!(filtered.len(), 2);
}
#[test]
fn test_filter_by_labels_invalid_format() {
let metrics = vec![MetricResponse {
name: "http_requests_total".to_string(),
description: None,
unit: None,
metric_type: "counter".to_string(),
value: MetricValue::Counter(100),
timestamp: 1234567890000000000,
attributes: HashMap::from([("method".to_string(), "GET".to_string())]),
resource: None,
}];
let filters = vec!["method".to_string()];
let filtered = filter_by_labels(metrics.clone(), &filters);
assert_eq!(filtered.len(), 1);
}
#[test]
fn test_filter_by_labels_partial_match() {
let metrics = vec![MetricResponse {
name: "http_requests_total".to_string(),
description: None,
unit: None,
metric_type: "counter".to_string(),
value: MetricValue::Counter(100),
timestamp: 1234567890000000000,
attributes: HashMap::from([
("method".to_string(), "GET".to_string()),
("status".to_string(), "200".to_string()),
]),
resource: None,
}];
let filters = vec!["method=GET".to_string(), "endpoint=/api".to_string()];
let filtered = filter_by_labels(metrics, &filters);
assert_eq!(filtered.len(), 0);
}
#[test]
fn test_filter_by_name() {
let metrics = vec![
MetricResponse {
name: "http_requests_total".to_string(),
description: None,
unit: None,
metric_type: "counter".to_string(),
value: MetricValue::Counter(100),
timestamp: 1234567890000000000,
attributes: HashMap::new(),
resource: None,
},
MetricResponse {
name: "http_response_time_ms".to_string(),
description: None,
unit: None,
metric_type: "histogram".to_string(),
value: MetricValue::Histogram(HistogramValue {
count: 10,
sum: 1500.0,
buckets: vec![],
}),
timestamp: 1234567890000000000,
attributes: HashMap::new(),
resource: None,
},
MetricResponse {
name: "cpu_usage_percent".to_string(),
description: None,
unit: None,
metric_type: "gauge".to_string(),
value: MetricValue::Gauge(45.0),
timestamp: 1234567890000000000,
attributes: HashMap::new(),
resource: None,
},
];
let filtered = filter_by_name(metrics.clone(), "http");
assert_eq!(filtered.len(), 2);
let filtered = filter_by_name(metrics.clone(), "cpu");
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].name, "cpu_usage_percent");
let filtered = filter_by_name(metrics, "memory");
assert_eq!(filtered.len(), 0);
}
#[test]
fn test_filter_by_type() {
let metrics = vec![
MetricResponse {
name: "requests_total".to_string(),
description: None,
unit: None,
metric_type: "counter".to_string(),
value: MetricValue::Counter(100),
timestamp: 1234567890000000000,
attributes: HashMap::new(),
resource: None,
},
MetricResponse {
name: "cpu_usage".to_string(),
description: None,
unit: None,
metric_type: "gauge".to_string(),
value: MetricValue::Gauge(45.0),
timestamp: 1234567890000000000,
attributes: HashMap::new(),
resource: None,
},
MetricResponse {
name: "response_time".to_string(),
description: None,
unit: None,
metric_type: "histogram".to_string(),
value: MetricValue::Histogram(HistogramValue {
count: 10,
sum: 1500.0,
buckets: vec![],
}),
timestamp: 1234567890000000000,
attributes: HashMap::new(),
resource: None,
},
];
let filtered = filter_by_type(metrics.clone(), "counter");
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].name, "requests_total");
let filtered = filter_by_type(metrics.clone(), "GAUGE");
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].name, "cpu_usage");
let filtered = filter_by_type(metrics.clone(), "histogram");
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].name, "response_time");
let filtered = filter_by_type(metrics, "summary");
assert_eq!(filtered.len(), 0);
}
}