Merge pull request #2683 from bunnei/telemetry-framework

Telemetry framework Part 1
merge-requests/60/head
bunnei 2017-05-24 19:33:54 +07:00 committed by GitHub
commit 634229ff45
9 changed files with 342 additions and 0 deletions

@ -38,6 +38,7 @@ set(SRCS
param_package.cpp param_package.cpp
scm_rev.cpp scm_rev.cpp
string_util.cpp string_util.cpp
telemetry.cpp
thread.cpp thread.cpp
timer.cpp timer.cpp
) )
@ -74,6 +75,7 @@ set(HEADERS
string_util.h string_util.h
swap.h swap.h
synchronized_wrapper.h synchronized_wrapper.h
telemetry.h
thread.h thread.h
thread_queue_list.h thread_queue_list.h
timer.h timer.h

@ -0,0 +1,40 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include "common/telemetry.h"
namespace Telemetry {
void FieldCollection::Accept(VisitorInterface& visitor) const {
for (const auto& field : fields) {
field.second->Accept(visitor);
}
}
void FieldCollection::AddField(std::unique_ptr<FieldInterface> field) {
fields[field->GetName()] = std::move(field);
}
template <class T>
void Field<T>::Accept(VisitorInterface& visitor) const {
visitor.Visit(*this);
}
template class Field<bool>;
template class Field<double>;
template class Field<float>;
template class Field<u8>;
template class Field<u16>;
template class Field<u32>;
template class Field<u64>;
template class Field<s8>;
template class Field<s16>;
template class Field<s32>;
template class Field<s64>;
template class Field<std::string>;
template class Field<const char*>;
template class Field<std::chrono::microseconds>;
} // namespace Telemetry

@ -0,0 +1,196 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <chrono>
#include <map>
#include <memory>
#include <string>
#include "common/common_types.h"
namespace Telemetry {
/// Field type, used for grouping fields together in the final submitted telemetry log
enum class FieldType : u8 {
None = 0, ///< No specified field group
App, ///< Citra application fields (e.g. version, branch, etc.)
Session, ///< Emulated session fields (e.g. title ID, log, etc.)
Performance, ///< Emulated performance (e.g. fps, emulated CPU speed, etc.)
UserFeedback, ///< User submitted feedback (e.g. star rating, user notes, etc.)
UserConfig, ///< User configuration fields (e.g. emulated CPU core, renderer, etc.)
UserSystem, ///< User system information (e.g. host CPU type, RAM, etc.)
};
struct VisitorInterface;
/**
* Interface class for telemetry data fields.
*/
class FieldInterface : NonCopyable {
public:
virtual ~FieldInterface() = default;
/**
* Accept method for the visitor pattern.
* @param visitor Reference to the visitor that will visit this field.
*/
virtual void Accept(VisitorInterface& visitor) const = 0;
/**
* Gets the name of this field.
* @returns Name of this field as a string.
*/
virtual const std::string& GetName() const = 0;
};
/**
* Represents a telemetry data field, i.e. a unit of data that gets logged and submitted to our
* telemetry web service.
*/
template <typename T>
class Field : public FieldInterface {
public:
Field(FieldType type, std::string name, const T& value)
: type(type), name(std::move(name)), value(value) {}
Field(FieldType type, std::string name, T&& value)
: type(type), name(std::move(name)), value(std::move(value)) {}
Field(const Field& other) : Field(other.type, other.name, other.value) {}
Field& operator=(const Field& other) {
type = other.type;
name = other.name;
value = other.value;
return *this;
}
Field& operator=(Field&& other) {
type = other.type;
name = std::move(other.name);
value = std::move(other.value);
return *this;
}
void Accept(VisitorInterface& visitor) const override;
const std::string& GetName() const override {
return name;
}
/**
* Returns the type of the field.
*/
FieldType GetType() const {
return type;
}
/**
* Returns the value of the field.
*/
const T& GetValue() const {
return value;
}
inline bool operator==(const Field<T>& other) {
return (type == other.type) && (name == other.name) && (value == other.value);
}
inline bool operator!=(const Field<T>& other) {
return !(*this == other);
}
private:
std::string name; ///< Field name, must be unique
FieldType type{}; ///< Field type, used for grouping fields together
T value; ///< Field value
};
/**
* Collection of data fields that have been logged.
*/
class FieldCollection final : NonCopyable {
public:
FieldCollection() = default;
/**
* Accept method for the visitor pattern, visits each field in the collection.
* @param visitor Reference to the visitor that will visit each field.
*/
void Accept(VisitorInterface& visitor) const;
/**
* Creates a new field and adds it to the field collection.
* @param type Type of the field to add.
* @param name Name of the field to add.
* @param value Value for the field to add.
*/
template <typename T>
void AddField(FieldType type, const char* name, T value) {
return AddField(std::make_unique<Field<T>>(type, name, std::move(value)));
}
/**
* Adds a new field to the field collection.
* @param field Field to add to the field collection.
*/
void AddField(std::unique_ptr<FieldInterface> field);
private:
std::map<std::string, std::unique_ptr<FieldInterface>> fields;
};
/**
* Telemetry fields visitor interface class. A backend to log to a web service should implement
* this interface.
*/
struct VisitorInterface : NonCopyable {
virtual ~VisitorInterface() = default;
virtual void Visit(const Field<bool>& field) = 0;
virtual void Visit(const Field<double>& field) = 0;
virtual void Visit(const Field<float>& field) = 0;
virtual void Visit(const Field<u8>& field) = 0;
virtual void Visit(const Field<u16>& field) = 0;
virtual void Visit(const Field<u32>& field) = 0;
virtual void Visit(const Field<u64>& field) = 0;
virtual void Visit(const Field<s8>& field) = 0;
virtual void Visit(const Field<s16>& field) = 0;
virtual void Visit(const Field<s32>& field) = 0;
virtual void Visit(const Field<s64>& field) = 0;
virtual void Visit(const Field<std::string>& field) = 0;
virtual void Visit(const Field<const char*>& field) = 0;
virtual void Visit(const Field<std::chrono::microseconds>& field) = 0;
/// Completion method, called once all fields have been visited
virtual void Complete() = 0;
};
/**
* Empty implementation of VisitorInterface that drops all fields. Used when a functional
* backend implementation is not available.
*/
struct NullVisitor : public VisitorInterface {
~NullVisitor() = default;
void Visit(const Field<bool>& /*field*/) override {}
void Visit(const Field<double>& /*field*/) override {}
void Visit(const Field<float>& /*field*/) override {}
void Visit(const Field<u8>& /*field*/) override {}
void Visit(const Field<u16>& /*field*/) override {}
void Visit(const Field<u32>& /*field*/) override {}
void Visit(const Field<u64>& /*field*/) override {}
void Visit(const Field<s8>& /*field*/) override {}
void Visit(const Field<s16>& /*field*/) override {}
void Visit(const Field<s32>& /*field*/) override {}
void Visit(const Field<s64>& /*field*/) override {}
void Visit(const Field<std::string>& /*field*/) override {}
void Visit(const Field<const char*>& /*field*/) override {}
void Visit(const Field<std::chrono::microseconds>& /*field*/) override {}
void Complete() override {}
};
} // namespace Telemetry

@ -174,6 +174,7 @@ set(SRCS
memory.cpp memory.cpp
perf_stats.cpp perf_stats.cpp
settings.cpp settings.cpp
telemetry_session.cpp
) )
set(HEADERS set(HEADERS
@ -366,6 +367,7 @@ set(HEADERS
mmio.h mmio.h
perf_stats.h perf_stats.h
settings.h settings.h
telemetry_session.h
) )
include_directories(../../externals/dynarmic/include) include_directories(../../externals/dynarmic/include)

@ -132,6 +132,8 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
cpu_core = std::make_unique<ARM_DynCom>(USER32MODE); cpu_core = std::make_unique<ARM_DynCom>(USER32MODE);
} }
telemetry_session = std::make_unique<Core::TelemetrySession>();
CoreTiming::Init(); CoreTiming::Init();
HW::Init(); HW::Init();
Kernel::Init(system_mode); Kernel::Init(system_mode);
@ -162,6 +164,7 @@ void System::Shutdown() {
CoreTiming::Shutdown(); CoreTiming::Shutdown();
cpu_core = nullptr; cpu_core = nullptr;
app_loader = nullptr; app_loader = nullptr;
telemetry_session = nullptr;
LOG_DEBUG(Core, "Shutdown OK"); LOG_DEBUG(Core, "Shutdown OK");
} }

@ -9,6 +9,7 @@
#include "common/common_types.h" #include "common/common_types.h"
#include "core/memory.h" #include "core/memory.h"
#include "core/perf_stats.h" #include "core/perf_stats.h"
#include "core/telemetry_session.h"
class EmuWindow; class EmuWindow;
class ARM_Interface; class ARM_Interface;
@ -80,6 +81,14 @@ public:
return cpu_core != nullptr; return cpu_core != nullptr;
} }
/**
* Returns a reference to the telemetry session for this emulation session.
* @returns Reference to the telemetry session.
*/
Core::TelemetrySession& TelemetrySession() const {
return *telemetry_session;
}
/// Prepare the core emulation for a reschedule /// Prepare the core emulation for a reschedule
void PrepareReschedule(); void PrepareReschedule();
@ -117,6 +126,9 @@ private:
/// When true, signals that a reschedule should happen /// When true, signals that a reschedule should happen
bool reschedule_pending{}; bool reschedule_pending{};
/// Telemetry session for this emulation session
std::unique_ptr<Core::TelemetrySession> telemetry_session;
static System s_instance; static System s_instance;
}; };
@ -124,4 +136,8 @@ inline ARM_Interface& CPU() {
return System::GetInstance().CPU(); return System::GetInstance().CPU();
} }
inline TelemetrySession& Telemetry() {
return System::GetInstance().TelemetrySession();
}
} // namespace Core } // namespace Core

@ -9,6 +9,7 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "common/swap.h" #include "common/swap.h"
#include "core/core.h"
#include "core/file_sys/archive_selfncch.h" #include "core/file_sys/archive_selfncch.h"
#include "core/hle/kernel/process.h" #include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/resource_limit.h"
@ -339,6 +340,8 @@ ResultStatus AppLoader_NCCH::Load() {
LOG_INFO(Loader, "Program ID: %016" PRIX64, ncch_header.program_id); LOG_INFO(Loader, "Program ID: %016" PRIX64, ncch_header.program_id);
Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", ncch_header.program_id);
is_loaded = true; // Set state to loaded is_loaded = true; // Set state to loaded
result = LoadExec(); // Load the executable into memory for booting result = LoadExec(); // Load the executable into memory for booting

@ -0,0 +1,42 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include "common/scm_rev.h"
#include "core/telemetry_session.h"
namespace Core {
TelemetrySession::TelemetrySession() {
// TODO(bunnei): Replace with a backend that logs to our web service
backend = std::make_unique<Telemetry::NullVisitor>();
// Log one-time session start information
const auto duration{std::chrono::steady_clock::now().time_since_epoch()};
const auto start_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()};
AddField(Telemetry::FieldType::Session, "StartTime", start_time);
// Log one-time application information
const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr};
AddField(Telemetry::FieldType::App, "GitIsDirty", is_git_dirty);
AddField(Telemetry::FieldType::App, "GitBranch", Common::g_scm_branch);
AddField(Telemetry::FieldType::App, "GitRevision", Common::g_scm_rev);
}
TelemetrySession::~TelemetrySession() {
// Log one-time session end information
const auto duration{std::chrono::steady_clock::now().time_since_epoch()};
const auto end_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()};
AddField(Telemetry::FieldType::Session, "EndTime", end_time);
// Complete the session, submitting to web service if necessary
// This is just a placeholder to wrap up the session once the core completes and this is
// destroyed. This will be moved elsewhere once we are actually doing real I/O with the service.
field_collection.Accept(*backend);
backend->Complete();
backend = nullptr;
}
} // namespace Core

@ -0,0 +1,38 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include "common/telemetry.h"
namespace Core {
/**
* Instruments telemetry for this emulation session. Creates a new set of telemetry fields on each
* session, logging any one-time fields. Interfaces with the telemetry backend used for submitting
* data to the web service. Submits session data on close.
*/
class TelemetrySession : NonCopyable {
public:
TelemetrySession();
~TelemetrySession();
/**
* Wrapper around the Telemetry::FieldCollection::AddField method.
* @param type Type of the field to add.
* @param name Name of the field to add.
* @param value Value for the field to add.
*/
template <typename T>
void AddField(Telemetry::FieldType type, const char* name, T value) {
field_collection.AddField(type, name, std::move(value));
}
private:
Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session
std::unique_ptr<Telemetry::VisitorInterface> backend; ///< Backend interface that logs fields
};
} // namespace Core