1use crate::outcome::{
4 Diagnostics, FilesystemViolation, NetworkDeniedHost, NetworkHostContact, ResetMode,
5 ResetSummary,
6};
7use crate::persistent::PoolStats;
8
9#[derive(Clone, Debug, Default)]
11pub struct SandboxTelemetry {
12 pub cpu_ms_used: Option<u64>,
14 pub queue_wait_ms: Option<u64>,
16 pub prepare_ms: Option<u64>,
18 pub cleanup_ms: Option<u64>,
20 pub filesystem: FilesystemTelemetry,
22 pub network: NetworkTelemetry,
24 pub reset: Option<ResetTelemetry>,
26 pub memory: MemoryTelemetry,
28}
29
30#[derive(Clone, Debug, Default)]
32pub struct FilesystemTelemetry {
33 pub bytes_written: Option<u64>,
35 pub violations: Vec<FilesystemViolation>,
37}
38
39#[derive(Clone, Debug, Default)]
41pub struct NetworkTelemetry {
42 pub allowed: Vec<NetworkHostContact>,
44 pub blocked: Vec<NetworkDeniedHost>,
46}
47
48#[derive(Clone, Debug, Default)]
50pub struct MemoryTelemetry {
51 pub py_heap_kib: Option<u64>,
53 pub rss_kib_before: Option<u64>,
55 pub rss_kib_after: Option<u64>,
57}
58
59#[derive(Clone, Debug)]
61pub struct ResetTelemetry {
62 pub mode: ResetMode,
64 pub duration_ms: u64,
66 pub engine_generation: u64,
68}
69
70#[derive(Clone, Debug, Default)]
72pub struct PoolTelemetry {
73 pub total_isolates: usize,
75 pub idle_isolates: usize,
77 pub busy_isolates: usize,
79 pub waiting_calls: usize,
81 pub invocations: u64,
83 pub average_queue_wait_ms: f64,
85 pub queue_wait_p50_ms: Option<f64>,
87 pub queue_wait_p95_ms: Option<f64>,
89 pub quarantine_events: u64,
91 pub quarantine_heap_hits: u64,
93 pub quarantine_rss_hits: u64,
95 pub scaledown_events: u64,
97}
98
99impl From<&Diagnostics> for SandboxTelemetry {
100 fn from(value: &Diagnostics) -> Self {
101 Self {
102 cpu_ms_used: value.cpu_ms_used,
103 queue_wait_ms: value.queue_wait_ms,
104 prepare_ms: value.prepare_ms,
105 cleanup_ms: value.cleanup_ms,
106 filesystem: FilesystemTelemetry {
107 bytes_written: value.filesystem_bytes_written,
108 violations: value.filesystem_violations.clone(),
109 },
110 network: NetworkTelemetry {
111 allowed: value.network_hosts_contacted.clone(),
112 blocked: value.network_hosts_blocked.clone(),
113 },
114 reset: value.reset.as_ref().map(ResetTelemetry::from),
115 memory: MemoryTelemetry {
116 py_heap_kib: value.py_heap_kib,
117 rss_kib_before: value.rss_kib_before,
118 rss_kib_after: value.rss_kib_after,
119 },
120 }
121 }
122}
123
124impl From<&ResetSummary> for ResetTelemetry {
125 fn from(summary: &ResetSummary) -> Self {
126 Self {
127 mode: summary.mode.clone(),
128 duration_ms: summary.duration_ms,
129 engine_generation: summary.engine_generation,
130 }
131 }
132}
133
134impl SandboxTelemetry {
135 pub fn has_policy_violations(&self) -> bool {
137 !self.network.blocked.is_empty() || !self.filesystem.violations.is_empty()
138 }
139}
140
141impl From<&PoolStats> for PoolTelemetry {
142 fn from(stats: &PoolStats) -> Self {
143 Self {
144 total_isolates: stats.total,
145 idle_isolates: stats.idle,
146 busy_isolates: stats.busy,
147 waiting_calls: stats.waiting,
148 invocations: stats.invocations,
149 average_queue_wait_ms: stats.average_queue_wait_ms,
150 queue_wait_p50_ms: stats.queue_wait_p50_ms,
151 queue_wait_p95_ms: stats.queue_wait_p95_ms,
152 quarantine_events: stats.quarantine_events,
153 quarantine_heap_hits: stats.quarantine_heap_hits,
154 quarantine_rss_hits: stats.quarantine_rss_hits,
155 scaledown_events: stats.scaledown_events,
156 }
157 }
158}