diff options
| author | bunnei <bunneidev@gmail.com> | 2017-07-12 21:31:12 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2017-07-12 21:31:12 -0400 | 
| commit | 9cf261ba8ba4fd9929d275cc793d48d13df624f3 (patch) | |
| tree | 2eb47ab96bde081a84c5dd7c8107a21cb8a3511c | |
| parent | 669757a97bb0b8324bf155ac84192e3975745789 (diff) | |
| parent | 28c35756790bfad06e339b82c551ab521268561b (diff) | |
Merge pull request #2819 from bunnei/telemetry-submit
Telemetry: Submit logged data to the Citra service
| -rw-r--r-- | .gitmodules | 6 | ||||
| -rw-r--r-- | CMakeLists.txt | 5 | ||||
| -rw-r--r-- | externals/CMakeLists.txt | 12 | ||||
| m--------- | externals/cpr | 0 | ||||
| m--------- | externals/json | 0 | ||||
| -rw-r--r-- | src/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | src/citra/config.cpp | 4 | ||||
| -rw-r--r-- | src/citra/default_ini.h | 4 | ||||
| -rw-r--r-- | src/citra_qt/configuration/config.cpp | 12 | ||||
| -rw-r--r-- | src/common/logging/backend.cpp | 3 | ||||
| -rw-r--r-- | src/common/logging/log.h | 1 | ||||
| -rw-r--r-- | src/core/CMakeLists.txt | 3 | ||||
| -rw-r--r-- | src/core/settings.h | 3 | ||||
| -rw-r--r-- | src/core/telemetry_session.cpp | 10 | ||||
| -rw-r--r-- | src/web_service/CMakeLists.txt | 14 | ||||
| -rw-r--r-- | src/web_service/telemetry_json.cpp | 87 | ||||
| -rw-r--r-- | src/web_service/telemetry_json.h | 54 | ||||
| -rw-r--r-- | src/web_service/web_backend.cpp | 52 | ||||
| -rw-r--r-- | src/web_service/web_backend.h | 31 | 
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 @@  [submodule "externals/enet"]      path = externals/enet      url = https://github.com/lsalzman/enet +[submodule "cpr"] +    path = externals/cpr +    url = https://github.com/whoshuu/cpr.git +[submodule "json"] +    path = externals/json +    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)  option(ENABLE_QT "Enable the Qt frontend" ON)  option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF) +option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) +  if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit)      message(STATUS "Copying pre-commit hook")      file(COPY hooks/pre-commit @@ -223,6 +225,9 @@ if (ENABLE_QT)      find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})  endif() +if (ENABLE_WEB_SERVICE) +    add_definitions(-DENABLE_WEB_SERVICE) +endif()  # Platform-specific library requirements  # ====================================== 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()  # ENet  add_subdirectory(enet)  target_include_directories(enet INTERFACE ./enet/include) + +if (ENABLE_WEB_SERVICE) +    # CPR +    option(BUILD_TESTING OFF) +    option(BUILD_CPR_TESTS OFF) +    add_subdirectory(cpr) +    target_include_directories(cpr INTERFACE ./cpr/include) + +    # JSON +    add_library(json-headers INTERFACE) +    target_include_directories(json-headers INTERFACE ./json/src) +endif() 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()  if (ENABLE_QT)      add_subdirectory(citra_qt)  endif() +if (ENABLE_WEB_SERVICE) +    add_subdirectory(web_service) +endif() 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() {      Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);      Settings::values.gdbstub_port =          static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689)); + +    // Web Service +    Settings::values.telemetry_endpoint_url = sdl2_config->Get( +        "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry");  }  void 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  # Port for listening to GDB connections.  use_gdbstub=false  gdbstub_port=24689 + +[WebService] +# Endpoint URL for submitting telemetry data +telemetry_endpoint_url =  )";  } 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() {      Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();      qt_config->endGroup(); +    qt_config->beginGroup("WebService"); +    Settings::values.telemetry_endpoint_url = +        qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry") +            .toString() +            .toStdString(); +    qt_config->endGroup(); +      qt_config->beginGroup("UI");      qt_config->beginGroup("UILayout"); @@ -268,6 +275,11 @@ void Config::SaveValues() {      qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);      qt_config->endGroup(); +    qt_config->beginGroup("WebService"); +    qt_config->setValue("telemetry_endpoint_url", +                        QString::fromStdString(Settings::values.telemetry_endpoint_url)); +    qt_config->endGroup(); +      qt_config->beginGroup("UI");      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 {      SUB(Audio, Sink)                                                                               \      CLS(Input)                                                                                     \      CLS(Network)                                                                                   \ -    CLS(Loader) +    CLS(Loader)                                                                                    \ +    CLS(WebService)  // GetClassName is a macro defined by Windows.h, grrr...  const 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 {      Loader,            ///< ROM loader      Input,             ///< Input emulation      Network,           ///< Network emulation +    WebService,        ///< Interface to Citra Web Services      Count              ///< Total number of logging classes  }; 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})  add_library(core STATIC ${SRCS} ${HEADERS})  target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)  target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt) +if (ENABLE_WEB_SERVICE) +    target_link_libraries(core PUBLIC json-headers web_service) +endif() 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 {      // Debugging      bool use_gdbstub;      u16 gdbstub_port; + +    // WebService +    std::string telemetry_endpoint_url;  } extern values;  // 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 @@  #include "common/scm_rev.h"  #include "core/telemetry_session.h" +#ifdef ENABLE_WEB_SERVICE +#include "web_service/telemetry_json.h" +#endif +  namespace Core {  TelemetrySession::TelemetrySession() { -    // TODO(bunnei): Replace with a backend that logs to our web service +#ifdef ENABLE_WEB_SERVICE +    backend = std::make_unique<WebService::TelemetryJson>(); +#else      backend = std::make_unique<Telemetry::NullVisitor>(); - +#endif      // 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()}; 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 @@ +set(SRCS +            telemetry_json.cpp +            web_backend.cpp +            ) + +set(HEADERS +            telemetry_json.h +            web_backend.h +            ) + +create_directory_groups(${SRCS} ${HEADERS}) + +add_library(web_service STATIC ${SRCS} ${HEADERS}) +target_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 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "core/settings.h" +#include "web_service/telemetry_json.h" +#include "web_service/web_backend.h" + +namespace WebService { + +template <class T> +void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) { +    sections[static_cast<u8>(type)][name] = value; +} + +void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) { +    TopSection()[name] = sections[static_cast<unsigned>(type)]; +} + +void TelemetryJson::Visit(const Telemetry::Field<bool>& field) { +    Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<double>& field) { +    Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<float>& field) { +    Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<u8>& field) { +    Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<u16>& field) { +    Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<u32>& field) { +    Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<u64>& field) { +    Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<s8>& field) { +    Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<s16>& field) { +    Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<s32>& field) { +    Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<s64>& field) { +    Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) { +    Serialize(field.GetType(), field.GetName(), field.GetValue()); +} + +void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) { +    Serialize(field.GetType(), field.GetName(), std::string(field.GetValue())); +} + +void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) { +    Serialize(field.GetType(), field.GetName(), field.GetValue().count()); +} + +void TelemetryJson::Complete() { +    SerializeSection(Telemetry::FieldType::App, "App"); +    SerializeSection(Telemetry::FieldType::Session, "Session"); +    SerializeSection(Telemetry::FieldType::Performance, "Performance"); +    SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback"); +    SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig"); +    SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem"); +    PostJson(Settings::values.telemetry_endpoint_url, TopSection().dump()); +} + +} // 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 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <string> +#include <json.hpp> +#include "common/telemetry.h" + +namespace WebService { + +/** + * Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the + * Citra web service + */ +class TelemetryJson : public Telemetry::VisitorInterface { +public: +    TelemetryJson() = default; +    ~TelemetryJson() = default; + +    void Visit(const Telemetry::Field<bool>& field) override; +    void Visit(const Telemetry::Field<double>& field) override; +    void Visit(const Telemetry::Field<float>& field) override; +    void Visit(const Telemetry::Field<u8>& field) override; +    void Visit(const Telemetry::Field<u16>& field) override; +    void Visit(const Telemetry::Field<u32>& field) override; +    void Visit(const Telemetry::Field<u64>& field) override; +    void Visit(const Telemetry::Field<s8>& field) override; +    void Visit(const Telemetry::Field<s16>& field) override; +    void Visit(const Telemetry::Field<s32>& field) override; +    void Visit(const Telemetry::Field<s64>& field) override; +    void Visit(const Telemetry::Field<std::string>& field) override; +    void Visit(const Telemetry::Field<const char*>& field) override; +    void Visit(const Telemetry::Field<std::chrono::microseconds>& field) override; + +    void Complete() override; + +private: +    nlohmann::json& TopSection() { +        return sections[static_cast<u8>(Telemetry::FieldType::None)]; +    } + +    template <class T> +    void Serialize(Telemetry::FieldType type, const std::string& name, T value); + +    void SerializeSection(Telemetry::FieldType type, const std::string& name); + +    nlohmann::json output; +    std::array<nlohmann::json, 7> sections; +}; + +} // 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 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cpr/cpr.h> +#include <stdlib.h> +#include "common/logging/log.h" +#include "web_service/web_backend.h" + +namespace WebService { + +static constexpr char API_VERSION[]{"1"}; +static constexpr char ENV_VAR_USERNAME[]{"CITRA_WEB_SERVICES_USERNAME"}; +static constexpr char ENV_VAR_TOKEN[]{"CITRA_WEB_SERVICES_TOKEN"}; + +static std::string GetEnvironmentVariable(const char* name) { +    const char* value{getenv(name)}; +    if (value) { +        return value; +    } +    return {}; +} + +const std::string& GetUsername() { +    static const std::string username{GetEnvironmentVariable(ENV_VAR_USERNAME)}; +    return username; +} + +const std::string& GetToken() { +    static const std::string token{GetEnvironmentVariable(ENV_VAR_TOKEN)}; +    return token; +} + +void PostJson(const std::string& url, const std::string& data) { +    if (url.empty()) { +        LOG_ERROR(WebService, "URL is invalid"); +        return; +    } + +    if (GetUsername().empty() || GetToken().empty()) { +        LOG_ERROR(WebService, "Environment variables %s and %s must be set to POST JSON", +                  ENV_VAR_USERNAME, ENV_VAR_TOKEN); +        return; +    } + +    cpr::PostAsync(cpr::Url{url}, cpr::Body{data}, cpr::Header{{"Content-Type", "application/json"}, +                                                               {"x-username", GetUsername()}, +                                                               {"x-token", GetToken()}, +                                                               {"api-version", API_VERSION}}); +} + +} // 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 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include "common/common_types.h" + +namespace WebService { + +/** + * Gets the current username for accessing services.citra-emu.org. + * @returns Username as a string, empty if not set. + */ +const std::string& GetUsername(); + +/** + * Gets the current token for accessing services.citra-emu.org. + * @returns Token as a string, empty if not set. + */ +const std::string& GetToken(); + +/** + * Posts JSON to services.citra-emu.org. + * @param url URL of the services.citra-emu.org endpoint to post data to. + * @param data String of JSON data to use for the body of the POST request. + */ +void PostJson(const std::string& url, const std::string& data); + +} // namespace WebService  | 
