diff options
Diffstat (limited to 'libs')
| -rw-r--r-- | libs/debugstore/OWNERS | 3 | ||||
| -rw-r--r-- | libs/debugstore/rust/Android.bp | 71 | ||||
| -rw-r--r-- | libs/debugstore/rust/Cargo.toml | 9 | ||||
| -rw-r--r-- | libs/debugstore/rust/src/core.rs | 198 | ||||
| -rw-r--r-- | libs/debugstore/rust/src/event.rs | 72 | ||||
| -rw-r--r-- | libs/debugstore/rust/src/event_type.rs | 43 | ||||
| -rw-r--r-- | libs/debugstore/rust/src/lib.rs | 73 | ||||
| -rw-r--r-- | libs/debugstore/rust/src/storage.rs | 134 |
8 files changed, 603 insertions, 0 deletions
diff --git a/libs/debugstore/OWNERS b/libs/debugstore/OWNERS new file mode 100644 index 0000000000..428a1a2215 --- /dev/null +++ b/libs/debugstore/OWNERS @@ -0,0 +1,3 @@ +benmiles@google.com +gaillard@google.com +mohamadmahmoud@google.com diff --git a/libs/debugstore/rust/Android.bp b/libs/debugstore/rust/Android.bp new file mode 100644 index 0000000000..55ba3c32d1 --- /dev/null +++ b/libs/debugstore/rust/Android.bp @@ -0,0 +1,71 @@ +// Copyright (C) 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_team: "trendy_team_android_telemetry_infra", + default_applicable_licenses: ["frameworks_native_license"], +} + +rust_defaults { + name: "libdebugstore_defaults", + srcs: ["src/lib.rs"], + rustlibs: [ + "libcrossbeam_queue", + "libparking_lot", + "libonce_cell", + "libcxx", + ], + shared_libs: ["libutils"], + edition: "2021", +} + +rust_ffi_static { + name: "libdebugstore_rust_ffi", + crate_name: "debugstore", + defaults: ["libdebugstore_defaults"], +} + +cc_library { + name: "libdebugstore_cxx", + generated_headers: ["libdebugstore_cxx_bridge_header"], + generated_sources: ["libdebugstore_cxx_bridge_code"], + export_generated_headers: ["libdebugstore_cxx_bridge_header"], + shared_libs: ["libutils"], + whole_static_libs: ["libdebugstore_rust_ffi"], +} + +rust_test { + name: "libdebugstore_tests", + defaults: ["libdebugstore_defaults"], + test_options: { + unit_test: true, + }, + shared_libs: ["libdebugstore_cxx"], +} + +genrule { + name: "libdebugstore_cxx_bridge_header", + tools: ["cxxbridge"], + cmd: "$(location cxxbridge) $(in) --header >> $(out)", + srcs: ["src/lib.rs"], + out: ["debugstore/debugstore_cxx_bridge.rs.h"], +} + +genrule { + name: "libdebugstore_cxx_bridge_code", + tools: ["cxxbridge"], + cmd: "$(location cxxbridge) $(in) >> $(out)", + srcs: ["src/lib.rs"], + out: ["debugstore/debugstore_cxx_bridge.rs.cpp"], +} diff --git a/libs/debugstore/rust/Cargo.toml b/libs/debugstore/rust/Cargo.toml new file mode 100644 index 0000000000..23a8d24647 --- /dev/null +++ b/libs/debugstore/rust/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "debugstore" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies]
\ No newline at end of file diff --git a/libs/debugstore/rust/src/core.rs b/libs/debugstore/rust/src/core.rs new file mode 100644 index 0000000000..1dfa512151 --- /dev/null +++ b/libs/debugstore/rust/src/core.rs @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +use super::event::Event; +use super::event_type::EventType; +use super::storage::Storage; +use crate::cxxffi::uptimeMillis; +use once_cell::sync::Lazy; +use std::fmt; +use std::sync::atomic::{AtomicU64, Ordering}; + +// Lazily initialized static instance of DebugStore. +static INSTANCE: Lazy<DebugStore> = Lazy::new(DebugStore::new); + +/// The `DebugStore` struct is responsible for managing debug events and data. +pub struct DebugStore { + /// Atomic counter for generating unique event IDs. + id_generator: AtomicU64, + /// Non-blocking storage for debug events. + event_store: Storage<Event, { DebugStore::DEFAULT_EVENT_LIMIT }>, +} + +impl DebugStore { + /// The default limit for the number of events that can be stored. + /// + /// This limit is used to initialize the storage for debug events. + const DEFAULT_EVENT_LIMIT: usize = 16; + /// A designated identifier used for events that cannot be closed. + /// + /// This ID is used for point/instantaneous events, or events do not have + /// a distinct end. + const NON_CLOSABLE_ID: u64 = 0; + /// The version number for the encoding of debug store data. + /// + /// This constant is used as a part of the debug store's data format, + /// allowing for version tracking and compatibility checks. + const ENCODE_VERSION: u32 = 1; + + /// Creates a new instance of `DebugStore` with specified event limit and maximum delay. + fn new() -> Self { + Self { id_generator: AtomicU64::new(1), event_store: Storage::new() } + } + + /// Returns a shared instance of `DebugStore`. + /// + /// This method provides a singleton pattern access to `DebugStore`. + pub fn get_instance() -> &'static DebugStore { + &INSTANCE + } + + /// Begins a new debug event with the given name and data. + /// + /// This method logs the start of a debug event, assigning it a unique ID and marking its start time. + /// - `name`: The name of the debug event. + /// - `data`: Associated data as key-value pairs. + /// - Returns: A unique ID for the debug event. + pub fn begin(&self, name: String, data: Vec<(String, String)>) -> u64 { + let id = self.generate_id(); + self.event_store.insert(Event::new( + id, + Some(name), + uptimeMillis(), + EventType::DurationStart, + data, + )); + id + } + + /// Records a debug event without a specific duration, with the given name and data. + /// + /// This method logs an instantaneous debug event, useful for events that don't have a duration but are significant. + /// - `name`: The name of the debug event. + /// - `data`: Associated data as key-value pairs. + pub fn record(&self, name: String, data: Vec<(String, String)>) { + self.event_store.insert(Event::new( + Self::NON_CLOSABLE_ID, + Some(name), + uptimeMillis(), + EventType::Point, + data, + )); + } + + /// Ends a debug event that was previously started with the given ID. + /// + /// This method marks the end of a debug event, completing its lifecycle. + /// - `id`: The unique ID of the debug event to end. + /// - `data`: Additional data to log at the end of the event. + pub fn end(&self, id: u64, data: Vec<(String, String)>) { + if id != Self::NON_CLOSABLE_ID { + self.event_store.insert(Event::new( + id, + None, + uptimeMillis(), + EventType::DurationEnd, + data, + )); + } + } + + fn generate_id(&self) -> u64 { + let mut id = self.id_generator.fetch_add(1, Ordering::Relaxed); + while id == Self::NON_CLOSABLE_ID { + id = self.id_generator.fetch_add(1, Ordering::Relaxed); + } + id + } +} + +impl fmt::Display for DebugStore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let uptime_now = uptimeMillis(); + write!(f, "{},{},{}::", Self::ENCODE_VERSION, self.event_store.len(), uptime_now)?; + + write!( + f, + "{}", + self.event_store.fold(String::new(), |mut acc, event| { + if !acc.is_empty() { + acc.push_str("||"); + } + acc.push_str(&event.to_string()); + acc + }) + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_begin_event() { + let debug_store = DebugStore::new(); + let _event_id = debug_store.begin("test_event".to_string(), vec![]); + let output = debug_store.to_string(); + assert!( + output.contains("test_event"), + "The output should contain the event name 'test_event'" + ); + } + + #[test] + fn test_unique_event_ids() { + let debug_store = DebugStore::new(); + let event_id1 = debug_store.begin("event1".to_string(), vec![]); + let event_id2 = debug_store.begin("event2".to_string(), vec![]); + assert_ne!(event_id1, event_id2, "Event IDs should be unique"); + } + + #[test] + fn test_end_event() { + let debug_store = DebugStore::new(); + let event_id = debug_store.begin("test_event".to_string(), vec![]); + debug_store.end(event_id, vec![]); + let output = debug_store.to_string(); + + let id_pattern = format!("ID:{},", event_id); + assert!( + output.contains("test_event"), + "The output should contain the event name 'test_event'" + ); + assert_eq!( + output.matches(&id_pattern).count(), + 2, + "The output should contain two events (start and end) associated with the given ID" + ); + } + + #[test] + fn test_event_data_handling() { + let debug_store = DebugStore::new(); + debug_store + .record("data_event".to_string(), vec![("key".to_string(), "value".to_string())]); + let output = debug_store.to_string(); + assert!( + output.contains("data_event"), + "The output should contain the event name 'data_event'" + ); + assert!( + output.contains("key=value"), + "The output should contain the event data 'key=value'" + ); + } +} diff --git a/libs/debugstore/rust/src/event.rs b/libs/debugstore/rust/src/event.rs new file mode 100644 index 0000000000..0c16665df4 --- /dev/null +++ b/libs/debugstore/rust/src/event.rs @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use super::event_type::EventType; +use std::fmt; + +/// Represents a single debug event within the Debug Store system. +/// +/// It contains all the necessary information for a debug event. +#[derive(Clone)] +pub struct Event { + /// The unique identifier for this event. + pub id: u64, + /// The optional name of the event. + pub name: Option<String>, + /// The system uptime when the event occurred. + pub timestamp: i64, + /// The type of the event. + pub event_type: EventType, + /// Additional data associated with the event, stored in the given order as key-value pairs. + data: Vec<(String, String)>, +} + +impl Event { + /// Constructs a new `Event`. + /// + /// - `id`: The unique identifier for the event. + /// - `name`: An optional name for the event. + /// - `timestamp`: The system uptime when the event occurred. + /// - `event_type`: The type of the event. + /// - `data`: Additional data for the event, represented as ordered key-value pairs. + pub fn new( + id: u64, + name: Option<String>, + timestamp: i64, + event_type: EventType, + data: Vec<(String, String)>, + ) -> Self { + Self { id, name, timestamp, event_type, data } + } +} + +impl fmt::Display for Event { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ID:{},C:{},T:{}", self.id, self.event_type, self.timestamp)?; + + if let Some(ref name) = self.name { + write!(f, ",N:{}", name)?; + } + + if !self.data.is_empty() { + let data_str = + self.data.iter().map(|(k, v)| format!("{}={}", k, v)).collect::<Vec<_>>().join(";"); + write!(f, ",D:{}", data_str)?; + } + + Ok(()) + } +} diff --git a/libs/debugstore/rust/src/event_type.rs b/libs/debugstore/rust/src/event_type.rs new file mode 100644 index 0000000000..6f4bafb58a --- /dev/null +++ b/libs/debugstore/rust/src/event_type.rs @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +use std::fmt; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum EventType { + /// Marks the an unknown or invalid event, for convenient mapping to a protobuf enum. + Invalid, + /// Marks the beginning of a duration-based event, indicating the start of a timed operation. + DurationStart, + /// Marks the end of a duration-based event, indicating the end of a timed operation. + DurationEnd, + /// Represents a single, instantaneous event with no duration. + Point, +} + +impl fmt::Display for EventType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}", + match self { + EventType::Invalid => "I", + EventType::DurationStart => "S", + EventType::DurationEnd => "E", + EventType::Point => "P", + } + ) + } +} diff --git a/libs/debugstore/rust/src/lib.rs b/libs/debugstore/rust/src/lib.rs new file mode 100644 index 0000000000..f2195c0529 --- /dev/null +++ b/libs/debugstore/rust/src/lib.rs @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//! # Debug Store Crate +/// The Debug Store Crate provides functionalities for storing debug events. +/// It allows logging and retrieval of debug events, and associated data. +mod core; +mod event; +mod event_type; +mod storage; + +pub use core::*; +pub use event::*; + +use cxx::{CxxString, CxxVector}; + +#[cxx::bridge(namespace = "android::debugstore")] +#[allow(unsafe_op_in_unsafe_fn)] +mod cxxffi { + extern "Rust" { + fn debug_store_to_string() -> String; + fn debug_store_record(name: &CxxString, data: &CxxVector<CxxString>); + fn debug_store_begin(name: &CxxString, data: &CxxVector<CxxString>) -> u64; + fn debug_store_end(id: u64, data: &CxxVector<CxxString>); + } + + #[namespace = "android"] + unsafe extern "C++" { + include!("utils/SystemClock.h"); + fn uptimeMillis() -> i64; + } +} + +fn debug_store_to_string() -> String { + DebugStore::get_instance().to_string() +} + +fn debug_store_record(name: &CxxString, data: &CxxVector<CxxString>) { + DebugStore::get_instance().record(name.to_string_lossy().into_owned(), cxx_vec_to_pairs(data)); +} + +fn debug_store_begin(name: &CxxString, data: &CxxVector<CxxString>) -> u64 { + DebugStore::get_instance().begin(name.to_string_lossy().into_owned(), cxx_vec_to_pairs(data)) +} + +fn debug_store_end(id: u64, data: &CxxVector<CxxString>) { + DebugStore::get_instance().end(id, cxx_vec_to_pairs(data)); +} + +fn cxx_vec_to_pairs(vec: &CxxVector<CxxString>) -> Vec<(String, String)> { + vec.iter() + .map(|s| s.to_string_lossy().into_owned()) + .collect::<Vec<_>>() + .chunks(2) + .filter_map(|chunk| match chunk { + [k, v] => Some((k.clone(), v.clone())), + _ => None, + }) + .collect() +} diff --git a/libs/debugstore/rust/src/storage.rs b/libs/debugstore/rust/src/storage.rs new file mode 100644 index 0000000000..2ad7f4e0b4 --- /dev/null +++ b/libs/debugstore/rust/src/storage.rs @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +use crossbeam_queue::ArrayQueue; + +/// A thread-safe storage that allows non-blocking attempts to store and visit elements. +pub struct Storage<T, const N: usize> { + insertion_buffer: ArrayQueue<T>, +} + +impl<T, const N: usize> Storage<T, N> { + /// Creates a new Storage with the specified size. + pub fn new() -> Self { + Self { insertion_buffer: ArrayQueue::new(N) } + } + + /// Inserts a value into the storage, returning an error if the lock cannot be acquired. + pub fn insert(&self, value: T) { + self.insertion_buffer.force_push(value); + } + + /// Folds over the elements in the storage using the provided function. + pub fn fold<U, F>(&self, init: U, mut func: F) -> U + where + F: FnMut(U, &T) -> U, + { + let mut acc = init; + while let Some(value) = self.insertion_buffer.pop() { + acc = func(acc, &value); + } + acc + } + + /// Returns the number of elements that have been inserted into the storage. + pub fn len(&self) -> usize { + self.insertion_buffer.len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_insert_and_retrieve() { + let storage = Storage::<i32, 10>::new(); + storage.insert(7); + + let sum = storage.fold(0, |acc, &x| acc + x); + assert_eq!(sum, 7, "The sum of the elements should be equal to the inserted value."); + } + + #[test] + fn test_fold_functionality() { + let storage = Storage::<i32, 5>::new(); + storage.insert(1); + storage.insert(2); + storage.insert(3); + + let sum = storage.fold(0, |acc, &x| acc + x); + assert_eq!( + sum, 6, + "The sum of the elements should be equal to the sum of inserted values." + ); + } + + #[test] + fn test_insert_and_retrieve_multiple_values() { + let storage = Storage::<i32, 10>::new(); + storage.insert(1); + storage.insert(2); + storage.insert(5); + + let first_sum = storage.fold(0, |acc, &x| acc + x); + assert_eq!(first_sum, 8, "The sum of the elements should be equal to the inserted values."); + + storage.insert(30); + storage.insert(22); + + let second_sum = storage.fold(0, |acc, &x| acc + x); + assert_eq!( + second_sum, 52, + "The sum of the elements should be equal to the inserted values." + ); + } + + #[test] + fn test_storage_limit() { + let storage = Storage::<i32, 1>::new(); + storage.insert(1); + // This value should overwrite the previously inserted value (1). + storage.insert(4); + let sum = storage.fold(0, |acc, &x| acc + x); + assert_eq!(sum, 4, "The sum of the elements should be equal to the inserted values."); + } + + #[test] + fn test_concurrent_insertions() { + use std::sync::Arc; + use std::thread; + + let storage = Arc::new(Storage::<i32, 100>::new()); + let threads: Vec<_> = (0..10) + .map(|_| { + let storage_clone = Arc::clone(&storage); + thread::spawn(move || { + for i in 0..10 { + storage_clone.insert(i); + } + }) + }) + .collect(); + + for thread in threads { + thread.join().expect("Thread should finish without panicking"); + } + + let count = storage.fold(0, |acc, _| acc + 1); + assert_eq!(count, 100, "Storage should be filled to its limit with concurrent insertions."); + } +} |