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