Skip to main content

selinux/kernel/avc/
cache_stats.rs

1// Copyright 2026 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 crossbeam_utils::CachePadded;
6use std::sync::atomic::{AtomicU64, Ordering};
7
8/// Performance statistics of a cache implementation.
9#[derive(Default, Debug, Clone, PartialEq)]
10pub struct CacheStats {
11    /// Cumulative count of lookups performed on the cache.
12    pub lookups: u64,
13    /// Cumulative count of lookups that returned data from an existing cache entry.
14    pub hits: u64,
15    /// Cumulative count of lookups that did not match any existing cache entry.
16    pub misses: u64,
17    /// Cumulative count of insertions into the cache.
18    pub allocs: u64,
19    /// Cumulative count of evictions from the cache, to make space for a new insertion.
20    pub reclaims: u64,
21    /// Cumulative count of evictions from the cache due to no longer being deemed relevant.
22    /// This is not used in our current implementation.
23    pub frees: u64,
24}
25
26impl std::ops::Add for &CacheStats {
27    type Output = CacheStats;
28
29    fn add(self, other: &CacheStats) -> CacheStats {
30        CacheStats {
31            lookups: self.lookups + other.lookups,
32            hits: self.hits + other.hits,
33            misses: self.misses + other.misses,
34            allocs: self.allocs + other.allocs,
35            reclaims: self.reclaims + other.reclaims,
36            frees: self.frees + other.frees,
37        }
38    }
39}
40
41#[derive(Default, Debug)]
42pub(super) struct AtomicCacheStats {
43    pub lookups: AtomicU64,
44    pub hits: AtomicU64,
45    pub misses: AtomicU64,
46    pub allocs: AtomicU64,
47    pub reclaims: AtomicU64,
48    frees: AtomicU64,
49}
50
51impl AtomicCacheStats {
52    fn snapshot(&self) -> CacheStats {
53        CacheStats {
54            lookups: self.lookups.load(Ordering::Relaxed),
55            hits: self.hits.load(Ordering::Relaxed),
56            misses: self.misses.load(Ordering::Relaxed),
57            allocs: self.allocs.load(Ordering::Relaxed),
58            reclaims: self.reclaims.load(Ordering::Relaxed),
59            frees: self.frees.load(Ordering::Relaxed),
60        }
61    }
62
63    fn reset(&self) {
64        self.lookups.store(0, Ordering::Relaxed);
65        self.hits.store(0, Ordering::Relaxed);
66        self.misses.store(0, Ordering::Relaxed);
67        self.allocs.store(0, Ordering::Relaxed);
68        self.reclaims.store(0, Ordering::Relaxed);
69        self.frees.store(0, Ordering::Relaxed);
70    }
71}
72
73/// Number of shards to use for the cache stats.
74// TODO: https://fxbug.dev/483629131 - Do per-CPU sharding using rseq.
75fn num_shards() -> usize {
76    8
77}
78
79unsafe extern "C" {
80    fn thrd_current() -> std::ffi::c_ulong;
81}
82
83/// Sharded accumulator for cache statistics.
84pub(super) struct ShardedCacheStats {
85    shards: Vec<CachePadded<AtomicCacheStats>>,
86}
87
88impl ShardedCacheStats {
89    pub fn new() -> ShardedCacheStats {
90        ShardedCacheStats {
91            shards: (0..num_shards())
92                .map(|_| CachePadded::new(AtomicCacheStats::default()))
93                .collect(),
94        }
95    }
96
97    pub fn shard(&self) -> &AtomicCacheStats {
98        // SAFETY: there's nothing unsafe about this, we're just calling a C function.
99        let index = (rapidhash::rapidhash(&unsafe { thrd_current() }.to_ne_bytes()) as usize)
100            % num_shards();
101        &self.shards[index]
102    }
103
104    pub fn reset(&self) {
105        for shard in &self.shards {
106            shard.reset();
107        }
108    }
109
110    // TODO: https://fxbug.dev/483629131 - Report per-CPU stats.
111    pub fn read(&self) -> CacheStats {
112        self.shards.iter().fold(CacheStats::default(), |acc, stats| &acc + &stats.snapshot())
113    }
114}