Files
o3de/Gems/AssetMemoryAnalyzer/Code/Source/AssetMemoryAnalyzer.cpp
T
lumberyard-employee-dm 090aa8f053 Removed ununeeded includes from EBus EBus.h and Policies.h (#4256)
* Removed ununeeded includes from EBus EBus.h and Policies.h

Updated the locations which needed those includes

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Adding missing include for <memory> to AWsClientAuthBus.h

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Remove the while true loop in the EBusQueuePolicy Execute() function

The while true loop in Execute was for allowing additional functions to be queued in the middle of execution of current list of functions.
That functionality was dangerous, because if a queued function added itself during execution unconditionally, then it would result in an infinite loop

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the AssetManager::DispatchEvents function to pump the AssetBus event queue until empty

Queued Events on the AssetBus is able to queue additional events on that Bus during execution of those events.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Changed the AssetManager::DispatchEvents function to only execute the
AssetBus queued events once

Changed the AssetJobsFloodTest.AssetWithNoLoadReference_LoadDependencies_BehaviorObeyed test to dispatch events until the OnAssetContainerReady callback is signaled.

This happens after every asset load to make sure that the
expiring AssetContainer instances are removed from `AssetManager::m_ownedAssetContainer` container before retrying to load the same asset.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Added a MaxTimeoutSeconds constant for the maximum amount of the time to run a single DispatchEvents loop

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
2021-09-29 18:13:37 -05:00

331 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/containers/unordered_set.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();
}
}