1 // Copyright 2021, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 //! A Rust interface for the StatsD pull API.
16
17 use lazy_static::lazy_static;
18 use statslog_rust_header::{Atoms, Stat, StatsError};
19 use statspull_bindgen::*;
20 use std::collections::HashMap;
21 use std::convert::TryInto;
22 use std::os::raw::c_void;
23 use std::sync::Mutex;
24
25 /// The return value of callbacks.
26 pub type StatsPullResult = Vec<Box<dyn Stat>>;
27
28 /// A wrapper for AStatsManager_PullAtomMetadata.
29 /// It calls AStatsManager_PullAtomMetadata_release on drop.
30 pub struct Metadata {
31 metadata: *mut AStatsManager_PullAtomMetadata,
32 }
33
34 impl Metadata {
35 /// Calls AStatsManager_PullAtomMetadata_obtain.
new() -> Self36 pub fn new() -> Self {
37 // Safety: We panic if the memory allocation fails.
38 let metadata = unsafe { AStatsManager_PullAtomMetadata_obtain() };
39 if metadata.is_null() {
40 panic!("Cannot obtain pull atom metadata.");
41 } else {
42 Metadata { metadata }
43 }
44 }
45
46 /// Calls AStatsManager_PullAtomMetadata_setCoolDownMillis.
set_cooldown_millis(&mut self, cooldown_millis: i64)47 pub fn set_cooldown_millis(&mut self, cooldown_millis: i64) {
48 // Safety: Metadata::new ensures that self.metadata is a valid object.
49 unsafe { AStatsManager_PullAtomMetadata_setCoolDownMillis(self.metadata, cooldown_millis) }
50 }
51
52 /// Calls AStatsManager_PullAtomMetadata_getCoolDownMillis.
get_cooldown_millis(&self) -> i6453 pub fn get_cooldown_millis(&self) -> i64 {
54 // Safety: Metadata::new ensures that self.metadata is a valid object.
55 unsafe { AStatsManager_PullAtomMetadata_getCoolDownMillis(self.metadata) }
56 }
57
58 /// Calls AStatsManager_PullAtomMetadata_setTimeoutMillis.
set_timeout_millis(&mut self, timeout_millis: i64)59 pub fn set_timeout_millis(&mut self, timeout_millis: i64) {
60 // Safety: Metadata::new ensures that self.metadata is a valid object.
61 unsafe { AStatsManager_PullAtomMetadata_setTimeoutMillis(self.metadata, timeout_millis) }
62 }
63
64 /// Calls AStatsManager_PullAtomMetadata_getTimeoutMillis.
get_timeout_millis(&self) -> i6465 pub fn get_timeout_millis(&self) -> i64 {
66 // Safety: Metadata::new ensures that self.metadata is a valid object.
67 unsafe { AStatsManager_PullAtomMetadata_getTimeoutMillis(self.metadata) }
68 }
69
70 /// Calls AStatsManager_PullAtomMetadata_setAdditiveFields.
set_additive_fields(&mut self, additive_fields: &mut Vec<i32>)71 pub fn set_additive_fields(&mut self, additive_fields: &mut Vec<i32>) {
72 // Safety: Metadata::new ensures that self.metadata is a valid object.
73 unsafe {
74 AStatsManager_PullAtomMetadata_setAdditiveFields(
75 self.metadata,
76 additive_fields.as_mut_ptr(),
77 additive_fields.len().try_into().expect("Cannot convert length to i32"),
78 )
79 }
80 }
81
82 /// Calls AStatsManager_PullAtomMetadata_getAdditiveFields.
get_additive_fields(&self) -> Vec<i32>83 pub fn get_additive_fields(&self) -> Vec<i32> {
84 // Safety: Metadata::new ensures that self.metadata is a valid object.
85 // We call getNumAdditiveFields to ensure we pass getAdditiveFields a large enough array.
86 unsafe {
87 let num_fields = AStatsManager_PullAtomMetadata_getNumAdditiveFields(self.metadata)
88 .try_into()
89 .expect("Cannot convert num additive fields to usize");
90 let mut fields = vec![0; num_fields];
91 AStatsManager_PullAtomMetadata_getAdditiveFields(self.metadata, fields.as_mut_ptr());
92 fields
93 }
94 }
95 }
96
97 impl Drop for Metadata {
drop(&mut self)98 fn drop(&mut self) {
99 // Safety: Metadata::new ensures that self.metadata is a valid object.
100 unsafe { AStatsManager_PullAtomMetadata_release(self.metadata) }
101 }
102 }
103
104 impl Default for Metadata {
default() -> Self105 fn default() -> Self {
106 Self::new()
107 }
108 }
109
110 lazy_static! {
111 static ref COOKIES: Mutex<HashMap<i32, fn() -> StatsPullResult>> = Mutex::new(HashMap::new());
112 }
113
114 // Safety: We store our callbacks in the global so they are valid.
callback_wrapper( atom_tag: i32, data: *mut AStatsEventList, _cookie: *mut c_void, ) -> AStatsManager_PullAtomCallbackReturn115 unsafe extern "C" fn callback_wrapper(
116 atom_tag: i32,
117 data: *mut AStatsEventList,
118 _cookie: *mut c_void,
119 ) -> AStatsManager_PullAtomCallbackReturn {
120 if !data.is_null() {
121 let map = COOKIES.lock().unwrap();
122 let cb = map.get(&atom_tag);
123 match cb {
124 None => log::error!("No callback found for {}", atom_tag),
125 Some(cb) => {
126 let stats = cb();
127 let result = stats
128 .iter()
129 .map(|stat| stat.add_astats_event(&mut *data))
130 .collect::<Result<Vec<()>, StatsError>>();
131 match result {
132 Ok(_) => {
133 return AStatsManager_PULL_SUCCESS as AStatsManager_PullAtomCallbackReturn
134 }
135 _ => log::error!("Error adding astats events: {:?}", result),
136 }
137 }
138 }
139 }
140 AStatsManager_PULL_SKIP as AStatsManager_PullAtomCallbackReturn
141 }
142
143 /// Rust wrapper for AStatsManager_setPullAtomCallback.
set_pull_atom_callback( atom: Atoms, metadata: Option<&Metadata>, callback: fn() -> StatsPullResult, )144 pub fn set_pull_atom_callback(
145 atom: Atoms,
146 metadata: Option<&Metadata>,
147 callback: fn() -> StatsPullResult,
148 ) {
149 COOKIES.lock().unwrap().insert(atom as i32, callback);
150 let metadata_raw = match metadata {
151 Some(m) => m.metadata,
152 None => std::ptr::null_mut(),
153 };
154 // Safety: We pass a valid function as the callback.
155 unsafe {
156 AStatsManager_setPullAtomCallback(
157 atom as i32,
158 metadata_raw,
159 Some(callback_wrapper),
160 std::ptr::null_mut(),
161 );
162 }
163 }
164
165 /// Rust wrapper for AStatsManager_clearPullAtomCallback.
clear_pull_atom_callback(atom: Atoms)166 pub fn clear_pull_atom_callback(atom: Atoms) {
167 COOKIES.lock().unwrap().remove(&(atom as i32));
168 // Safety: No memory allocations.
169 unsafe { AStatsManager_clearPullAtomCallback(atom as i32) }
170 }
171