blob: f7253f5a0596d642348f6904dc0a099eaf2ec56a [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.
#include "src/media/audio/audio_core/reporter.h"
#include <lib/syslog/cpp/macros.h>
#include <queue>
#include "src/media/audio/audio_core/audio_driver.h"
#include "src/media/audio/audio_core/media_metrics_registry.cb.h"
namespace media::audio {
////////////////////////////////////////////////////////////////////////////////
// Singletons
namespace {
static std::mutex singleton_mutex;
static Reporter singleton_nop;
static std::unique_ptr<Reporter> singleton_real;
} // namespace
////////////////////////////////////////////////////////////////////////////////
// No-op implementations, used before the Reporter has been initialized
namespace {
class OutputDeviceNop : public Reporter::OutputDevice {
public:
void Destroy() override {}
void StartSession(zx::time start_time) override {}
void StopSession(zx::time stop_time) override {}
void SetDriverInfo(const AudioDriver& driver) override {}
void SetGainInfo(const fuchsia::media::AudioGainInfo& gain_info,
fuchsia::media::AudioGainValidFlags set_flags) override {}
void DeviceUnderflow(zx::time start_time, zx::time end_time) override {}
void PipelineUnderflow(zx::time start_time, zx::time end_time) override {}
};
class InputDeviceNop : public Reporter::InputDevice {
public:
void Destroy() override {}
void StartSession(zx::time start_time) override {}
void StopSession(zx::time stop_time) override {}
void SetDriverInfo(const AudioDriver& driver) override {}
void SetGainInfo(const fuchsia::media::AudioGainInfo& gain_info,
fuchsia::media::AudioGainValidFlags set_flags) override {}
};
class RendererNop : public Reporter::Renderer {
public:
void Destroy() override {}
void StartSession(zx::time start_time) override {}
void StopSession(zx::time stop_time) override {}
void SetUsage(RenderUsage usage) override {}
void SetFormat(const Format& format) override {}
void SetGain(float gain_db) override {}
void SetGainWithRamp(float gain_db, zx::duration duration,
fuchsia::media::audio::RampType ramp_type) override {}
void SetFinalGain(float gain_db) override {}
void SetMute(bool muted) override {}
void SetMinLeadTime(zx::duration min_lead_time) override {}
void SetPtsContinuityThreshold(float threshold_seconds) override {}
void AddPayloadBuffer(uint32_t buffer_id, uint64_t size) override {}
void RemovePayloadBuffer(uint32_t buffer_id) override {}
void SendPacket(const fuchsia::media::StreamPacket& packet) override {}
void Underflow(zx::time start_time, zx::time end_time) override {}
};
class CapturerNop : public Reporter::Capturer {
public:
void Destroy() override {}
void StartSession(zx::time start_time) override {}
void StopSession(zx::time stop_time) override {}
void SetUsage(CaptureUsage usage) override {}
void SetFormat(const Format& format) override {}
void SetGain(float gain_db) override {}
void SetGainWithRamp(float gain_db, zx::duration duration,
fuchsia::media::audio::RampType ramp_type) override {}
void SetMute(bool muted) override {}
void SetMinFenceTime(zx::duration min_fence_time) override {}
void AddPayloadBuffer(uint32_t buffer_id, uint64_t size) override {}
void SendPacket(const fuchsia::media::StreamPacket& packet) override {}
void Overflow(zx::time start_time, zx::time end_time) override {}
};
} // namespace
////////////////////////////////////////////////////////////////////////////////
// Real implementations of reporting objects.
class Reporter::OverflowUnderflowTracker {
public:
// Trackers begin in a "stopped" state and must move to a "started" state
// before metrics can be reported. The Start/Stop events are intended to
// mirror higher-level Play/Pause or Record/Stop events. If a session is not
// stopped explicitly, it's stopped automatically by our destructor.
void StartSession(zx::time start_time);
void StopSession(zx::time stop_time);
// Report an event with the given start and end times.
void Report(zx::time start_time, zx::time end_time);
struct Args {
uint32_t component;
std::string event_name;
inspect::Node& parent_node;
Reporter::Impl& impl;
bool is_underflow;
uint32_t cobalt_component_id;
};
OverflowUnderflowTracker(Args args);
~OverflowUnderflowTracker();
private:
void RestartSession();
zx::duration ComputeDurationOfAllSessions();
void LogCobaltDuration(uint32_t metric_id, std::vector<uint32_t> event_codes, zx::duration d);
std::mutex mutex_;
enum class State { Stopped, Started };
State state_ FXL_GUARDED_BY(mutex_);
// Ideally we'd record final cobalt metrics when the component exits, however we can't
// be notified of component exit until we've switched to Components v2. In the interim,
// we automatically restart sessions every hour. Inspect metrics don't have this limitation
// and can use the "real" session times.
static constexpr auto kMaxSessionDuration = zx::hour(1);
async::TaskClosureMethod<OverflowUnderflowTracker, &OverflowUnderflowTracker::RestartSession>
restart_session_timer_ FXL_GUARDED_BY(mutex_){this};
zx::time last_event_time_ FXL_GUARDED_BY(mutex_); // for cobalt
zx::time session_start_time_ FXL_GUARDED_BY(mutex_); // for cobalt
zx::time session_real_start_time_ FXL_GUARDED_BY(mutex_); // for inspect
zx::duration past_sessions_duration_ FXL_GUARDED_BY(mutex_); // for inspect
inspect::Node node_;
inspect::UintProperty event_count_;
inspect::UintProperty event_duration_;
inspect::UintProperty session_count_;
inspect::LazyNode total_duration_;
Reporter::Impl& impl_;
const uint32_t cobalt_component_id_;
const uint32_t cobalt_event_duration_metric_id_;
const uint32_t cobalt_time_since_last_event_or_session_start_metric_id_;
};
class FormatInfo {
public:
FormatInfo(inspect::Node& parent_node, const std::string& name)
: node_(parent_node.CreateChild(name)),
sample_format_(node_.CreateString("sample format", "unknown")),
channels_(node_.CreateUint("channels", 0)),
frames_per_second_(node_.CreateUint("frames per second", 0)) {}
void Set(const Format& f) {
switch (f.sample_format()) {
case fuchsia::media::AudioSampleFormat::UNSIGNED_8:
sample_format_.Set("UNSIGNED_8");
break;
case fuchsia::media::AudioSampleFormat::SIGNED_16:
sample_format_.Set("SIGNED_16");
break;
case fuchsia::media::AudioSampleFormat::SIGNED_24_IN_32:
sample_format_.Set("SIGNED_24_IN_32");
break;
case fuchsia::media::AudioSampleFormat::FLOAT:
sample_format_.Set("FLOAT");
break;
default:
sample_format_.Set("unknown");
FX_LOGS(ERROR) << "Unhandled sample stream_type type "
<< fidl::ToUnderlying(f.sample_format());
break;
}
channels_.Set(f.channels());
frames_per_second_.Set(f.frames_per_second());
}
private:
inspect::Node node_;
inspect::StringProperty sample_format_;
inspect::UintProperty channels_;
inspect::UintProperty frames_per_second_;
};
class DeviceDriverInfo {
public:
DeviceDriverInfo(inspect::Node& parent_node)
: node_(parent_node.CreateChild("driver")),
name_(node_.CreateString("name", "unknown")),
total_delay_(node_.CreateUint("external delay + fifo delay (ns)", 0)),
external_delay_(node_.CreateUint("external delay (ns)", 0)),
fifo_delay_(node_.CreateUint("fifo delay (ns)", 0)),
fifo_depth_(node_.CreateUint("fifo depth in frames", 0)),
format_(parent_node, "format") {}
void Set(const AudioDriver& d) {
name_.Set(d.manufacturer_name() + ' ' + d.product_name());
total_delay_.Set((d.external_delay() + d.fifo_depth_duration()).get());
external_delay_.Set(d.external_delay().get());
fifo_delay_.Set(d.fifo_depth_duration().get());
fifo_depth_.Set(d.fifo_depth_frames());
if (auto f = d.GetFormat(); f.has_value()) {
format_.Set(*f);
}
}
private:
inspect::Node node_;
inspect::StringProperty name_;
inspect::BoolProperty alive_;
inspect::UintProperty total_delay_;
inspect::UintProperty external_delay_;
inspect::UintProperty fifo_delay_;
inspect::UintProperty fifo_depth_;
FormatInfo format_;
};
class DeviceGainInfo {
public:
DeviceGainInfo(inspect::Node& node)
: gain_db_(node.CreateDouble("gain db", 0.0)),
muted_(node.CreateBool("muted", false)),
agc_supported_(node.CreateBool("agc supported", false)),
agc_enabled_(node.CreateBool("agc enabled", false)) {}
void Set(const fuchsia::media::AudioGainInfo& gain_info,
fuchsia::media::AudioGainValidFlags set_flags) {
if ((set_flags & fuchsia::media::AudioGainValidFlags::GAIN_VALID) ==
fuchsia::media::AudioGainValidFlags::GAIN_VALID) {
gain_db_.Set(gain_info.gain_db);
}
if ((set_flags & fuchsia::media::AudioGainValidFlags::MUTE_VALID) ==
fuchsia::media::AudioGainValidFlags::MUTE_VALID) {
muted_.Set((gain_info.flags & fuchsia::media::AudioGainInfoFlags::MUTE) ==
fuchsia::media::AudioGainInfoFlags::MUTE);
}
if ((set_flags & fuchsia::media::AudioGainValidFlags::AGC_VALID) ==
fuchsia::media::AudioGainValidFlags::AGC_VALID) {
agc_supported_.Set((gain_info.flags & fuchsia::media::AudioGainInfoFlags::AGC_SUPPORTED) ==
fuchsia::media::AudioGainInfoFlags::AGC_SUPPORTED);
agc_enabled_.Set((gain_info.flags & fuchsia::media::AudioGainInfoFlags::AGC_ENABLED) ==
fuchsia::media::AudioGainInfoFlags::AGC_ENABLED);
}
}
private:
inspect::DoubleProperty gain_db_;
inspect::BoolProperty muted_;
inspect::BoolProperty agc_supported_;
inspect::BoolProperty agc_enabled_;
};
class Reporter::OutputDeviceImpl : public Reporter::OutputDevice {
public:
OutputDeviceImpl(Reporter::Impl& impl, const std::string& name)
: node_(impl.outputs_node.CreateChild(name)),
alive_(node_.CreateBool("alive", true)),
driver_info_(node_),
gain_info_(node_),
device_underflows_(
std::make_unique<OverflowUnderflowTracker>(OverflowUnderflowTracker::Args{
.event_name = "device underflows",
.parent_node = node_,
.impl = impl,
.is_underflow = true,
.cobalt_component_id = AudioSessionDurationMetricDimensionComponent::OutputDevice,
})),
pipeline_underflows_(
std::make_unique<OverflowUnderflowTracker>(OverflowUnderflowTracker::Args{
.event_name = "pipeline underflows",
.parent_node = node_,
.impl = impl,
.is_underflow = true,
.cobalt_component_id = AudioSessionDurationMetricDimensionComponent::OutputPipeline,
})) {}
void Destroy() override { alive_.Set(false); }
void StartSession(zx::time start_time) override {
device_underflows_->StartSession(start_time);
pipeline_underflows_->StartSession(start_time);
}
void StopSession(zx::time stop_time) override {
device_underflows_->StopSession(stop_time);
pipeline_underflows_->StopSession(stop_time);
}
void SetDriverInfo(const AudioDriver& driver) override { driver_info_.Set(driver); }
void SetGainInfo(const fuchsia::media::AudioGainInfo& gain_info,
fuchsia::media::AudioGainValidFlags set_flags) override {
gain_info_.Set(gain_info, set_flags);
}
void DeviceUnderflow(zx::time start_time, zx::time end_time) override {
device_underflows_->Report(start_time, end_time);
}
void PipelineUnderflow(zx::time start_time, zx::time end_time) override {
pipeline_underflows_->Report(start_time, end_time);
}
private:
inspect::Node node_;
inspect::BoolProperty alive_;
DeviceDriverInfo driver_info_;
DeviceGainInfo gain_info_;
std::unique_ptr<OverflowUnderflowTracker> device_underflows_;
std::unique_ptr<OverflowUnderflowTracker> pipeline_underflows_;
};
class Reporter::InputDeviceImpl : public Reporter::InputDevice {
public:
InputDeviceImpl(Reporter::Impl& impl, const std::string& name)
: node_(impl.inputs_node.CreateChild(name)),
alive_(node_.CreateBool("alive", true)),
driver_info_(node_),
gain_info_(node_) {}
void Destroy() override { alive_.Set(false); }
void StartSession(zx::time start_time) override {}
void StopSession(zx::time stop_time) override {}
void SetDriverInfo(const AudioDriver& driver) override { driver_info_.Set(driver); }
void SetGainInfo(const fuchsia::media::AudioGainInfo& gain_info,
fuchsia::media::AudioGainValidFlags set_flags) override {
gain_info_.Set(gain_info, set_flags);
}
private:
inspect::Node node_;
inspect::BoolProperty alive_;
DeviceDriverInfo driver_info_;
DeviceGainInfo gain_info_;
};
class ClientPort {
public:
ClientPort(inspect::Node& node)
: format_(node, "format"),
payload_buffers_node_(node.CreateChild("payload buffers")),
gain_db_(node.CreateDouble("gain db", 0.0)),
muted_(node.CreateBool("muted", false)),
set_gain_with_ramp_calls_(node.CreateUint("calls to SetGainWithRamp", 0)) {}
void SetFormat(const Format& format) { format_.Set(format); }
void SetGain(float gain_db) { gain_db_.Set(gain_db); }
void SetGainWithRamp(float gain_db, zx::duration duration,
fuchsia::media::audio::RampType ramp_type) {
set_gain_with_ramp_calls_.Add(1);
}
void SetMute(bool muted) { muted_.Set(muted); }
void AddPayloadBuffer(uint32_t buffer_id, uint64_t size) {
std::lock_guard<std::mutex> lock(mutex_);
payload_buffers_.emplace(
buffer_id,
PayloadBuffer(payload_buffers_node_.CreateChild(std::to_string(buffer_id)), size));
}
void RemovePayloadBuffer(uint32_t buffer_id) {
std::lock_guard<std::mutex> lock(mutex_);
payload_buffers_.erase(buffer_id);
}
void SendPacket(const fuchsia::media::StreamPacket& packet) {
std::lock_guard<std::mutex> lock(mutex_);
auto b = payload_buffers_.find(packet.payload_buffer_id);
if (b == payload_buffers_.end()) {
FX_LOGS(ERROR) << "Specified payload buffer not found";
return;
}
b->second.packets_.Add(1);
}
private:
FormatInfo format_;
inspect::Node payload_buffers_node_;
struct PayloadBuffer {
PayloadBuffer(inspect::Node node, uint64_t size)
: node_(std::move(node)),
size_(node_.CreateUint("size", size)),
packets_(node_.CreateUint("packets", 0)) {}
inspect::Node node_;
inspect::UintProperty size_;
inspect::UintProperty packets_;
};
std::mutex mutex_;
std::unordered_map<uint32_t, PayloadBuffer> payload_buffers_ FXL_GUARDED_BY(mutex_);
inspect::DoubleProperty gain_db_;
inspect::BoolProperty muted_;
// Just counting these for now.
inspect::UintProperty set_gain_with_ramp_calls_;
};
class Reporter::RendererImpl : public Reporter::Renderer {
public:
RendererImpl(Reporter::Impl& impl)
: node_(impl.renderers_node.CreateChild(impl.NextRendererName())),
client_port_(node_),
alive_(node_.CreateBool("alive", true)),
min_lead_time_ns_(node_.CreateUint("min lead time (ns)", 0)),
pts_continuity_threshold_seconds_(node_.CreateDouble("pts continuity threshold (s)", 0.0)),
final_stream_gain_(node_.CreateDouble("final stream gain (post-volume) dbfs", 0.0)),
usage_(node_.CreateString("usage", "default")),
underflows_(std::make_unique<OverflowUnderflowTracker>(OverflowUnderflowTracker::Args{
.event_name = "underflows",
.parent_node = node_,
.impl = impl,
.is_underflow = true,
.cobalt_component_id = AudioSessionDurationMetricDimensionComponent::Renderer,
})) {}
void Destroy() override { alive_.Set(false); }
void StartSession(zx::time start_time) override { underflows_->StartSession(start_time); }
void StopSession(zx::time stop_time) override { underflows_->StopSession(stop_time); }
void SetUsage(RenderUsage usage) override { usage_.Set(RenderUsageToString(usage)); }
void SetFormat(const Format& format) override { client_port_.SetFormat(format); }
void SetGain(float gain_db) override { client_port_.SetGain(gain_db); }
void SetGainWithRamp(float gain_db, zx::duration duration,
fuchsia::media::audio::RampType ramp_type) override {
client_port_.SetGainWithRamp(gain_db, duration, ramp_type);
}
void SetFinalGain(float gain_db) override { final_stream_gain_.Set(gain_db); }
void SetMute(bool muted) override { client_port_.SetMute(muted); }
void SetMinLeadTime(zx::duration min_lead_time) override {
min_lead_time_ns_.Set(min_lead_time.to_nsecs());
}
void SetPtsContinuityThreshold(float threshold_seconds) override {
pts_continuity_threshold_seconds_.Set(threshold_seconds);
}
void AddPayloadBuffer(uint32_t buffer_id, uint64_t size) override {
client_port_.AddPayloadBuffer(buffer_id, size);
}
void RemovePayloadBuffer(uint32_t buffer_id) override {
client_port_.RemovePayloadBuffer(buffer_id);
}
void SendPacket(const fuchsia::media::StreamPacket& packet) override {
client_port_.SendPacket(packet);
}
void Underflow(zx::time start_time, zx::time end_time) override {
underflows_->Report(start_time, end_time);
}
private:
inspect::Node node_;
ClientPort client_port_;
inspect::BoolProperty alive_;
inspect::UintProperty min_lead_time_ns_;
inspect::DoubleProperty pts_continuity_threshold_seconds_;
inspect::DoubleProperty final_stream_gain_;
inspect::StringProperty usage_;
std::unique_ptr<OverflowUnderflowTracker> underflows_;
};
class Reporter::CapturerImpl : public Reporter::Capturer {
public:
CapturerImpl(Reporter::Impl& impl)
: node_(impl.capturers_node.CreateChild(impl.NextCapturerName())),
client_port_(node_),
alive_(node_.CreateBool("alive", true)),
min_fence_time_ns_(node_.CreateUint("min fence time (ns)", 0)),
usage_(node_.CreateString("usage", "default")),
overflows_(std::make_unique<OverflowUnderflowTracker>(OverflowUnderflowTracker::Args{
.event_name = "overflows",
.parent_node = node_,
.impl = impl,
.is_underflow = false,
.cobalt_component_id = AudioSessionDurationMetricDimensionComponent::Capturer,
})) {}
void Destroy() override { alive_.Set(false); }
void StartSession(zx::time start_time) override { overflows_->StartSession(start_time); }
void StopSession(zx::time stop_time) override { overflows_->StopSession(stop_time); }
void SetUsage(CaptureUsage usage) override { usage_.Set(CaptureUsageToString(usage)); }
void SetFormat(const Format& format) override { client_port_.SetFormat(format); }
void SetGain(float gain_db) override { client_port_.SetGain(gain_db); }
void SetGainWithRamp(float gain_db, zx::duration duration,
fuchsia::media::audio::RampType ramp_type) override {
client_port_.SetGainWithRamp(gain_db, duration, ramp_type);
}
void SetMute(bool muted) override { client_port_.SetMute(muted); }
void SetMinFenceTime(zx::duration min_fence_time) override {
min_fence_time_ns_.Set(min_fence_time.to_nsecs());
}
void AddPayloadBuffer(uint32_t buffer_id, uint64_t size) override {
client_port_.AddPayloadBuffer(buffer_id, size);
}
void SendPacket(const fuchsia::media::StreamPacket& packet) override {
client_port_.SendPacket(packet);
}
void Overflow(zx::time start_time, zx::time end_time) override {
overflows_->Report(start_time, end_time);
}
private:
inspect::Node node_;
ClientPort client_port_;
inspect::BoolProperty alive_;
inspect::UintProperty min_fence_time_ns_;
inspect::StringProperty usage_;
std::unique_ptr<OverflowUnderflowTracker> overflows_;
};
////////////////////////////////////////////////////////////////////////////////
// Reporter implementation
// static
Reporter& Reporter::Singleton() {
std::lock_guard<std::mutex> lock(singleton_mutex);
if (singleton_real) {
return *singleton_real;
}
FX_LOGS_FIRST_N(INFO, 1)
<< "Creating reporting objects before the Reporter singleton has been initialized";
return singleton_nop;
}
// static
void Reporter::InitializeSingleton(sys::ComponentContext& component_context,
ThreadingModel& threading_model) {
std::lock_guard<std::mutex> lock(singleton_mutex);
if (singleton_real) {
FX_LOGS(ERROR) << "Reporter::Singleton double initialized";
return;
}
singleton_real = std::make_unique<Reporter>(component_context, threading_model);
}
Reporter::Reporter(sys::ComponentContext& component_context, ThreadingModel& threading_model)
: impl_(std::make_unique<Impl>(component_context, threading_model)) {
// This lock isn't necessary, but the lock analysis can't tell that.
std::lock_guard<std::mutex> lock(mutex_);
InitInspect();
InitCobalt();
}
void Reporter::InitInspect() {
impl_->inspector = std::make_unique<sys::ComponentInspector>(&impl_->component_context);
inspect::Node& root_node = impl_->inspector->root();
impl_->failed_to_open_device_count = root_node.CreateUint("count of failures to open device", 0);
impl_->failed_to_obtain_fdio_service_channel_count =
root_node.CreateUint("count of failures to obtain device fdio service channel", 0);
impl_->failed_to_obtain_stream_channel_count =
root_node.CreateUint("count of failures to obtain device stream channel", 0);
impl_->failed_to_start_device_count =
root_node.CreateUint("count of failures to start a device", 0);
impl_->outputs_node = root_node.CreateChild("output devices");
impl_->inputs_node = root_node.CreateChild("input devices");
impl_->renderers_node = root_node.CreateChild("renderers");
impl_->capturers_node = root_node.CreateChild("capturers");
}
void Reporter::InitCobalt() {
impl_->component_context.svc()->Connect(impl_->cobalt_factory.NewRequest());
if (!impl_->cobalt_factory) {
FX_LOGS(ERROR) << "audio_core could not connect to cobalt. No metrics will be captured.";
return;
}
impl_->cobalt_factory->CreateLoggerFromProjectId(
kProjectId, impl_->cobalt_logger.NewRequest(), [this](fuchsia::cobalt::Status status) {
if (status != fuchsia::cobalt::Status::OK) {
FX_LOGS(ERROR) << "audio_core could not create Cobalt logger, status = "
<< fidl::ToUnderlying(status);
std::lock_guard<std::mutex> lock(mutex_);
impl_->cobalt_logger = nullptr;
}
});
}
Reporter::Container<Reporter::OutputDevice>::Ptr Reporter::CreateOutputDevice(
const std::string& name) {
std::lock_guard<std::mutex> lock(mutex_);
return outputs_.New(impl_ ? static_cast<OutputDevice*>(new OutputDeviceImpl(*impl_, name))
: static_cast<OutputDevice*>(new OutputDeviceNop));
}
Reporter::Container<Reporter::InputDevice>::Ptr Reporter::CreateInputDevice(
const std::string& name) {
std::lock_guard<std::mutex> lock(mutex_);
return inputs_.New(impl_ ? static_cast<InputDevice*>(new InputDeviceImpl(*impl_, name))
: static_cast<InputDevice*>(new InputDeviceNop));
}
Reporter::Container<Reporter::Renderer>::Ptr Reporter::CreateRenderer() {
std::lock_guard<std::mutex> lock(mutex_);
return renderers_.New(impl_ ? static_cast<Renderer*>(new RendererImpl(*impl_))
: static_cast<Renderer*>(new RendererNop));
}
Reporter::Container<Reporter::Capturer>::Ptr Reporter::CreateCapturer() {
std::lock_guard<std::mutex> lock(mutex_);
return capturers_.New(impl_ ? static_cast<Capturer*>(new CapturerImpl(*impl_))
: static_cast<Capturer*>(new CapturerNop));
}
void Reporter::FailedToOpenDevice(const std::string& name, bool is_input, int err) {
std::lock_guard<std::mutex> lock(mutex_);
if (!impl_) {
return;
}
impl_->failed_to_open_device_count.Add(1);
}
void Reporter::FailedToObtainFdioServiceChannel(const std::string& name, bool is_input,
zx_status_t status) {
std::lock_guard<std::mutex> lock(mutex_);
if (!impl_) {
return;
}
impl_->failed_to_obtain_fdio_service_channel_count.Add(1);
}
void Reporter::FailedToObtainStreamChannel(const std::string& name, bool is_input,
zx_status_t status) {
std::lock_guard<std::mutex> lock(mutex_);
if (!impl_) {
return;
}
impl_->failed_to_obtain_stream_channel_count.Add(1);
}
void Reporter::FailedToStartDevice(const std::string& name) {
std::lock_guard<std::mutex> lock(mutex_);
if (!impl_) {
return;
}
impl_->failed_to_start_device_count.Add(1);
}
Reporter::Impl::Impl(sys::ComponentContext& cc, ThreadingModel& tm)
: component_context(cc), threading_model(tm) {}
Reporter::Impl::~Impl() {}
//////////////////////////////////////////////////////////////////////////////////
// OverflowUnderflowTracker implementation
namespace {
constexpr auto kEventSessionStart = 0;
constexpr auto kEventOverflowUnderflow = 1;
static_assert(AudioTimeSinceLastOverflowOrSessionStartMetricDimensionLastEvent::SessionStart == 0);
static_assert(AudioTimeSinceLastUnderflowOrSessionStartMetricDimensionLastEvent::SessionStart == 0);
static_assert(AudioTimeSinceLastOverflowOrSessionStartMetricDimensionLastEvent::Overflow == 1);
static_assert(AudioTimeSinceLastUnderflowOrSessionStartMetricDimensionLastEvent::Underflow == 1);
} // namespace
Reporter::OverflowUnderflowTracker::OverflowUnderflowTracker(Args args)
: state_(State::Stopped),
impl_(args.impl),
cobalt_component_id_(args.cobalt_component_id),
cobalt_event_duration_metric_id_(args.is_underflow ? kAudioUnderflowDurationMetricId
: kAudioOverflowDurationMetricId),
cobalt_time_since_last_event_or_session_start_metric_id_(
args.is_underflow ? kAudioTimeSinceLastUnderflowOrSessionStartMetricId
: kAudioTimeSinceLastOverflowOrSessionStartMetricId) {
node_ = args.parent_node.CreateChild(args.event_name);
event_count_ = node_.CreateUint("count", 0);
event_duration_ = node_.CreateUint("duration (ns)", 0);
session_count_ = node_.CreateUint("session count", 0);
total_duration_ = node_.CreateLazyValues("@wrapper", [this] {
inspect::Inspector i;
i.GetRoot().CreateUint("total duration of all parent sessions (ns)",
ComputeDurationOfAllSessions().get(), &i);
return fit::make_ok_promise(std::move(i));
});
}
Reporter::OverflowUnderflowTracker::~OverflowUnderflowTracker() {
bool started;
{
// Technically a lock is not required here, but the lock analysis can't tell that.
std::lock_guard<std::mutex> lock(mutex_);
started = (state_ == State::Started);
}
if (started) {
StopSession(zx::clock::get_monotonic());
}
}
void Reporter::OverflowUnderflowTracker::StartSession(zx::time start_time) {
std::lock_guard<std::mutex> lock(mutex_);
if (state_ == State::Started) {
FX_LOGS(ERROR) << "StartSession called on session that is already started";
return;
}
session_count_.Add(1);
state_ = State::Started;
last_event_time_ = start_time;
session_start_time_ = start_time;
session_real_start_time_ = start_time;
restart_session_timer_.PostDelayed(impl_.threading_model.IoDomain().dispatcher(),
kMaxSessionDuration);
}
void Reporter::OverflowUnderflowTracker::StopSession(zx::time stop_time) {
std::lock_guard<std::mutex> lock(mutex_);
if (state_ == State::Stopped) {
FX_LOGS(ERROR) << "StopSession called on session that is already stopped";
return;
}
LogCobaltDuration(kAudioSessionDurationMetricId, {cobalt_component_id_},
stop_time - session_start_time_);
LogCobaltDuration(cobalt_time_since_last_event_or_session_start_metric_id_,
{cobalt_component_id_, kEventSessionStart}, stop_time - last_event_time_);
state_ = State::Stopped;
past_sessions_duration_ += stop_time - session_real_start_time_;
restart_session_timer_.Cancel();
}
void Reporter::OverflowUnderflowTracker::RestartSession() {
auto stop_time = zx::clock::get_monotonic();
std::lock_guard<std::mutex> lock(mutex_);
if (state_ == State::Stopped) {
return; // must have been stopped concurrently
}
LogCobaltDuration(kAudioSessionDurationMetricId, {cobalt_component_id_},
stop_time - session_start_time_);
LogCobaltDuration(cobalt_time_since_last_event_or_session_start_metric_id_,
{cobalt_component_id_, kEventSessionStart}, stop_time - last_event_time_);
last_event_time_ = stop_time;
session_start_time_ = stop_time;
restart_session_timer_.PostDelayed(impl_.threading_model.IoDomain().dispatcher(),
kMaxSessionDuration);
}
zx::duration Reporter::OverflowUnderflowTracker::ComputeDurationOfAllSessions() {
std::lock_guard<std::mutex> lock(mutex_);
auto dt = past_sessions_duration_;
if (state_ == State::Started) {
dt += zx::clock::get_monotonic() - session_real_start_time_;
}
return dt;
}
void Reporter::OverflowUnderflowTracker::Report(zx::time start_time, zx::time end_time) {
if (end_time < start_time) {
FX_LOGS(ERROR) << "Reported overflow/underflow with negative duration: " << start_time.get()
<< " to " << end_time.get();
}
std::lock_guard<std::mutex> lock(mutex_);
auto event_duration = end_time - start_time;
event_count_.Add(1);
event_duration_.Add(event_duration.get());
LogCobaltDuration(cobalt_event_duration_metric_id_, {cobalt_component_id_}, event_duration);
if (state_ != State::Started) {
// This can happen because reporting can race with session boundaries. For example:
// If the mixer detects a renderer underflow as the client concurrently pauses the
// renderer, the Report and StopSession calls will race.
FX_LOGS_FIRST_N(INFO, 20) << "Overflow/Underflow event arrived when the session is stopped";
return;
}
LogCobaltDuration(cobalt_time_since_last_event_or_session_start_metric_id_,
{cobalt_component_id_, kEventOverflowUnderflow}, start_time - last_event_time_);
last_event_time_ = end_time;
}
void Reporter::OverflowUnderflowTracker::LogCobaltDuration(uint32_t metric_id,
std::vector<uint32_t> event_codes,
zx::duration d) {
auto& logger = impl_.cobalt_logger;
if (!logger) {
return;
}
auto e = fuchsia::cobalt::CobaltEvent{
.metric_id = metric_id,
.event_codes = event_codes,
.payload = fuchsia::cobalt::EventPayload::WithElapsedMicros(d.get()),
};
impl_.threading_model.FidlDomain().PostTask([&logger, e = std::move(e)]() mutable {
logger->LogCobaltEvent(std::move(e), [](fuchsia::cobalt::Status status) {
if (status == fuchsia::cobalt::Status::OK) {
return;
}
if (status == fuchsia::cobalt::Status::BUFFER_FULL) {
FX_LOGS_FIRST_N(WARNING, 50) << "Cobalt logger failed with buffer full";
} else {
FX_LOGS(ERROR) << "Cobalt logger failed with code " << static_cast<int>(status);
}
});
});
}
} // namespace media::audio