Files
o3de/Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzer.cpp
T
Esteban Papp 1f9b284de2 Merge branch 'development' into cmake/SPEC-7179
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
2021-07-16 15:42:37 -07:00

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();
}
}