You're breathtaking!
This commit is contained in:
270
Source/Editor/Analytics/EditorAnalytics.cpp
Normal file
270
Source/Editor/Analytics/EditorAnalytics.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "EditorAnalytics.h"
|
||||
#include "EditorAnalyticsController.h"
|
||||
#include "Engine/Threading/Threading.h"
|
||||
#include "Engine/Platform/FileSystem.h"
|
||||
#include "Engine/Core/Log.h"
|
||||
#include "Engine/Core/Math/Vector2.h"
|
||||
#include "Engine/Core/Types/DateTime.h"
|
||||
#include "Editor/Editor.h"
|
||||
#include "Engine/Engine/EngineService.h"
|
||||
#include "Engine/Graphics/GPUDevice.h"
|
||||
#include "Engine/Utilities/StringConverter.h"
|
||||
#include "FlaxEngine.Gen.h"
|
||||
#include <ThirdParty/UniversalAnalytics/universal-analytics.h>
|
||||
|
||||
#define FLAX_EDITOR_GOOGLE_ID "UA-88357703-3"
|
||||
|
||||
// Helper doc: https://developers.google.com/analytics/devguides/collection/protocol/v1/parameters
|
||||
|
||||
namespace EditorAnalyticsImpl
|
||||
{
|
||||
UATracker Tracker = nullptr;
|
||||
|
||||
StringAnsi ClientId;
|
||||
StringAnsi ProjectName;
|
||||
StringAnsi ScreenResolution;
|
||||
StringAnsi UserLanguage;
|
||||
StringAnsi GPU;
|
||||
DateTime SessionStartTime;
|
||||
|
||||
CriticalSection Locker;
|
||||
bool IsSessionActive = false;
|
||||
EditorAnalyticsController Controller;
|
||||
Array<char> TmpBuffer;
|
||||
}
|
||||
|
||||
using namespace EditorAnalyticsImpl;
|
||||
|
||||
class EditorAnalyticsService : public EngineService
|
||||
{
|
||||
public:
|
||||
|
||||
EditorAnalyticsService()
|
||||
: EngineService(TEXT("Editor Analytics"))
|
||||
{
|
||||
}
|
||||
|
||||
bool Init() override;
|
||||
void Dispose() override;
|
||||
};
|
||||
|
||||
EditorAnalyticsService EditorAnalyticsServiceInstance;
|
||||
|
||||
bool EditorAnalytics::IsSessionActive()
|
||||
{
|
||||
return EditorAnalyticsImpl::IsSessionActive;
|
||||
}
|
||||
|
||||
void EditorAnalytics::StartSession()
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
if (EditorAnalyticsImpl::IsSessionActive)
|
||||
return;
|
||||
|
||||
// Prepare client metadata
|
||||
if (ClientId.IsEmpty())
|
||||
{
|
||||
ClientId = Platform::GetUniqueDeviceId().ToString(Guid::FormatType::N).ToStringAnsi();
|
||||
}
|
||||
if (ScreenResolution.IsEmpty())
|
||||
{
|
||||
const Vector2 desktopSize = Platform::GetDesktopSize();
|
||||
ScreenResolution = StringAnsi(StringUtils::ToString((int32)desktopSize.X)) + "x" + StringAnsi(StringUtils::ToString((int32)desktopSize.Y));
|
||||
}
|
||||
if (UserLanguage.IsEmpty())
|
||||
{
|
||||
UserLanguage = Platform::GetUserLocaleName().ToStringAnsi();
|
||||
}
|
||||
if (GPU.IsEmpty())
|
||||
{
|
||||
const auto gpu = GPUDevice::Instance;
|
||||
if (gpu && gpu->GetState() == GPUDevice::DeviceState::Ready)
|
||||
GPU = StringAsANSI<>(gpu->GetAdapter()->GetDescription().GetText()).Get();
|
||||
}
|
||||
if (ProjectName.IsEmpty())
|
||||
{
|
||||
ProjectName = Editor::Project->Name.ToStringAnsi();
|
||||
}
|
||||
SessionStartTime = DateTime::Now();
|
||||
|
||||
// Initialize the analytics tracker
|
||||
Tracker = createTracker(FLAX_EDITOR_GOOGLE_ID, ClientId.Get(), nullptr);
|
||||
Tracker->user_agent = "Flax Editor";
|
||||
|
||||
// Store these options permanently (for the lifetime of the tracker)
|
||||
setTrackerOption(Tracker, UA_OPTION_QUEUE, 1);
|
||||
UASettings GlobalSettings =
|
||||
{
|
||||
{
|
||||
{ UA_DOCUMENT_PATH, 0, "Flax Editor" },
|
||||
{ UA_DOCUMENT_TITLE, 0, "Flax Editor" },
|
||||
#if PLATFORM_WINDOWS
|
||||
{ UA_USER_AGENT, 0, "Windows " FLAXENGINE_VERSION_TEXT },
|
||||
#else
|
||||
#error "Unknown platform"
|
||||
#endif
|
||||
{ UA_ANONYMIZE_IP, 0, "0" },
|
||||
{ UA_APP_ID, 0, "Flax Editor " FLAXENGINE_VERSION_TEXT },
|
||||
{ UA_APP_INSTALLER_ID, 0, "Flax Editor" },
|
||||
{ UA_APP_NAME, 0, "Flax Editor" },
|
||||
{ UA_APP_VERSION, 0, FLAXENGINE_VERSION_TEXT },
|
||||
{ UA_SCREEN_NAME, 0, "Flax Editor " FLAXENGINE_VERSION_TEXT },
|
||||
{ UA_SCREEN_RESOLUTION, 0, ScreenResolution.Get() },
|
||||
{ UA_USER_LANGUAGE, 0, UserLanguage.Get() },
|
||||
}
|
||||
};
|
||||
setParameters(Tracker, &GlobalSettings);
|
||||
|
||||
// Send the initial session event
|
||||
UAOptions sessionViewOptions =
|
||||
{
|
||||
{
|
||||
{ UA_EVENT_CATEGORY, 0, "Session" },
|
||||
{ UA_EVENT_ACTION, 0, "Start Editor" },
|
||||
{ UA_EVENT_LABEL, 0, "Start Editor" },
|
||||
{ UA_SESSION_CONTROL, 0, "start" },
|
||||
{ UA_DOCUMENT_TITLE, 0, ProjectName.Get() },
|
||||
}
|
||||
};
|
||||
sendTracking(Tracker, UA_SCREENVIEW, &sessionViewOptions);
|
||||
|
||||
EditorAnalyticsImpl::IsSessionActive = true;
|
||||
|
||||
Controller.Init();
|
||||
|
||||
// Report GPU model
|
||||
if (GPU.HasChars())
|
||||
{
|
||||
SendEvent("Telemetry", "GPU.Model", GPU.Get());
|
||||
}
|
||||
}
|
||||
|
||||
void EditorAnalytics::EndSession()
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
if (!EditorAnalyticsImpl::IsSessionActive)
|
||||
return;
|
||||
|
||||
Controller.Cleanup();
|
||||
|
||||
StringAnsi sessionLength = StringAnsi::Format("{0}", (int32)(DateTime::Now() - SessionStartTime).GetTotalSeconds());
|
||||
|
||||
// Send the end session event
|
||||
UAOptions sessionEventOptions =
|
||||
{
|
||||
{
|
||||
{ UA_EVENT_CATEGORY, 0, "Session" },
|
||||
{ UA_EVENT_ACTION, 0, "Session Length" },
|
||||
{ UA_EVENT_LABEL, 0, "Session Length" },
|
||||
{ UA_EVENT_VALUE, 0, sessionLength.Get() },
|
||||
{ UA_CUSTOM_DIMENSION, 1, "Session Length" },
|
||||
{ UA_CUSTOM_METRIC, 1, sessionLength.Get() },
|
||||
}
|
||||
};
|
||||
sendTracking(Tracker, UA_EVENT, &sessionEventOptions);
|
||||
|
||||
// Send the end session event
|
||||
UAOptions sessionViewOptions =
|
||||
{
|
||||
{
|
||||
{ UA_EVENT_CATEGORY, 0, "Session" },
|
||||
{ UA_EVENT_ACTION, 0, "End Editor" },
|
||||
{ UA_EVENT_LABEL, 0, "End Editor" },
|
||||
{ UA_EVENT_VALUE, 0, sessionLength.Get() },
|
||||
{ UA_SESSION_CONTROL, 0, "end" },
|
||||
}
|
||||
};
|
||||
sendTracking(Tracker, UA_SCREENVIEW, &sessionViewOptions);
|
||||
|
||||
// Cleanup
|
||||
removeTracker(Tracker);
|
||||
Tracker = nullptr;
|
||||
|
||||
EditorAnalyticsImpl::IsSessionActive = false;
|
||||
}
|
||||
|
||||
void EditorAnalytics::SendEvent(const char* category, const char* name, const char* label)
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
if (!EditorAnalyticsImpl::IsSessionActive)
|
||||
return;
|
||||
|
||||
UAOptions opts =
|
||||
{
|
||||
{
|
||||
{ UA_EVENT_CATEGORY, 0, (char*)category },
|
||||
{ UA_EVENT_ACTION, 0, (char*)name },
|
||||
{ UA_EVENT_LABEL, 0, (char*)label },
|
||||
}
|
||||
};
|
||||
|
||||
sendTracking(Tracker, UA_EVENT, &opts);
|
||||
}
|
||||
|
||||
void EditorAnalytics::SendEvent(const char* category, const char* name, const StringView& label)
|
||||
{
|
||||
SendEvent(category, name, label.Get());
|
||||
}
|
||||
|
||||
void EditorAnalytics::SendEvent(const char* category, const char* name, const Char* label)
|
||||
{
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
if (!EditorAnalyticsImpl::IsSessionActive)
|
||||
return;
|
||||
|
||||
ASSERT(category && name && label);
|
||||
|
||||
const int32 labelLength = StringUtils::Length(label);
|
||||
TmpBuffer.Clear();
|
||||
TmpBuffer.Resize(labelLength + 1);
|
||||
StringUtils::ConvertUTF162ANSI(label, TmpBuffer.Get(), labelLength);
|
||||
TmpBuffer[labelLength] = 0;
|
||||
|
||||
UAOptions opts =
|
||||
{
|
||||
{
|
||||
{ UA_EVENT_CATEGORY, 0, (char*)category },
|
||||
{ UA_EVENT_ACTION, 0, (char*)name },
|
||||
{ UA_EVENT_LABEL, 0, (char*)TmpBuffer.Get() },
|
||||
}
|
||||
};
|
||||
|
||||
sendTracking(Tracker, UA_EVENT, &opts);
|
||||
}
|
||||
|
||||
bool EditorAnalyticsService::Init()
|
||||
{
|
||||
#if COMPILE_WITH_DEV_ENV
|
||||
// Disable analytics from the dev (internal) builds
|
||||
LOG(Info, "Editor analytics service is disabled in dev builds.");
|
||||
return false;
|
||||
#endif
|
||||
|
||||
// If file '<editor_install_root>/noTracking' exists then do not use analytics (per engine instance)
|
||||
// If file '%appdata/Flax/noTracking' exists then do not use analytics (globally)
|
||||
String appDataPath;
|
||||
FileSystem::GetSpecialFolderPath(SpecialFolder::AppData, appDataPath);
|
||||
if (FileSystem::FileExists(Globals::StartupFolder / TEXT("noTracking")) ||
|
||||
FileSystem::FileExists(appDataPath / TEXT("Flax/noTracking")))
|
||||
{
|
||||
LOG(Info, "Editor analytics service is disabled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG(Info, "Editor analytics service is enabled. Curl version: {0}", TEXT(LIBCURL_VERSION));
|
||||
|
||||
EditorAnalytics::StartSession();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void EditorAnalyticsService::Dispose()
|
||||
{
|
||||
EditorAnalytics::EndSession();
|
||||
}
|
||||
Reference in New Issue
Block a user