Permalink
Browse files

Merge pull request #7517 from delroth/analytics-perf

Report performance information through our analytics subsystem
  • Loading branch information...
delroth committed Oct 27, 2018
2 parents dd92266 + 83c3370 commit 756a2fe14d87e47df8d08202117f7a84148eb235
@@ -5,6 +5,7 @@
#include <cmath>
#include <cstdio>
#include <string>
#include <type_traits>
#include "Common/Analytics.h"
#include "Common/CommonTypes.h"
@@ -17,7 +18,7 @@ namespace
{
// Format version number, used as the first byte of every report sent.
// Increment for any change to the wire format.
constexpr u8 WIRE_FORMAT_VERSION = 0;
constexpr u8 WIRE_FORMAT_VERSION = 1;
// Identifiers for the value types supported by the analytics reporting wire
// format.
@@ -28,8 +29,17 @@ enum class TypeId : u8
UINT = 2,
SINT = 3,
FLOAT = 4,
// Flags which can be combined with other types.
ARRAY = 0x80,
};
TypeId operator|(TypeId l, TypeId r)
{
using ut = std::underlying_type_t<TypeId>;
return static_cast<TypeId>(static_cast<ut>(l) | static_cast<ut>(r));
}
void AppendBool(std::string* out, bool v)
{
out->push_back(v ? '\xFF' : '\x00');
@@ -112,6 +122,15 @@ void AnalyticsReportBuilder::AppendSerializedValue(std::string* report, float v)
AppendBytes(report, reinterpret_cast<u8*>(&v), sizeof(v), false);
}
void AnalyticsReportBuilder::AppendSerializedValueVector(std::string* report,
const std::vector<u32>& v)
{
AppendType(report, TypeId::UINT | TypeId::ARRAY);
AppendVarInt(report, v.size());
for (u32 x : v)
AppendVarInt(report, x);
}
AnalyticsReporter::AnalyticsReporter()
{
m_reporter_thread = std::thread(&AnalyticsReporter::ThreadProc, this);
@@ -95,6 +95,15 @@ class AnalyticsReportBuilder
return *this;
}
template <typename T>
AnalyticsReportBuilder& AddData(const std::string& key, const std::vector<T>& value)
{
std::lock_guard<std::mutex> lk(m_lock);
AppendSerializedValue(&m_report, key);
AppendSerializedValueVector(&m_report, value);
return *this;
}
std::string Get() const
{
std::lock_guard<std::mutex> lk(m_lock);
@@ -118,6 +127,8 @@ class AnalyticsReportBuilder
static void AppendSerializedValue(std::string* report, s32 v);
static void AppendSerializedValue(std::string* report, float v);
static void AppendSerializedValueVector(std::string* report, const std::vector<u32>& v);
// Should really be a std::shared_mutex, unfortunately that's C++17 only.
mutable std::mutex m_lock;
std::string m_report;
@@ -5,6 +5,7 @@
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#if defined(_WIN32)
#include <windows.h>
@@ -20,6 +21,7 @@
#include "Common/CommonTypes.h"
#include "Common/Random.h"
#include "Common/StringUtil.h"
#include "Common/Timer.h"
#include "Common/Version.h"
#include "Core/ConfigManager.h"
#include "Core/HW/GCPad.h"
@@ -128,6 +130,71 @@ void DolphinAnalytics::ReportGameStart()
Common::AnalyticsReportBuilder builder(m_per_game_builder);
builder.AddData("type", "game-start");
Send(builder);
InitializePerformanceSampling();
}
void DolphinAnalytics::ReportPerformanceInfo(PerformanceSample&& sample)
{
if (ShouldStartPerformanceSampling())
{
m_sampling_performance_info = true;
}
if (m_sampling_performance_info)
{
m_performance_samples.emplace_back(std::move(sample));
}
if (m_performance_samples.size() >= NUM_PERFORMANCE_SAMPLES_PER_REPORT)
{
std::vector<u32> speed_times_1000(m_performance_samples.size());
std::vector<u32> num_prims(m_performance_samples.size());
std::vector<u32> num_draw_calls(m_performance_samples.size());
for (size_t i = 0; i < m_performance_samples.size(); ++i)
{
speed_times_1000[i] = static_cast<u32>(m_performance_samples[i].speed_ratio * 1000);
num_prims[i] = m_performance_samples[i].num_prims;
num_draw_calls[i] = m_performance_samples[i].num_draw_calls;
}
// The per game builder should already exist -- there is no way we can be reporting performance
// info without a game start event having been generated.
Common::AnalyticsReportBuilder builder(m_per_game_builder);
builder.AddData("type", "performance");
builder.AddData("speed", speed_times_1000);
builder.AddData("prims", num_prims);
builder.AddData("draw-calls", num_draw_calls);
Send(builder);
// Clear up and stop sampling until next time ShouldStartPerformanceSampling() says so.
m_performance_samples.clear();
m_sampling_performance_info = false;
}
}
void DolphinAnalytics::InitializePerformanceSampling()
{
m_performance_samples.clear();
m_sampling_performance_info = false;
u64 wait_us =
PERFORMANCE_SAMPLING_INITIAL_WAIT_TIME_SECS * 1000000 +
Common::Random::GenerateValue<u64>() % (PERFORMANCE_SAMPLING_WAIT_TIME_JITTER_SECS * 1000000);
m_sampling_next_start_us = Common::Timer::GetTimeUs() + wait_us;
}
bool DolphinAnalytics::ShouldStartPerformanceSampling()
{
if (Common::Timer::GetTimeUs() < m_sampling_next_start_us)
return false;
u64 wait_us =
PERFORMANCE_SAMPLING_INTERVAL_SECS * 1000000 +
Common::Random::GenerateValue<u64>() % (PERFORMANCE_SAMPLING_WAIT_TIME_JITTER_SECS * 1000000);
m_sampling_next_start_us = Common::Timer::GetTimeUs() + wait_us;
return true;
}
void DolphinAnalytics::MakeBaseBuilder()
@@ -7,8 +7,10 @@
#include <memory>
#include <mutex>
#include <string>
#include <vector>
#include "Common/Analytics.h"
#include "Common/CommonTypes.h"
#if defined(ANDROID)
#include <functional>
@@ -40,6 +42,18 @@ class DolphinAnalytics
// per-game base data.
void ReportGameStart();
struct PerformanceSample
{
double speed_ratio; // See SystemTimers::GetEstimatedEmulationPerformance().
int num_prims;
int num_draw_calls;
};
// Reports performance information. This method performs its own throttling / aggregation --
// calling it does not guarantee when a report will actually be sent.
//
// This method is NOT thread-safe.
void ReportPerformanceInfo(PerformanceSample&& sample);
// Forward Send method calls to the reporter.
template <typename T>
void Send(T report)
@@ -63,6 +77,22 @@ class DolphinAnalytics
// values created by MakeUniqueId.
std::string m_unique_id;
// Performance sampling configuration constants.
//
// 5min after startup + rand(0, 3min) jitter time, collect performance for 100 frames in a row.
// Repeat collection after 30min + rand(0, 3min).
static constexpr int NUM_PERFORMANCE_SAMPLES_PER_REPORT = 100;
static constexpr int PERFORMANCE_SAMPLING_INITIAL_WAIT_TIME_SECS = 300;
static constexpr int PERFORMANCE_SAMPLING_WAIT_TIME_JITTER_SECS = 180;
static constexpr int PERFORMANCE_SAMPLING_INTERVAL_SECS = 1800;
// Performance sampling state & internal helpers.
void InitializePerformanceSampling(); // Called on game start / title switch.
bool ShouldStartPerformanceSampling();
u64 m_sampling_next_start_us; // Next timestamp (in us) at which to trigger sampling.
bool m_sampling_performance_info = false; // Whether we are currently collecting samples.
std::vector<PerformanceSample> m_performance_samples;
// Builder that contains all non variable data that should be sent with all
// reports.
Common::AnalyticsReportBuilder m_base_builder;
Oops, something went wrong.

0 comments on commit 756a2fe

Please sign in to comment.