blob: b35391627ffde2f431b87559e304549869525bc3 [file] [log] [blame]
// Copyright 2016 The Android Open Source Project
//
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
#include "android/metrics/MetricsReporter.h"
#include "android/base/threads/Async.h"
#include "android/base/async/ThreadLooper.h"
#include "android/base/memory/LazyInstance.h"
#include "android/base/Uuid.h"
#include "android/metrics/AsyncMetricsReporter.h"
#include "android/metrics/CrashMetricsReporting.h"
#include "android/metrics/FileMetricsWriter.h"
#include "android/metrics/MetricsPaths.h"
#include "android/metrics/NullMetricsReporter.h"
#include "android/metrics/StudioConfig.h"
#include "android/metrics/proto/clientanalytics.pb.h"
#include "android/metrics/proto/studio_stats.pb.h"
#include <type_traits>
using android::base::System;
namespace android {
namespace metrics {
namespace {
static base::LazyInstance<NullMetricsReporter> sNullInstance = {};
// A small class that ensures there's always an instance of metrics reporter
// ready to process a metrics write request.
// By default it's an instance of NullMetricsReporter() which discards all
// requests, but it can be reset to anything that implements MetricsReporter
// interface.
class ReporterHolder final {
public:
ReporterHolder() : mPtr(sNullInstance.ptr()) {}
void reset(MetricsReporter::Ptr newPtr) {
if (newPtr) {
mPtr = newPtr.release();
} else {
// Replace the current instance with a null one and delete the old
// reporter (if it wasn't already a null reporter.
MetricsReporter* other = sNullInstance.ptr();
std::swap(mPtr, other);
if (other != sNullInstance.ptr()) {
delete other;
}
}
}
MetricsReporter& reporter() const { return *mPtr; }
private:
MetricsReporter* mPtr;
};
static base::LazyInstance<ReporterHolder> sInstance = {};
} // namespace
void MetricsReporter::start(const std::string& sessionId,
base::StringView emulatorVersion,
base::StringView emulatorFullVersion,
base::StringView qemuVersion) {
const bool optIn = studio::getUserMetricsOptIn();
if (!optIn) {
sInstance->reset({});
} else {
auto writer = FileMetricsWriter::create(
getSpoolDirectory(),
sessionId,
1000, // record limit per single file
base::ThreadLooper::get(),
10 * 60 * 1000); // time limit for a single file, ms
sInstance->reset(
Ptr(new AsyncMetricsReporter(writer,
emulatorVersion,
emulatorFullVersion,
qemuVersion)));
// Run the asynchronous cleanup/reporting job now.
base::async([] {
const auto sessions =
FileMetricsWriter::finalizeAbandonedSessionFiles(
getSpoolDirectory());
reportCrashMetrics(get(), sessions);
});
}
}
void MetricsReporter::stop() {
// Finalize the metrics with a "didn't crash" message.
sInstance->reporter().report(
[](android_studio::AndroidStudioEvent* event) {
event->mutable_emulator_details()->set_crashes(0);
});
sInstance->reset({});
}
MetricsReporter& MetricsReporter::get() {
return sInstance->reporter();
}
void MetricsReporter::report(Callback callback) {
if (!callback) {
return;
}
reportConditional([callback](android_studio::AndroidStudioEvent* event) {
callback(event);
return true;
});
}
MetricsReporter::MetricsReporter(bool enabled, MetricsWriter::Ptr writer,
base::StringView emulatorVersion,
base::StringView emulatorFullVersion,
base::StringView qemuVersion)
: mWriter(std::move(writer)),
mEnabled(enabled),
mStartTimeMs(System::get()->getUnixTimeUs() / 1000),
mEmulatorVersion(emulatorVersion),
mEmulatorFullVersion(emulatorFullVersion),
mQemuVersion(qemuVersion) {
assert(mWriter);
}
MetricsReporter::~MetricsReporter() = default;
bool MetricsReporter::isReportingEnabled() const {
return mEnabled;
}
const std::string& MetricsReporter::sessionId() const {
// Protect this from unexpected changes in the MetricsWriter interface.
static_assert(std::is_reference<decltype(mWriter->sessionId())>::value,
"MetricsWriter::sessionId() must return a reference");
return mWriter->sessionId();
}
void MetricsReporter::sendToWriter(
android_studio::AndroidStudioEvent* event) {
wireless_android_play_playlog::LogEvent logEvent;
const auto timeMs = System::get()->getUnixTimeUs() / 1000;
logEvent.set_event_time_ms(timeMs);
logEvent.set_event_uptime_ms(timeMs - mStartTimeMs);
if (!event->has_kind()) {
event->set_kind(android_studio::AndroidStudioEvent::EMULATOR_PING);
}
event->mutable_product_details()->set_product(
android_studio::ProductDetails::EMULATOR);
if (!mEmulatorVersion.empty()) {
event->mutable_product_details()->set_version(mEmulatorVersion);
}
if (!mEmulatorFullVersion.empty()) {
event->mutable_product_details()->set_build(mEmulatorFullVersion);
}
if (!mQemuVersion.empty()) {
event->mutable_emulator_details()->set_core_version(mQemuVersion);
}
const auto times = System::get()->getProcessTimes();
event->mutable_emulator_details()->set_system_time(times.systemMs);
event->mutable_emulator_details()->set_user_time(times.userMs);
event->mutable_emulator_details()->set_wall_time(times.wallClockMs);
// Only set the session ID if it isn't set: some messages might be reported
// on behalf of a different (e.g. crashed) session.
if (!event->has_studio_session_id()) {
event->set_studio_session_id(sessionId());
}
event->SerializeToString(logEvent.mutable_source_extension());
mWriter->write(logEvent);
}
} // namespace metrics
} // namespace android