1f9b284de2
Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com> # Conflicts: # Code/Editor/Plugins/ComponentEntityEditorPlugin/ComponentEntityEditorPlugin_precompiled.h # Code/Editor/Plugins/EditorCommon/EditorCommon_precompiled.h # Code/Editor/Plugins/EditorCommon/stdafx.cpp # Code/Editor/Plugins/FFMPEGPlugin/FFMPEGPlugin_precompiled.h # Code/Editor/Plugins/PerforcePlugin/PerforcePlugin_precompiled.h # Code/Editor/Plugins/ProjectSettingsTool/ProjectSettingsTool_precompiled.h # Code/Framework/AzToolsFramework/AzToolsFramework/AzToolsFramework_precompiled.h # Code/Tools/AssetProcessor/native/precompiled.h # Code/Tools/Standalone/StandaloneTools_precompiled.h # Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzer_precompiled.h # Gems/Atom/Asset/ImageProcessingAtom/Code/Source/ImageProcessing_precompiled.h # Gems/Atom/RHI/DX12/Code/Source/RHI/Atom_RHI_DX12_precompiled.h # Gems/Atom/RHI/Metal/Code/Include/Platform/Mac/Atom_RHI_Metal_precompiled_Platform.h # Gems/Atom/RHI/Metal/Code/Include/Platform/iOS/Atom_RHI_Metal_precompiled_Platform.h # Gems/Atom/RHI/Metal/Code/Source/Atom_RHI_Metal_precompiled.h # Gems/Atom/RHI/Metal/Code/atom_rhi_metal_common_files.cmake # Gems/Atom/RHI/Null/Code/Source/Atom_RHI_Null_precompiled.h # Gems/Atom/RHI/Null/Code/atom_rhi_null_common_files.cmake # Gems/Atom/RHI/Vulkan/Code/Include/Platform/Android/Atom_RHI_Vulkan_precompiled_Platform.h # Gems/Atom/RHI/Vulkan/Code/Include/Platform/Linux/Atom_RHI_Vulkan_precompiled_Platform.h # Gems/Atom/RHI/Vulkan/Code/Include/Platform/Mac/Atom_RHI_Vulkan_precompiled_Platform.h # Gems/Atom/RHI/Vulkan/Code/Include/Platform/Windows/Atom_RHI_Vulkan_precompiled_Platform.h # Gems/Atom/RHI/Vulkan/Code/Source/Atom_RHI_Vulkan_precompiled.h # Gems/Atom/RHI/Vulkan/Code/Source/RHI/SwapChain.cpp # Gems/Atom/RHI/Vulkan/Code/atom_rhi_vulkan_common_files.cmake # Gems/AtomLyIntegration/AtomFont/Code/Include/AtomLyIntegration/AtomFont/AtomFont_precompiled.h # Gems/Blast/Code/Source/StdAfx.cpp # Gems/Camera/Code/Source/Camera_precompiled.h # Gems/EMotionFX/Code/Source/EMotionFX_precompiled.h # Gems/FastNoise/Code/Source/FastNoise_precompiled.h # Gems/Gestures/Code/Source/Gestures_precompiled.h # Gems/GradientSignal/Code/Source/GradientSignal_precompiled.h # Gems/GraphCanvas/Code/precompiled.h # Gems/ImGui/Code/Source/ImGui_precompiled.h # Gems/InAppPurchases/Code/Source/InAppPurchases_precompiled.h # Gems/LmbrCentral/Code/Source/LmbrCentral_precompiled.h # Gems/LmbrCentral/Code/Tests/ShapeGeometryUtilTest.cpp # Gems/LyShine/Code/Editor/UiCanvasEditor_precompiled.h # Gems/LyShine/Code/Source/Animation/LyShine_precompiled.h # Gems/LyShine/Code/Source/LyShine_precompiled.h # Gems/LyShineExamples/Code/Source/LyShineExamples_precompiled.h # Gems/Maestro/Code/Source/Cinematics/Maestro_precompiled.h # Gems/Maestro/Code/Source/Maestro_precompiled.h # Gems/MessagePopup/Code/Source/MessagePopup_precompiled.h # Gems/Metastream/Code/Source/Metastream_precompiled.h # Gems/Microphone/Code/Source/Microphone_precompiled.h # Gems/Multiplayer/Code/Source/Multiplayer_precompiled.h # Gems/PhysX/Code/NumericalMethods/Source/NumericalMethods_precompiled.h # Gems/PhysX/Code/Source/PhysXUnsupported_precompiled.h # Gems/PhysX/Code/Source/PhysX_precompiled.h # Gems/PhysX/Code/physx_unsupported_files.cmake # Gems/PhysXDebug/Code/Source/PhysXDebugUnsupported_precompiled.h # Gems/PhysXDebug/Code/Source/PhysXDebug_precompiled.h # Gems/ScriptCanvas/Code/Editor/precompiled.h # Gems/ScriptCanvas/Code/Source/precompiled.h # Gems/ScriptCanvasDeveloper/Code/Source/precompiled.h # Gems/ScriptCanvasPhysics/Code/Source/ScriptCanvasPhysics_precompiled.h # Gems/ScriptEvents/Code/Source/precompiled.h # Gems/ScriptEvents/Code/Tests/Editor/EditorTests.cpp # Gems/ScriptedEntityTweener/Code/Source/ScriptedEntityTweener_precompiled.h # Gems/SliceFavorites/Code/Source/SliceFavorites_precompiled.h # Gems/StartingPointCamera/Code/Source/StartingPointCamera_precompiled.h # Gems/StartingPointInput/Code/Source/StartingPointInput_precompiled.h # Gems/StartingPointMovement/Code/Source/StartingPointMovement_precompiled.h # Gems/SurfaceData/Code/Source/SurfaceData_precompiled.h # Gems/TextureAtlas/Code/Source/TextureAtlas_precompiled.h # Gems/TickBusOrderViewer/Code/Source/TickBusOrderViewer_precompiled.h # Gems/Twitch/Code/Source/Twitch_precompiled.h # Gems/VirtualGamepad/Code/Source/VirtualGamepad_precompiled.h # Gems/WhiteBox/Code/Source/WhiteBoxUnsupported_precompiled.h # Gems/WhiteBox/Code/Source/WhiteBox_precompiled.h
330 lines
13 KiB
C++
330 lines
13 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
|
|
*
|
|
*/
|
|
|
|
#include "AssetMemoryAnalyzer.h"
|
|
|
|
#include <AzCore/Memory/MemoryDrillerBus.h>
|
|
#include <AzCore/Debug/AssetTrackingTypesImpl.h>
|
|
#include <AzCore/Debug/AssetTracking.h>
|
|
#include <AzCore/std/smart_ptr/make_shared.h>
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// CodePoint hash-table support
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
template<>
|
|
struct AZStd::hash<AssetMemoryAnalyzer::Data::CodePoint>
|
|
{
|
|
size_t operator()(const AssetMemoryAnalyzer::Data::CodePoint& codePoint) const
|
|
{
|
|
size_t seed = 0;
|
|
AZStd::hash_combine(seed, codePoint.m_file);
|
|
AZStd::hash_combine(seed, codePoint.m_line);
|
|
|
|
return seed;
|
|
}
|
|
};
|
|
|
|
namespace AssetMemoryAnalyzer
|
|
{
|
|
namespace Data
|
|
{
|
|
inline bool operator==(const CodePoint& lhs, const CodePoint& rhs)
|
|
{
|
|
return lhs.m_file == rhs.m_file &&
|
|
lhs.m_line == rhs.m_line;
|
|
}
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// AnalyzerImpl class
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace AssetMemoryAnalyzer
|
|
{
|
|
class AnalyzerImpl :
|
|
public AZ::Debug::MemoryDrillerBus::Handler
|
|
{
|
|
public:
|
|
AZ_TYPE_INFO(AnalyzerImpl, "{E460E4DE-2160-4171-A4B6-3C2DB6692C32}");
|
|
AZ_CLASS_ALLOCATOR(AnalyzerImpl, AZ::Debug::AssetTrackingAllocator, 0);
|
|
|
|
AnalyzerImpl();
|
|
~AnalyzerImpl();
|
|
|
|
// MemoryDrillerBus
|
|
void RegisterAllocator(AZ::IAllocator* allocator) override;
|
|
void UnregisterAllocator(AZ::IAllocator* allocator) override;
|
|
void DumpAllAllocations() override;
|
|
void RegisterAllocation(AZ::IAllocator* allocator, void* address, size_t byteSize, size_t alignment, const char* name, const char* fileName, int lineNum, unsigned int stackSuppressCount) override;
|
|
void UnregisterAllocation(AZ::IAllocator* allocator, void* address, size_t byteSize, size_t alignment, AZ::Debug::AllocationInfo* info) override;
|
|
void ReallocateAllocation(AZ::IAllocator* allocator, void* prevAddress, void* newAddress, size_t newByteSize, size_t newAlignment) override;
|
|
void ResizeAllocation(AZ::IAllocator* allocator, void* address, size_t newSize) override;
|
|
|
|
AZStd::shared_ptr<FrameAnalysis> GetAnalysis();
|
|
|
|
private:
|
|
void RegisterAllocationCommon(void* address, size_t byteSize, const char* fileName, int lineNum, Data::AllocationData::CategoryInfo categoryInfo, Data::AllocationCategories category);
|
|
void UnregisterAllocationCommon(void* address);
|
|
|
|
using AssetTree = AZ::Debug::AssetTree<Data::AssetData>;
|
|
using AssetTreeNode = typename AssetTree::NodeType;
|
|
using AllocationTable = AZ::Debug::AllocationTable<Data::AllocationData>;
|
|
using CodePoints = AZStd::unordered_set<Data::CodePoint, AZStd::hash<Data::CodePoint>, AZStd::equal_to<Data::CodePoint>, AZ::Debug::AZStdAssetTrackingAllocator>;
|
|
using mutex_type = AZStd::mutex;
|
|
using lock_type = AZStd::lock_guard<mutex_type>;
|
|
|
|
mutex_type m_mutex;
|
|
CodePoints m_codePoints;
|
|
AssetTree m_assetTree;
|
|
AllocationTable m_allocationTable;
|
|
AZ::Debug::AssetTracking m_assetTracking;
|
|
bool m_captureUncategorizedAllocations = false;
|
|
bool m_performingAnalysis = false;
|
|
};
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// AnalyzerImpl functions
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
AnalyzerImpl::AnalyzerImpl() :
|
|
m_allocationTable(m_mutex),
|
|
m_assetTracking(&m_assetTree, &m_allocationTable)
|
|
{
|
|
AZ::Debug::MemoryDrillerBus::Handler::BusConnect();
|
|
}
|
|
|
|
AnalyzerImpl::~AnalyzerImpl()
|
|
{
|
|
AZ::Debug::MemoryDrillerBus::Handler::BusDisconnect();
|
|
}
|
|
|
|
void AnalyzerImpl::RegisterAllocator(AZ::IAllocator* allocator)
|
|
{
|
|
AZ_UNUSED(allocator);
|
|
}
|
|
|
|
void AnalyzerImpl::UnregisterAllocator(AZ::IAllocator* allocator)
|
|
{
|
|
AZ_UNUSED(allocator);
|
|
}
|
|
|
|
void AnalyzerImpl::DumpAllAllocations()
|
|
{
|
|
}
|
|
|
|
void AnalyzerImpl::RegisterAllocation(AZ::IAllocator* allocator, void* address, size_t byteSize, size_t alignment, const char* name, const char* fileName, int lineNum, unsigned int stackSuppressCount)
|
|
{
|
|
AZ_UNUSED(name);
|
|
AZ_UNUSED(alignment);
|
|
AZ_UNUSED(stackSuppressCount);
|
|
|
|
Data::AllocationData::CategoryInfo categoryInfo;
|
|
categoryInfo.m_heapInfo.m_allocator = allocator;
|
|
RegisterAllocationCommon(address, byteSize, fileName, lineNum, categoryInfo, Data::AllocationCategories::HEAP);
|
|
}
|
|
|
|
void AnalyzerImpl::UnregisterAllocation(AZ::IAllocator* allocator, void* address, size_t byteSize, size_t alignment, AZ::Debug::AllocationInfo* info)
|
|
{
|
|
AZ_UNUSED(allocator);
|
|
AZ_UNUSED(byteSize);
|
|
AZ_UNUSED(alignment);
|
|
AZ_UNUSED(info);
|
|
|
|
UnregisterAllocationCommon(address);
|
|
}
|
|
|
|
void AnalyzerImpl::ReallocateAllocation(AZ::IAllocator* allocator, void* prevAddress, void* newAddress, size_t newByteSize, size_t newAlignment)
|
|
{
|
|
AZ_UNUSED(allocator);
|
|
AZ_UNUSED(newAlignment);
|
|
|
|
if (m_performingAnalysis)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_allocationTable.ReallocateAllocation(prevAddress, newAddress, newByteSize);
|
|
}
|
|
|
|
void AnalyzerImpl::ResizeAllocation(AZ::IAllocator* allocator, void* address, size_t newSize)
|
|
{
|
|
AZ_UNUSED(allocator);
|
|
|
|
if (m_performingAnalysis)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_allocationTable.ResizeAllocation(address, newSize);
|
|
}
|
|
|
|
void AnalyzerImpl::RegisterAllocationCommon(void* address, size_t byteSize, const char* fileName, int lineNum, Data::AllocationData::CategoryInfo categoryInfo, Data::AllocationCategories category)
|
|
{
|
|
if (m_performingAnalysis)
|
|
{
|
|
return;
|
|
}
|
|
|
|
AZ::Debug::AssetTreeNodeBase* activeAsset = m_assetTracking.GetCurrentThreadAsset();
|
|
|
|
if (!activeAsset)
|
|
{
|
|
if (m_captureUncategorizedAllocations)
|
|
{
|
|
activeAsset = &m_assetTree.GetRoot();
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
{
|
|
// Store a record for this allocation, at this code-point
|
|
lock_type lock(m_mutex);
|
|
auto insertResult = m_codePoints.emplace(Data::CodePoint{ fileName ? fileName : "<unknown>", lineNum, category });
|
|
Data::CodePoint* cp = &*insertResult.first;
|
|
m_allocationTable.Get().emplace(address, AllocationTable::RecordType{ activeAsset, (uint32_t)byteSize, Data::AllocationData{ cp, categoryInfo } });
|
|
static_cast<typename AssetTree::NodeType*>(activeAsset)->m_data.m_totalAllocations[(int)category]++;
|
|
}
|
|
}
|
|
|
|
void AnalyzerImpl::UnregisterAllocationCommon(void* address)
|
|
{
|
|
if (m_performingAnalysis)
|
|
{
|
|
return;
|
|
}
|
|
|
|
{
|
|
// Delete the record of this allocation if it exists
|
|
lock_type lock(m_mutex);
|
|
auto& table = m_allocationTable.Get();
|
|
auto itr = table.find(address);
|
|
|
|
if (itr != table.end())
|
|
{
|
|
static_cast<typename AssetTree::NodeType*>(itr->second.m_asset)->m_data.m_totalAllocations[(int)itr->second.m_data.m_codePoint->m_category]--;
|
|
table.erase(address);
|
|
}
|
|
}
|
|
}
|
|
|
|
AZStd::shared_ptr<FrameAnalysis> AnalyzerImpl::GetAnalysis()
|
|
{
|
|
using namespace Data;
|
|
|
|
lock_type lock(m_mutex);
|
|
m_performingAnalysis = true; // Prevent recursive allocations from disrupting our work
|
|
|
|
auto result = AZStd::allocate_shared<FrameAnalysis>(AZ::Debug::AZStdAssetTrackingAllocator());
|
|
FrameAnalysis* analysis = result.get();
|
|
|
|
// Walk through all allocations and record their individual contributions to the analysisData for their owning asset
|
|
for (auto& allocationInfo : m_allocationTable.Get())
|
|
{
|
|
auto assetData = &static_cast<typename AssetTree::NodeType*>(allocationInfo.second.m_asset)->m_data;
|
|
auto category = allocationInfo.second.m_data.m_codePoint->m_category;
|
|
|
|
// Update total bytes for this asset
|
|
assetData->m_totalBytes[(int)category] += allocationInfo.second.m_size;
|
|
|
|
// Locate or create a recording of this code point within the analysis for this asset
|
|
auto codePointItr = assetData->m_codePointsToAllocations.find(allocationInfo.second.m_data.m_codePoint);
|
|
|
|
if (codePointItr == assetData->m_codePointsToAllocations.end())
|
|
{
|
|
codePointItr = assetData->m_codePointsToAllocations.emplace(allocationInfo.second.m_data.m_codePoint, AssetData::CodePointInfo()).first;
|
|
codePointItr->second.m_category = category;
|
|
}
|
|
|
|
// Update the code point within the analysis for this asset with information about this allocation
|
|
codePointItr->second.m_allocations.emplace_back(AllocationPoint::AllocationInfo{ allocationInfo.second.m_size });
|
|
codePointItr->second.m_totalBytes += allocationInfo.second.m_size;
|
|
}
|
|
|
|
// Declare function to recurse through the asset tree, converting the analysisData of every node into matching information in the public API (AssetMemory:: namespace)
|
|
AZStd::function<void(AssetInfo*, AssetTreeNode*, int)> recurse;
|
|
recurse = [&recurse](AssetInfo* outAsset, AssetTreeNode* inAsset, int depth)
|
|
{
|
|
outAsset->m_id = inAsset->m_primaryinfo ? inAsset->m_primaryinfo->m_id->m_id.c_str() : nullptr;
|
|
|
|
// For every code point in this asset node, record its allocations
|
|
for (auto& codePointInfo : inAsset->m_data.m_codePointsToAllocations)
|
|
{
|
|
outAsset->m_allocationPoints.emplace_back(AllocationPoint());
|
|
auto allocationPoint = &outAsset->m_allocationPoints.back();
|
|
allocationPoint->m_codePoint = codePointInfo.first;
|
|
allocationPoint->m_allocations.swap(codePointInfo.second.m_allocations);
|
|
allocationPoint->m_totalAllocatedMemory = codePointInfo.second.m_totalBytes;
|
|
|
|
// Add these allocations to our total count of allocations for this asset
|
|
int categoryIndex = (int)codePointInfo.first->m_category;
|
|
outAsset->m_localSummary[categoryIndex].m_allocationCount += (uint32_t)allocationPoint->m_allocations.size();
|
|
|
|
// Reserve memory for the next frame, as the number of allocations are unlikely to change much over time
|
|
codePointInfo.second.m_allocations.reserve(allocationPoint->m_allocations.size());
|
|
codePointInfo.second.m_totalBytes = 0; // Reset for next frame
|
|
}
|
|
|
|
// Initialize the local and total summary
|
|
for (int categoryIndex = 0; categoryIndex < ALLOCATION_CATEGORY_COUNT; categoryIndex++)
|
|
{
|
|
outAsset->m_localSummary[categoryIndex].m_allocatedMemory = inAsset->m_data.m_totalBytes[categoryIndex];
|
|
outAsset->m_totalSummary[categoryIndex] = outAsset->m_localSummary[categoryIndex];
|
|
}
|
|
|
|
// Recurse over child assets
|
|
outAsset->m_childAssets.resize(inAsset->m_children.size());
|
|
size_t childIdx = 0;
|
|
|
|
for (auto& inChildItr : inAsset->m_children)
|
|
{
|
|
auto outChild = &outAsset->m_childAssets[childIdx++];
|
|
recurse(outChild, &inChildItr.second, depth + 1);
|
|
|
|
// Have child assets contribute to the total summary
|
|
for (int categoryIndex = 0; categoryIndex < ALLOCATION_CATEGORY_COUNT; categoryIndex++)
|
|
{
|
|
outAsset->m_totalSummary[categoryIndex].m_allocatedMemory += outChild->m_totalSummary[categoryIndex].m_allocatedMemory;
|
|
outAsset->m_totalSummary[categoryIndex].m_allocationCount += outChild->m_totalSummary[categoryIndex].m_allocationCount;
|
|
}
|
|
}
|
|
|
|
// Clear analysis data out for the next frame
|
|
AZStd::for_each(inAsset->m_data.m_totalBytes, inAsset->m_data.m_totalBytes + ALLOCATION_CATEGORY_COUNT, [](uint32_t& x) { x = 0; });
|
|
};
|
|
|
|
recurse(&analysis->m_rootAsset, static_cast<typename AssetTree::NodeType*>(&m_assetTree.GetRoot()), 0);
|
|
|
|
m_performingAnalysis = false;
|
|
|
|
return result;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Analyzer functions
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
Analyzer::Analyzer() : m_impl(aznew AnalyzerImpl)
|
|
{
|
|
}
|
|
|
|
Analyzer::~Analyzer()
|
|
{
|
|
}
|
|
|
|
AZStd::shared_ptr<FrameAnalysis> Analyzer::GetAnalysis()
|
|
{
|
|
return m_impl->GetAnalysis();
|
|
}
|
|
}
|