blob: e052794c9e602cd7ff829bce4807ff79d63f07d6 [file] [log] [blame] [edit]
// Copyright 2024 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "feature_benchmark/metrics.h"
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/json/json_writer.h>
#include <algorithm>
#include <cmath>
#include <numeric>
#include <utility>
#include "cros-camera/common.h"
namespace cros::tests {
namespace {
double Stddev(const std::vector<double>& data, double avg) {
std::vector<double> diff(data.size());
std::transform(data.begin(), data.end(), diff.begin(),
[avg](double x) { return x - avg; });
double sq_sum =
std::inner_product(diff.begin(), diff.end(), diff.begin(), 0.0);
return std::sqrt(sq_sum / data.size());
}
} // namespace
void Metrics::AddMetric(const std::string& name,
const std::string& unit,
bool bigger_is_better) {
auto it = metric_dict_.find(name);
CHECK(it == metric_dict_.end());
metric_dict_[name] = {.unit = unit, .bigger_is_better = bigger_is_better};
}
void Metrics::AddMetricSample(const std::string& name, double val) {
auto it = metric_dict_.find(name);
CHECK(it != metric_dict_.end());
it->second.samples.push_back(val);
}
void Metrics::OutputMetricsToJsonFile(const base::FilePath& output_file_path) {
CalculateStatistics();
base::Value::List json_output;
constexpr char kFunctionNameKey[] = "function_name";
constexpr char kMetricNameKey[] = "metric_name";
constexpr char kAvgMetricName[] = "avg";
constexpr char kStddevMetricName[] = "stddev";
constexpr char kMinMetricName[] = "min";
constexpr char kMaxMetricName[] = "max";
constexpr char kCountMetricName[] = "count";
constexpr char kUnitKey[] = "unit";
constexpr char kCountUnit[] = "count";
constexpr char kValueKey[] = "value";
constexpr char kBiggerIsBetterKey[] = "bigger_is_better";
// Following the spec of FunctionMetrics in
// //camera/tracing/sql/camera_core_metrics.proto
for (auto const& [name, metric] : metric_dict_) {
json_output.Append(
base::Value::Dict()
.Set(kFunctionNameKey, name)
.Set(kMetricNameKey, kAvgMetricName)
.Set(kUnitKey, metric.unit)
.Set(kValueKey, static_cast<int>(metric.statistics.avg))
.Set(kBiggerIsBetterKey, metric.bigger_is_better));
json_output.Append(
base::Value::Dict()
.Set(kFunctionNameKey, name)
.Set(kMetricNameKey, kStddevMetricName)
.Set(kUnitKey, metric.unit)
.Set(kValueKey, static_cast<int>(metric.statistics.stddev))
.Set(kBiggerIsBetterKey, false));
json_output.Append(
base::Value::Dict()
.Set(kFunctionNameKey, name)
.Set(kMetricNameKey, kMinMetricName)
.Set(kUnitKey, metric.unit)
.Set(kValueKey, static_cast<int>(metric.statistics.min))
.Set(kBiggerIsBetterKey, metric.bigger_is_better));
json_output.Append(
base::Value::Dict()
.Set(kFunctionNameKey, name)
.Set(kMetricNameKey, kMaxMetricName)
.Set(kUnitKey, metric.unit)
.Set(kValueKey, static_cast<int>(metric.statistics.max))
.Set(kBiggerIsBetterKey, metric.bigger_is_better));
json_output.Append(
base::Value::Dict()
.Set(kFunctionNameKey, name)
.Set(kMetricNameKey, kCountMetricName)
.Set(kUnitKey, kCountUnit)
.Set(kValueKey, static_cast<int>(metric.statistics.count))
.Set(kBiggerIsBetterKey, true));
}
std::string json_string;
base::JSONWriter::WriteWithOptions(
json_output, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_string);
if (base::WriteFile(output_file_path, json_string.data(),
json_string.size()) < 0) {
LOGF(FATAL) << "Failed to write metrics JSON to path: " << output_file_path;
}
}
void Metrics::CalculateStatistics() {
for (auto& [metric_name, metric] : metric_dict_) {
if (metric.samples.empty()) {
metric.statistics = {
.avg = 0,
.stddev = 0,
.min = 0,
.max = 0,
.count = 0,
};
continue;
}
metric.statistics.avg =
std::reduce(metric.samples.begin(), metric.samples.end()) /
metric.samples.size();
metric.statistics.stddev = Stddev(metric.samples, metric.statistics.avg);
metric.statistics.min =
*std::min_element(metric.samples.begin(), metric.samples.end());
metric.statistics.max =
*std::max_element(metric.samples.begin(), metric.samples.end());
metric.statistics.count = metric.samples.size();
}
}
} // namespace cros::tests