blob: 08fc086e05230442880195a3f482bd0707bcdeaa [file] [log] [blame]
// Copyright 2021 The Chromium OS 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 "metrics/structured/recorder.h"
#include <memory>
#include <sys/file.h>
#include <utility>
#include <base/bind.h>
#include <base/macros.h>
#include <base/logging.h>
#include <base/guid.h>
#include <metrics/structured/structured_events.h>
#include <metrics/structured/event_base.h>
#include <metrics/structured/proto/storage.pb.h>
#include <base/files/file_util.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/strcat.h>
namespace metrics {
namespace structured {
namespace {
constexpr char kEventsPath[] = "/var/lib/metrics/structured/events";
constexpr char kKeysPath[] = "/var/lib/metrics/structured/keys";
constexpr mode_t kFilePermissions = 0660;
// Writes |events| to a file within |directory|. Fails if |directory| doesn't
// exist. Returns whether the write was successful.
bool WriteEventsProtoToDir(const std::string& directory,
const EventsProto& events) {
const std::string guid = base::GenerateGUID();
if (guid.empty())
return false;
const std::string filepath = base::StrCat({directory, "/", guid});
base::ScopedFD file_descriptor(
open(filepath.c_str(), O_WRONLY | O_APPEND | O_CREAT | O_CLOEXEC, 0600));
if (file_descriptor.get() < 0) {
PLOG(ERROR) << filepath << " cannot open";
return false;
}
if (!events.SerializeToFileDescriptor(file_descriptor.get())) {
PLOG(ERROR) << filepath << " write error";
return false;
}
// Explicitly set permissions on the created event file. This is done
// separately to the open call to be independent of the umask.
if (fchmod(file_descriptor.get(), kFilePermissions) < 0) {
PLOG(ERROR) << filepath << " cannot chmod";
return false;
}
return true;
}
} // namespace
// static
Recorder* Recorder::GetInstance() {
static base::NoDestructor<Recorder> recorder{kEventsPath, kKeysPath};
return recorder.get();
}
Recorder::Recorder(const std::string& events_directory,
const std::string& keys_path)
: events_directory_(events_directory), key_data_(keys_path) {}
Recorder::~Recorder() = default;
bool Recorder::Record(const EventBase& event) {
if (!metrics_library_.AreMetricsEnabled())
return false;
EventsProto events_proto;
StructuredEventProto* event_proto;
if (event.id_type() == EventBase::IdType::kUmaId) {
// TODO(crbug.com/1148168): Unimplemented.
NOTREACHED();
return false;
} else {
event_proto = events_proto.add_non_uma_events();
}
// Set the ID for this event, if any.
switch (event.id_type()) {
case EventBase::IdType::kProjectId:
event_proto->set_profile_event_id(
key_data_.Id(event.project_name_hash()));
break;
case EventBase::IdType::kUmaId:
// TODO(crbug.com/1148168): Unimplemented.
NOTREACHED();
break;
case EventBase::IdType::kUnidentified:
// Do nothing.
break;
default:
// In case id_type is uninitialized.
NOTREACHED();
break;
}
// Set the event type. Do this with a switch statement to catch when the event
// type is UNKNOWN or uninitialized.
switch (event.event_type()) {
case StructuredEventProto_EventType_REGULAR:
case StructuredEventProto_EventType_RAW_STRING:
event_proto->set_event_type(event.event_type());
break;
default:
NOTREACHED();
break;
}
event_proto->set_event_name_hash(event.name_hash());
// Set each metric's name hash and value.
for (const auto& metric : event.metrics()) {
auto* metric_proto = event_proto->add_metrics();
metric_proto->set_name_hash(metric.name_hash);
switch (metric.type) {
case EventBase::MetricType::kHmac:
metric_proto->set_value_hmac(key_data_.HmacMetric(
event.project_name_hash(), metric.name_hash, metric.hmac_value));
break;
case EventBase::MetricType::kInt:
metric_proto->set_value_int64(metric.int_value);
break;
case EventBase::MetricType::kRawString:
metric_proto->set_value_string(metric.string_value);
break;
}
}
return WriteEventsProtoToDir(events_directory_, events_proto);
}
} // namespace structured
} // namespace metrics