blob: e9657939ea6507c3f74f43700cca529875be28f4 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Defines `CobaltSender` to encapsulate encoding `CobaltEvent` and sending them to the Cobalt
//! FIDL service.
use {
cobalt_client::traits::AsEventCodes,
fidl_fuchsia_cobalt::{CobaltEvent, CountEvent, EventPayload, HistogramBucket},
futures::channel::mpsc,
log::{error, info},
std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
macro_rules! gen_comment {
($x:expr) => {
#[doc = $x]
extern {}
};
($x:expr, $($tt:tt)*) => {
#[doc = $x]
$($tt)*
};
}
/// Wraps around an `mpsc::Sender` to allow sending CobaltEvents asynchronously to the Cobalt
/// FIDL service.
#[derive(Clone, Debug)]
pub struct CobaltSender {
sender: mpsc::Sender<CobaltEvent>,
is_blocked: Arc<AtomicBool>,
}
macro_rules! impl_wrapped_methods {
($(#[$variant:ident] $name:ident($($arg:ident: $ty:ty),*))*) => {
$(
gen_comment!{
concat! {
"Logs a CobaltEvent of type `EventPayload::",
stringify!($variant),
"` by wrapping a call to `CobaltSenderWithComponent::",
stringify!($name), "`."
},
pub fn $name<Codes: AsEventCodes>(
&mut self,
metric_id: u32,
event_codes: Codes,
$($arg: $ty),*
) {
self.with_component().$name::<_, String, _>(
metric_id,
event_codes,
None,
$($arg),*
);
}
}
)*
}
}
impl CobaltSender {
/// Constructs a new CobaltSender object.
///
/// # Arguments
///
/// * `sender` - The sending end of a `mpsc::channel`
pub fn new(sender: mpsc::Sender<CobaltEvent>) -> CobaltSender {
CobaltSender { sender, is_blocked: Arc::new(AtomicBool::new(false)) }
}
/// Accesses the sidecar struct `CobaltSenderWithComponent` to allow logging to cobalt with
/// component strings.
pub fn with_component(&mut self) -> CobaltSenderWithComponent<'_> {
CobaltSenderWithComponent(self)
}
/// Logs a CobaltEvent of type `EventPayload::Event`.
pub fn log_event<Codes: AsEventCodes>(&mut self, metric_id: u32, event_codes: Codes) {
self.log_event_value(CobaltEvent {
metric_id,
event_codes: event_codes.as_event_codes(),
component: None,
payload: EventPayload::Event(fidl_fuchsia_cobalt::Event {}),
});
}
/// Logs a plain CobaltEvent.
pub fn log_cobalt_event(&mut self, event: CobaltEvent) {
self.log_event_value(event);
}
fn log_event_value(&mut self, event: CobaltEvent) {
if self.sender.try_send(event).is_err() {
#[allow(deprecated)] // TODO(fxbug.dev/67113) migrate to compare_exchange
let was_blocked = self.is_blocked.compare_and_swap(false, true, Ordering::SeqCst);
if !was_blocked {
error!("cobalt sender drops a event/events: either buffer is full or no receiver is waiting");
}
} else {
#[allow(deprecated)] // TODO(fxbug.dev/67113) migrate to compare_exchange
let was_blocked = self.is_blocked.compare_and_swap(true, false, Ordering::SeqCst);
if was_blocked {
info!("cobalt sender recovers and resumes sending")
}
}
}
impl_wrapped_methods! {
#[CountEvent]
log_event_count(period_duration_micros: i64, count: i64)
#[ElapsedMicros]
log_elapsed_time(elapsed_micros: i64)
#[Fps]
log_frame_rate(fps: f32)
#[MemoryBytesUsed]
log_memory_usage(bytes: i64)
#[IntHistogram]
log_int_histogram(values: Vec<HistogramBucket>)
}
}
/// Allows logging to cobalt with component strings
///
/// Component strings are relatively uncommon, so this is a sidecar struct that contains
/// methods with component string arguments. To access it, you call
/// `CobaltSender::with_component()` followed by the method. Only event types that accept
/// component strings are present on this struct.
pub struct CobaltSenderWithComponent<'a>(&'a mut CobaltSender);
macro_rules! impl_log_methods{
($(#[$variant:ident] $name:ident($($arg:ident: $ty:ty),*) => $payload:expr)*) => {
$(
gen_comment!{
concat! {
"Logs a CobaltEvent of type `EventPayload::",
stringify!($variant),
"` with a component string"
},
pub fn $name<Codes, S, Component>(
&mut self,
metric_id: u32,
event_codes: Codes,
component: Component,
$($arg: $ty),*
) where
Codes: AsEventCodes,
S: Into<String>,
Component: Into<Option<S>>
{
self.0.log_event_value(CobaltEvent {
metric_id,
component: component.into().map(|s| s.into()),
event_codes: event_codes.as_event_codes(),
payload: $payload
})
}
}
)*
}
}
impl<'a> CobaltSenderWithComponent<'a> {
impl_log_methods! {
#[CountEvent]
log_event_count(period_duration_micros: i64, count: i64) => {
EventPayload::EventCount(CountEvent {
period_duration_micros,
count,
})
}
#[ElapsedMicros]
log_elapsed_time(elapsed_micros: i64) => {
EventPayload::ElapsedMicros(elapsed_micros)
}
#[Fps]
log_frame_rate(fps: f32) => {
EventPayload::Fps(fps)
}
#[MemoryBytesUsed]
log_memory_usage(bytes: i64) => {
EventPayload::MemoryBytesUsed(bytes)
}
#[IntHistogram]
log_int_histogram(values: Vec<HistogramBucket>) => {
EventPayload::IntHistogram(values)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cobalt_sender() {
let (sender, mut receiver) = mpsc::channel(1);
let mut sender = CobaltSender::new(sender);
sender.log_event(1, 1);
assert_eq!(
receiver.try_next().unwrap().unwrap(),
CobaltEvent {
metric_id: 1,
event_codes: vec![1],
component: None,
payload: EventPayload::Event(fidl_fuchsia_cobalt::Event {}),
}
);
sender.log_event_count(2, (), 10, 1);
assert_eq!(
receiver.try_next().unwrap().unwrap(),
CobaltEvent {
metric_id: 2,
event_codes: vec![],
component: None,
payload: EventPayload::EventCount(CountEvent {
period_duration_micros: 10,
count: 1
})
}
);
sender.with_component().log_event_count(2, (), "test", 11, 2);
assert_eq!(
receiver.try_next().unwrap().unwrap(),
CobaltEvent {
metric_id: 2,
event_codes: vec![],
component: Some("test".to_owned()),
payload: EventPayload::EventCount(CountEvent {
period_duration_micros: 11,
count: 2
})
}
);
sender.log_elapsed_time(3, [1, 2], 30);
assert_eq!(
receiver.try_next().unwrap().unwrap(),
CobaltEvent {
metric_id: 3,
event_codes: vec![1, 2],
component: None,
payload: EventPayload::ElapsedMicros(30),
}
);
sender.with_component().log_elapsed_time(3, [1, 2], "test".to_owned(), 30);
assert_eq!(
receiver.try_next().unwrap().unwrap(),
CobaltEvent {
metric_id: 3,
event_codes: vec![1, 2],
component: Some("test".to_owned()),
payload: EventPayload::ElapsedMicros(30),
}
);
sender.log_frame_rate(4, [1, 2, 3, 4], 10.0);
assert_eq!(
receiver.try_next().unwrap().unwrap(),
CobaltEvent {
metric_id: 4,
event_codes: vec![1, 2, 3, 4],
component: None,
payload: EventPayload::Fps(10.0),
}
);
sender.with_component().log_frame_rate(4, (), "testing", 100.0);
assert_eq!(
receiver.try_next().unwrap().unwrap(),
CobaltEvent {
metric_id: 4,
event_codes: vec![],
component: Some("testing".to_owned()),
payload: EventPayload::Fps(100.0),
}
);
sender.log_memory_usage(5, [1, 2, 3, 4, 5], 100);
assert_eq!(
receiver.try_next().unwrap().unwrap(),
CobaltEvent {
metric_id: 5,
event_codes: vec![1, 2, 3, 4, 5],
component: None,
payload: EventPayload::MemoryBytesUsed(100),
}
);
sender.with_component().log_memory_usage(5, [1, 2, 3, 4, 5], "a test", 100);
assert_eq!(
receiver.try_next().unwrap().unwrap(),
CobaltEvent {
metric_id: 5,
event_codes: vec![1, 2, 3, 4, 5],
component: Some("a test".to_owned()),
payload: EventPayload::MemoryBytesUsed(100),
}
);
sender.log_int_histogram(4, [1, 2, 3], vec![HistogramBucket { index: 2, count: 2 }]);
assert_eq!(
receiver.try_next().unwrap().unwrap(),
CobaltEvent {
metric_id: 4,
event_codes: vec![1, 2, 3],
component: None,
payload: EventPayload::IntHistogram(vec![HistogramBucket { index: 2, count: 2 }]),
}
);
sender.with_component().log_int_histogram(
4,
[1, 2, 3],
"Component",
vec![HistogramBucket { index: 2, count: 2 }],
);
assert_eq!(
receiver.try_next().unwrap().unwrap(),
CobaltEvent {
metric_id: 4,
event_codes: vec![1, 2, 3],
component: Some("Component".to_owned()),
payload: EventPayload::IntHistogram(vec![HistogramBucket { index: 2, count: 2 }]),
}
);
}
}