Skip to content

Commit bb79564

Browse files
committed
feat: Implement container metrics and management features
- Added new API endpoints and client methods for retrieving detailed container metrics, including CPU and memory usage. - Introduced UI components for displaying container metrics, logs, and configurations, enhancing the user experience in managing containers. - Updated the container management workflow to support starting, stopping, and restarting containers through a new action dialog. - Enhanced the environment dashboard to integrate container management features, allowing users to view and interact with their containers seamlessly. - Improved logging and error handling for container actions, ensuring better visibility and reliability during operations.
1 parent ecf44ce commit bb79564

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+4917
-241
lines changed

.github/workflows/release.yml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ jobs:
3434
targets: x86_64-unknown-linux-gnu
3535

3636
- name: Install wasm-pack
37-
run: curl https://rustwasm.org/wasm-pack/installer/init.sh -sSf | sh
37+
run: |
38+
curl https://rustwasm.org/wasm-pack/installer/init.sh -sSf | sh
39+
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
3840
3941
- name: Cache dependencies
4042
uses: Swatinem/rust-cache@v2
@@ -109,7 +111,9 @@ jobs:
109111
targets: x86_64-apple-darwin
110112

111113
- name: Install wasm-pack
112-
run: curl https://rustwasm.org/wasm-pack/installer/init.sh -sSf | sh
114+
run: |
115+
curl https://rustwasm.org/wasm-pack/installer/init.sh -sSf | sh
116+
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
113117
114118
- name: Cache dependencies
115119
uses: Swatinem/rust-cache@v2
@@ -184,7 +188,9 @@ jobs:
184188
targets: aarch64-apple-darwin
185189

186190
- name: Install wasm-pack
187-
run: curl https://rustwasm.org/wasm-pack/installer/init.sh -sSf | sh
191+
run: |
192+
curl https://rustwasm.org/wasm-pack/installer/init.sh -sSf | sh
193+
echo "$HOME/.cargo/bin" >> $GITHUB_PATH
188194
189195
- name: Cache dependencies
190196
uses: Swatinem/rust-cache@v2

crates/temps-analytics-session-replay/src/handlers/handler.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use std::sync::Arc;
1515
use temps_core::error_builder::ErrorBuilder;
1616
use temps_core::problemdetails::Problem;
1717
use temps_core::RequestMetadata;
18-
use tracing::info;
18+
use tracing::debug;
1919
use utoipa::{OpenApi, ToSchema};
2020

2121
/// OpenAPI documentation for session replay endpoints
@@ -379,7 +379,7 @@ pub async fn get_project_session_replays(
379379
State(state): State<Arc<AppState>>,
380380
Query(query): Query<GetProjectSessionReplaysQuery>,
381381
) -> Result<Json<GetProjectSessionReplaysResponse>, Problem> {
382-
info!("Getting session replays for project: {}", query.project_id);
382+
debug!("Getting session replays for project: {}", query.project_id);
383383

384384
let page = query.page.unwrap_or(1);
385385
let per_page = query.per_page.unwrap_or(50).min(100); // Cap at 100 items per page
@@ -424,7 +424,7 @@ pub async fn get_visitor_sessions(
424424
Path(visitor_id): Path<i32>,
425425
Query(query): Query<GetVisitorSessionsQuery>,
426426
) -> Result<Json<GetVisitorSessionsResponse>, Problem> {
427-
info!("Getting session replays for visitor: {}", visitor_id);
427+
debug!("Getting session replays for visitor: {}", visitor_id);
428428

429429
let page = query.page.unwrap_or(1);
430430
let per_page = query.per_page.unwrap_or(50).min(100); // Cap at 100 items per page
@@ -469,7 +469,7 @@ pub async fn get_session_replay(
469469
State(state): State<Arc<AppState>>,
470470
Path((visitor_id, session_id)): Path<(i32, i32)>,
471471
) -> Result<Json<GetSessionReplayResponse>, Problem> {
472-
info!(
472+
debug!(
473473
"Getting session replay: {} for visitor: {}",
474474
session_id, visitor_id
475475
);
@@ -505,7 +505,7 @@ pub async fn get_session_replay_events(
505505
State(state): State<Arc<AppState>>,
506506
Path((visitor_id, session_id)): Path<(i32, i32)>,
507507
) -> Result<Json<SessionReplayWithEventsDto>, Problem> {
508-
info!(
508+
debug!(
509509
"Getting session replay events: {} for visitor: {}",
510510
session_id, visitor_id
511511
);
@@ -541,7 +541,7 @@ pub async fn update_session_duration(
541541
Path((visitor_id, session_id)): Path<(i32, String)>,
542542
Json(request): Json<UpdateSessionDurationRequest>,
543543
) -> Result<Json<UpdateSessionDurationResponse>, Problem> {
544-
info!(
544+
debug!(
545545
"Updating duration for session: {} for visitor: {}",
546546
session_id, visitor_id
547547
);
@@ -577,7 +577,7 @@ pub async fn delete_session_replay(
577577
State(state): State<Arc<AppState>>,
578578
Path((visitor_id, session_id)): Path<(i32, String)>,
579579
) -> Result<StatusCode, Problem> {
580-
info!(
580+
debug!(
581581
"Deleting session replay: {} for visitor: {}",
582582
session_id, visitor_id
583583
);
@@ -614,7 +614,7 @@ pub async fn add_events(
614614
Path((visitor_id, session_id)): Path<(i32, String)>,
615615
Json(request): Json<AddEventsRequest>,
616616
) -> Result<Json<AddEventsResponse>, Problem> {
617-
info!(
617+
debug!(
618618
"Adding events to session: {} for visitor: {}",
619619
session_id, visitor_id
620620
);
@@ -649,7 +649,7 @@ pub async fn init_session_replay(
649649
Extension(metadata): Extension<RequestMetadata>,
650650
Json(request): Json<SessionReplayInitRequest>,
651651
) -> Result<(StatusCode, Json<SessionReplayInitResponse>), Problem> {
652-
info!(
652+
debug!(
653653
"Initializing session replay for session: {}",
654654
request.session_id
655655
);
@@ -668,7 +668,7 @@ pub async fn init_session_replay(
668668
let environment_id = route_info.environment.as_ref().map(|e| e.id);
669669
let deployment_id = route_info.deployment.as_ref().map(|d| d.id);
670670

671-
info!(
671+
debug!(
672672
"Resolved host {} to project={}, env={:?}, deploy={:?}",
673673
metadata.host, project_id, environment_id, deployment_id
674674
);
@@ -715,7 +715,7 @@ pub async fn init_session_replay(
715715
.await
716716
{
717717
Ok(session_id) => {
718-
info!("Successfully initialized session replay: {}", session_id);
718+
debug!("Successfully initialized session replay: {}", session_id);
719719
Ok((
720720
StatusCode::CREATED,
721721
Json(SessionReplayInitResponse {
@@ -745,7 +745,7 @@ pub async fn add_session_replay_events(
745745
State(state): State<Arc<AppState>>,
746746
Json(request): Json<SessionReplayEventsRequest>,
747747
) -> Result<Json<AddEventsResponse>, Problem> {
748-
info!(
748+
debug!(
749749
"Adding events to session replay for session: {}",
750750
request.session_id
751751
);
@@ -756,7 +756,7 @@ pub async fn add_session_replay_events(
756756
.await
757757
{
758758
Ok(event_count) => {
759-
info!(
759+
debug!(
760760
"Successfully added {} events to session: {}",
761761
event_count, request.session_id
762762
);

crates/temps-deployer/src/docker.rs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -983,6 +983,106 @@ impl ContainerDeployer for DockerRuntime {
983983
})
984984
}
985985

986+
async fn get_container_stats(
987+
&self,
988+
container_id: &str,
989+
) -> Result<crate::ContainerStats, DeployerError> {
990+
use bollard::query_parameters::StatsOptions;
991+
992+
// Get container info first to get the name
993+
let container_info = self.get_container_info(container_id).await?;
994+
995+
// Get stats from Docker - stream but take only first stat and close
996+
let mut stats_stream = self.docker.stats(
997+
container_id,
998+
Some(StatsOptions {
999+
stream: false, // Only get one stat, don't stream
1000+
one_shot: true, // Return immediately after first stat
1001+
}),
1002+
);
1003+
1004+
// Take the first stat
1005+
let stats_data = stats_stream
1006+
.try_next()
1007+
.await
1008+
.map_err(|e| DeployerError::Other(format!("Failed to get container stats: {}", e)))?
1009+
.ok_or_else(|| DeployerError::Other("No stats available".to_string()))?;
1010+
1011+
// Extract CPU percentage
1012+
let cpu_percent = if let (Some(cpu), Some(system)) = (
1013+
stats_data
1014+
.cpu_stats
1015+
.as_ref()
1016+
.and_then(|cs| cs.cpu_usage.as_ref()),
1017+
stats_data
1018+
.cpu_stats
1019+
.as_ref()
1020+
.and_then(|cs| cs.system_cpu_usage),
1021+
) {
1022+
let cpu_total = cpu.total_usage.unwrap_or(0);
1023+
if system > 0 && cpu_total > 0 {
1024+
let cpu_delta = cpu_total as f64;
1025+
let system_delta = system as f64;
1026+
let num_cpus = stats_data
1027+
.cpu_stats
1028+
.as_ref()
1029+
.and_then(|cs| cs.online_cpus)
1030+
.unwrap_or(1) as f64;
1031+
((cpu_delta / system_delta) * num_cpus * 100.0)
1032+
.min(100.0)
1033+
.max(0.0)
1034+
} else {
1035+
0.0
1036+
}
1037+
} else {
1038+
0.0
1039+
};
1040+
1041+
// Extract memory stats
1042+
let memory_stats = stats_data.memory_stats.as_ref();
1043+
let memory_bytes = memory_stats.and_then(|ms| ms.usage).unwrap_or(0) as u64;
1044+
let memory_limit_bytes = memory_stats.and_then(|ms| ms.limit).map(|l| l as u64);
1045+
1046+
let memory_percent = if let Some(limit) = memory_limit_bytes {
1047+
if limit > 0 {
1048+
Some(
1049+
((memory_bytes as f64 / limit as f64) * 100.0)
1050+
.min(100.0)
1051+
.max(0.0),
1052+
)
1053+
} else {
1054+
None
1055+
}
1056+
} else {
1057+
None
1058+
};
1059+
1060+
// Extract network stats
1061+
let default_networks = Default::default();
1062+
let networks_stats = stats_data.networks.as_ref().unwrap_or(&default_networks);
1063+
let (network_rx_bytes, network_tx_bytes) =
1064+
if let Some(net_stat) = networks_stats.values().next() {
1065+
(
1066+
net_stat.rx_bytes.unwrap_or(0) as u64,
1067+
net_stat.tx_bytes.unwrap_or(0) as u64,
1068+
)
1069+
} else {
1070+
(0, 0)
1071+
};
1072+
1073+
Ok(crate::ContainerStats {
1074+
container_id: container_info.container_id,
1075+
container_name: container_info.container_name,
1076+
cpu_percent,
1077+
memory_bytes,
1078+
memory_limit_bytes,
1079+
memory_percent,
1080+
network_rx_bytes,
1081+
network_tx_bytes,
1082+
timestamp: chrono::Utc::now(),
1083+
})
1084+
}
1085+
9861086
async fn list_containers(&self) -> Result<Vec<ContainerInfo>, DeployerError> {
9871087
let containers = self
9881088
.docker

crates/temps-deployer/src/lib.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,27 @@ pub struct ContainerInfo {
163163
pub environment_vars: HashMap<String, String>,
164164
}
165165

166+
/// Container performance statistics (CPU, memory, network)
167+
#[derive(Debug, Clone, Serialize, Deserialize)]
168+
pub struct ContainerStats {
169+
pub container_id: String,
170+
pub container_name: String,
171+
/// CPU usage percentage (0-100)
172+
pub cpu_percent: f64,
173+
/// Memory usage in bytes
174+
pub memory_bytes: u64,
175+
/// Memory limit in bytes (if set)
176+
pub memory_limit_bytes: Option<u64>,
177+
/// Memory usage percentage (0-100) if limit is set
178+
pub memory_percent: Option<f64>,
179+
/// Network bytes received
180+
pub network_rx_bytes: u64,
181+
/// Network bytes transmitted
182+
pub network_tx_bytes: u64,
183+
/// Timestamp of metrics collection
184+
pub timestamp: UtcDateTime,
185+
}
186+
166187
/// Configuration for stopping containers
167188
#[derive(Debug, Clone)]
168189
pub struct ContainerStopSpec {
@@ -330,6 +351,12 @@ pub trait ContainerDeployer: Send + Sync {
330351
/// Get container information
331352
async fn get_container_info(&self, container_id: &str) -> Result<ContainerInfo, DeployerError>;
332353

354+
/// Get container performance metrics (CPU, memory, network)
355+
async fn get_container_stats(
356+
&self,
357+
container_id: &str,
358+
) -> Result<ContainerStats, DeployerError>;
359+
333360
/// List running containers
334361
async fn list_containers(&self) -> Result<Vec<ContainerInfo>, DeployerError>;
335362

0 commit comments

Comments
 (0)