[development] Consolidated programmatic profiler capture API (#4969)
Merged overlapping profiler EBuses/Interfaces into AzCore Fixed ambiguous LogLevel type in some unity file scenarios Added cvar/console access for profiler capture Added utilities for reflecting AZ::Interfaces through the BehaviorContext Included profiler system sample python script Signed-off-by: AMZN-ScottR 24445312+AMZN-ScottR@users.noreply.github.com
This commit is contained in:
@@ -1175,7 +1175,10 @@ namespace PhysX
|
||||
using physx::PxGeometryType;
|
||||
|
||||
bool isProfilingActive = false;
|
||||
AZ::Debug::ProfilerRequestBus::BroadcastResult(isProfilingActive, &AZ::Debug::ProfilerRequests::IsActive);
|
||||
if (auto profilerSystem = AZ::Debug::ProfilerSystemInterface::Get(); profilerSystem)
|
||||
{
|
||||
isProfilingActive = profilerSystem->IsActive();
|
||||
}
|
||||
|
||||
if (!isProfilingActive)
|
||||
{
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/std/string/string.h>
|
||||
|
||||
namespace Profiler
|
||||
{
|
||||
class ProfilerRequests
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(ProfilerRequests, "{3757c4e5-1941-457c-85ae-16305e17a4c6}");
|
||||
virtual ~ProfilerRequests() = default;
|
||||
|
||||
//! Enable/Disable the CpuProfiler
|
||||
virtual void SetProfilerEnabled(bool enabled) = 0;
|
||||
|
||||
//! Dump a single frame of Cpu profiling data
|
||||
virtual bool CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath) = 0;
|
||||
|
||||
//! Start a multiframe capture of CPU profiling data.
|
||||
virtual bool BeginContinuousCpuProfilingCapture() = 0;
|
||||
|
||||
//! End and dump an in-progress continuous capture.
|
||||
virtual bool EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath) = 0;
|
||||
};
|
||||
|
||||
class ProfilerBusTraits
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
// EBusTraits overrides
|
||||
static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
|
||||
};
|
||||
|
||||
class ProfilerNotifications
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
virtual ~ProfilerNotifications() = default;
|
||||
|
||||
//! Notify when the current CpuProfilingStatistics capture is finished
|
||||
//! @param result Set to true if it's finished successfully
|
||||
//! @param info The output file path or error information which depends on the return.
|
||||
virtual void OnCaptureCpuProfilingStatisticsFinished(bool result, const AZStd::string& info) = 0;
|
||||
};
|
||||
|
||||
using ProfilerInterface = AZ::Interface<ProfilerRequests>;
|
||||
using ProfilerRequestBus = AZ::EBus<ProfilerRequests, ProfilerBusTraits>;
|
||||
using ProfilerNotificationBus = AZ::EBus<ProfilerNotifications>;
|
||||
} // namespace Profiler
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
#include <ImGuiCpuProfiler.h>
|
||||
|
||||
#include <Profiler/ProfilerBus.h>
|
||||
#include <CpuProfilerImpl.h>
|
||||
|
||||
#include <AzCore/Debug/ProfilerBus.h>
|
||||
#include <AzCore/IO/FileIO.h>
|
||||
#include <AzCore/JSON/filereadstream.h>
|
||||
#include <AzCore/Outcome/Outcome.h>
|
||||
@@ -26,8 +26,6 @@
|
||||
|
||||
namespace Profiler
|
||||
{
|
||||
static constexpr const char* defaultSaveLocation = "@user@/Profiler";
|
||||
|
||||
namespace CpuProfilerImGuiHelper
|
||||
{
|
||||
float TicksToMs(double ticks)
|
||||
@@ -156,16 +154,7 @@ namespace Profiler
|
||||
|
||||
if (m_captureToFile)
|
||||
{
|
||||
AZStd::string timeString;
|
||||
AZStd::to_string(timeString, AZStd::GetTimeNowSecond());
|
||||
|
||||
const AZStd::string frameDataFilePath = AZStd::string::format("%s/cpu_single_%s.json", defaultSaveLocation, timeString.c_str());
|
||||
|
||||
char resolvedPath[AZ::IO::MaxPathLength];
|
||||
AZ::IO::FileIOBase::GetInstance()->ResolvePath(frameDataFilePath.c_str(), resolvedPath, AZ::IO::MaxPathLength);
|
||||
m_lastCapturedFilePath = resolvedPath;
|
||||
|
||||
ProfilerRequestBus::Broadcast(&ProfilerRequestBus::Events::CaptureCpuProfilingStatistics, frameDataFilePath);
|
||||
AZ::Debug::ProfilerSystemInterface::Get()->CaptureFrame(GenerateOutputFile("single"));
|
||||
}
|
||||
m_captureToFile = false;
|
||||
|
||||
@@ -206,24 +195,15 @@ namespace Profiler
|
||||
bool isInProgress = CpuProfiler::Get()->IsContinuousCaptureInProgress();
|
||||
if (ImGui::Button(isInProgress ? "End" : "Begin"))
|
||||
{
|
||||
auto profilerSystem = AZ::Debug::ProfilerSystemInterface::Get();
|
||||
if (isInProgress)
|
||||
{
|
||||
AZStd::string timeString;
|
||||
AZStd::to_string(timeString, AZStd::GetTimeNowSecond());
|
||||
|
||||
const AZStd::string frameDataFilePath = AZStd::string::format("%s/cpu_multi_%s.json", defaultSaveLocation, timeString.c_str());
|
||||
|
||||
char resolvedPath[AZ::IO::MaxPathLength];
|
||||
AZ::IO::FileIOBase::GetInstance()->ResolvePath(frameDataFilePath.c_str(), resolvedPath, AZ::IO::MaxPathLength);
|
||||
m_lastCapturedFilePath = resolvedPath;
|
||||
|
||||
ProfilerRequestBus::Broadcast(&ProfilerRequestBus::Events::EndContinuousCpuProfilingCapture, frameDataFilePath);
|
||||
|
||||
profilerSystem->EndCapture();
|
||||
m_paused = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProfilerRequestBus::Broadcast(&ProfilerRequestBus::Events::BeginContinuousCpuProfilingCapture);
|
||||
profilerSystem->StartCapture(GenerateOutputFile("multi"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,8 +215,10 @@ namespace Profiler
|
||||
// Only update the cached file list when opened so that we aren't making IO calls on every frame.
|
||||
m_cachedCapturePaths.clear();
|
||||
|
||||
AZ::IO::FixedMaxPathString captureOutput = AZ::Debug::GetProfilerCaptureLocation();
|
||||
|
||||
auto* base = AZ::IO::FileIOBase::GetInstance();
|
||||
base->FindFiles(defaultSaveLocation, "*.json",
|
||||
base->FindFiles(captureOutput.c_str(), "*.json",
|
||||
[&paths = m_cachedCapturePaths](const char* path) -> bool
|
||||
{
|
||||
auto foundPath = AZ::IO::Path(path);
|
||||
@@ -418,6 +400,18 @@ namespace Profiler
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
AZStd::string ImGuiCpuProfiler::GenerateOutputFile(const char* nameHint)
|
||||
{
|
||||
AZ::IO::FixedMaxPathString captureOutput = AZ::Debug::GetProfilerCaptureLocation();
|
||||
|
||||
const AZ::IO::FixedMaxPathString frameDataFilePath =
|
||||
AZ::IO::FixedMaxPathString::format("%s/cpu_%s_%lld.json", captureOutput.c_str(), nameHint, AZStd::GetTimeNowSecond());
|
||||
|
||||
AZ::IO::FileIOBase::GetInstance()->ResolvePath(m_lastCapturedFilePath, frameDataFilePath.c_str());
|
||||
|
||||
return m_lastCapturedFilePath.String();
|
||||
}
|
||||
|
||||
void ImGuiCpuProfiler::LoadFile()
|
||||
{
|
||||
const AZ::IO::Path& pathToLoad = m_cachedCapturePaths[m_currentFileIndex];
|
||||
|
||||
@@ -107,6 +107,9 @@ namespace Profiler
|
||||
//! Draws the statistical view of the CPU profiling data.
|
||||
void DrawStatisticsView();
|
||||
|
||||
//! Generates the full output timestamped file path based on nameHint
|
||||
AZStd::string GenerateOutputFile(const char* nameHint);
|
||||
|
||||
//! Callback invoked when the "Load File" button is pressed in the file picker.
|
||||
void LoadFile();
|
||||
|
||||
@@ -214,7 +217,7 @@ namespace Profiler
|
||||
AZStd::vector<CpuTimingEntry> m_cpuTimingStatisticsWhenPause;
|
||||
AZStd::sys_time_t m_frameToFrameTime{};
|
||||
|
||||
AZStd::string m_lastCapturedFilePath;
|
||||
AZ::IO::FixedMaxPath m_lastCapturedFilePath;
|
||||
|
||||
bool m_showFilePicker = false;
|
||||
|
||||
|
||||
@@ -51,32 +51,6 @@ namespace Profiler
|
||||
int m_framesLeft{ 0 };
|
||||
};
|
||||
|
||||
class ProfilerNotificationBusHandler final
|
||||
: public ProfilerNotificationBus::Handler
|
||||
, public AZ::BehaviorEBusHandler
|
||||
{
|
||||
public:
|
||||
AZ_EBUS_BEHAVIOR_BINDER(ProfilerNotificationBusHandler, "{44161459-B816-4876-95A4-BA16DEC767D6}", AZ::SystemAllocator,
|
||||
OnCaptureCpuProfilingStatisticsFinished
|
||||
);
|
||||
|
||||
void OnCaptureCpuProfilingStatisticsFinished(bool result, const AZStd::string& info) override
|
||||
{
|
||||
Call(FN_OnCaptureCpuProfilingStatisticsFinished, result, info);
|
||||
}
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->EBus<ProfilerNotificationBus>("ProfilerNotificationBus")
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
||||
->Attribute(AZ::Script::Attributes::Module, "profiler")
|
||||
->Handler<ProfilerNotificationBusHandler>();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bool SerializeCpuProfilingData(const AZStd::ring_buffer<CpuProfiler::TimeRegionMap>& data, AZStd::string outputFilePath, bool wasEnabled)
|
||||
{
|
||||
AZ_TracePrintf("ProfilerSystemComponent", "Beginning serialization of %zu frames of profiling data\n", data.size());
|
||||
@@ -107,8 +81,8 @@ namespace Profiler
|
||||
CpuProfiler::Get()->SetProfilerEnabled(false);
|
||||
}
|
||||
|
||||
// Notify listeners that the pass' PipelineStatistics queries capture has finished.
|
||||
ProfilerNotificationBus::Broadcast(&ProfilerNotificationBus::Events::OnCaptureCpuProfilingStatisticsFinished,
|
||||
// Notify listeners that the profiler capture has finished.
|
||||
AZ::Debug::ProfilerNotificationBus::Broadcast(&AZ::Debug::ProfilerNotificationBus::Events::OnCaptureFinished,
|
||||
saveResult.IsSuccess(),
|
||||
captureInfo);
|
||||
|
||||
@@ -128,21 +102,9 @@ namespace Profiler
|
||||
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
|
||||
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("System"))
|
||||
->Attribute(AZ::Edit::Attributes::AutoExpand, true);
|
||||
|
||||
ProfilerNotificationBusHandler::Reflect(context);
|
||||
}
|
||||
}
|
||||
|
||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
|
||||
{
|
||||
behaviorContext->EBus<ProfilerRequestBus>("ProfilerRequestBus")
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
||||
->Attribute(AZ::Script::Attributes::Module, "profiler")
|
||||
->Event("CaptureCpuProfilingStatistics", &ProfilerRequestBus::Events::CaptureCpuProfilingStatistics);
|
||||
|
||||
ProfilerNotificationBusHandler::Reflect(context);
|
||||
}
|
||||
|
||||
CpuProfilingStatisticsSerializer::Reflect(context);
|
||||
}
|
||||
|
||||
@@ -166,24 +128,22 @@ namespace Profiler
|
||||
|
||||
ProfilerSystemComponent::ProfilerSystemComponent()
|
||||
{
|
||||
if (ProfilerInterface::Get() == nullptr)
|
||||
if (AZ::Debug::ProfilerSystemInterface::Get() == nullptr)
|
||||
{
|
||||
ProfilerInterface::Register(this);
|
||||
AZ::Debug::ProfilerSystemInterface::Register(this);
|
||||
}
|
||||
}
|
||||
|
||||
ProfilerSystemComponent::~ProfilerSystemComponent()
|
||||
{
|
||||
if (ProfilerInterface::Get() == this)
|
||||
if (AZ::Debug::ProfilerSystemInterface::Get() == this)
|
||||
{
|
||||
ProfilerInterface::Unregister(this);
|
||||
AZ::Debug::ProfilerSystemInterface::Unregister(this);
|
||||
}
|
||||
}
|
||||
|
||||
void ProfilerSystemComponent::Activate()
|
||||
{
|
||||
ProfilerRequestBus::Handler::BusConnect();
|
||||
|
||||
m_cpuProfiler.Init();
|
||||
}
|
||||
|
||||
@@ -191,8 +151,6 @@ namespace Profiler
|
||||
{
|
||||
m_cpuProfiler.Shutdown();
|
||||
|
||||
ProfilerRequestBus::Handler::BusDisconnect();
|
||||
|
||||
// Block deactivation until the IO thread has finished serializing the CPU data
|
||||
if (m_cpuDataSerializationThread.joinable())
|
||||
{
|
||||
@@ -200,12 +158,17 @@ namespace Profiler
|
||||
}
|
||||
}
|
||||
|
||||
void ProfilerSystemComponent::SetProfilerEnabled(bool enabled)
|
||||
bool ProfilerSystemComponent::IsActive() const
|
||||
{
|
||||
return m_cpuProfiler.IsProfilerEnabled();
|
||||
}
|
||||
|
||||
void ProfilerSystemComponent::SetActive(bool enabled)
|
||||
{
|
||||
m_cpuProfiler.SetProfilerEnabled(enabled);
|
||||
}
|
||||
|
||||
bool ProfilerSystemComponent::CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath)
|
||||
bool ProfilerSystemComponent::CaptureFrame(const AZStd::string& outputFilePath)
|
||||
{
|
||||
bool expected = false;
|
||||
if (!m_cpuCaptureInProgress.compare_exchange_strong(expected, true))
|
||||
@@ -236,12 +199,13 @@ namespace Profiler
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProfilerSystemComponent::BeginContinuousCpuProfilingCapture()
|
||||
bool ProfilerSystemComponent::StartCapture(AZStd::string outputFilePath)
|
||||
{
|
||||
m_captureFile = AZStd::move(outputFilePath);
|
||||
return m_cpuProfiler.BeginContinuousCapture();
|
||||
}
|
||||
|
||||
bool ProfilerSystemComponent::EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath)
|
||||
bool ProfilerSystemComponent::EndCapture()
|
||||
{
|
||||
bool expected = false;
|
||||
if (!m_cpuDataSerializationInProgress.compare_exchange_strong(expected, true))
|
||||
@@ -263,7 +227,7 @@ namespace Profiler
|
||||
|
||||
// cpuProfilingData could be 1GB+ once saved, so use an IO thread to write it to disk.
|
||||
auto threadIoFunction =
|
||||
[data = AZStd::move(captureResult), filePath = AZStd::string(outputFilePath), &flag = m_cpuDataSerializationInProgress]()
|
||||
[data = AZStd::move(captureResult), filePath = m_captureFile, &flag = m_cpuDataSerializationInProgress]()
|
||||
{
|
||||
SerializeCpuProfilingData(data, filePath, true);
|
||||
flag.store(false);
|
||||
|
||||
@@ -8,17 +8,17 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Profiler/ProfilerBus.h>
|
||||
#include <CpuProfilerImpl.h>
|
||||
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Debug/ProfilerBus.h>
|
||||
#include <AzCore/std/parallel/thread.h>
|
||||
|
||||
namespace Profiler
|
||||
{
|
||||
class ProfilerSystemComponent
|
||||
: public AZ::Component
|
||||
, protected ProfilerRequestBus::Handler
|
||||
, protected AZ::Debug::ProfilerRequests
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(ProfilerSystemComponent, "{3f52c1d7-d920-4781-8ed7-88077ec4f305}");
|
||||
@@ -38,11 +38,12 @@ namespace Profiler
|
||||
void Activate() override;
|
||||
void Deactivate() override;
|
||||
|
||||
// ProfilerRequestBus interface implementation
|
||||
void SetProfilerEnabled(bool enabled) override;
|
||||
bool CaptureCpuProfilingStatistics(const AZStd::string& outputFilePath) override;
|
||||
bool BeginContinuousCpuProfilingCapture() override;
|
||||
bool EndContinuousCpuProfilingCapture(const AZStd::string& outputFilePath) override;
|
||||
// ProfilerRequests interface implementation
|
||||
bool IsActive() const override;
|
||||
void SetActive(bool active) override;
|
||||
bool CaptureFrame(const AZStd::string& outputFilePath) override;
|
||||
bool StartCapture(AZStd::string outputFilePath) override;
|
||||
bool EndCapture() override;
|
||||
|
||||
|
||||
AZStd::thread m_cpuDataSerializationThread;
|
||||
@@ -51,6 +52,7 @@ namespace Profiler
|
||||
AZStd::atomic_bool m_cpuCaptureInProgress{ false };
|
||||
|
||||
CpuProfilerImpl m_cpuProfiler;
|
||||
AZStd::string m_captureFile;
|
||||
};
|
||||
|
||||
} // namespace Profiler
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#
|
||||
|
||||
set(FILES
|
||||
Include/Profiler/ProfilerBus.h
|
||||
Include/Profiler/ProfilerImGuiBus.h
|
||||
Source/CpuProfiler.h
|
||||
Source/CpuProfilerImpl.cpp
|
||||
|
||||
Reference in New Issue
Block a user