blob: 59730d3c4b0aa24bb2bfa4d07a591e795a3c0b8f [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "trunks/trunks_metrics.h"
#include <algorithm>
#include <cstdint>
#include <string>
#include <base/check_op.h>
#include <base/logging.h>
#include <base/time/time.h>
#include "trunks/error_codes.h"
extern "C" {
#include <sys/sysinfo.h>
}
namespace trunks {
namespace {
constexpr base::TimeDelta kMinMetricsTimeout = base::Minutes(0);
constexpr base::TimeDelta kMaxMetricsTimeout = base::Minutes(5);
constexpr int kNumBuckets = 100;
constexpr char kFirstTimeoutWritingCommand[] =
"Platform.Trunks.FirstTimeoutWritingCommand";
constexpr char kFirstTimeoutWritingTime[] =
"Platform.Trunks.FirstTimeoutWritingTime";
constexpr char kRecoverableWriteErrorNo[] =
"Platform.Trunks.RecoverableWriteErrorNo";
constexpr char kUnrecoverableWriteErrorNo[] =
"Platform.Trunks.UnrecoverableWriteErrorNo";
constexpr char kTransitionedWriteErrorNo[] =
"Platform.Trunks.TransitionedWriteErrorNo";
constexpr char kTpmErrorCode[] = "Platform.Trunks.TpmErrorCode";
// The total event time.
constexpr char kEventTime[] = "Platform.Trunks.EventTime.";
// The time we spend on the TPM that is directly related to the event.
constexpr char kEventRelatedTime[] = "Platform.Trunks.EventRelatedTime.";
// The time we spend on the TPM that is not directly related to the event.
constexpr char kEventIrrelatedTime[] = "Platform.Trunks.EventIrrelatedTime.";
} // namespace
bool TrunksMetrics::ReportTpmHandleTimeoutCommandAndTime(int error_result,
TPM_CC command_code) {
std::string command_metrics, time_metrics;
switch (error_result) {
case TRUNKS_RC_WRITE_ERROR:
command_metrics = kFirstTimeoutWritingCommand;
time_metrics = kFirstTimeoutWritingTime;
break;
case TRUNKS_RC_READ_ERROR:
return true;
default:
LOG(INFO) << "Reporting unexpected error: " << error_result;
return false;
}
metrics_library_.SendSparseToUMA(command_metrics,
static_cast<int>(command_code));
struct sysinfo info;
if (sysinfo(&info) == 0) {
constexpr int kMinUptimeInSeconds = 1;
constexpr int kMaxUptimeInSeconds = 7 * 24 * 60 * 60; // 1 week
constexpr int kNumUptimeBuckets = 50;
metrics_library_.SendToUMA(time_metrics, info.uptime, kMinUptimeInSeconds,
kMaxUptimeInSeconds, kNumUptimeBuckets);
} else {
PLOG(WARNING) << "Error getting system uptime";
}
return true;
}
void TrunksMetrics::ReportTpmErrorCode(TPM_RC error_code) {
metrics_library_.SendSparseToUMA(kTpmErrorCode, static_cast<int>(error_code));
}
void TrunksMetrics::ReportWriteErrorNo(int prev, int next) {
// Don't record any UMA if the state is good or just goes from good to bad.
if (prev <= 0) {
return;
}
static bool has_error_transitioned = false;
if (next <= 0) {
metrics_library_.SendSparseToUMA(kRecoverableWriteErrorNo, prev);
} else if (prev == next) {
// It is possible for the error to change, and the new error keeps
// happending. In that case, it is not conclusive if the error is
// unrecoverable until the next process cycle.
if (has_error_transitioned) {
return;
}
// Since the status gets stuck in a single error, the same call occurs for
// every single TPM commands, need a call-once guard for this case.
static bool call_once = [&]() {
this->metrics_library_.SendSparseToUMA(kUnrecoverableWriteErrorNo, prev);
return true;
}();
(void)(call_once);
} else {
metrics_library_.SendSparseToUMA(kTransitionedWriteErrorNo, prev);
has_error_transitioned = true;
}
}
void TrunksMetrics::StartEvent(const std::string& event, uint64_t sender) {
events[event] = EventDetail{
.sender = sender,
.start_time = base::Time::Now(),
.related_time = base::Seconds(0),
.irrelated_time = base::Seconds(0),
};
}
void TrunksMetrics::StopEvent(const std::string& event, uint64_t sender) {
auto it = events.find(event);
if (it == events.end()) {
LOG(WARNING) << "Stop event(" << event << ") without starting it.";
return;
}
// Total event time.
metrics_library_.SendTimeToUMA(
kEventTime + event, base::Time::Now() - it->second.start_time,
kMinMetricsTimeout, kMaxMetricsTimeout, kNumBuckets);
// Related event time.
metrics_library_.SendTimeToUMA(kEventRelatedTime + event,
it->second.related_time, kMinMetricsTimeout,
kMaxMetricsTimeout, kNumBuckets);
// Irrelated event time.
metrics_library_.SendTimeToUMA(kEventIrrelatedTime + event,
it->second.irrelated_time, kMinMetricsTimeout,
kMaxMetricsTimeout, kNumBuckets);
events.erase(it);
}
void TrunksMetrics::ReportCommandTime(uint64_t sender,
base::TimeDelta duration) {
for (auto& [event, detail] : events) {
base::TimeDelta event_time = duration;
// If the command starts before we starts the event, we should only record
// the time within the event range.
base::Time now = base::Time::Now();
base::TimeDelta delta = now - detail.start_time;
if (event_time > delta) {
event_time = delta;
}
if (detail.sender == sender) {
detail.related_time += event_time;
} else {
detail.irrelated_time += event_time;
}
}
}
} // namespace trunks