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                            ..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                    // Record `start_time` before the first stats query, and `end_time` *after* the
163                    // second stats query completes. This ensures the "total time" (`end_time` -
164                    // `start_time`) will never be less than the duration spanned by `start_stats`
165                    // to `end_stats`, which would be invalid.
166                    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
184/// Uses start / end times and corresponding PerCpuStats to calculate and return a vector of per-CPU
185/// load values as floats in the range 0.0 - 100.0.
186fn 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        // The server should close the channel when it receives an invalid argument
284        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    // Takes a vector of CPU loads and generates the necessary parameters that can be fed into
307    // `calculate_cpu_loads` to result in those load calculations.
308    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        // CPU0 loaded to 75%
333        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        // CPU1 loaded to 75%
341        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}