builtins/
kernel_stats.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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
12/// An implementation of the `fuchsia.kernel.Stats` protocol.
13pub struct KernelStats {
14    resource: Resource,
15}
16
17impl KernelStats {
18    /// `resource` must be the info resource.
19    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                    // Record `start_time` before the first stats query, and `end_time` *after* the
168                    // second stats query completes. This ensures the "total time" (`end_time` -
169                    // `start_time`) will never be less than the duration spanned by `start_stats`
170                    // to `end_stats`, which would be invalid.
171                    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
189/// Uses start / end times and corresponding PerCpuStats to calculate and return a vector of per-CPU
190/// load values as floats in the range 0.0 - 100.0.
191fn 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        // The server should close the channel when it receives an invalid argument
289        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    // Takes a vector of CPU loads and generates the necessary parameters that can be fed into
312    // `calculate_cpu_loads` to result in those load calculations.
313    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        // CPU0 loaded to 75%
338        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        // CPU1 loaded to 75%
346        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}