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 normalized_busy_time: Some(cpu_stat.normalized_busy_time),
149 active_energy_consumption_nj: Some(
150 cpu_stat.active_energy_consumption_nj,
151 ),
152 idle_energy_consumption_nj: Some(cpu_stat.idle_energy_consumption_nj),
153 ..Default::default()
154 });
155 }
156 let stats = fkernel::CpuStats {
157 actual_num_cpus: per_cpu_stats.len() as u64,
158 per_cpu_stats: Some(per_cpu_stats),
159 };
160 responder.send(&stats)?;
161 }
162 fkernel::StatsRequest::GetCpuLoad { duration, responder } => {
163 if duration <= 0 {
164 return Err(anyhow::anyhow!("Duration must be greater than 0"));
165 }
166
167 let start_time = fuchsia_async::MonotonicInstant::now();
172 let start_stats = self.resource.cpu_stats()?;
173 fuchsia_async::Timer::new(
174 zx::MonotonicDuration::from_nanos(duration).after_now(),
175 )
176 .await;
177 let end_stats = self.resource.cpu_stats()?;
178 let end_time = fuchsia_async::MonotonicInstant::now();
179
180 let loads = calculate_cpu_loads(start_time, start_stats, end_time, end_stats);
181 responder.send(&loads)?;
182 }
183 }
184 }
185 Ok(())
186 }
187}
188
189fn calculate_cpu_loads(
192 start_time: fuchsia_async::MonotonicInstant,
193 start_stats: Vec<zx::PerCpuStats>,
194 end_time: fuchsia_async::MonotonicInstant,
195 end_stats: Vec<zx::PerCpuStats>,
196) -> Vec<f32> {
197 let elapsed_time = (end_time - start_time).into_nanos();
198 start_stats
199 .iter()
200 .zip(end_stats.iter())
201 .map(|(start, end)| {
202 let normalized_busy_time = end.normalized_busy_time - start.normalized_busy_time;
203 let load_pct = normalized_busy_time as f64 / elapsed_time as f64 * 100.0;
204 load_pct as f32
205 })
206 .collect::<Vec<f32>>()
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212 use fuchsia_component::client::connect_to_protocol;
213 use {fidl_fuchsia_kernel as fkernel, fuchsia_async as fasync};
214
215 async fn get_info_resource() -> Result<Resource, Error> {
216 let info_resource_provider = connect_to_protocol::<fkernel::InfoResourceMarker>()?;
217 let info_resource_handle = info_resource_provider.get().await?;
218 Ok(Resource::from(info_resource_handle))
219 }
220
221 enum OnError {
222 Panic,
223 Ignore,
224 }
225
226 async fn serve_kernel_stats(on_error: OnError) -> Result<fkernel::StatsProxy, Error> {
227 let info_resource = get_info_resource().await?;
228
229 let (proxy, stream) = fidl::endpoints::create_proxy_and_stream::<fkernel::StatsMarker>();
230 fasync::Task::local(KernelStats::new(info_resource).serve(stream).unwrap_or_else(
231 move |e| match on_error {
232 OnError::Panic => panic!("Error while serving kernel stats: {}", e),
233 _ => {}
234 },
235 ))
236 .detach();
237 Ok(proxy)
238 }
239
240 #[fuchsia::test]
241 async fn get_mem_stats() -> Result<(), Error> {
242 let kernel_stats_provider = serve_kernel_stats(OnError::Panic).await?;
243 let mem_stats = kernel_stats_provider.get_memory_stats().await?;
244
245 assert!(mem_stats.total_bytes.unwrap() > 0);
246 assert!(mem_stats.total_heap_bytes.unwrap() > 0);
247 assert!(mem_stats.slab_bytes.unwrap() > 0);
248 Ok(())
249 }
250
251 #[fuchsia::test]
252 async fn get_mem_stats_extended() -> Result<(), Error> {
253 let kernel_stats_provider = serve_kernel_stats(OnError::Panic).await?;
254 let mem_stats_extended = kernel_stats_provider.get_memory_stats_extended().await?;
255
256 assert!(mem_stats_extended.total_bytes.unwrap() > 0);
257 assert!(mem_stats_extended.total_heap_bytes.unwrap() > 0);
258
259 Ok(())
260 }
261
262 #[fuchsia::test]
263 async fn get_cpu_stats() -> Result<(), Error> {
264 let kernel_stats_provider = serve_kernel_stats(OnError::Panic).await?;
265 let cpu_stats = kernel_stats_provider.get_cpu_stats().await?;
266 let actual_num_cpus = cpu_stats.actual_num_cpus;
267 assert!(actual_num_cpus > 0);
268 let per_cpu_stats = cpu_stats.per_cpu_stats.unwrap();
269
270 let mut idle_time_sum = 0;
271 let mut syscalls_sum = 0;
272
273 for per_cpu_stat in per_cpu_stats.iter() {
274 idle_time_sum += per_cpu_stat.idle_time.unwrap();
275 syscalls_sum += per_cpu_stat.syscalls.unwrap();
276 }
277
278 assert!(idle_time_sum > 0);
279 assert!(syscalls_sum > 0);
280
281 Ok(())
282 }
283
284 #[fuchsia::test]
285 async fn get_cpu_load_invalid_duration() {
286 let kernel_stats_provider = serve_kernel_stats(OnError::Ignore).await.unwrap();
287
288 assert_matches::assert_matches!(
290 kernel_stats_provider.get_cpu_load(0).await,
291 Err(fidl::Error::ClientChannelClosed { .. })
292 );
293 }
294
295 #[fuchsia::test]
296 async fn get_cpu_load() -> Result<(), Error> {
297 let kernel_stats_provider = serve_kernel_stats(OnError::Panic).await?;
298 let cpu_loads = kernel_stats_provider
299 .get_cpu_load(zx::MonotonicDuration::from_seconds(1).into_nanos())
300 .await?;
301
302 assert!(
303 cpu_loads.iter().all(|l| l > &0.0 && l <= &100.0),
304 "Invalid CPU load value (expected range 0.0 - 100.0, received {:?}",
305 cpu_loads
306 );
307
308 Ok(())
309 }
310
311 fn parameters_for_expected_cpu_loads(
314 cpu_loads: Vec<f32>,
315 ) -> (
316 fuchsia_async::MonotonicInstant,
317 Vec<zx::PerCpuStats>,
318 fuchsia_async::MonotonicInstant,
319 Vec<zx::PerCpuStats>,
320 ) {
321 let start_time = fuchsia_async::MonotonicInstant::from_nanos(0);
322 let end_time = fuchsia_async::MonotonicInstant::from_nanos(1000000000);
323
324 let (start_stats, end_stats) = std::iter::repeat(zx::PerCpuStats::default())
325 .zip(cpu_loads.into_iter().map(|load| {
326 let end_time_f32 = end_time.into_nanos() as f32;
327 let normalized_busy_time = (load / 100.0 * end_time_f32) as i64;
328 zx::PerCpuStats { normalized_busy_time, ..zx::PerCpuStats::default() }
329 }))
330 .unzip();
331
332 (start_time, start_stats, end_time, end_stats)
333 }
334
335 #[fuchsia::test]
336 fn test_calculate_cpu_loads() -> Result<(), Error> {
337 let (start_time, start_stats, end_time, end_stats) =
339 parameters_for_expected_cpu_loads(vec![75.0, 0.0]);
340 assert_eq!(
341 calculate_cpu_loads(start_time, start_stats, end_time, end_stats),
342 vec![75.0, 0.0]
343 );
344
345 let (start_time, start_stats, end_time, end_stats) =
347 parameters_for_expected_cpu_loads(vec![0.0, 75.0]);
348 assert_eq!(
349 calculate_cpu_loads(start_time, start_stats, end_time, end_stats),
350 vec![0.0, 75.0]
351 );
352
353 Ok(())
354 }
355}