[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:
Scott Romero
2021-11-02 09:00:15 -07:00
committed by GitHub
21 changed files with 536 additions and 267 deletions
+4 -1
View File
@@ -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
+20 -26
View File
@@ -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];
+4 -1
View File
@@ -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
-1
View File
@@ -7,7 +7,6 @@
#
set(FILES
Include/Profiler/ProfilerBus.h
Include/Profiler/ProfilerImGuiBus.h
Source/CpuProfiler.h
Source/CpuProfilerImpl.cpp