summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar bunnei2017-07-12 21:31:12 -0400
committerGravatar GitHub2017-07-12 21:31:12 -0400
commit9cf261ba8ba4fd9929d275cc793d48d13df624f3 (patch)
tree2eb47ab96bde081a84c5dd7c8107a21cb8a3511c
parentMerge pull request #2815 from mailwl/bossp (diff)
parentweb_backend: Specify api-version on JSON post. (diff)
downloadyuzu-9cf261ba8ba4fd9929d275cc793d48d13df624f3.tar.gz
yuzu-9cf261ba8ba4fd9929d275cc793d48d13df624f3.tar.xz
yuzu-9cf261ba8ba4fd9929d275cc793d48d13df624f3.zip
Merge pull request #2819 from bunnei/telemetry-submit
Telemetry: Submit logged data to the Citra service
-rw-r--r--.gitmodules6
-rw-r--r--CMakeLists.txt5
-rw-r--r--externals/CMakeLists.txt12
m---------externals/cpr0
m---------externals/json0
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/citra/config.cpp4
-rw-r--r--src/citra/default_ini.h4
-rw-r--r--src/citra_qt/configuration/config.cpp12
-rw-r--r--src/common/logging/backend.cpp3
-rw-r--r--src/common/logging/log.h1
-rw-r--r--src/core/CMakeLists.txt3
-rw-r--r--src/core/settings.h3
-rw-r--r--src/core/telemetry_session.cpp10
-rw-r--r--src/web_service/CMakeLists.txt14
-rw-r--r--src/web_service/telemetry_json.cpp87
-rw-r--r--src/web_service/telemetry_json.h54
-rw-r--r--src/web_service/web_backend.cpp52
-rw-r--r--src/web_service/web_backend.h31
19 files changed, 301 insertions, 3 deletions
diff --git a/.gitmodules b/.gitmodules
index ac0df914d..45ff650ef 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -28,3 +28,9 @@
28[submodule "externals/enet"] 28[submodule "externals/enet"]
29 path = externals/enet 29 path = externals/enet
30 url = https://github.com/lsalzman/enet 30 url = https://github.com/lsalzman/enet
31[submodule "cpr"]
32 path = externals/cpr
33 url = https://github.com/whoshuu/cpr.git
34[submodule "json"]
35 path = externals/json
36 url = https://github.com/nlohmann/json.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4668d4bea..ad73cf495 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,6 +11,8 @@ option(CITRA_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" OFF)
11option(ENABLE_QT "Enable the Qt frontend" ON) 11option(ENABLE_QT "Enable the Qt frontend" ON)
12option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF) 12option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF)
13 13
14option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
15
14if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit) 16if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit)
15 message(STATUS "Copying pre-commit hook") 17 message(STATUS "Copying pre-commit hook")
16 file(COPY hooks/pre-commit 18 file(COPY hooks/pre-commit
@@ -223,6 +225,9 @@ if (ENABLE_QT)
223 find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT}) 225 find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
224endif() 226endif()
225 227
228if (ENABLE_WEB_SERVICE)
229 add_definitions(-DENABLE_WEB_SERVICE)
230endif()
226 231
227# Platform-specific library requirements 232# Platform-specific library requirements
228# ====================================== 233# ======================================
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index cc47166fc..ccc7f13b6 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -52,3 +52,15 @@ endif()
52# ENet 52# ENet
53add_subdirectory(enet) 53add_subdirectory(enet)
54target_include_directories(enet INTERFACE ./enet/include) 54target_include_directories(enet INTERFACE ./enet/include)
55
56if (ENABLE_WEB_SERVICE)
57 # CPR
58 option(BUILD_TESTING OFF)
59 option(BUILD_CPR_TESTS OFF)
60 add_subdirectory(cpr)
61 target_include_directories(cpr INTERFACE ./cpr/include)
62
63 # JSON
64 add_library(json-headers INTERFACE)
65 target_include_directories(json-headers INTERFACE ./json/src)
66endif()
diff --git a/externals/cpr b/externals/cpr
new file mode 160000
Subproject b5758fbc88021437f968fe5174f121b8b92f5d5
diff --git a/externals/json b/externals/json
new file mode 160000
Subproject d3496347fcd1382896fca3aaf78a0d803c2f52e
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 655bd83aa..e11940f59 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -14,3 +14,6 @@ endif()
14if (ENABLE_QT) 14if (ENABLE_QT)
15 add_subdirectory(citra_qt) 15 add_subdirectory(citra_qt)
16endif() 16endif()
17if (ENABLE_WEB_SERVICE)
18 add_subdirectory(web_service)
19endif()
diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index 957d8dc86..69247b166 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -151,6 +151,10 @@ void Config::ReadValues() {
151 Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false); 151 Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
152 Settings::values.gdbstub_port = 152 Settings::values.gdbstub_port =
153 static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); 153 static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
154
155 // Web Service
156 Settings::values.telemetry_endpoint_url = sdl2_config->Get(
157 "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry");
154} 158}
155 159
156void Config::Reload() { 160void Config::Reload() {
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index d8a8fe44f..a12498e0f 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -168,5 +168,9 @@ log_filter = *:Info
168# Port for listening to GDB connections. 168# Port for listening to GDB connections.
169use_gdbstub=false 169use_gdbstub=false
170gdbstub_port=24689 170gdbstub_port=24689
171
172[WebService]
173# Endpoint URL for submitting telemetry data
174telemetry_endpoint_url =
171)"; 175)";
172} 176}
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index 64ffc9152..40142b6d9 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -133,6 +133,13 @@ void Config::ReadValues() {
133 Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt(); 133 Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();
134 qt_config->endGroup(); 134 qt_config->endGroup();
135 135
136 qt_config->beginGroup("WebService");
137 Settings::values.telemetry_endpoint_url =
138 qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry")
139 .toString()
140 .toStdString();
141 qt_config->endGroup();
142
136 qt_config->beginGroup("UI"); 143 qt_config->beginGroup("UI");
137 144
138 qt_config->beginGroup("UILayout"); 145 qt_config->beginGroup("UILayout");
@@ -268,6 +275,11 @@ void Config::SaveValues() {
268 qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port); 275 qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);
269 qt_config->endGroup(); 276 qt_config->endGroup();
270 277
278 qt_config->beginGroup("WebService");
279 qt_config->setValue("telemetry_endpoint_url",
280 QString::fromStdString(Settings::values.telemetry_endpoint_url));
281 qt_config->endGroup();
282
271 qt_config->beginGroup("UI"); 283 qt_config->beginGroup("UI");
272 284
273 qt_config->beginGroup("UILayout"); 285 qt_config->beginGroup("UILayout");
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 0e4b85a76..4b83eeb28 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -73,7 +73,8 @@ namespace Log {
73 SUB(Audio, Sink) \ 73 SUB(Audio, Sink) \
74 CLS(Input) \ 74 CLS(Input) \
75 CLS(Network) \ 75 CLS(Network) \
76 CLS(Loader) 76 CLS(Loader) \
77 CLS(WebService)
77 78
78// GetClassName is a macro defined by Windows.h, grrr... 79// GetClassName is a macro defined by Windows.h, grrr...
79const char* GetLogClassName(Class log_class) { 80const char* GetLogClassName(Class log_class) {
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 8f13b80b3..fe4dfed69 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -91,6 +91,7 @@ enum class Class : ClassType {
91 Loader, ///< ROM loader 91 Loader, ///< ROM loader
92 Input, ///< Input emulation 92 Input, ///< Input emulation
93 Network, ///< Network emulation 93 Network, ///< Network emulation
94 WebService, ///< Interface to Citra Web Services
94 Count ///< Total number of logging classes 95 Count ///< Total number of logging classes
95}; 96};
96 97
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index ea09819e5..b80efe192 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -388,3 +388,6 @@ create_directory_groups(${SRCS} ${HEADERS})
388add_library(core STATIC ${SRCS} ${HEADERS}) 388add_library(core STATIC ${SRCS} ${HEADERS})
389target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) 389target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
390target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt) 390target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt)
391if (ENABLE_WEB_SERVICE)
392 target_link_libraries(core PUBLIC json-headers web_service)
393endif()
diff --git a/src/core/settings.h b/src/core/settings.h
index 03c64c94c..ee16bb90a 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -126,6 +126,9 @@ struct Values {
126 // Debugging 126 // Debugging
127 bool use_gdbstub; 127 bool use_gdbstub;
128 u16 gdbstub_port; 128 u16 gdbstub_port;
129
130 // WebService
131 std::string telemetry_endpoint_url;
129} extern values; 132} extern values;
130 133
131// a special value for Values::region_value indicating that citra will automatically select a region 134// a special value for Values::region_value indicating that citra will automatically select a region
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index ddc8b262e..70eff4340 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -7,12 +7,18 @@
7#include "common/scm_rev.h" 7#include "common/scm_rev.h"
8#include "core/telemetry_session.h" 8#include "core/telemetry_session.h"
9 9
10#ifdef ENABLE_WEB_SERVICE
11#include "web_service/telemetry_json.h"
12#endif
13
10namespace Core { 14namespace Core {
11 15
12TelemetrySession::TelemetrySession() { 16TelemetrySession::TelemetrySession() {
13 // TODO(bunnei): Replace with a backend that logs to our web service 17#ifdef ENABLE_WEB_SERVICE
18 backend = std::make_unique<WebService::TelemetryJson>();
19#else
14 backend = std::make_unique<Telemetry::NullVisitor>(); 20 backend = std::make_unique<Telemetry::NullVisitor>();
15 21#endif
16 // Log one-time session start information 22 // Log one-time session start information
17 const auto duration{std::chrono::steady_clock::now().time_since_epoch()}; 23 const auto duration{std::chrono::steady_clock::now().time_since_epoch()};
18 const auto start_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()}; 24 const auto start_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()};
diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt
new file mode 100644
index 000000000..334d82a8a
--- /dev/null
+++ b/src/web_service/CMakeLists.txt
@@ -0,0 +1,14 @@
1set(SRCS
2 telemetry_json.cpp
3 web_backend.cpp
4 )
5
6set(HEADERS
7 telemetry_json.h
8 web_backend.h
9 )
10
11create_directory_groups(${SRCS} ${HEADERS})
12
13add_library(web_service STATIC ${SRCS} ${HEADERS})
14target_link_libraries(web_service PUBLIC common cpr json-headers)
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
new file mode 100644
index 000000000..a2d007e77
--- /dev/null
+++ b/src/web_service/telemetry_json.cpp
@@ -0,0 +1,87 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/assert.h"
6#include "core/settings.h"
7#include "web_service/telemetry_json.h"
8#include "web_service/web_backend.h"
9
10namespace WebService {
11
12template <class T>
13void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) {
14 sections[static_cast<u8>(type)][name] = value;
15}
16
17void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) {
18 TopSection()[name] = sections[static_cast<unsigned>(type)];
19}
20
21void TelemetryJson::Visit(const Telemetry::Field<bool>& field) {
22 Serialize(field.GetType(), field.GetName(), field.GetValue());
23}
24
25void TelemetryJson::Visit(const Telemetry::Field<double>& field) {
26 Serialize(field.GetType(), field.GetName(), field.GetValue());
27}
28
29void TelemetryJson::Visit(const Telemetry::Field<float>& field) {
30 Serialize(field.GetType(), field.GetName(), field.GetValue());
31}
32
33void TelemetryJson::Visit(const Telemetry::Field<u8>& field) {
34 Serialize(field.GetType(), field.GetName(), field.GetValue());
35}
36
37void TelemetryJson::Visit(const Telemetry::Field<u16>& field) {
38 Serialize(field.GetType(), field.GetName(), field.GetValue());
39}
40
41void TelemetryJson::Visit(const Telemetry::Field<u32>& field) {
42 Serialize(field.GetType(), field.GetName(), field.GetValue());
43}
44
45void TelemetryJson::Visit(const Telemetry::Field<u64>& field) {
46 Serialize(field.GetType(), field.GetName(), field.GetValue());
47}
48
49void TelemetryJson::Visit(const Telemetry::Field<s8>& field) {
50 Serialize(field.GetType(), field.GetName(), field.GetValue());
51}
52
53void TelemetryJson::Visit(const Telemetry::Field<s16>& field) {
54 Serialize(field.GetType(), field.GetName(), field.GetValue());
55}
56
57void TelemetryJson::Visit(const Telemetry::Field<s32>& field) {
58 Serialize(field.GetType(), field.GetName(), field.GetValue());
59}
60
61void TelemetryJson::Visit(const Telemetry::Field<s64>& field) {
62 Serialize(field.GetType(), field.GetName(), field.GetValue());
63}
64
65void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) {
66 Serialize(field.GetType(), field.GetName(), field.GetValue());
67}
68
69void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) {
70 Serialize(field.GetType(), field.GetName(), std::string(field.GetValue()));
71}
72
73void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) {
74 Serialize(field.GetType(), field.GetName(), field.GetValue().count());
75}
76
77void TelemetryJson::Complete() {
78 SerializeSection(Telemetry::FieldType::App, "App");
79 SerializeSection(Telemetry::FieldType::Session, "Session");
80 SerializeSection(Telemetry::FieldType::Performance, "Performance");
81 SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
82 SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
83 SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
84 PostJson(Settings::values.telemetry_endpoint_url, TopSection().dump());
85}
86
87} // namespace WebService
diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h
new file mode 100644
index 000000000..39038b4f9
--- /dev/null
+++ b/src/web_service/telemetry_json.h
@@ -0,0 +1,54 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <string>
9#include <json.hpp>
10#include "common/telemetry.h"
11
12namespace WebService {
13
14/**
15 * Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the
16 * Citra web service
17 */
18class TelemetryJson : public Telemetry::VisitorInterface {
19public:
20 TelemetryJson() = default;
21 ~TelemetryJson() = default;
22
23 void Visit(const Telemetry::Field<bool>& field) override;
24 void Visit(const Telemetry::Field<double>& field) override;
25 void Visit(const Telemetry::Field<float>& field) override;
26 void Visit(const Telemetry::Field<u8>& field) override;
27 void Visit(const Telemetry::Field<u16>& field) override;
28 void Visit(const Telemetry::Field<u32>& field) override;
29 void Visit(const Telemetry::Field<u64>& field) override;
30 void Visit(const Telemetry::Field<s8>& field) override;
31 void Visit(const Telemetry::Field<s16>& field) override;
32 void Visit(const Telemetry::Field<s32>& field) override;
33 void Visit(const Telemetry::Field<s64>& field) override;
34 void Visit(const Telemetry::Field<std::string>& field) override;
35 void Visit(const Telemetry::Field<const char*>& field) override;
36 void Visit(const Telemetry::Field<std::chrono::microseconds>& field) override;
37
38 void Complete() override;
39
40private:
41 nlohmann::json& TopSection() {
42 return sections[static_cast<u8>(Telemetry::FieldType::None)];
43 }
44
45 template <class T>
46 void Serialize(Telemetry::FieldType type, const std::string& name, T value);
47
48 void SerializeSection(Telemetry::FieldType type, const std::string& name);
49
50 nlohmann::json output;
51 std::array<nlohmann::json, 7> sections;
52};
53
54} // namespace WebService
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
new file mode 100644
index 000000000..13e4555ac
--- /dev/null
+++ b/src/web_service/web_backend.cpp
@@ -0,0 +1,52 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <cpr/cpr.h>
6#include <stdlib.h>
7#include "common/logging/log.h"
8#include "web_service/web_backend.h"
9
10namespace WebService {
11
12static constexpr char API_VERSION[]{"1"};
13static constexpr char ENV_VAR_USERNAME[]{"CITRA_WEB_SERVICES_USERNAME"};
14static constexpr char ENV_VAR_TOKEN[]{"CITRA_WEB_SERVICES_TOKEN"};
15
16static std::string GetEnvironmentVariable(const char* name) {
17 const char* value{getenv(name)};
18 if (value) {
19 return value;
20 }
21 return {};
22}
23
24const std::string& GetUsername() {
25 static const std::string username{GetEnvironmentVariable(ENV_VAR_USERNAME)};
26 return username;
27}
28
29const std::string& GetToken() {
30 static const std::string token{GetEnvironmentVariable(ENV_VAR_TOKEN)};
31 return token;
32}
33
34void PostJson(const std::string& url, const std::string& data) {
35 if (url.empty()) {
36 LOG_ERROR(WebService, "URL is invalid");
37 return;
38 }
39
40 if (GetUsername().empty() || GetToken().empty()) {
41 LOG_ERROR(WebService, "Environment variables %s and %s must be set to POST JSON",
42 ENV_VAR_USERNAME, ENV_VAR_TOKEN);
43 return;
44 }
45
46 cpr::PostAsync(cpr::Url{url}, cpr::Body{data}, cpr::Header{{"Content-Type", "application/json"},
47 {"x-username", GetUsername()},
48 {"x-token", GetToken()},
49 {"api-version", API_VERSION}});
50}
51
52} // namespace WebService
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h
new file mode 100644
index 000000000..2753d3b68
--- /dev/null
+++ b/src/web_service/web_backend.h
@@ -0,0 +1,31 @@
1// Copyright 2017 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <string>
8#include "common/common_types.h"
9
10namespace WebService {
11
12/**
13 * Gets the current username for accessing services.citra-emu.org.
14 * @returns Username as a string, empty if not set.
15 */
16const std::string& GetUsername();
17
18/**
19 * Gets the current token for accessing services.citra-emu.org.
20 * @returns Token as a string, empty if not set.
21 */
22const std::string& GetToken();
23
24/**
25 * Posts JSON to services.citra-emu.org.
26 * @param url URL of the services.citra-emu.org endpoint to post data to.
27 * @param data String of JSON data to use for the body of the POST request.
28 */
29void PostJson(const std::string& url, const std::string& data);
30
31} // namespace WebService