starnix_logging/
not_found.rs

1// Copyright 2023 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 bstr::BString;
6use fuchsia_inspect::Inspector;
7use futures::future::BoxFuture;
8use regex::bytes::Regex;
9use starnix_sync::Mutex;
10use std::collections::hash_map::Entry;
11use std::collections::{BTreeMap, HashMap};
12use std::sync::LazyLock;
13
14/// Path prefixes for which Starnix is responsible.
15const DESIRED_PATH_PREFIXES: &[&str] = &["/dev/", "/proc/", "/sys/"];
16
17/// Path prefixes excluded from inspect output.
18const IGNORED_PATH_PREFIXES: &[&str] = &[
19    // This path should only be implemented on ARM, ignore it everywhere else.
20    #[cfg(not(target_arch = "aarch64"))]
21    "/proc/sys/abi/swp",
22    //TODO(https://fxbug.dev/306735736) stubbing these device directories seems to break adb.
23    "/sys/class/android_usb",
24    // TODO(https://fxbug.dev/322165853) these directories have dynamically generated contents that
25    // are difficult to stub, so just exclude them from the not_found list.
26    "/sys/dev/block",
27    "/sys/dev/char",
28    // Ignore paths for specific filesystems we don't implement.
29    "/sys/fs/f2fs",
30    "/sys/fs/incremental-fs",
31    "/sys/fs/pstore",
32    // TODO(https://fxbug.dev/311449535) we may need to implement tracing directories under these
33    // paths but actually stubbing them breaks the current perfetto integration. They don't need to
34    // be in the ENOENT list when they're already tracked elsewhere.
35    "/sys/kernel/tracing",
36    "/sys/kernel/debug/tracing",
37];
38
39/// Regular expression to deduplicate commonly seen numbered elements of paths in internal
40/// filesystems.
41const NUMBER_DEDUPER: &str = r#"(block/[A-Za-z]+|cpu|proc/|pid_|uid_|task|task/)\d+"#;
42
43static NOT_FOUND_COUNTS: LazyLock<Mutex<HashMap<BString, u64>>> =
44    LazyLock::new(|| Mutex::new(HashMap::new()));
45
46pub fn track_file_not_found(path: BString) {
47    if DESIRED_PATH_PREFIXES.iter().any(|&prefix| path.starts_with(prefix.as_bytes())) {
48        match NOT_FOUND_COUNTS.lock().entry(path) {
49            Entry::Occupied(mut o) => *o.get_mut() += 1,
50            Entry::Vacant(v) => {
51                crate::log_debug!(
52                    tag = "not_found",
53                    path:% = v.key();
54                    "couldn't resolve",
55                );
56                v.insert(1);
57            }
58        }
59    }
60}
61
62pub fn not_found_lazy_node_callback() -> BoxFuture<'static, Result<Inspector, anyhow::Error>> {
63    Box::pin(async {
64        let inspector = Inspector::default();
65
66        // The internal paths we care about often include process or user IDs and those don't
67        // matter for our ability to understand feature gaps. Replace them all with `N` and merge
68        // the counts together.
69        //
70        // This regular expression should be bounded to the number of digits that appear in a path,
71        // and we only run this code when inspect is being collected.
72        let original_counts = NOT_FOUND_COUNTS.lock();
73        let original_counts = original_counts.iter().map(|(p, n)| (p.as_slice(), *n));
74        for (path, count) in dedupe_uninteresting_numbers_in_paths(original_counts) {
75            // TODO(https://fxbug.dev/297438732) uevent paths are hard to stub individually because
76            // they are dynamically generated from all of the device classes.
77            if path.ends_with("uevent") {
78                continue;
79            }
80            if IGNORED_PATH_PREFIXES.iter().any(|prefix| path.starts_with(prefix)) {
81                continue;
82            }
83            inspector.root().record_uint(path, count);
84        }
85        Ok(inspector)
86    })
87}
88
89fn dedupe_uninteresting_numbers_in_paths<'a>(
90    original_counts: impl Iterator<Item = (&'a [u8], u64)>,
91) -> BTreeMap<String, u64> {
92    let number_deduper = Regex::new(NUMBER_DEDUPER).unwrap();
93    let mut numbers_collapsed = BTreeMap::new();
94    for (orig_path, count) in original_counts {
95        let collapsed = number_deduper.replace_all(&*orig_path, "${1}N".as_bytes());
96        *numbers_collapsed.entry(String::from_utf8_lossy(&*collapsed).to_string()).or_default() +=
97            count;
98    }
99    numbers_collapsed
100}
101
102#[cfg(test)]
103mod tests {
104    use super::dedupe_uninteresting_numbers_in_paths;
105
106    #[test]
107    fn dedupe_expected_paths() {
108        let original_paths = &[
109            "/dev/pmsg0",
110            "/proc/1006/cgroup",
111            "/proc/1006/schedstat",
112            "/proc/268/cgroup",
113            "/proc/470/schedstat",
114            "/proc/47/schedstat",
115            "/proc/32/cgroup",
116            "/proc/2/schedstat",
117            "/proc/2/cgroup",
118            "/proc/sys/kernel/domainname",
119            "/proc/sys/net/ipv4/conf",
120            "/proc/sys/net/ipv6/conf/default/accept_ra_rt_info_min_plen",
121            "/proc/uid_concurrent_policy_time",
122            "/sys/block/loop0/queue/nr_requests",
123            "/sys/block/loop10/queue/nr_requests",
124            "/sys/devices/system/cpu/cpu0/cpufreq/stats/time_in_state",
125            "/sys/devices/system/cpu/cpu1/cpufreq/stats/time_in_state",
126            "/sys/devices/system/cpu/cpu0uevent",
127            "/sys/devices/system/cpu/cpu1uevent",
128            "/sys/devices/virtual/block/loop0/queueuevent",
129            "/sys/devices/virtual/block/loop1/queueuevent",
130            "/sys/fs/f2fs/features",
131            "/sys/kernel/debug/tracing/events/ext4/ext4_da_write_begin/enable",
132            "/sys/kernel/debug/tracing/events/f2fs/f2fs_get_data_block/enable",
133            "/sys/kernel/debug/tracing/events/gpu_mem/gpu_mem_total/enable",
134            "/sys/kernel/debug/tracing/events/i2c/enable",
135            "/sys/kernel/debug/tracing/events/i2c/i2c_read/enable",
136            "/sys/kernel/debug/tracing/per_cpu/cpu20/trace",
137            "/sys/kernel/debug/tracing/per_cpu/cpu7/trace",
138            "/sys/kernel/tracing/options/record-tgid",
139            "/sys/kernel/tracing/per_cpu/cpu20/trace",
140            "/sys/kernel/tracing/per_cpu/cpu3/trace",
141            "/proc/1/task/1004/wchan",
142            "/proc/1/task/1009/wchan",
143            "/proc/2/task1004/wchan",
144            "/proc/2/task1009/wchan",
145        ];
146        let observed =
147            dedupe_uninteresting_numbers_in_paths(original_paths.iter().map(|p| (p.as_bytes(), 1)))
148                .into_iter()
149                .map(|(p, n)| (p.to_string(), n))
150                .collect::<Vec<(String, u64)>>();
151        let expected = [
152            ("/dev/pmsg0", 1),
153            ("/proc/N/cgroup", 4),
154            ("/proc/N/schedstat", 4),
155            ("/proc/N/task/N/wchan", 2),
156            ("/proc/N/taskN/wchan", 2),
157            ("/proc/sys/kernel/domainname", 1),
158            ("/proc/sys/net/ipv4/conf", 1),
159            ("/proc/sys/net/ipv6/conf/default/accept_ra_rt_info_min_plen", 1),
160            ("/proc/uid_concurrent_policy_time", 1),
161            ("/sys/block/loopN/queue/nr_requests", 2),
162            ("/sys/devices/system/cpu/cpuN/cpufreq/stats/time_in_state", 2),
163            ("/sys/devices/system/cpu/cpuNuevent", 2),
164            ("/sys/devices/virtual/block/loopN/queueuevent", 2),
165            ("/sys/fs/f2fs/features", 1),
166            ("/sys/kernel/debug/tracing/events/ext4/ext4_da_write_begin/enable", 1),
167            ("/sys/kernel/debug/tracing/events/f2fs/f2fs_get_data_block/enable", 1),
168            ("/sys/kernel/debug/tracing/events/gpu_mem/gpu_mem_total/enable", 1),
169            ("/sys/kernel/debug/tracing/events/i2c/enable", 1),
170            ("/sys/kernel/debug/tracing/events/i2c/i2c_read/enable", 1),
171            ("/sys/kernel/debug/tracing/per_cpu/cpuN/trace", 2),
172            ("/sys/kernel/tracing/options/record-tgid", 1),
173            ("/sys/kernel/tracing/per_cpu/cpuN/trace", 2),
174        ]
175        .iter()
176        .map(|(p, n)| (p.to_string(), *n))
177        .collect::<Vec<(String, u64)>>();
178        pretty_assertions::assert_eq!(observed, expected);
179    }
180}