blob: 6a267af4293a3a5b957ffe3e3c099f961e69dc8d [file] [log] [blame]
// Copyright 2020 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/camera/bin/benchmark/bandwidth.h"
#include <lib/async/cpp/task.h>
#include <lib/stdcompat/source_location.h>
#include <lib/syslog/cpp/macros.h>
namespace camera::benchmark {
// Launching a component or changing configurations may transiently increase bandwidth consumption.
// A delay is used to allow the system to reach steady-state before taking measurements.
constexpr auto kDelayInterval = zx::sec(5);
static fit::function<void(zx_status_t)> MakeErrorHandler(
std::string name, cpp20::source_location source_location = cpp20::source_location::current()) {
return [name, source_location](zx_status_t status) {
FX_PLOGS(FATAL, status) << source_location.file_name() + 5 << "(" << source_location.line()
<< "): " << name << " server disconnected.";
};
}
Bandwidth::Bandwidth(fuchsia::sysmem::AllocatorHandle sysmem_allocator,
fuchsia::camera3::DeviceWatcherHandle camera_device_watcher,
fuchsia::hardware::ram::metrics::DeviceHandle metrics_device,
async_dispatcher_t* dispatcher)
: dispatcher_(dispatcher) {
sysmem_allocator_.set_error_handler(MakeErrorHandler("Camera DeviceWatcher"));
camera_device_watcher_.set_error_handler(MakeErrorHandler("Camera DeviceWatcher"));
metrics_device_.set_error_handler(MakeErrorHandler("Metrics Device"));
sysmem_allocator_.Bind(std::move(sysmem_allocator), dispatcher_);
camera_device_watcher_.Bind(std::move(camera_device_watcher), dispatcher_);
metrics_device_.Bind(std::move(metrics_device), dispatcher_);
}
void Bandwidth::Profile(std::ostream& sink, fit::closure callback) {
sink_ = &sink;
callback_ = std::move(callback);
sink << "[";
async::PostDelayedTask(
dispatcher_,
[this] {
MeasureRamChannels([this](std::vector<RamChannelMeasurement> results) {
WriteResults("Baseline", results);
camera_device_watcher_->WatchDevices(
fit::bind_member<&Bandwidth::OnDevicesChanged>(this));
});
},
kDelayInterval);
}
void Bandwidth::OnDevicesChanged(std::vector<fuchsia::camera3::WatchDevicesEvent> events) {
for (const auto& event : events) {
if (event.is_added()) {
camera_device_.set_error_handler(MakeErrorHandler("Camera Device"));
camera_device_watcher_->ConnectToDevice(event.added(),
camera_device_.NewRequest(dispatcher_));
camera_device_->GetIdentifier([this](fidl::StringPtr identifier) {
constexpr auto kSherlockCameraIdentifier = "18D1F00D";
if (identifier == kSherlockCameraIdentifier) {
camera_device_->GetConfigurations(
[this](std::vector<fuchsia::camera3::Configuration> configurations) {
camera_configurations_ = std::move(configurations);
camera_device_->WatchCurrentConfiguration(
fit::bind_member<&Bandwidth::OnConfigurationChanged>(this));
});
// Only attempt to benchmark the sherlock camera.
return;
}
});
}
}
camera_device_watcher_->WatchDevices(fit::bind_member<&Bandwidth::OnDevicesChanged>(this));
}
void Bandwidth::OnConfigurationChanged(uint32_t index) {
StartAllStreams(index, [this, index] {
MeasureRamChannels([this, index](std::vector<RamChannelMeasurement> results) {
WriteResults("Configuration" + std::to_string(index), results);
streams_.clear();
async::PostDelayedTask(
dispatcher_,
[this, next = index + 1] {
if (next < camera_configurations_.size()) {
camera_device_->SetCurrentConfiguration(next);
camera_device_->WatchCurrentConfiguration(
fit::bind_member<&Bandwidth::OnConfigurationChanged>(this));
} else {
sink() << "\n]\n";
callback_();
}
},
kDelayInterval);
});
});
}
void Bandwidth::StartAllStreams(uint32_t configuration_index, fit::closure callback) {
streams_.resize(camera_configurations_[configuration_index].streams.size());
warm_streams_ = 0;
fit::closure warm = [this, callback = std::move(callback)] {
if (++warm_streams_ == streams_.size()) {
callback();
}
};
for (auto& stream : streams_) {
stream.ptr.set_error_handler(MakeErrorHandler("Camera Stream"));
stream.token.set_error_handler(MakeErrorHandler("Sysmem BufferCollectionToken"));
stream.collection.set_error_handler(MakeErrorHandler("Sysmem BufferCollection"));
stream.frame_callback = [this, &stream, warm = warm.share()](fuchsia::camera3::FrameInfo info) {
constexpr uint32_t kWarmupFrames = 20;
if (++stream.frames_received == kWarmupFrames) {
warm();
}
if (!streams_.empty()) {
stream.ptr->GetNextFrame(stream.frame_callback.share());
}
};
}
ConnectSequential(0);
}
void Bandwidth::ConnectSequential(uint32_t stream_index) {
if (stream_index >= streams_.size()) {
return;
}
camera_device_->ConnectToStream(stream_index, streams_[stream_index].ptr.NewRequest(dispatcher_));
sysmem_allocator_->AllocateSharedCollection(streams_[stream_index].token.NewRequest());
streams_[stream_index].ptr->SetBufferCollection(std::move(streams_[stream_index].token));
streams_[stream_index].ptr->WatchBufferCollection(
[this, stream_index](fuchsia::sysmem::BufferCollectionTokenHandle token) {
sysmem_allocator_->BindSharedCollection(
std::move(token), streams_[stream_index].collection.NewRequest(dispatcher_));
streams_[stream_index].collection->SetConstraints(
true, {.usage{.none = fuchsia::sysmem::noneUsage},
.min_buffer_count_for_camping = 2,
.has_buffer_memory_constraints = true,
.buffer_memory_constraints{.ram_domain_supported = true}});
streams_[stream_index].collection->WaitForBuffersAllocated(
[this, stream_index](zx_status_t status,
fuchsia::sysmem::BufferCollectionInfo_2 buffers) {
streams_[stream_index].ptr->GetNextFrame(
streams_[stream_index].frame_callback.share());
ConnectSequential(stream_index + 1);
});
});
}
void Bandwidth::MeasureRamChannels(
fit::function<void(std::vector<RamChannelMeasurement>)> callback) {
// Select cycle count such that the duration on Sherlock is an integral number of frames:
// Cycles Per Frame = 792000000Hz / 5FPS = 158400000
// Max Frames = floor(uint32_t_max / 158400000) = 27
// Cycles to Measure = 27 * 158400000 = 4276800000
constexpr uint64_t kCyclesToMeasure = 4276800000ull;
static constexpr struct {
std::string_view name;
uint64_t port_value;
} kRamChannels[]{{"CPU", aml_ram::kDefaultChannelCpu},
{"ISP", aml_ram::kPortIdMipiIsp},
{"GDC", aml_ram::kPortIdGDC},
{"GE2D", aml_ram::kPortIdGe2D}};
fuchsia::hardware::ram::metrics::BandwidthMeasurementConfig measurement_config{
.cycles_to_measure = kCyclesToMeasure};
auto channel_port_value = measurement_config.channels.begin();
for (auto channel : kRamChannels) {
*channel_port_value++ = channel.port_value;
}
metrics_device_->MeasureBandwidth(
measurement_config,
[callback = std::move(callback)](
fuchsia::hardware::ram::metrics::Device_MeasureBandwidth_Result result) {
if (result.is_err()) {
FX_PLOGS(FATAL, result.err()) << "Measure failed.";
return;
}
auto& info = result.response().info;
std::vector<RamChannelMeasurement> results;
auto channel_cycles = info.channels.begin();
for (auto channel : kRamChannels) {
uint64_t channel_bytes = (channel_cycles++)->readwrite_cycles * info.bytes_per_cycle;
uint64_t channel_bytes_per_second = channel_bytes * kCyclesToMeasure / info.frequency;
results.push_back(
{.name = channel.name, .bandwidth_bytes_per_second = channel_bytes_per_second});
}
callback(std::move(results));
});
}
void Bandwidth::WriteResults(std::string mode, std::vector<RamChannelMeasurement> results) {
for (auto result : results) {
if (!first_result_) {
sink() << ",";
}
first_result_ = false;
sink() << "\n";
sink() << " {\n";
sink() << " \"label\":\"" << mode << "/" << result.name << "\",\n";
sink() << " \"test_suite\":\"fuchsia.camera-benchmark\",\n";
sink() << " \"unit\":\"bytes/second\",\n";
sink() << " \"values\":[" << result.bandwidth_bytes_per_second << "]\n";
sink() << " }";
sink().flush();
}
}
std::ostream& Bandwidth::sink() { return *sink_; }
} // namespace camera::benchmark