1use fuchsia_inspect::Inspector;
11use fuchsia_sync::Mutex;
12use futures::future::BoxFuture;
13use std::collections::HashMap;
14use std::num::NonZeroU64;
15use std::panic::Location;
16use std::sync::LazyLock;
17
18static STUB_COUNTS: LazyLock<Mutex<HashMap<Invocation, Counts>>> =
19 LazyLock::new(|| Mutex::new(HashMap::new()));
20
21#[macro_export]
31macro_rules! track_stub {
32 (TODO($bug_url:literal), $message:expr, $flags:expr $(,)?) => {{
33 $crate::__track_stub_inner(
34 $crate::bug_ref!($bug_url),
35 $message,
36 Some($flags.into()),
37 std::panic::Location::caller(),
38 );
39 }};
40 (TODO($bug_url:literal), $message:expr $(,)?) => {{
41 $crate::__track_stub_inner(
42 $crate::bug_ref!($bug_url),
43 $message,
44 None,
45 std::panic::Location::caller(),
46 );
47 }};
48}
49
50#[macro_export]
61macro_rules! track_stub_log {
62 ($level:expr, TODO($bug_url:literal), $message:expr, $flags:expr $(,)?) => {{
63 $crate::__track_stub_inner_with_level(
64 $level,
65 $crate::bug_ref!($bug_url),
66 $message,
67 Some($flags.into()),
68 std::panic::Location::caller(),
69 );
70 }};
71 ($level:expr, TODO($bug_url:literal), $message:expr $(,)?) => {{
72 $crate::__track_stub_inner_with_level(
73 $level,
74 $crate::bug_ref!($bug_url),
75 $message,
76 None,
77 std::panic::Location::caller(),
78 );
79 }};
80}
81
82#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
83struct Invocation {
84 location: &'static Location<'static>,
85 message: &'static str,
86 bug: BugRef,
87}
88
89#[derive(Default)]
90struct Counts {
91 by_flags: HashMap<Option<u64>, u64>,
92}
93
94#[doc(hidden)]
95#[inline]
96pub fn __track_stub_inner(
97 bug: BugRef,
98 message: &'static str,
99 flags: Option<u64>,
100 location: &'static Location<'static>,
101) -> u64 {
102 __track_stub_inner_with_level(log::Level::Debug, bug, message, flags, location)
103}
104
105#[doc(hidden)]
106#[inline]
107pub fn __track_stub_inner_with_level(
108 level: log::Level,
109 bug: BugRef,
110 message: &'static str,
111 flags: Option<u64>,
112 location: &'static Location<'static>,
113) -> u64 {
114 let mut counts = STUB_COUNTS.lock();
115 let message_counts = counts.entry(Invocation { location, message, bug }).or_default();
116 let context_count = message_counts.by_flags.entry(flags).or_default();
117
118 if *context_count == 0 {
120 match flags {
121 Some(flags) => {
122 log::log!(level, tag = "track_stub", location:%; "{bug} {message}: 0x{flags:x}");
123 }
124 None => {
125 log::log!(level, tag = "track_stub", location:%; "{bug} {message}");
126 }
127 }
128 }
129
130 *context_count += 1;
131 *context_count
132}
133
134pub fn track_stub_lazy_node_callback() -> BoxFuture<'static, Result<Inspector, anyhow::Error>> {
139 Box::pin(async {
140 let inspector = Inspector::default();
141 for (Invocation { location, message, bug }, context_counts) in STUB_COUNTS.lock().iter() {
142 inspector.root().atomic_update(|root| {
143 root.record_child(*message, |message_node| {
144 message_node.record_string("file", location.file());
145 message_node.record_uint("line", location.line().into());
146 message_node.record_string("bug", bug.to_string());
147
148 let mut context_counts = context_counts.by_flags.clone();
150
151 if let Some(no_context_count) = context_counts.remove(&None) {
152 message_node.record_uint("count", no_context_count);
155 }
156
157 if !context_counts.is_empty() {
158 message_node.record_child("counts", |counts_node| {
159 for (context, count) in context_counts {
160 if let Some(c) = context {
161 counts_node.record_uint(format!("0x{c:x}"), count);
162 }
163 }
164 });
165 }
166 });
167 });
168 }
169 Ok(inspector)
170 })
171}
172
173#[macro_export]
177macro_rules! bug_ref {
178 ($bug_url:literal) => {{
179 const __REF: $crate::BugRef = match $crate::BugRef::from_str($bug_url) {
181 Some(b) => b,
182 None => panic!("bug references must have the form `https://fxbug.dev/123456789`"),
183 };
184 __REF
185 }};
186}
187
188#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
192pub struct BugRef {
193 number: u64,
194}
195
196impl BugRef {
197 #[doc(hidden)] pub const fn from_str(url: &'static str) -> Option<Self> {
199 let expected_prefix = b"https://fxbug.dev/";
200 let url = str::as_bytes(url);
201
202 if url.len() < expected_prefix.len() {
203 return None;
204 }
205 let (scheme_and_domain, number_str) = url.split_at(expected_prefix.len());
206 if number_str.is_empty() {
207 return None;
208 }
209
210 {
212 let mut i = 0;
213 while i < scheme_and_domain.len() {
214 if scheme_and_domain[i] != expected_prefix[i] {
215 return None;
216 }
217 i += 1;
218 }
219 }
220
221 let mut number = 0;
223 {
224 let mut i = 0;
225 while i < number_str.len() {
226 number *= 10;
227 number += match number_str[i] {
228 b'0' => 0,
229 b'1' => 1,
230 b'2' => 2,
231 b'3' => 3,
232 b'4' => 4,
233 b'5' => 5,
234 b'6' => 6,
235 b'7' => 7,
236 b'8' => 8,
237 b'9' => 9,
238 _ => return None,
239 };
240 i += 1;
241 }
242 }
243
244 assert!(number != 0, "Zero does not a valid bug number make.");
245
246 Some(Self { number })
247 }
248}
249
250impl From<NonZeroU64> for BugRef {
251 fn from(value: NonZeroU64) -> Self {
253 Self { number: value.get() }
254 }
255}
256
257impl Into<NonZeroU64> for BugRef {
258 fn into(self) -> NonZeroU64 {
260 NonZeroU64::new(self.number).unwrap()
261 }
262}
263
264impl std::fmt::Display for BugRef {
265 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267 write!(f, "https://fxbug.dev/{}", self.number)
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn valid_url_parses() {
277 assert_eq!(BugRef::from_str("https://fxbug.dev/1234567890").unwrap().number, 1234567890);
278 }
279
280 #[test]
281 fn missing_prefix_fails() {
282 assert_eq!(BugRef::from_str("1234567890"), None);
283 }
284
285 #[test]
286 fn missing_number_fails() {
287 assert_eq!(BugRef::from_str("https://fxbug.dev/"), None);
288 }
289
290 #[test]
291 fn short_prefixes_fail() {
292 assert_eq!(BugRef::from_str("b/1234567890"), None);
293 assert_eq!(BugRef::from_str("fxb/1234567890"), None);
294 assert_eq!(BugRef::from_str("fxbug.dev/1234567890"), None);
295 }
296}