blob: 76c20740ed8157d92ef096f4a437f9cfea8f17e8 [file] [log] [blame]
// Copyright (C) 2023 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.
//! Tracing-subscriber layer for libatrace_rust.
use ::atrace::AtraceTag;
use std::fmt::Write;
use tracing::span::Attributes;
use tracing::span::Record;
use tracing::{Event, Id, Subscriber};
use tracing_subscriber::field::Visit;
use tracing_subscriber::layer::{Context, Layer};
use tracing_subscriber::registry::LookupSpan;
/// Subscriber layer that forwards events to ATrace.
pub struct AtraceSubscriber {
tag: AtraceTag,
should_record_fields: bool,
should_filter: bool,
}
impl Default for AtraceSubscriber {
fn default() -> Self {
Self::new(AtraceTag::App)
}
}
impl AtraceSubscriber {
/// Makes a new subscriber with tag.
pub fn new(tag: AtraceTag) -> AtraceSubscriber {
AtraceSubscriber { tag, should_filter: false, should_record_fields: true }
}
/// Enables event and span filtering. With filtering enabled, this layer would filter events for
/// all the layers of the subscriber.
/// Use this to speed up the subscriber if it's the only layer. Do not enable if you need other
/// layers to receive events when ATrace is disabled.
pub fn with_filter(self) -> AtraceSubscriber {
AtraceSubscriber { should_filter: true, ..self }
}
/// Disables recording of field values.
pub fn without_fields(self) -> AtraceSubscriber {
AtraceSubscriber { should_record_fields: false, ..self }
}
}
// Internal methods.
impl AtraceSubscriber {
/// Checks that events and spans should be recorded in the span/event notification.
fn should_process_event(&self) -> bool {
// If `should_filter == true` we don't need to check the tag - it was already checked by
// the layer filter in the `Layer::enabled()` method.
// The checks are done in this order:
// * `Layer::register_callsite()` - once per callsite, the result is cached.
// * `Layer::enabled()` - once per span or event construction if the callsite is enabled.
// * `should_process_event()` - on every notification like new span, span enter/exit/record, event.
// The first two checks are global, i.e. affect other layers, and only enabled with `should_filter`.
// Read more:
// https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/index.html#filtering-with-layers
self.should_filter || atrace::atrace_is_tag_enabled(self.tag)
}
}
impl<S: Subscriber + for<'lookup> LookupSpan<'lookup>> Layer<S> for AtraceSubscriber {
fn register_callsite(
&self,
_metadata: &'static tracing::Metadata<'static>,
) -> tracing::subscriber::Interest {
if self.should_filter {
// When we return `Interest::sometimes()`, the `enabled()` method would get checked
// every time.
// We can't use callsite caching (`Interest::never()`) because there's no callback
// for when tracing gets enabled - we need to check it every time.
tracing::subscriber::Interest::sometimes()
} else {
// If we do not disable events in the layer, we always receive the notifications.
tracing::subscriber::Interest::always()
}
}
// When filtering in this layer is enabled, this method would get called on every event and span.
// This filter affects all layers, so if this method returns false, it would disable the event
// for others as well.
fn enabled(&self, _metadata: &tracing::Metadata<'_>, _ctx: Context<'_, S>) -> bool {
!self.should_filter || atrace::atrace_is_tag_enabled(self.tag)
}
fn on_new_span(&self, attrs: &Attributes, id: &Id, ctx: Context<S>) {
if !self.should_record_fields || attrs.fields().is_empty() || !self.should_process_event() {
return;
}
let span = ctx.span(id).unwrap();
let mut formatter = FieldFormatter::for_span(span.metadata().name());
attrs.record(&mut formatter);
span.extensions_mut().insert(formatter);
}
fn on_record(&self, span: &Id, values: &Record, ctx: Context<S>) {
if !self.should_record_fields || !self.should_process_event() {
return;
}
values
.record(ctx.span(span).unwrap().extensions_mut().get_mut::<FieldFormatter>().unwrap());
}
fn on_enter(&self, id: &Id, ctx: Context<S>) {
if !self.should_process_event() {
return;
}
let span = ctx.span(id).unwrap();
if span.fields().is_empty() || !self.should_record_fields {
atrace::atrace_begin(self.tag, span.metadata().name());
} else {
let span_extensions = span.extensions();
let formatter = span_extensions.get::<FieldFormatter>().unwrap();
atrace::atrace_begin(self.tag, formatter.as_str());
}
}
fn on_exit(&self, _id: &Id, _ctx: Context<S>) {
if !self.should_process_event() {
return;
}
atrace::atrace_end(self.tag);
}
fn on_event(&self, event: &Event, _ctx: Context<S>) {
if !self.should_process_event() {
return;
}
if self.should_record_fields {
let mut formatter = FieldFormatter::for_event();
event.record(&mut formatter);
atrace::atrace_instant(self.tag, formatter.as_str());
} else if let Some(field) = event.metadata().fields().field("message") {
struct MessageVisitor<'a> {
tag: AtraceTag,
field: &'a tracing::field::Field,
}
impl Visit for MessageVisitor<'_> {
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
if field == self.field {
atrace::atrace_instant(self.tag, value);
}
}
fn record_debug(
&mut self,
field: &tracing::field::Field,
value: &dyn std::fmt::Debug,
) {
if field == self.field {
atrace::atrace_instant(self.tag, &format!("{:?}", value));
}
}
}
event.record(&mut MessageVisitor { tag: self.tag, field: &field });
} else {
atrace::atrace_instant(
self.tag,
&format!("{} event", event.metadata().level().as_str()),
);
}
}
}
struct FieldFormatter {
is_event: bool,
s: String,
}
impl FieldFormatter {
fn new() -> FieldFormatter {
const DEFAULT_STR_CAPACITY: usize = 128; // Should fit most events without realloc.
FieldFormatter { is_event: true, s: String::with_capacity(DEFAULT_STR_CAPACITY) }
}
fn for_event() -> FieldFormatter {
FieldFormatter { is_event: true, ..FieldFormatter::new() }
}
fn for_span(span_name: &str) -> FieldFormatter {
let mut formatter = FieldFormatter { is_event: false, ..FieldFormatter::new() };
formatter.s.push_str(span_name);
formatter
}
fn as_str(&self) -> &str {
&self.s
}
fn add_delimeter_if_needed(&mut self) {
if !self.s.is_empty() {
self.s.push_str(", ");
}
}
}
impl Visit for FieldFormatter {
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
self.add_delimeter_if_needed();
if self.is_event && field.name() == "message" {
self.s.push_str(value);
} else {
write!(&mut self.s, "{} = \"{}\"", field.name(), value).unwrap();
}
}
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
self.add_delimeter_if_needed();
if self.is_event && field.name() == "message" {
write!(&mut self.s, "{:?}", value).unwrap();
} else {
write!(&mut self.s, "{} = {:?}", field.name(), value).unwrap();
}
}
}
#[cfg(test)]
use self::tests::mock_atrace as atrace;
#[cfg(test)]
mod tests {
use super::*;
use tracing::Level;
use tracing_subscriber::prelude::__tracing_subscriber_SubscriberExt;
pub mod mock_atrace {
use atrace::AtraceTag;
use std::cell::RefCell;
/// Contains logic to check binding calls.
/// Implement this trait in the test with mocking logic and checks in implemented functions.
/// Default implementations panic.
pub trait ATraceMocker {
fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
panic!("Unexpected call");
}
fn atrace_begin(&mut self, _tag: AtraceTag, _name: &str) {
panic!("Unexpected call");
}
fn atrace_end(&mut self, _tag: AtraceTag) {
panic!("Unexpected call");
}
fn atrace_instant(&mut self, _tag: AtraceTag, _name: &str) {
panic!("Unexpected call");
}
/// This method should contain checks to be performed at the end of the test.
fn finish(&self) {}
}
struct DefaultMocker;
impl ATraceMocker for DefaultMocker {}
// Global mock object is thread-local, so that the tests can run safely in parallel.
thread_local!(static MOCKER: RefCell<Box<dyn ATraceMocker>> = RefCell::new(Box::new(DefaultMocker{})));
/// Sets the global mock object.
fn set_mocker(mocker: Box<dyn ATraceMocker>) {
MOCKER.with(|m| *m.borrow_mut() = mocker)
}
/// Calls the passed method `f` with a mutable reference to the global mock object.
/// Example:
/// ```
/// with_mocker(|mocker| mocker.atrace_begin(tag, name))
/// ```
fn with_mocker<F, R>(f: F) -> R
where
F: FnOnce(&mut dyn ATraceMocker) -> R,
{
MOCKER.with(|m| f(m.borrow_mut().as_mut()))
}
/// Finish the test and perform final checks in the mocker.
/// Calls `finish()` on the global mocker.
///
/// Needs to be called manually at the end of each test that uses mocks.
///
/// May panic, so it can not be called in `drop()` methods,
/// since it may result in double panic.
pub fn mocker_finish() {
with_mocker(|m| m.finish())
}
/// RAII guard that resets the mock to the default implementation.
pub struct MockerGuard;
impl Drop for MockerGuard {
fn drop(&mut self) {
set_mocker(Box::new(DefaultMocker {}));
}
}
/// Sets the mock object for the duration of the scope.
///
/// Returns a RAII guard that resets the mock back to default on destruction.
pub fn set_scoped_mocker<T: ATraceMocker + 'static>(m: T) -> MockerGuard {
set_mocker(Box::new(m));
MockerGuard {}
}
// Wrapped functions that forward calls into mocker.
pub fn atrace_is_tag_enabled(tag: AtraceTag) -> bool {
with_mocker(|m| m.atrace_is_tag_enabled(tag))
}
pub fn atrace_begin(tag: AtraceTag, name: &str) {
with_mocker(|m| m.atrace_begin(tag, name))
}
pub fn atrace_end(tag: AtraceTag) {
with_mocker(|m| m.atrace_end(tag))
}
pub fn atrace_instant(tag: AtraceTag, name: &str) {
with_mocker(|m| m.atrace_instant(tag, name))
}
}
#[test]
fn emits_span_begin() {
#[derive(Default)]
struct CallCheck {
begin_count: u32,
}
impl mock_atrace::ATraceMocker for CallCheck {
fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
true
}
fn atrace_begin(&mut self, tag: AtraceTag, name: &str) {
self.begin_count += 1;
assert!(self.begin_count < 2);
assert_eq!(tag, AtraceTag::App);
assert_eq!(name, "test span");
}
fn atrace_end(&mut self, _tag: AtraceTag) {}
fn finish(&self) {
assert_eq!(self.begin_count, 1);
}
}
let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
let _subscriber_guard = tracing::subscriber::set_default(
tracing_subscriber::registry().with(AtraceSubscriber::default()),
);
let _span = tracing::info_span!("test span").entered();
mock_atrace::mocker_finish();
}
#[test]
fn emits_span_end() {
#[derive(Default)]
struct CallCheck {
end_count: u32,
}
impl mock_atrace::ATraceMocker for CallCheck {
fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
true
}
fn atrace_begin(&mut self, _tag: AtraceTag, _name: &str) {}
fn atrace_end(&mut self, tag: AtraceTag) {
self.end_count += 1;
assert!(self.end_count < 2);
assert_eq!(tag, AtraceTag::App);
}
fn finish(&self) {
assert_eq!(self.end_count, 1);
}
}
let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
let _subscriber_guard = tracing::subscriber::set_default(
tracing_subscriber::registry().with(AtraceSubscriber::default()),
);
{
let _span = tracing::info_span!("test span").entered();
}
mock_atrace::mocker_finish();
}
#[test]
fn span_begin_end_is_ordered() {
#[derive(Default)]
struct CallCheck {
begin_count: u32,
instant_count: u32,
end_count: u32,
}
impl mock_atrace::ATraceMocker for CallCheck {
fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
true
}
fn atrace_begin(&mut self, _tag: AtraceTag, _name: &str) {
assert_eq!(self.end_count, 0);
assert_eq!(self.instant_count, 0);
self.begin_count += 1;
assert!(self.begin_count < 2);
}
fn atrace_instant(&mut self, _tag: AtraceTag, _name: &str) {
assert_eq!(self.begin_count, 1);
assert_eq!(self.end_count, 0);
self.instant_count += 1;
assert!(self.instant_count < 2);
}
fn atrace_end(&mut self, _tag: AtraceTag) {
assert_eq!(self.begin_count, 1);
assert_eq!(self.instant_count, 1);
self.end_count += 1;
assert!(self.end_count < 2);
}
fn finish(&self) {
assert_eq!(self.begin_count, 1);
assert_eq!(self.end_count, 1);
assert_eq!(self.instant_count, 1);
}
}
let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
let _subscriber_guard = tracing::subscriber::set_default(
tracing_subscriber::registry().with(AtraceSubscriber::default()),
);
{
let _span = tracing::info_span!("span").entered();
tracing::info!("test info");
}
mock_atrace::mocker_finish();
}
#[test]
fn emits_instant_event() {
#[derive(Default)]
struct CallCheck {
instant_count: u32,
}
impl mock_atrace::ATraceMocker for CallCheck {
fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
true
}
fn atrace_instant(&mut self, tag: AtraceTag, name: &str) {
self.instant_count += 1;
assert!(self.instant_count < 2);
assert_eq!(tag, AtraceTag::App);
assert_eq!(name, "test info");
}
fn finish(&self) {
assert_eq!(self.instant_count, 1);
}
}
let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
let _subscriber_guard = tracing::subscriber::set_default(
tracing_subscriber::registry().with(AtraceSubscriber::default()),
);
tracing::info!("test info");
mock_atrace::mocker_finish();
}
#[test]
fn formats_event_without_message_with_fields_disabled() {
#[derive(Default)]
struct CallCheck {
instant_count: u32,
}
impl mock_atrace::ATraceMocker for CallCheck {
fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
true
}
fn atrace_instant(&mut self, _tag: AtraceTag, name: &str) {
self.instant_count += 1;
assert!(self.instant_count < 2);
assert_eq!(name, "DEBUG event");
}
fn finish(&self) {
assert_eq!(self.instant_count, 1);
}
}
let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
let _subscriber_guard = tracing::subscriber::set_default(
tracing_subscriber::registry().with(AtraceSubscriber::default().without_fields()),
);
tracing::debug!(foo = 1);
mock_atrace::mocker_finish();
}
#[test]
fn formats_event_without_message_with_fields_enabled() {
#[derive(Default)]
struct CallCheck {
instant_count: u32,
}
impl mock_atrace::ATraceMocker for CallCheck {
fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
true
}
fn atrace_instant(&mut self, _tag: AtraceTag, name: &str) {
self.instant_count += 1;
assert!(self.instant_count < 2);
assert_eq!(name, "foo = 1");
}
fn finish(&self) {
assert_eq!(self.instant_count, 1);
}
}
let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
let _subscriber_guard = tracing::subscriber::set_default(
tracing_subscriber::registry().with(AtraceSubscriber::default()),
);
tracing::debug!(foo = 1);
mock_atrace::mocker_finish();
}
#[test]
fn can_set_tag() {
#[derive(Default)]
struct CallCheck {
begin_count: u32,
instant_count: u32,
end_count: u32,
}
impl mock_atrace::ATraceMocker for CallCheck {
fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
true
}
fn atrace_begin(&mut self, tag: AtraceTag, _name: &str) {
self.begin_count += 1;
assert!(self.begin_count < 2);
assert_eq!(tag, AtraceTag::WindowManager);
}
fn atrace_instant(&mut self, tag: AtraceTag, _name: &str) {
self.instant_count += 1;
assert!(self.instant_count < 2);
assert_eq!(tag, AtraceTag::WindowManager);
}
fn atrace_end(&mut self, tag: AtraceTag) {
self.end_count += 1;
assert!(self.end_count < 2);
assert_eq!(tag, AtraceTag::WindowManager);
}
fn finish(&self) {
assert_eq!(self.begin_count, 1);
assert_eq!(self.end_count, 1);
assert_eq!(self.instant_count, 1);
}
}
let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
let _subscriber_guard = tracing::subscriber::set_default(
tracing_subscriber::registry().with(AtraceSubscriber::new(AtraceTag::WindowManager)),
);
{
let _span = tracing::info_span!("span").entered();
tracing::info!("test info");
}
mock_atrace::mocker_finish();
}
#[test]
fn fields_ignored_when_disabled() {
#[derive(Default)]
struct CallCheck {
begin_count: u32,
instant_count: u32,
}
impl mock_atrace::ATraceMocker for CallCheck {
fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
true
}
fn atrace_begin(&mut self, _tag: AtraceTag, name: &str) {
self.begin_count += 1;
assert!(self.begin_count < 2);
assert_eq!(name, "test span");
}
fn atrace_instant(&mut self, _tag: AtraceTag, name: &str) {
self.instant_count += 1;
assert!(self.instant_count < 2);
assert_eq!(name, "test info");
}
fn atrace_end(&mut self, _tag: AtraceTag) {}
fn finish(&self) {
assert_eq!(self.begin_count, 1);
assert_eq!(self.instant_count, 1);
}
}
let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
let _subscriber_guard = tracing::subscriber::set_default(
tracing_subscriber::registry().with(AtraceSubscriber::default().without_fields()),
);
let _span = tracing::info_span!("test span", bar = "foo").entered();
tracing::event!(Level::INFO, foo = "bar", "test info");
mock_atrace::mocker_finish();
}
#[test]
fn formats_instant_event_fields() {
#[derive(Default)]
struct CallCheck {
instant_count: u32,
}
impl mock_atrace::ATraceMocker for CallCheck {
fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
true
}
fn atrace_instant(&mut self, _tag: AtraceTag, name: &str) {
self.instant_count += 1;
assert!(self.instant_count < 2);
assert_eq!(name, "test info, foo = \"bar\", baz = 5");
}
fn finish(&self) {
assert_eq!(self.instant_count, 1);
}
}
let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
let _subscriber_guard = tracing::subscriber::set_default(
tracing_subscriber::registry().with(AtraceSubscriber::default()),
);
tracing::event!(Level::INFO, foo = "bar", baz = 5, "test info");
mock_atrace::mocker_finish();
}
#[test]
fn formats_span_fields() {
#[derive(Default)]
struct CallCheck {
begin_count: u32,
}
impl mock_atrace::ATraceMocker for CallCheck {
fn atrace_is_tag_enabled(&mut self, _tag: AtraceTag) -> bool {
true
}
fn atrace_begin(&mut self, _tag: AtraceTag, name: &str) {
self.begin_count += 1;
assert!(self.begin_count < 2);
assert_eq!(name, "test span, foo = \"bar\", baz = 5");
}
fn atrace_end(&mut self, _tag: AtraceTag) {}
fn finish(&self) {
assert_eq!(self.begin_count, 1);
}
}
let _mock_guard = mock_atrace::set_scoped_mocker(CallCheck::default());
let _subscriber_guard = tracing::subscriber::set_default(
tracing_subscriber::registry().with(AtraceSubscriber::default()),
);
let _span = tracing::info_span!("test span", foo = "bar", baz = 5).entered();
mock_atrace::mocker_finish();
}
}