/
BirdLeon
/
ROBLOX2016
Обзор
Документация
Войти
/
BirdLeon
/
ROBLOX2016
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
App/util/Analytics.cpp
536 строк
15 KB
PatoFlamejanteTV
full source code
19 дек 2024, 19:11
19 дек 2024, 19:11
05db15d
Код
Авторство
О чём код?
#include "stdafx.h" #include "Util/Analytics.h" #include "util/ThreadPool.h" #include "Util/Http.h" #include "Util/Statistics.h" #include "Util/RobloxGoogleAnalytics.h" #include "v8datamodel/GameBasicSettings.h" #include "v8datamodel/DebugSettings.h" #include "RobloxServicesTools.h" #include <boost/algorithm/string.hpp> using namespace RBX; DYNAMIC_LOGVARIABLE(AnalyticsLog, 0) // google analytics DYNAMIC_LOGGROUP(GoogleAnalyticsTracking) FASTFLAG(GoogleAnalyticsTrackingEnabled) FASTFLAG(DebugAnalyticsForceLotteryWin) DYNAMIC_FASTSTRING(RobloxAnalyticsURL) DYNAMIC_FASTFLAG(RobloxAnalyticsTrackingEnabled) DYNAMIC_FASTFLAG(DebugAnalyticsSendUserId) FASTFLAG(SendStudioEventsWithStudioSID) FASTFLAG(UseBuildGenericGameUrl) // influx DYNAMIC_FASTSTRING(HttpInfluxURL) DYNAMIC_FASTSTRING(HttpInfluxDatabase) DYNAMIC_FASTSTRING(HttpInfluxUser) DYNAMIC_FASTSTRING(HttpInfluxPassword) DYNAMIC_FASTFLAGVARIABLE(InfluxDb09Enabled, false) namespace { // http utils const int kNumberThreadPoolThreads = 16; ThreadPool defaultThreadPool(kNumberThreadPoolThreads, BaseThreadPool::NoAction, 500); void httpHandler(std::string url, std::string params, std::string optionalContentType, bool isGet, bool externalRequest) { std::istringstream sparams(params); std::string response; std::string fullUrl = isGet ? format("%s?%s", url.c_str(), params.c_str()) : url; RBX::Http http(fullUrl); http.recordStatistics = false; http.doNotUseCachedResponse = true; try { if(isGet) http.get(response); else http.post(sparams, optionalContentType.empty() ? Http::kContentTypeUrlEncoded : optionalContentType, params.size() > 256, response, externalRequest); } catch (const RBX::base_exception &ex) { FASTLOGS(DFLog::AnalyticsLog, "Exception in analytics httpHandler: %s", ex.what()); } } void postOrGet(ThreadPool& threadPool, const std::string& url, const std::string& data, const std::string& optionalContentType, bool blocking, bool isGet, bool externalRequest) { if (blocking) { httpHandler(url, data, optionalContentType, isGet, externalRequest); } else { bool didSchedule = threadPool.schedule(boost::bind(&httpHandler, url, data, optionalContentType, isGet, externalRequest)); RBXASSERT(didSchedule); if (!didSchedule) { FASTLOGS(DFLog::AnalyticsLog, "Analytics task was not scheduled in the threadpool: %s", data.c_str()); } } } } namespace RBX { namespace Analytics { static std::string appVersion; static std::string reporter; static std::string location; static std::string userId; static std::string placeId; std::string sanitizeParam(const std::string& p) { std::string result = p; result.erase(std::remove_if(result.begin(), result.end(), boost::is_any_of(", ")), result.end()); return result; } void setUserId(int id) { userId = RBX::format("%d", id); } void setPlaceId(int id) { placeId = RBX::format("%d", id); } void setAppVersion(const std::string& version) { appVersion = sanitizeParam(version); } void setLocation(const std::string& loc) { location = sanitizeParam(loc); } void setReporter(const std::string& rep) { reporter = sanitizeParam(rep); } namespace EphemeralCounter { const std::string countersApiKey = "76E5A40C-3AE1-4028-9F10-7C62520BD94F"; ThreadPool countersThreadPool(kNumberThreadPoolThreads, BaseThreadPool::NoAction, 500); void reportStats(const std::string& category, float value, bool blocking) { std::string baseUrl = ::GetBaseURL(); if (baseUrl[baseUrl.size() - 1] != '/') baseUrl += '/'; std::string valueStr = RBX::format("%f", value); std::string url; if (FFlag::UseBuildGenericGameUrl) { url = BuildGenericGameUrl(baseUrl, RBX::format("game/report-stats?name=%s&value=%s", Http::urlEncode(category).c_str(), Http::urlEncode(valueStr).c_str())); } else { url = RBX::format("%sgame/report-stats?name=%s&value=%s", baseUrl.c_str(), Http::urlEncode(category).c_str(), Http::urlEncode(valueStr).c_str()); } // post postOrGet(countersThreadPool, url, "", Http::kContentTypeUrlEncoded, blocking, false, false); } void reportCountersCSV(const std::string& counterNamesCSV, bool blocking) { std::string counterUrl = GetCountersMultiIncrementUrl(::GetBaseURL(), countersApiKey); std::string data = "counterNamesCsv=" + counterNamesCSV; // post postOrGet(countersThreadPool, counterUrl, data, Http::kContentTypeUrlEncoded, blocking, false, false); } void reportCounter(const std::string& counterName, int amount, bool blocking) { std::string counterUrl = GetCountersUrl(::GetBaseURL(), countersApiKey); std::string url = RBX::format("%s&counterName=%s&amount=%d", counterUrl.c_str(), counterName.c_str(), amount); // post postOrGet(countersThreadPool, url, "", Http::kContentTypeUrlEncoded, blocking, false, false); } } // namespace EphemeralCounter namespace GoogleAnalytics { bool initialized = false; bool canUseGA = false; bool canUserRobloxEvents = false; std::string googleClientID; std::string googleAccountPropertyID; std::string robloxProductName; std::string experimentString; std::string robloxSessionKey; static rbx::signal<void()> analyticTrackedSignalVar; bool atteptedToUseBeforeInit = false; std::string generateClientID() { RBX::Guid guid; std::string clientGUID; guid.generateStandardGUID(clientGUID); // OS Generated GUIDs come in braced form ie "{f81d4fae-7dec-11d0-a765-00a0c91e6bf6}" // whereas RFC 4122 http://www.ietf.org/rfc/rfc4122.txt is non-braced. clientGUID.erase(std::find(clientGUID.begin(), clientGUID.end(), '{')); clientGUID.erase(std::find(clientGUID.begin(), clientGUID.end(), '}')); return clientGUID; } inline std::string googleCollectionParams(const std::string &hitType) { std::stringstream ss; ss << "v=1" << "&tid=" << googleAccountPropertyID << "&cid=" << googleClientID << "&t=" << hitType; if (DFFlag::DebugAnalyticsSendUserId && userId.length() > 0) ss << "&userId=" << userId; return ss.str(); } void lotteryInit(const std::string &accountPropertyID, int lotteryThreshold, const std::string& productName /*= ""*/, int robloxAnalyticsLottery /* = -1 */, const std::string &sessionKey /* = "sessionID=" */) { GoogleAnalytics::init(accountPropertyID, productName); robloxSessionKey = sessionKey; if(robloxAnalyticsLottery == -1) { robloxAnalyticsLottery = lotteryThreshold; } std::size_t lottery = boost::hash_value(googleClientID) % 100; FASTLOG1(DFLog::GoogleAnalyticsTracking, "Google analytics lottery number = %d", lottery); // initialize google analytics if (lottery < (std::size_t)lotteryThreshold || FFlag::DebugAnalyticsForceLotteryWin) { canUseGA = true; } if (lottery < (std::size_t)robloxAnalyticsLottery || FFlag::DebugAnalyticsForceLotteryWin) { canUserRobloxEvents = true; } } void init(const std::string &accountPropertyID, const std::string& productName) { if(atteptedToUseBeforeInit) RBXASSERT(false); // Did you try to send events before analytics is initialized? Set breakpoints to trackEvent* to find out. if (initialized) { FASTLOGS(DFLog::GoogleAnalyticsTracking, "%s", "Google analytics already initialized!"); return; } robloxProductName = productName; googleAccountPropertyID = accountPropertyID; RBXASSERT(googleAccountPropertyID.length() > 0); std::string clientId = GameBasicSettings::singleton().getGoogleAnalyticsClientId(); if (clientId.length() == 0) { // generate and store new client ID clientId = generateClientID(); GameBasicSettings::singleton().setGoogleAnalyticsClientId(clientId); } googleClientID = clientId; RBXASSERT(googleClientID.length() > 0); initialized = true; } bool getCanUse() { return canUseGA || canUserRobloxEvents; } void setCanUse() { canUseGA = true; canUserRobloxEvents = true; } void sendEventGA(const char* category, const char* action, const char* label, int value, bool sync) { std::stringstream params; params << googleCollectionParams("event") << "&ec=" << category << "&ea=" << action << "&ev=" << value << "&el=" << label; postOrGet(defaultThreadPool, RobloxGoogleAnalytics::kGoogleAnalyticsBaseURL, params.str(), Http::kContentTypeDefaultUnspecified, sync, false, true); } void sendEventRoblox(const char* category, const char* action, const char* label, int value, bool sync) { if (!initialized) { atteptedToUseBeforeInit = true; return; } if(DFFlag::RobloxAnalyticsTrackingEnabled && !robloxProductName.empty()) { std::stringstream params; params << (FFlag::SendStudioEventsWithStudioSID ? robloxSessionKey : "sessionId=") << googleClientID << "&userID=" << userId << "&placeID=" << placeId << "&category=" << category << "&evt=" << action << "&label=" << label << "&value=" << value << experimentString; postOrGet(defaultThreadPool, RBX::format("%s%s/e.png",DFString::RobloxAnalyticsURL.c_str(), robloxProductName.c_str()), params.str(), Http::kContentTypeDefaultUnspecified, sync, true, true); } } void trackEvent(const char *category, const char *action, const char *label, int value, bool sync) { if (!initialized) { atteptedToUseBeforeInit = true; return; } if(canUseGA) { sendEventGA(category, action, label, value, sync); } if(canUserRobloxEvents) { sendEventRoblox(category, action, label, value, sync); } analyticTrackedSignalVar(); } void trackEventWithoutThrottling(const char* category, const char* action, const char* label, int value, bool sync) { if (!initialized) { atteptedToUseBeforeInit = true; return; } sendEventGA(category, action, label, value, sync); sendEventRoblox(category, action, label, value, sync); analyticTrackedSignalVar(); } void trackUserTiming(const char *category, const char *variable, int milliseconds, const char *label, bool sync) { if (!initialized || !canUseGA) { return; } std::stringstream params; params << googleCollectionParams("timing") << "&utc=" << category << "&utv=" << variable << "&utt=" << milliseconds << "&utl=" << label; postOrGet(defaultThreadPool, RobloxGoogleAnalytics::kGoogleAnalyticsBaseURL, params.str(), Http::kContentTypeDefaultUnspecified, sync, false, true); analyticTrackedSignalVar(); } rbx::signal<void()>& analyticTrackedSignal() { return analyticTrackedSignalVar; } void setExperimentVariation(const std::string& name, int value) { std::stringstream s; s << "&experiment=" << name << "&variation=" << value; experimentString = s.str(); } const std::string& getSessionId() { return googleClientID; } } // namespace GoogleAnalytics namespace InfluxDb { int r = 10000; // throttle std::size_t hash_value(const Point& p) { return boost::hash<std::string>()(p.name); } void init() { r = rand() % 10000; } bool canSend(int throttleHundredthsPercentage) { return r < throttleHundredthsPercentage && !DFString::HttpInfluxURL.empty(); } std::string escapeTag(std::string tag) { std::string s = tag; boost::replace_all(s, " ", "\\ "); boost::replace_all(s, ",", "\\,"); return s; } using namespace RBX::Analytics::InfluxDb; void reportPointsV2(const std::string& resource, const boost::unordered_set<Point>& points, int throttleHundredthsPercentage, bool blocking, const std::string& userIdOverride) { if (!canSend(throttleHundredthsPercentage)) return; RBXASSERT(!points.empty()); // Pushes to InfluxDB std::string url = DFString::HttpInfluxURL + "/write?db=" + DFString::HttpInfluxDatabase + "&u=" + DFString::HttpInfluxUser + "&p=" + DFString::HttpInfluxPassword; // format: https://docs.influxdata.com/influxdb/v0.10/write_protocols/line/ // <measurement>[,<tag-key>=<tag-value>...] <field-key>=<field-value>[,<field2-key>=<field2-value>...] [unix-nano-timestamp] // convert all guest IDs to -1 std::string uid = (userIdOverride.empty() ? (!userId.empty() ? userId.c_str() : "0") : userIdOverride.c_str()); if (atoi(uid.c_str()) < 0) uid = "-1"; // tags std::stringstream ss; ss << RBX::format("%s,placeid=%s,userid=%s,reporter=%s,platform=%s,device=%s,appversion=%s,location=%s,osversion=%s ", resource.c_str(), (!placeId.empty() ? placeId.c_str() : "0"), uid.c_str(), escapeTag(reporter).c_str(), escapeTag(DebugSettings::singleton().osPlatform()).c_str(), escapeTag(DebugSettings::singleton().deviceName()).c_str(), escapeTag(appVersion).c_str(), escapeTag(location).c_str(), escapeTag(DebugSettings::singleton().osVer()).c_str()); // fields boost::unordered_set<Point>::const_iterator it = points.begin(); for (int index = 0; points.end() != it; ++it, ++index) { if (index > 0) ss << ','; ss << it->name << '=' << it->json; } postOrGet(defaultThreadPool, url, ss.str(), "", blocking, false, true); } void reportPoints(const std::string& resource, const boost::unordered_set<Point>& points, int throttleHundredthsPercentage, bool blocking, const std::string& userIdOverride) { if (DFFlag::InfluxDb09Enabled) { reportPointsV2(resource, points, throttleHundredthsPercentage, blocking, userIdOverride); return; } if (!canSend(throttleHundredthsPercentage)) return; RBXASSERT(!points.empty()); // Pushes to InfluxDB. std::string url = DFString::HttpInfluxURL + "/db/" + DFString::HttpInfluxDatabase + "/series?u=" + DFString::HttpInfluxUser + "&p=" + Http::urlEncode(DFString::HttpInfluxPassword); std::string json = "[{"; json += "\"name\":\"" + resource + "\","; json += "\"columns\":[\"placeid\",\"platform\",\"device\",\"osversion\",\"appversion\",\"userid\",\"reporter\",\"location\""; for (boost::unordered_set<Point>::const_iterator it = points.begin(); points.end() != it; ++it) { json += ",\"" + it->name + "\""; } json += "],"; // two levels are here because we are actually sending a list of several points, // even though this function only handles one row of points at a time json += "\"points\":[["; json += (!placeId.empty() ? placeId : "0"); json += ",\"" + DebugSettings::singleton().osPlatform() + "\""; json += ",\"" + DebugSettings::singleton().deviceName() + "\""; json += ",\"" + DebugSettings::singleton().osVer() + "\""; json += ",\"" + appVersion + "\""; if (!userIdOverride.empty()) json += "," + userIdOverride; else json += "," + (!userId.empty() ? userId : "0"); json += ",\"" + reporter + "\""; json += ",\"" + location + "\""; for (boost::unordered_set<Point>::const_iterator it = points.begin(); points.end() != it; ++it) { json += "," + it->json; } json += "]]"; json += "}]"; postOrGet(defaultThreadPool, url, json, Http::kContentTypeApplicationJson, blocking, false, true); } } // namespace InfluxDb } // namespace ExternalAnalytics } // namespace RBX