1use anyhow::Error;
6use fidl_fuchsia_kernel as fkernel;
7use fuchsia_async::DurationExt as _;
8use futures::prelude::*;
9use std::sync::Arc;
10use zx::{self as zx, Resource};
11
12pub struct KernelStats {
14 resource: Resource,
15}
16
17impl KernelStats {
18 pub fn new(resource: Resource) -> Arc<Self> {
20 Arc::new(Self { resource })
21 }
22
23 pub async fn serve(
24 self: Arc<Self>,
25 mut stream: fkernel::StatsRequestStream,
26 ) -> Result<(), Error> {
27 while let Some(stats_request) = stream.try_next().await? {
28 match stats_request {
29 fkernel::StatsRequest::GetMemoryStats { responder } => {
30 let mem_stats = &self.resource.mem_stats()?;
31 let stats = fkernel::MemoryStats {
32 total_bytes: Some(mem_stats.total_bytes),
33 free_bytes: Some(mem_stats.free_bytes),
34 wired_bytes: Some(mem_stats.wired_bytes),
35 total_heap_bytes: Some(mem_stats.total_heap_bytes),
36 free_heap_bytes: Some(mem_stats.free_heap_bytes),
37 vmo_bytes: Some(mem_stats.vmo_bytes),
38 mmu_overhead_bytes: Some(mem_stats.mmu_overhead_bytes),
39 ipc_bytes: Some(mem_stats.ipc_bytes),
40 other_bytes: Some(mem_stats.other_bytes),
41 free_loaned_bytes: Some(mem_stats.free_loaned_bytes),
42 cache_bytes: Some(mem_stats.cache_bytes),
43 slab_bytes: Some(mem_stats.slab_bytes),
44 zram_bytes: Some(mem_stats.zram_bytes),
45 vmo_reclaim_total_bytes: Some(mem_stats.vmo_reclaim_total_bytes),
46 vmo_reclaim_newest_bytes: Some(mem_stats.vmo_reclaim_newest_bytes),
47 vmo_reclaim_oldest_bytes: Some(mem_stats.vmo_reclaim_oldest_bytes),
48 vmo_reclaim_disabled_bytes: Some(mem_stats.vmo_reclaim_disabled_bytes),
49 vmo_discardable_locked_bytes: Some(mem_stats.vmo_discardable_locked_bytes),
50 vmo_discardable_unlocked_bytes: Some(
51 mem_stats.vmo_discardable_unlocked_bytes,
52 ),
53 ..Default::default()
54 };
55 responder.send(&stats)?;
56 }
57 fkernel::StatsRequest::GetMemoryStatsExtended { responder } => {
58 let mem_stats_extended = &self.resource.mem_stats_extended()?;
59 let stats = fkernel::MemoryStatsExtended {
60 total_bytes: Some(mem_stats_extended.total_bytes),
61 free_bytes: Some(mem_stats_extended.free_bytes),
62 wired_bytes: Some(mem_stats_extended.wired_bytes),
63 total_heap_bytes: Some(mem_stats_extended.total_heap_bytes),
64 free_heap_bytes: Some(mem_stats_extended.free_heap_bytes),
65 vmo_bytes: Some(mem_stats_extended.vmo_bytes),
66 vmo_pager_total_bytes: Some(mem_stats_extended.vmo_pager_total_bytes),
67 vmo_pager_newest_bytes: Some(mem_stats_extended.vmo_pager_newest_bytes),
68 vmo_pager_oldest_bytes: Some(mem_stats_extended.vmo_pager_oldest_bytes),
69 vmo_discardable_locked_bytes: Some(
70 mem_stats_extended.vmo_discardable_locked_bytes,
71 ),
72 vmo_discardable_unlocked_bytes: Some(
73 mem_stats_extended.vmo_discardable_unlocked_bytes,
74 ),
75 mmu_overhead_bytes: Some(mem_stats_extended.mmu_overhead_bytes),
76 ipc_bytes: Some(mem_stats_extended.ipc_bytes),
77 other_bytes: Some(mem_stats_extended.other_bytes),
78 ..Default::default()
79 };
80 responder.send(&stats)?;
81 }
82 fkernel::StatsRequest::GetMemoryStatsCompression { responder } => {
83 let mem_stats_compression = &self.resource.mem_stats_compression()?;
84 let stats = fkernel::MemoryStatsCompression {
85 uncompressed_storage_bytes: Some(
86 mem_stats_compression.uncompressed_storage_bytes,
87 ),
88 compressed_storage_bytes: Some(
89 mem_stats_compression.compressed_storage_bytes,
90 ),
91 compressed_fragmentation_bytes: Some(
92 mem_stats_compression.compressed_fragmentation_bytes,
93 ),
94 compression_time: Some(mem_stats_compression.compression_time),
95 decompression_time: Some(mem_stats_compression.decompression_time),
96 total_page_compression_attempts: Some(
97 mem_stats_compression.total_page_compression_attempts,
98 ),
99 failed_page_compression_attempts: Some(
100 mem_stats_compression.failed_page_compression_attempts,
101 ),
102 total_page_decompressions: Some(
103 mem_stats_compression.total_page_decompressions,
104 ),
105 compressed_page_evictions: Some(
106 mem_stats_compression.compressed_page_evictions,
107 ),
108 eager_page_compressions: Some(
109 mem_stats_compression.eager_page_compressions,
110 ),
111 memory_pressure_page_compressions: Some(
112 mem_stats_compression.memory_pressure_page_compressions,
113 ),
114 critical_memory_page_compressions: Some(
115 mem_stats_compression.critical_memory_page_compressions,
116 ),
117 pages_decompressed_unit_ns: Some(
118 mem_stats_compression.pages_decompressed_unit_ns,
119 ),
120 pages_decompressed_within_log_time: Some(
121 mem_stats_compression.pages_decompressed_within_log_time,
122 ),
123 ..Default::default()
124 };
125 responder.send(&stats)?;
126 }
127 fkernel::StatsRequest::GetCpuStats { responder } => {
128 let cpu_stats = &self.resource.cpu_stats()?;
129 let mut per_cpu_stats: Vec<fkernel::PerCpuStats> =
130 Vec::with_capacity(cpu_stats.len());
131 for cpu_stat in cpu_stats.iter() {
132 per_cpu_stats.push(fkernel::PerCpuStats {
133 cpu_number: Some(cpu_stat.cpu_number),
134 flags: Some(cpu_stat.flags),
135 idle_time: Some(cpu_stat.idle_time),
136 reschedules: Some(cpu_stat.reschedules),
137 context_switches: Some(cpu_stat.context_switches),
138 irq_preempts: Some(cpu_stat.irq_preempts),
139 yields: Some(cpu_stat.yields),
140 ints: Some(cpu_stat.ints),
141 timer_ints: Some(cpu_stat.timer_ints),
142 timers: Some(cpu_stat.timers),
143 page_faults: Some(cpu_stat.page_faults),
144 exceptions: Some(cpu_stat.exceptions),
145 syscalls: Some(cpu_stat.syscalls),
146 reschedule_ipis: Some(cpu_stat.reschedule_ipis),
147 generic_ipis: Some(cpu_stat.generic_ipis),
148 ..Default::default()
149 });
150 }
151 let stats = fkernel::CpuStats {
152 actual_num_cpus: per_cpu_stats.len() as u64,
153 per_cpu_stats: Some(per_cpu_stats),
154 };
155 responder.send(&stats)?;
156 }
157 fkernel::StatsRequest::GetCpuLoad { duration, responder } => {
158 if duration <= 0 {
159 return Err(anyhow::anyhow!("Duration must be greater than 0"));
160 }
161
162 let start_time = fuchsia_async::MonotonicInstant::now();
167 let start_stats = self.resource.cpu_stats()?;
168 fuchsia_async::Timer::new(
169 zx::MonotonicDuration::from_nanos(duration).after_now(),
170 )
171 .await;
172 let end_stats = self.resource.cpu_stats()?;
173 let end_time = fuchsia_async::MonotonicInstant::now();
174
175 let loads = calculate_cpu_loads(start_time, start_stats, end_time, end_stats);
176 responder.send(&loads)?;
177 }
178 }
179 }
180 Ok(())
181 }
182}
183
184fn calculate_cpu_loads(
187 start_time: fuchsia_async::MonotonicInstant,
188 start_stats: Vec<zx::PerCpuStats>,
189 end_time: fuchsia_async::MonotonicInstant,
190 end_stats: Vec<zx::PerCpuStats>,
191) -> Vec<f32> {
192 let elapsed_time = (end_time - start_time).into_nanos();
193 start_stats
194 .iter()
195 .zip(end_stats.iter())
196 .map(|(start, end)| {
197 let busy_time = elapsed_time - (end.idle_time - start.idle_time);
198 let load_pct = busy_time as f64 / elapsed_time as f64 * 100.0;
199 load_pct as f32
200 })
201 .collect::<Vec<f32>>()
202}
203
204#[cfg(test)]
205mod tests {
206 use super::*;
207 use fuchsia_component::client::connect_to_protocol;
208 use {fidl_fuchsia_kernel as fkernel, fuchsia_async as fasync};
209
210 async fn get_info_resource() -> Result<Resource, Error> {
211 let info_resource_provider = connect_to_protocol::<fkernel::InfoResourceMarker>()?;
212 let info_resource_handle = info_resource_provider.get().await?;
213 Ok(Resource::from(info_resource_handle))
214 }
215
216 enum OnError {
217 Panic,
218 Ignore,
219 }
220
221 async fn serve_kernel_stats(on_error: OnError) -> Result<fkernel::StatsProxy, Error> {
222 let info_resource = get_info_resource().await?;
223
224 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<fkernel::StatsMarker>();
225 fasync::Task::local(KernelStats::new(info_resource).serve(stream).unwrap_or_else(
226 move |e| match on_error {
227 OnError::Panic => panic!("Error while serving kernel stats: {}", e),
228 _ => {}
229 },
230 ))
231 .detach();
232 Ok(proxy)
233 }
234
235 #[fuchsia::test]
236 async fn get_mem_stats() -> Result<(), Error> {
237 let kernel_stats_provider = serve_kernel_stats(OnError::Panic).await?;
238 let mem_stats = kernel_stats_provider.get_memory_stats().await?;
239
240 assert!(mem_stats.total_bytes.unwrap() > 0);
241 assert!(mem_stats.total_heap_bytes.unwrap() > 0);
242 assert!(mem_stats.slab_bytes.unwrap() > 0);
243 Ok(())
244 }
245
246 #[fuchsia::test]
247 async fn get_mem_stats_extended() -> Result<(), Error> {
248 let kernel_stats_provider = serve_kernel_stats(OnError::Panic).await?;
249 let mem_stats_extended = kernel_stats_provider.get_memory_stats_extended().await?;
250
251 assert!(mem_stats_extended.total_bytes.unwrap() > 0);
252 assert!(mem_stats_extended.total_heap_bytes.unwrap() > 0);
253
254 Ok(())
255 }
256
257 #[fuchsia::test]
258 async fn get_cpu_stats() -> Result<(), Error> {
259 let kernel_stats_provider = serve_kernel_stats(OnError::Panic).await?;
260 let cpu_stats = kernel_stats_provider.get_cpu_stats().await?;
261 let actual_num_cpus = cpu_stats.actual_num_cpus;
262 assert!(actual_num_cpus > 0);
263 let per_cpu_stats = cpu_stats.per_cpu_stats.unwrap();
264
265 let mut idle_time_sum = 0;
266 let mut syscalls_sum = 0;
267
268 for per_cpu_stat in per_cpu_stats.iter() {
269 idle_time_sum += per_cpu_stat.idle_time.unwrap();
270 syscalls_sum += per_cpu_stat.syscalls.unwrap();
271 }
272
273 assert!(idle_time_sum > 0);
274 assert!(syscalls_sum > 0);
275
276 Ok(())
277 }
278
279 #[fuchsia::test]
280 async fn get_cpu_load_invalid_duration() {
281 let kernel_stats_provider = serve_kernel_stats(OnError::Ignore).await.unwrap();
282
283 assert_matches::assert_matches!(
285 kernel_stats_provider.get_cpu_load(0).await,
286 Err(fidl::Error::ClientChannelClosed { .. })
287 );
288 }
289
290 #[fuchsia::test]
291 async fn get_cpu_load() -> Result<(), Error> {
292 let kernel_stats_provider = serve_kernel_stats(OnError::Panic).await?;
293 let cpu_loads = kernel_stats_provider
294 .get_cpu_load(zx::MonotonicDuration::from_seconds(1).into_nanos())
295 .await?;
296
297 assert!(
298 cpu_loads.iter().all(|l| l > &0.0 && l <= &100.0),
299 "Invalid CPU load value (expected range 0.0 - 100.0, received {:?}",
300 cpu_loads
301 );
302
303 Ok(())
304 }
305
306 fn parameters_for_expected_cpu_loads(
309 cpu_loads: Vec<f32>,
310 ) -> (
311 fuchsia_async::MonotonicInstant,
312 Vec<zx::PerCpuStats>,
313 fuchsia_async::MonotonicInstant,
314 Vec<zx::PerCpuStats>,
315 ) {
316 let start_time = fuchsia_async::MonotonicInstant::from_nanos(0);
317 let end_time = fuchsia_async::MonotonicInstant::from_nanos(1000000000);
318
319 let (start_stats, end_stats) = std::iter::repeat(zx::PerCpuStats::default())
320 .zip(cpu_loads.into_iter().map(|load| {
321 let end_time_f32 = end_time.into_nanos() as f32;
322 let idle_time = (end_time_f32 - (load / 100.0 * end_time_f32)) as i64;
323 zx::PerCpuStats { idle_time, ..zx::PerCpuStats::default() }
324 }))
325 .unzip();
326
327 (start_time, start_stats, end_time, end_stats)
328 }
329
330 #[fuchsia::test]
331 fn test_calculate_cpu_loads() -> Result<(), Error> {
332 let (start_time, start_stats, end_time, end_stats) =
334 parameters_for_expected_cpu_loads(vec![75.0, 0.0]);
335 assert_eq!(
336 calculate_cpu_loads(start_time, start_stats, end_time, end_stats),
337 vec![75.0, 0.0]
338 );
339
340 let (start_time, start_stats, end_time, end_stats) =
342 parameters_for_expected_cpu_loads(vec![0.0, 75.0]);
343 assert_eq!(
344 calculate_cpu_loads(start_time, start_stats, end_time, end_stats),
345 vec![0.0, 75.0]
346 );
347
348 Ok(())
349 }
350}