diff --git a/agent/src/repository/metric_repository.rs b/agent/src/repository/metric_repository.rs index 6dbc1208..6eb03d3e 100644 --- a/agent/src/repository/metric_repository.rs +++ b/agent/src/repository/metric_repository.rs @@ -34,6 +34,7 @@ pub trait MetricStoreRepository { } #[derive(Debug, Serialize, Clone, PartialEq)] +#[serde(rename_all = "snake_case")] pub enum MetricType { CpuUsage, MemoryUsage, diff --git a/agent/src/services/studio.rs b/agent/src/services/studio.rs index 3e80a7da..a0a3abbf 100644 --- a/agent/src/services/studio.rs +++ b/agent/src/services/studio.rs @@ -24,6 +24,10 @@ use std::collections::VecDeque; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; +// The maximum JSON body size is actually 1MB +// We reserve 100KB as a buffer for Verifiable Credential capacity +const JSON_BODY_MAX_SIZE: usize = 900_000; + #[derive(Deserialize)] pub struct EmptyResponse {} @@ -409,60 +413,53 @@ impl MessageActivityRepository for Studio { } } -#[derive(Serialize)] -struct MetricStr { - metric_type: String, - value: f32, -} - -#[derive(Serialize)] -struct MetricsWithTimestampStr { - timestamp: chrono::DateTime, - metrics: Vec, -} - impl MetricStoreRepository for Studio { async fn save(&self, request: VecDeque) -> anyhow::Result<()> { - let metrics_str = request - .into_iter() - .map(|m| MetricsWithTimestampStr { - timestamp: m.timestamp, - metrics: m - .metrics - .into_iter() - .map(|metric| MetricStr { - metric_type: metric.metric_type.to_string(), - value: metric.value, - }) - .collect::>(), - }) - .collect::>(); - - let my_did = self.did_accessor.get_my_did(); - let my_keyring = self.did_accessor.get_my_keyring(); + let mut metrics = request; + while !metrics.is_empty() { + let my_did = self.did_accessor.get_my_did(); + let my_keyring = self.did_accessor.get_my_keyring(); + let mut metrics_str = Vec::new(); + let mut current_size = 0; + + while let Some(m) = metrics.pop_front() { + let item_size = serde_json::to_string(&m)?.len(); + if item_size > JSON_BODY_MAX_SIZE { + anyhow::bail!("invalid item size: JSON body size too large") + } + if current_size + item_size > JSON_BODY_MAX_SIZE { + metrics.push_front(m); + break; + } + current_size += item_size; + metrics_str.push(m); + } - let model = VerifiableCredentials::new(my_did, json!(metrics_str), chrono::Utc::now()); - let payload = DidVcService::generate(&self.did_repository, model, &my_keyring) - .context("failed to generate payload")?; + let model = VerifiableCredentials::new(my_did, json!(metrics_str), chrono::Utc::now()); + let payload = DidVcService::generate(&self.did_repository, model, &my_keyring) + .context("failed to generate payload")?; - let payload = serde_json::to_string(&payload).context("failed to serialize")?; - let res = self.http_client.post("/v1/metrics", &payload).await?; + let payload = serde_json::to_string(&payload).context("failed to serialize")?; + let res = self.http_client.post("/v1/metrics", &payload).await?; - let status = res.status(); - let json: Value = res.json().await.context("Failed to read response body")?; - let message = if let Some(message) = json.get("message").map(|v| v.to_string()) { - message - } else { - "".to_string() - }; - match status { - reqwest::StatusCode::OK => Ok(()), - reqwest::StatusCode::NOT_FOUND => anyhow::bail!("StatusCode=404, {}", message), - reqwest::StatusCode::INTERNAL_SERVER_ERROR => { - anyhow::bail!("StatusCode=500, {}", message); + let status = res.status(); + let json: Value = res.json().await.context("Failed to read response body")?; + let message = if let Some(message) = json.get("message").map(|v| v.to_string()) { + message + } else { + "".to_string() + }; + match status { + reqwest::StatusCode::OK => continue, + reqwest::StatusCode::NOT_FOUND => anyhow::bail!("StatusCode=404, {}", message), + reqwest::StatusCode::INTERNAL_SERVER_ERROR => { + anyhow::bail!("StatusCode=500, {}", message); + } + other => anyhow::bail!("StatusCode={other}, {}", message), } - other => anyhow::bail!("StatusCode={other}, {}", message), } + + Ok(()) } }