From fa4caae02403f64aca99da275b6dd13a0164619f Mon Sep 17 00:00:00 2001 From: Paul Richmond Date: Thu, 21 Nov 2024 15:56:11 +0000 Subject: [PATCH] Improvements to Telemetry (#1252) Telemetry: Switch to ID per user rather than per build directory This improves accuracy of unique user counting, especially for python usrs. --------- Co-authored-by: Peter Heywood --- README.md | 2 +- include/flamegpu/io/Telemetry.h | 18 ++++++ src/flamegpu/io/Telemetry.cpp | 104 +++++++++++++++++++++++++++++++- 3 files changed, 122 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a25f4959d..d4edfd658 100644 --- a/README.md +++ b/README.md @@ -328,7 +328,7 @@ Information is collected when a simulation, ensemble or test suite run have comp The [TelemetryDeck](https://telemetrydeck.com/) service is used to store telemetry data. All data is sent to their API endpoint of https://nom.telemetrydeck.com/v1/ via https. For more details please review the [TelmetryDeck privacy policy](https://telemetrydeck.com/privacy/). -We do not collect any personal data such as usernames, email addresses or machine identifiers. +We do not collect any personal data such as usernames, email addresses or hardware identifiers but we do generate a random user identifier. This identifier is salted and hashed by Telemetry deck. More information can be found in the [FLAMEGPU documentation](https://docs.flamegpu.com/guide/telemetry). diff --git a/include/flamegpu/io/Telemetry.h b/include/flamegpu/io/Telemetry.h index 1b6c87481..efefadf46 100644 --- a/include/flamegpu/io/Telemetry.h +++ b/include/flamegpu/io/Telemetry.h @@ -84,6 +84,24 @@ static void encourageUsage(); * @return if telemetry is currently in test mode or not. */ static bool isTestMode(); + +/** + * Gets a cross platform location for storing user configuration data. Required to store a per user Id. On windows this uses the AppData folder (CSIDL_APPDATA) and in Linux this uses XDG_CONFIG_HOME. + * @return Root directory for storing configuration files + */ +static std::string getConfigDirectory(); + +/** + * Generates a randomised 36 character alphanumeric string for use as a User Id. + * @return A 36 character randomised alphanumeric string + */ +static std::string generateRandomId(); + +/** + * Obtains a unique user Id. If a configuration file (i.e. ${XDG_CONFIG_HOME}/flamegpu/telemetry_user.cfg on linux) exists this will be loaded from disk otherwise it will be generated and stored in the configuration location. If the configuration location is not writeable a new user Id will be generated each time. The user Id will be further obfuscated by Telemetry Deck which will salt and hash the Id. + * @return A 36 character randomised alphanumeric string representing a unique user Id + */ +static std::string getUserId(); }; } // namespace io } // namespace flamegpu diff --git a/src/flamegpu/io/Telemetry.cpp b/src/flamegpu/io/Telemetry.cpp index e298cb136..cc2b292ee 100644 --- a/src/flamegpu/io/Telemetry.cpp +++ b/src/flamegpu/io/Telemetry.cpp @@ -6,11 +6,22 @@ #include #include #include +#include +#include #include #include #include #include #include +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#endif #include "flamegpu/version.h" @@ -159,7 +170,7 @@ std::string Telemetry::generateData(std::string event_name, std::map distribution(0, sizeof(charset) - 2); + + std::string randomId; + for (size_t i = 0; i < length; ++i) { + randomId += charset[distribution(generator)]; + } + return randomId; +} + +std::string Telemetry::getUserId() { + // Generate and a new random 36-character user ID + std::string userId = Telemetry::generateRandomId(); + + // Try to load an existing ID from file if it exists + try { + // Determine config file location + std::string configDir = Telemetry::getConfigDirectory() + "/flamegpu"; + std::filesystem::create_directories(configDir); // Ensure the directory exists + std::string filePath = configDir + "/telemetry_user.cfg"; + + // Check if the file exists + if (std::filesystem::exists(filePath)) { + std::ifstream file(filePath, std::ios_base::binary); + if (file.is_open()) { + std::string cached_id; + std::getline(file, cached_id); // overwrite existing Id + file.close(); + // Config file and user id found so return it if not empty (either because file is externally modified or file is a directory) + if (!cached_id.empty()) + return cached_id; + else + return userId; + } + else { + throw std::runtime_error("Unable to open user ID file for reading"); + } + } + + std::ofstream file(filePath, std::ios_base::binary); + if (file.is_open()) { + file << userId; + file.close(); + } + else { + throw std::runtime_error("Unable to create user ID file"); + } + } catch (const std::exception&) { + fprintf(stderr, "Warning: Telemetry User Id file is not read/writeable from config file. A new User Id will be used.\n"); + } + return userId; +} + + } // namespace io } // namespace flamegpu