You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/RADTelemetry/Code/Source/ProfileTelemetryComponent.cpp

345 lines
11 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifdef AZ_PROFILE_TELEMETRY
#include <AzCore/Debug/ProfilerBus.h>
#include <AzCore/PlatformIncl.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <RADTelemetry/ProfileTelemetry.h>
#include <RADTelemetry_Traits_Platform.h>
#include "ProfileTelemetryComponent.h"
namespace RADTelemetry
{
static const char * ProfileChannel = "RADTelemetry";
static const AZ::u32 MaxProfileThreadCount = 128;
static void MessageFrameTickType(AZ::Debug::ProfileFrameAdvanceType type)
{
const char * frameAdvanceTypeMessage = "Profile tick set to %s";
const char* frameAdvanceTypeString = (type == AZ::Debug::ProfileFrameAdvanceType::Game) ? "Game Thread" : "Render Frame";
AZ_Printf(ProfileChannel, frameAdvanceTypeMessage, frameAdvanceTypeString);
tmMessage(0, TMMF_SEVERITY_LOG, frameAdvanceTypeMessage, frameAdvanceTypeString);
}
ProfileTelemetryComponent::ProfileTelemetryComponent()
{
// Connecting in the constructor because we need to catch ALL created threads
AZStd::ThreadEventBus::Handler::BusConnect();
}
ProfileTelemetryComponent::~ProfileTelemetryComponent()
{
AZ_Assert(!m_running, "A telemetry session should not be open.");
AZStd::ThreadEventBus::Handler::BusDisconnect();
if (IsInitialized())
{
tmShutdown();
AZ_OS_FREE(m_buffer);
m_buffer = nullptr;
}
}
void ProfileTelemetryComponent::Activate()
{
AZ::Debug::ProfilerRequestBus::Handler::BusConnect();
ProfileTelemetryRequestBus::Handler::BusConnect();
AZ::SystemTickBus::Handler::BusConnect();
}
void ProfileTelemetryComponent::Deactivate()
{
AZ::SystemTickBus::Handler::BusDisconnect();
ProfileTelemetryRequestBus::Handler::BusDisconnect();
AZ::Debug::ProfilerRequestBus::Handler::BusDisconnect();
Disable();
}
void ProfileTelemetryComponent::OnThreadEnter(const AZStd::thread_id& id, const AZStd::thread_desc* desc)
{
(void)id;
(void)desc;
#if AZ_TRAIT_OS_USE_WINDOWS_THREADS
if (!desc)
{
// Skip unnamed threads
return;
}
if (IsInitialized())
{
// We can send the thread name to Telemetry now
const AZ::u32 newProfiledThreadCount = ++m_profiledThreadCount;
AZ_Assert(newProfiledThreadCount <= MaxProfileThreadCount, "RAD Telemetry profiled threadcount exceeded MaxProfileThreadCount!");
tmThreadName(0, id.m_id, desc->m_name);
return;
}
// Save off to send on the next connection
ScopedLock lock(m_threadNameLock);
auto end = m_threadNames.end();
auto itr = AZStd::find_if(m_threadNames.begin(), end, [id](const ThreadNameEntry& entry)
{
return entry.id == id;
});
if (itr != end)
{
itr->name = desc->m_name;
}
else
{
m_threadNames.push_back({ id, desc->m_name });
}
#else
const AZ::u32 newProfiledThreadCount = ++m_profiledThreadCount;
AZ_Assert(newProfiledThreadCount <= MaxProfileThreadCount, "RAD Telemetry profiled threadcount exceeded MaxProfileThreadCount!");
#endif
}
void ProfileTelemetryComponent::OnThreadExit(const AZStd::thread_id& id)
{
(void)id;
#if AZ_TRAIT_OS_USE_WINDOWS_THREADS
{
ScopedLock lock(m_threadNameLock);
auto end = m_threadNames.end();
auto itr = AZStd::find_if(m_threadNames.begin(), end, [id](const ThreadNameEntry& entry)
{
return entry.id == id;
});
if (itr != end)
{
m_threadNames.erase(itr);
}
else
{
// assume it was already sent on to RAD Telemetry
tmEndThread(0, id.m_id);
--m_profiledThreadCount;
}
}
#else
--m_profiledThreadCount;
#endif
}
void ProfileTelemetryComponent::OnSystemTick()
{
FrameAdvance(AZ::Debug::ProfileFrameAdvanceType::Game);
}
void ProfileTelemetryComponent::FrameAdvance(AZ::Debug::ProfileFrameAdvanceType type)
{
if (type == m_frameAdvanceType)
{
tmTick(0);
}
}
bool ProfileTelemetryComponent::IsActive()
{
return m_running;
}
void ProfileTelemetryComponent::ToggleEnabled()
{
Initialize();
if (!m_running)
{
Enable();
}
else
{
Disable();
}
}
tm_api* ProfileTelemetryComponent::GetApiInstance()
{
Initialize();
return TM_API_PTR;
}
void ProfileTelemetryComponent::Enable()
{
AZ_Printf(ProfileChannel, "Attempting to connect to the Telemetry server at %s:%d", m_address, m_port);
tmSetCaptureMask(m_captureMask);
tm_error result = tmOpen(
0, // unused
"ly", // program name, don't use slashes or weird character that will screw up a filename
__DATE__ " " __TIME__, // identifier, could be date time, or a build number ... whatever you want
m_address, // telemetry server address
TMCT_TCP, // network capture
m_port, // telemetry server port
AZ_TRAIT_RAD_TELEMETRY_OPEN_FLAGS,// flags
3000 // timeout in milliseconds ... pass -1 for infinite
);
switch (result)
{
case TM_OK:
{
m_running = true;
AZ_Printf(ProfileChannel, "Connected to the Telemetry server at %s:%d", m_address, m_port);
MessageFrameTickType(m_frameAdvanceType);
#if AZ_TRAIT_OS_USE_WINDOWS_THREADS
ScopedLock lock(m_threadNameLock);
for (const auto& threadNameEntry : m_threadNames)
{
const AZ::u32 newProfiledThreadCount = ++m_profiledThreadCount;
AZ_Assert(newProfiledThreadCount <= MaxProfileThreadCount, "RAD Telemetry profiled thread count exceeded MaxProfileThreadCount!");
tmThreadName(0, threadNameEntry.id.m_id, threadNameEntry.name.c_str());
}
m_threadNames.clear(); // Telemetry caches names so we can clear what we have sent on
#endif
break;
}
case TMERR_DISABLED:
AZ_Printf(ProfileChannel, "Telemetry is disabled via #define NTELEMETRY");
break;
case TMERR_UNINITIALIZED:
AZ_Printf(ProfileChannel, "tmInitialize failed or was not called");
break;
case TMERR_NETWORK_NOT_INITIALIZED:
AZ_Printf(ProfileChannel, "WSAStartup was not called before tmOpen! Call WSAStartup or pass TMOF_INIT_NETWORKING.");
break;
case TMERR_NULL_API:
AZ_Printf(ProfileChannel, "There is no Telemetry API (the DLL isn't in the EXE's path)!");
break;
case TMERR_COULD_NOT_CONNECT:
AZ_Printf(ProfileChannel, "Unable to connect to the Telemetry server at %s:%d (1. is it running? 2. check firewall settings)", m_address, m_port);
break;
case TMERR_UNKNOWN:
AZ_Printf(ProfileChannel, "Unknown error occurred");
break;
default:
AZ_Assert(false, "Unhandled tmOpen error case %d", result);
break;
}
}
void ProfileTelemetryComponent::Disable()
{
if (m_running)
{
m_running = false;
tmClose(0);
AZ_Printf(ProfileChannel, "Disconnected from the Telemetry server.");
}
}
TM_EXPORT_API tm_api* g_tm_api; // Required for the RAD Telemetry as static lib case
void ProfileTelemetryComponent::Initialize()
{
if (IsInitialized())
{
return;
}
tmLoadLibrary(TM_RELEASE);
if (!TM_API_PTR)
{
// Work around for UnixLike platforms that do not load RAD Telemetry static lib (they are incorrectly compiled with the dynamic library version of tmLoadLibrary. RAD is aware of the issue.)
TM_API_PTR = g_tm_api;
}
AZ_Assert(TM_API_PTR, "Invalid RAD Telemetry API pointer state");
tmSetMaxThreadCount(MaxProfileThreadCount);
const tm_int32 telemetryBufferSize = 16 * 1024 * 1024;
m_buffer = static_cast<char*>(AZ_OS_MALLOC(telemetryBufferSize, sizeof(void*)));
tmInitialize(telemetryBufferSize, m_buffer);
// Notify so individual modules can update their Telemetry pointer
AZ::Debug::ProfilerNotificationBus::Broadcast(&AZ::Debug::ProfilerNotifications::OnProfileSystemInitialized);
}
bool ProfileTelemetryComponent::IsInitialized() const {
return m_buffer != nullptr;
}
void ProfileTelemetryComponent::SetAddress(const char *address, AZ::u16 port)
{
m_address = address;
m_port = port;
}
void ProfileTelemetryComponent::SetCaptureMask(AZ::Debug::ProfileCategoryPrimitiveType mask)
{
m_captureMask = mask;
if (IsInitialized())
{
tmSetCaptureMask(m_captureMask);
}
}
void ProfileTelemetryComponent::SetFrameAdvanceType(AZ::Debug::ProfileFrameAdvanceType type)
{
if (type != m_frameAdvanceType)
{
MessageFrameTickType(type);
m_frameAdvanceType = type;
}
}
AZ::Debug::ProfileCategoryPrimitiveType ProfileTelemetryComponent::GetDefaultCaptureMaskInternal()
{
using MaskType = AZ::Debug::ProfileCategoryPrimitiveType;
// Set all the category bits "below" FirstDetailedCategory and do not enable memory capture by default
return (static_cast<MaskType>(1) << static_cast<MaskType>(FirstDetailedCategory)) - 1;
}
AZ::Debug::ProfileCategoryPrimitiveType ProfileTelemetryComponent::GetDefaultCaptureMask()
{
return GetDefaultCaptureMaskInternal();
}
AZ::Debug::ProfileCategoryPrimitiveType ProfileTelemetryComponent::GetCaptureMask()
{
return m_captureMask;
}
void ProfileTelemetryComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ProfileTelemetryComponent, AZ::Component>()
->Version(1)
;
}
}
void ProfileTelemetryComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ProfilerService"));
}
}
#endif