Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,382 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AssetMemoryAnalyzer_precompiled.h"
#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 Render::Debug::VRAMDrillerBus::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;
// VRAMDrillerBus
void RegisterCategory(Render::Debug::VRAMAllocationCategory category, const char* categoryName, const Render::Debug::VRAMSubCategoryType& subcategories) override;
void UnregisterAllCategories() override;
void RegisterAllocation(void* address, size_t byteSize, const char* allocationName, Render::Debug::VRAMAllocationCategory category, Render::Debug::VRAMAllocationSubcategory subcategories) override;
void UnregisterAllocation(void* address) override;
void GetCurrentVRAMStats(Render::Debug::VRAMAllocationCategory category, Render::Debug::VRAMAllocationSubcategory subcategory, AZStd::string& categoryName, AZStd::string& subcategoryName, size_t& numberBytesAllocated, size_t& numberAllocations) 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 MasterCodePoints = 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;
MasterCodePoints m_masterCodePoints;
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();
Render::Debug::VRAMDrillerBus::Handler::BusConnect();
}
AnalyzerImpl::~AnalyzerImpl()
{
AZ::Debug::MemoryDrillerBus::Handler::BusDisconnect();
Render::Debug::VRAMDrillerBus::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::RegisterCategory(Render::Debug::VRAMAllocationCategory category, const char* categoryName, const Render::Debug::VRAMSubCategoryType& subcategories)
{
AZ_UNUSED(category);
AZ_UNUSED(categoryName);
AZ_UNUSED(subcategories);
}
void AnalyzerImpl::UnregisterAllCategories()
{
}
void AnalyzerImpl::RegisterAllocation(void* address, size_t byteSize, const char* allocationName, Render::Debug::VRAMAllocationCategory category, Render::Debug::VRAMAllocationSubcategory subcategories)
{
// Bit-flip address so that it won't collide with heap allocations (calls to the VRAM driller tend to use the same pointers from the heap objects that own the VRAM)
address = (void*)~(size_t)address;
Data::AllocationData::CategoryInfo categoryInfo;
categoryInfo.m_vramInfo.m_category = category;
categoryInfo.m_vramInfo.m_subcategories = subcategories;
RegisterAllocationCommon(address, byteSize, allocationName, 0, categoryInfo, Data::AllocationCategories::VRAM);
}
void AnalyzerImpl::UnregisterAllocation(void* address)
{
address = (void*)~(size_t)address;
UnregisterAllocationCommon(address);
}
void AnalyzerImpl::GetCurrentVRAMStats(Render::Debug::VRAMAllocationCategory category, Render::Debug::VRAMAllocationSubcategory subcategory, AZStd::string& categoryName, AZStd::string& subcategoryName, size_t& numberBytesAllocated, size_t& numberAllocations)
{
AZ_UNUSED(category);
AZ_UNUSED(subcategory);
AZ_UNUSED(categoryName);
AZ_UNUSED(subcategoryName);
AZ_UNUSED(numberBytesAllocated);
AZ_UNUSED(numberAllocations);
}
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_masterCodePoints.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_masterInfo ? inAsset->m_masterInfo->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();
}
}
@@ -0,0 +1,170 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Debug/AssetTrackingTypes.h>
#include <Common/Memory/VRAMDriller.h>
namespace AssetMemoryAnalyzer
{
class AnalyzerImpl;
namespace Data
{
enum class AllocationCategories
{
HEAP,
VRAM,
COUNT
};
constexpr int ALLOCATION_CATEGORY_COUNT = (int)AllocationCategories::COUNT;
// A location in code
struct CodePoint
{
const char* m_file;
int m_line;
AllocationCategories m_category;
};
// Meta-information to attach to an individual allocation
struct AllocationData
{
union CategoryInfo
{
struct
{
AZ::IAllocator* m_allocator;
}
m_heapInfo;
struct
{
Render::Debug::VRAMAllocationCategory m_category;
Render::Debug::VRAMAllocationSubcategory m_subcategories;
}
m_vramInfo;
};
CodePoint* m_codePoint;
CategoryInfo m_categoryInfo;
};
// Information about a point in code where allocations occur
struct AllocationPoint
{
struct AllocationInfo
{
// Size in bytes
uint32_t m_size;
};
using AllocationInfos = AZStd::vector<AllocationInfo, AZ::Debug::AZStdAssetTrackingAllocator>;
// The point in code where allocations occur
const CodePoint* m_codePoint;
// Total memory allocated through this code point (will be the sum of m_allocations)
uint32_t m_totalAllocatedMemory = 0;
// Individual allocations that occurred through this code point
AllocationInfos m_allocations;
};
// Summary information about a group of allocations
struct Summary
{
// Total bytes allocated in the group
uint32_t m_allocatedMemory = 0;
// Total number of separate allocations in the group
uint32_t m_allocationCount = 0;
};
// Information about an asset
struct AssetData
{
struct CodePointInfo
{
AllocationPoint::AllocationInfos m_allocations;
uint32_t m_totalBytes = 0;
AllocationCategories m_category;
};
uint32_t m_totalAllocations[ALLOCATION_CATEGORY_COUNT];
uint32_t m_totalBytes[ALLOCATION_CATEGORY_COUNT];
AZStd::unordered_map<CodePoint*, CodePointInfo, AZStd::hash<CodePoint*>, AZStd::equal_to<CodePoint*>, AZ::Debug::AZStdAssetTrackingAllocator> m_codePointsToAllocations;
};
// Information about a specific asset.
struct AssetInfo
{
// Identifier for the asset.
const char* m_id = nullptr;
// Total allocations/bytes for this asset, including allocations for any child assets.
Summary m_totalSummary[ALLOCATION_CATEGORY_COUNT];
// Total allocations/bytes for this asset alone, excluding allocations for child assets.
Summary m_localSummary[ALLOCATION_CATEGORY_COUNT];
// Child assets (i.e. assets that enter into scope while this asset is already in scope)
AZStd::vector<AssetInfo, AZ::Debug::AZStdAssetTrackingAllocator> m_childAssets;
// Points in code at which this asset has made allocations
AZStd::vector<AllocationPoint, AZ::Debug::AZStdAssetTrackingAllocator> m_allocationPoints;
};
typedef AZStd::vector<AllocationPoint, AZ::Debug::AZStdAssetTrackingAllocator> AllocationPoints;
}
// Analysis of all loaded assets at a moment in time
class FrameAnalysis
{
public:
AZ_TYPE_INFO(FrameAnalysis, "{6B7287A6-EE5E-4A9D-B219-586DAD865537}");
AZ_CLASS_ALLOCATOR(FrameAnalysis, AZ::Debug::AssetTrackingAllocator, 0);
const Data::AssetInfo& GetRootAsset() const
{
return m_rootAsset;
}
const Data::AllocationPoints& GetAllocationPoints() const
{
return m_allocationPoints;
}
private:
Data::AssetInfo m_rootAsset;
Data::AllocationPoints m_allocationPoints;
friend AnalyzerImpl;
};
class Analyzer
{
public:
AZ_TYPE_INFO(Analyzer, "{00FB30E2-706C-41E6-9BDD-F52A40CF3366}");
AZ_CLASS_ALLOCATOR(Analyzer, AZ::Debug::AssetTrackingAllocator, 0);
Analyzer();
~Analyzer();
AZStd::shared_ptr<FrameAnalysis> GetAnalysis();
private:
AZStd::unique_ptr<AnalyzerImpl> m_impl;
};
}
@@ -0,0 +1,87 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AssetMemoryAnalyzer_precompiled.h"
#include <CryCommon/IConsole.h>
#include <AzCore/Memory/SystemAllocator.h>
#include "AssetMemoryAnalyzerSystemComponent.h"
#include <IGem.h>
namespace AssetMemoryAnalyzer
{
class AssetMemoryAnalyzerModule
: public CryHooksModule
{
public:
AZ_RTTI(AssetMemoryAnalyzerModule, "{899B0A20-E21D-49BF-ADAF-A2396C27CFCC}", CryHooksModule);
AZ_CLASS_ALLOCATOR(AssetMemoryAnalyzerModule, AZ::OSAllocator, 0);
AssetMemoryAnalyzerModule()
: CryHooksModule()
{
// Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
m_descriptors.insert(m_descriptors.end(), {
AssetMemoryAnalyzerSystemComponent::CreateDescriptor(),
});
}
/**
* Add required SystemComponents to the SystemEntity.
*/
AZ::ComponentTypeList GetRequiredSystemComponents() const override
{
return AZ::ComponentTypeList{
azrtti_typeid<AssetMemoryAnalyzerSystemComponent>(),
};
}
void OnCrySystemInitialized([[maybe_unused]] ISystem& system, [[maybe_unused]] const SSystemInitParams& systemInitParams) override
{
REGISTER_CVAR2_CB_DEV_ONLY(
"assetmem_enabled",
&m_cvarEnabled,
0,
VF_NULL,
"AssetMemoryAnalyzer: Enable or disable the Asset Memory Analyzer.",
[](ICVar* pArgs)
{
bool enabled = pArgs->GetIVal() ? true : false;
EBUS_EVENT(AssetMemoryAnalyzerRequestBus, SetEnabled, enabled);
}
);
REGISTER_COMMAND_DEV_ONLY(
"assetmem_export_json",
[](IConsoleCmdArgs*) { EBUS_EVENT(AssetMemoryAnalyzerRequestBus, ExportJSONFile, nullptr); },
0,
"AssetMemoryAnalyzer: Export JSON analysis to @log@ directory.");
REGISTER_COMMAND_DEV_ONLY(
"assetmem_export_csv",
[](IConsoleCmdArgs*) { EBUS_EVENT(AssetMemoryAnalyzerRequestBus, ExportCSVFile, nullptr); },
0,
"AssetMemoryAnalyzer: Export CSV analysis to @log@ directory. (Top-level assets only.)");
EBUS_EVENT(AssetMemoryAnalyzerRequestBus, SetEnabled, m_cvarEnabled != 0);
}
private:
int m_cvarEnabled = 0;
};
}
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_AssetMemoryAnalyzer, AssetMemoryAnalyzer::AssetMemoryAnalyzerModule)
@@ -0,0 +1,206 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AssetMemoryAnalyzer_precompiled.h"
#include <AzCore/Debug/AssetTrackingTypesImpl.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <Common/Memory/VRAMDrillerBus.h>
#include "AssetMemoryAnalyzerSystemComponent.h"
#include "AssetMemoryAnalyzer.h"
#include "DebugImGUI.h"
#include "ExportCSV.h"
#include "ExportJSON.h"
namespace AssetMemoryAnalyzer
{
namespace
{
static const char* GetExportFile(const char* customFilename, const char* extension)
{
static char sharedBuffer[AZ_MAX_PATH_LEN];
if (customFilename)
{
azsnprintf(sharedBuffer, sizeof(sharedBuffer), "@log@/%s", customFilename);
}
else
{
char timestampBuffer[64];
time_t ltime;
time(&ltime);
struct tm timeInfo;
AZ_TRAIT_CTIME_LOCALTIME(&timeInfo, &ltime);
strftime(timestampBuffer, sizeof(timestampBuffer), "@log@/assetmem-%Y-%m-%d-%H-%M-%S.%%s", &timeInfo);
azsnprintf(sharedBuffer, sizeof(sharedBuffer), timestampBuffer, extension);
}
return sharedBuffer;
}
}
static const char* VRAM_CATEGORIES[] =
{
"Texture",
"Buffer",
"Misc"
};
static const char* VRAM_SUBCATEGORIES[] =
{
"Rendertarget",
"Texture",
"Dynamic",
"VB",
"IB",
"CB",
"Other",
"Misc"
};
class AssetMemoryAnalyzerSystemComponent::Impl
{
private:
AZStd::unique_ptr<Analyzer> m_analyzer;
DebugImGUI m_debugImGUI;
ExportCSV m_exportCSV;
ExportJSON m_exportJSON;
friend class AssetMemoryAnalyzerSystemComponent;
};
AssetMemoryAnalyzerSystemComponent::AssetMemoryAnalyzerSystemComponent() : m_impl(new Impl)
{
AZ::AllocatorInstance<AZ::Debug::AssetTrackingAllocator>::Create();
}
AssetMemoryAnalyzerSystemComponent::~AssetMemoryAnalyzerSystemComponent()
{
m_impl.reset(); // Must delete objects before destroying the allocator
AZ::AllocatorInstance<AZ::Debug::AssetTrackingAllocator>::Destroy();
}
void AssetMemoryAnalyzerSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<AssetMemoryAnalyzerSystemComponent, AZ::Component>()
->Version(0);
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<AssetMemoryAnalyzerSystemComponent>("AssetMemoryAnalyzer", "Provides access to asset memory debugging features")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
;
}
}
}
void AssetMemoryAnalyzerSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("AssetMemoryAnalyzerService", 0x23c52412));
}
void AssetMemoryAnalyzerSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("AssetMemoryAnalyzerService", 0x23c52412));
}
void AssetMemoryAnalyzerSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
(void)required;
}
void AssetMemoryAnalyzerSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
(void)dependent;
}
const char** AssetMemoryAnalyzerSystemComponent::GetVRAMCategories()
{
return VRAM_CATEGORIES;
}
const char** AssetMemoryAnalyzerSystemComponent::GetVRAMSubCategories()
{
return VRAM_SUBCATEGORIES;
}
bool AssetMemoryAnalyzerSystemComponent::IsEnabled() const
{
return m_impl->m_analyzer.get() != nullptr;
}
AZStd::shared_ptr<FrameAnalysis> AssetMemoryAnalyzerSystemComponent::GetAnalysis()
{
AZStd::shared_ptr<FrameAnalysis> result;
if (m_impl->m_analyzer)
{
result = m_impl->m_analyzer->GetAnalysis();
}
return result;
}
void AssetMemoryAnalyzerSystemComponent::SetEnabled(bool enabled)
{
if (enabled)
{
if (!m_impl->m_analyzer)
{
m_impl->m_analyzer.reset(aznew Analyzer);
}
}
else
{
m_impl->m_analyzer.reset();
}
}
void AssetMemoryAnalyzerSystemComponent::ExportCSVFile(const char* path)
{
const char* outputPath = GetExportFile(path, "csv");
m_impl->m_exportCSV.OutputCSV(outputPath);
}
void AssetMemoryAnalyzerSystemComponent::ExportJSONFile(const char* path)
{
const char* outputPath = GetExportFile(path, "json");
m_impl->m_exportJSON.OutputJSON(outputPath);
}
void AssetMemoryAnalyzerSystemComponent::Init()
{
static_assert(AZ_ARRAY_SIZE(VRAM_CATEGORIES) == Render::Debug::VRAMAllocationCategory::VRAM_CATEGORY_NUMBER_CATEGORIES, "VRAMAllocationCategory has changed length! Fix VRAM_CATEGORIES to match.");
static_assert(AZ_ARRAY_SIZE(VRAM_SUBCATEGORIES) == Render::Debug::VRAMAllocationSubcategory::VRAM_SUBCATEGORY_NUMBER_SUBCATEGORIES, "VRAMAllocationSubcategory has changed length! Fix VRAM_SUBCATEGORIES to match.");
m_impl->m_debugImGUI.Init(this);
m_impl->m_exportCSV.Init(this);
m_impl->m_exportJSON.Init(this);
}
void AssetMemoryAnalyzerSystemComponent::Activate()
{
AssetMemoryAnalyzerRequestBus::Handler::BusConnect();
}
void AssetMemoryAnalyzerSystemComponent::Deactivate()
{
AssetMemoryAnalyzerRequestBus::Handler::BusDisconnect();
}
}
@@ -0,0 +1,67 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AssetMemoryAnalyzer/AssetMemoryAnalyzerBus.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
namespace AssetMemoryAnalyzer
{
class FrameAnalysis;
class AssetMemoryAnalyzerSystemComponent
: public AZ::Component
, protected AssetMemoryAnalyzerRequestBus::Handler
{
public:
AZ_COMPONENT(AssetMemoryAnalyzerSystemComponent, "{84428E10-24FF-48A7-B5EC-0A28D25C3C68}");
AssetMemoryAnalyzerSystemComponent();
~AssetMemoryAnalyzerSystemComponent();
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
static const char** GetVRAMCategories();
static const char** GetVRAMSubCategories();
bool IsEnabled() const;
////////////////////////////////////////////////////////////////////////
// AssetMemoryAnalyzerRequestBus interface implementation
void SetEnabled(bool enabled) override;
void ExportCSVFile(const char* path) override;
void ExportJSONFile(const char* path) override;
AZStd::shared_ptr<FrameAnalysis> GetAnalysis() override;
////////////////////////////////////////////////////////////////////////
protected:
////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Init() override;
void Activate() override;
void Deactivate() override;
////////////////////////////////////////////////////////////////////////
private:
class Impl;
private:
AZStd::unique_ptr<Impl> m_impl;
};
}
@@ -0,0 +1,12 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AssetMemoryAnalyzer_precompiled.h"
@@ -0,0 +1,14 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <platform.h> // Many CryCommon files require that this is included first.
@@ -0,0 +1,277 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AssetMemoryAnalyzer_precompiled.h"
#include "AssetMemoryAnalyzer.h"
#include "AssetMemoryAnalyzerSystemComponent.h"
#include "DebugImGUI.h"
#include "FormatUtils.h"
#include <AzCore/Debug/AssetTracking.h>
#include <AzCore/std/sort.h>
#include <imgui/imgui.h>
#include <ImGuiBus.h>
namespace AssetMemoryAnalyzer
{
namespace
{
template<Data::AllocationCategories Category>
struct SortFunctions
{
static bool SortChildAssetsByAllocatedMemory(const Data::AssetInfo* lhs, const Data::AssetInfo* rhs)
{
return lhs->m_totalSummary[(int)Category].m_allocatedMemory > rhs->m_totalSummary[(int)Category].m_allocatedMemory;
}
static bool SortAllocationPointsByAllocatedMemory(const Data::AllocationPoint* lhs, const Data::AllocationPoint* rhs)
{
return (lhs->m_codePoint->m_category == rhs->m_codePoint->m_category) ? (lhs->m_totalAllocatedMemory > rhs->m_totalAllocatedMemory) : (lhs->m_codePoint->m_category == Category);
}
static bool SortChildAssetsByAllocationCount(const Data::AssetInfo* lhs, const Data::AssetInfo* rhs)
{
return lhs->m_totalSummary[(int)Category].m_allocationCount > rhs->m_totalSummary[(int)Category].m_allocationCount;
}
static bool SortAllocationPointsByAllocationCount(const Data::AllocationPoint* lhs, const Data::AllocationPoint* rhs)
{
return (lhs->m_codePoint->m_category == rhs->m_codePoint->m_category) ? (lhs->m_allocations.size() > rhs->m_allocations.size()) : (lhs->m_codePoint->m_category == Category);
}
};
}
static const ImVec4 COLUMN_HEADER_COLOR(0.7f, 0.4f, 0.2f, 1.0f);
static const float COLUMN_WIDTH = 128.0f;
DebugImGUI::DebugImGUI()
{
ImGui::ImGuiUpdateListenerBus::Handler::BusConnect();
}
DebugImGUI::~DebugImGUI()
{
ImGui::ImGuiUpdateListenerBus::Handler::BusDisconnect();
}
void DebugImGUI::Init(AssetMemoryAnalyzerSystemComponent* owner)
{
m_owner = owner;
m_childAssetSortFn = &SortFunctions<Data::AllocationCategories::HEAP>::SortChildAssetsByAllocatedMemory;
m_allocationPointSortFn = &SortFunctions<Data::AllocationCategories::HEAP>::SortAllocationPointsByAllocatedMemory;
}
void DebugImGUI::OnImGuiUpdate()
{
using namespace Data;
// Append to main menu at top of screen.
if (ImGui::BeginMainMenuBar())
{
// Add new menu items.
if (ImGui::BeginMenu("AssetMemoryAnalyzer"))
{
if (ImGui::Button(m_enabled == false ? "Open" : "Close"))
{
ImGui::CloseCurrentPopup();
m_enabled = !m_enabled;
}
if (ImGui::Button("Export JSON"))
{
EBUS_EVENT(AssetMemoryAnalyzerRequestBus, ExportJSONFile, nullptr);
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Export CSV (top-level only)"))
{
EBUS_EVENT(AssetMemoryAnalyzerRequestBus, ExportCSVFile, nullptr);
ImGui::CloseCurrentPopup();
}
ImGui::EndMenu();
}
ImGui::EndMainMenuBar();
}
if (m_enabled)
{
// Draw the asset memory analysis window and its contents
ImGui::Begin("Asset Memory Analysis", &m_enabled);
#ifndef AZ_TRACK_ASSET_SCOPES
ImGui::TextColored(ImColor(255, 32, 32), "Asset scope tracking disabled in code. Recompile with AZ_TRACK_ASSET_SCOPES defined (see AssetTracking.h).");
#endif
if (!m_owner->IsEnabled())
{
ImGui::TextColored(ImColor(255, 32, 32), "Asset memory analysis must be enabled by setting the \"assetmem_enable\" CVar to 1.");
}
AZStd::shared_ptr<FrameAnalysis> analysis = m_owner->GetAnalysis();
if (analysis)
{
if (ImGui::Button("Heap Allocation Size"))
{
m_childAssetSortFn = &SortFunctions<AllocationCategories::HEAP>::SortChildAssetsByAllocatedMemory;
m_allocationPointSortFn = &SortFunctions<AllocationCategories::HEAP>::SortAllocationPointsByAllocatedMemory;
}
ImGui::SameLine();
if (ImGui::Button("Heap Allocation Count"))
{
m_childAssetSortFn = &SortFunctions<AllocationCategories::HEAP>::SortChildAssetsByAllocationCount;
m_allocationPointSortFn = &SortFunctions<AllocationCategories::HEAP>::SortAllocationPointsByAllocationCount;
}
ImGui::SameLine();
if (ImGui::Button("VRAM Allocation Size"))
{
m_childAssetSortFn = &SortFunctions<AllocationCategories::VRAM>::SortChildAssetsByAllocatedMemory;
m_allocationPointSortFn = &SortFunctions<AllocationCategories::VRAM>::SortAllocationPointsByAllocatedMemory;
}
ImGui::SameLine();
if (ImGui::Button("VRAM Allocation Count"))
{
m_childAssetSortFn = &SortFunctions<AllocationCategories::VRAM>::SortChildAssetsByAllocationCount;
m_allocationPointSortFn = &SortFunctions<AllocationCategories::VRAM>::SortAllocationPointsByAllocationCount;
}
ImGui::SameLine();
if (ImGui::Button("A -> Z"))
{
m_childAssetSortFn = [](const AssetInfo* lhs, const AssetInfo* rhs) { return strcmp(lhs->m_id, rhs->m_id) < 0; };
m_allocationPointSortFn = [](const AllocationPoint* lhs, const AllocationPoint* rhs) {
int cmp = strcmp(lhs->m_codePoint->m_file, rhs->m_codePoint->m_file);
return (cmp < 0) || (cmp == 0 && lhs->m_codePoint->m_line < rhs->m_codePoint->m_line);
};
}
ImGui::Text("Asset/Allocation");
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - COLUMN_WIDTH * 2);
ImGui::Text("Heap (#/kB)");
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - COLUMN_WIDTH);
ImGui::Text("VRAM (#/kB)");
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 255, 32, 1.0));
OutputLine("Totals", analysis->GetRootAsset().m_totalSummary[(int)AllocationCategories::HEAP], analysis->GetRootAsset().m_totalSummary[(int)AllocationCategories::VRAM]);
ImGui::PopStyleColor();
AZStd::function<void(const AssetInfo*, int depth)> recurse;
recurse = [this, &recurse](const AssetInfo* asset, int depth)
{
AZStd::vector<const AssetInfo*, AZ::OSStdAllocator> childAssetSorter;
childAssetSorter.resize(asset->m_childAssets.size());
AZStd::transform(asset->m_childAssets.begin(), asset->m_childAssets.end(), childAssetSorter.begin(), [](const AssetInfo& ai) { return &ai; });
AZStd::sort(childAssetSorter.begin(), childAssetSorter.end(), m_childAssetSortFn);
m_allocationPointSorter.resize(asset->m_allocationPoints.size());
AZStd::transform(asset->m_allocationPoints.begin(), asset->m_allocationPoints.end(), m_allocationPointSorter.begin(), [](const AllocationPoint& ap) { return &ap; });
AZStd::sort(m_allocationPointSorter.begin(), m_allocationPointSorter.end(), m_allocationPointSortFn);
if (asset->m_id)
{
float prevX = ImGui::GetCursorPosX();
OutputLine(nullptr, asset->m_totalSummary[(int)AllocationCategories::HEAP], asset->m_totalSummary[(int)AllocationCategories::VRAM]);
ImGui::SameLine();
ImGui::SetCursorPosX(prevX);
if (ImGui::TreeNode(asset->m_id))
{
prevX = ImGui::GetCursorPosX();
OutputLine(nullptr, asset->m_localSummary[(int)AllocationCategories::HEAP], asset->m_localSummary[(int)AllocationCategories::VRAM]);
ImGui::SameLine();
ImGui::SetCursorPosX(prevX);
if (ImGui::TreeNode("Scope allocations:"))
{
for (auto ap : m_allocationPointSorter)
{
Summary heapSummary;
Summary vramSummary;
switch (ap->m_codePoint->m_category)
{
case AllocationCategories::HEAP:
ImGui::Text(FormatUtils::FormatCodePoint(*ap->m_codePoint));
heapSummary.m_allocationCount = ap->m_allocations.size();
heapSummary.m_allocatedMemory = ap->m_totalAllocatedMemory;
break;
case AllocationCategories::VRAM:
ImGui::Text("%s", ap->m_codePoint->m_file);
vramSummary.m_allocationCount = ap->m_allocations.size();
vramSummary.m_allocatedMemory = ap->m_totalAllocatedMemory;
break;
}
ImGui::SameLine();
OutputLine(nullptr, heapSummary, vramSummary);
}
ImGui::TreePop();
}
for (auto child : childAssetSorter)
{
recurse(child, depth + 1);
}
ImGui::TreePop();
}
}
else
{
for (auto child : childAssetSorter)
{
recurse(child, depth + 1);
}
}
};
recurse(&analysis->GetRootAsset(), 0);
}
ImGui::End();
}
}
void DebugImGUI::OutputLine(const char* text, const Data::Summary& heapSummary, const Data::Summary& vramSummary)
{
if (text)
{
ImGui::Text(text);
ImGui::SameLine();
}
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - COLUMN_WIDTH * 2);
OutputField(heapSummary);
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - COLUMN_WIDTH);
OutputField(vramSummary);
}
void DebugImGUI::OutputField(const Data::Summary& summary)
{
if (summary.m_allocationCount)
{
ImGui::Text("%u / %s", summary.m_allocationCount, FormatUtils::FormatKB(summary.m_allocatedMemory));
}
else
{
ImGui::Text("-- / --");
}
}
}
@@ -0,0 +1,56 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <ImGuiBus.h>
namespace AssetMemoryAnalyzer
{
namespace Data
{
struct AllocationPoint;
struct AssetInfo;
struct Summary;
}
class AssetMemoryAnalyzerSystemComponent;
// This class provides debug UI for the gem using ImGUI.
class DebugImGUI
: public ImGui::ImGuiUpdateListenerBus::Handler
{
public:
AZ_TYPE_INFO(AssetMemoryAnalyzer::DebugImGUI, "{D121DA34-EF16-46C2-AFC4-A1EE69DA0851}");
AZ_CLASS_ALLOCATOR(DebugImGUI, AZ::OSAllocator, 0);
DebugImGUI();
~DebugImGUI();
void Init(AssetMemoryAnalyzerSystemComponent* owner);
// ImGuiUpdateListenerBus
void OnImGuiUpdate() override;
private:
void OutputLine(const char* text, const Data::Summary& heapSummary, const Data::Summary& vramSummary);
void OutputField(const Data::Summary& summary);
AssetMemoryAnalyzerSystemComponent* m_owner;
bool (*m_childAssetSortFn)(const Data::AssetInfo* lhs, const Data::AssetInfo* rhs) = nullptr;
AZStd::vector<const Data::AssetInfo*, AZ::OSStdAllocator> m_childAssetSorter;
bool (*m_allocationPointSortFn)(const Data::AllocationPoint* lhs, const Data::AllocationPoint* rhs) = nullptr;
AZStd::vector<const Data::AllocationPoint*, AZ::OSStdAllocator> m_allocationPointSorter;
bool m_enabled = false;
};
}
@@ -0,0 +1,86 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AssetMemoryAnalyzer_precompiled.h"
#include "ExportCSV.h"
#include "AssetMemoryAnalyzer.h"
#include "AssetMemoryAnalyzerSystemComponent.h"
#include "FormatUtils.h"
#include <AzCore/IO/FileIO.h>
namespace AssetMemoryAnalyzer
{
ExportCSV::ExportCSV()
{
}
ExportCSV::~ExportCSV()
{
}
void ExportCSV::Init(AssetMemoryAnalyzerSystemComponent* owner)
{
m_owner = owner;
}
void ExportCSV::OutputCSV(const char* path)
{
using namespace Data;
AZStd::shared_ptr<FrameAnalysis> analysis = m_owner->GetAnalysis();
if (!analysis)
{
return;
}
auto fs = AZ::IO::FileIOBase::GetDirectInstance();
AZ::IO::HandleType hdl;
if (!fs->Open(path, AZ::IO::OpenMode::ModeWrite, hdl))
{
AZ_Assert(false, "Unable to open file for writing: %s", path);
}
const AZStd::string header("Label,Heap Count,Heap kb,VRAM Count,VRAM kb\n");
fs->Write(hdl, header.c_str(), header.length());
char lineBuffer[4096];
const auto& rootAsset = analysis->GetRootAsset();
size_t length = snprintf(lineBuffer, sizeof(lineBuffer), "<uncategorized>,%d,%0.2f,%d,%0.2f\n",
rootAsset.m_localSummary[(int)AllocationCategories::HEAP].m_allocationCount,
rootAsset.m_localSummary[(int)AllocationCategories::HEAP].m_allocatedMemory / 1024.0f,
rootAsset.m_localSummary[(int)AllocationCategories::VRAM].m_allocationCount,
rootAsset.m_localSummary[(int)AllocationCategories::VRAM].m_allocatedMemory / 1024.0f
);
fs->Write(hdl, lineBuffer, length);
for (const auto& child : analysis->GetRootAsset().m_childAssets)
{
length = snprintf(lineBuffer, sizeof(lineBuffer), "%s,%d,%0.2f,%d,%0.2f\n",
child.m_id,
child.m_totalSummary[(int)AllocationCategories::HEAP].m_allocationCount,
child.m_totalSummary[(int)AllocationCategories::HEAP].m_allocatedMemory / 1024.0f,
child.m_totalSummary[(int)AllocationCategories::VRAM].m_allocationCount,
child.m_totalSummary[(int)AllocationCategories::VRAM].m_allocatedMemory / 1024.0f
);
fs->Write(hdl, lineBuffer, length);
}
fs->Close(hdl);
AZ_Printf("Debug", "Exported asset allocation list to %s", path);
}
}
@@ -0,0 +1,37 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
namespace AssetMemoryAnalyzer
{
class AssetMemoryAnalyzerSystemComponent;
// This class provides the service of exporting a capture of asset memory to a JSON file that is viewable in the web viewer.
class ExportCSV
{
public:
AZ_TYPE_INFO(AssetMemoryAnalyzer::ExportCSV, "{FEA7D137-EA93-4366-85C2-DCBCE00B3376}");
AZ_CLASS_ALLOCATOR(ExportCSV, AZ::OSAllocator, 0);
ExportCSV();
~ExportCSV();
void Init(AssetMemoryAnalyzerSystemComponent* owner);
void OutputCSV(const char* path);
private:
AssetMemoryAnalyzerSystemComponent* m_owner;
};
}
@@ -0,0 +1,188 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AssetMemoryAnalyzer_precompiled.h"
#include "ExportJSON.h"
#include "AssetMemoryAnalyzer.h"
#include "AssetMemoryAnalyzerSystemComponent.h"
#include "FormatUtils.h"
#include <AzCore/Debug/AssetTracking.h>
#include <Common/Memory/VRAMDrillerBus.h>
#include <AzCore/IO/FileIO.h>
#include <AzCore/JSON/filewritestream.h>
#include <AzCore/JSON/rapidjson.h>
#include <AzCore/JSON/prettywriter.h>
#include <AzCore/std/sort.h>
#include <imgui/imgui.h>
#include <ImGuiBus.h>
namespace AssetMemoryAnalyzer
{
namespace
{
template<class WriterT>
static void OutputAllocationInfo(WriterT& writer, size_t count, size_t bytes)
{
writer.StartObject();
writer.Key("count");
writer.Int((int)count);
writer.Key("kb");
writer.String(FormatUtils::FormatKB(bytes));
writer.EndObject();
}
template<class WriterT>
static void OutputAllocationInfo(WriterT& writer, const Data::Summary& summary)
{
OutputAllocationInfo(writer, summary.m_allocationCount, summary.m_allocatedMemory);
}
}
ExportJSON::ExportJSON()
{
}
ExportJSON::~ExportJSON()
{
}
void ExportJSON::Init(AssetMemoryAnalyzerSystemComponent* owner)
{
m_owner = owner;
}
void ExportJSON::OutputJSON(const char* path)
{
using namespace Data;
using namespace rapidjson;
AZStd::shared_ptr<FrameAnalysis> analysis = m_owner->GetAnalysis();
if (!analysis)
{
return;
}
StringBuffer buff;
PrettyWriter<StringBuffer> writer(buff);
size_t idCounter = 0;
AZStd::function<void(const AssetInfo&, int)> recurse;
recurse = [&recurse, &writer, &idCounter](const AssetInfo& asset, int depth)
{
writer.StartObject();
writer.Key("id");
writer.Int(idCounter++);
writer.Key("label");
writer.String(asset.m_id ? asset.m_id : "Root");
writer.Key("heap");
OutputAllocationInfo(writer, asset.m_totalSummary[(int)AllocationCategories::HEAP]);
writer.Key("vram");
OutputAllocationInfo(writer, asset.m_totalSummary[(int)AllocationCategories::VRAM]);
if (!asset.m_allocationPoints.empty() || !asset.m_childAssets.empty())
{
writer.Key("_children");
writer.StartArray();
if (!asset.m_allocationPoints.empty())
{
writer.StartObject();
writer.Key("id");
writer.Int(idCounter++);
writer.Key("label");
writer.String("<local allocations>");
writer.Key("heap");
OutputAllocationInfo(writer, asset.m_localSummary[(int)AllocationCategories::HEAP]);
writer.Key("vram");
OutputAllocationInfo(writer, asset.m_localSummary[(int)AllocationCategories::VRAM]);
writer.Key("_children");
writer.StartArray();
for (const auto& ap : asset.m_allocationPoints)
{
Summary heapSummary;
Summary vramSummary;
writer.StartObject();
writer.Key("id");
writer.Int(idCounter++);
writer.Key("label");
switch (ap.m_codePoint->m_category)
{
case AllocationCategories::HEAP:
writer.String(FormatUtils::FormatCodePoint(*ap.m_codePoint));
heapSummary.m_allocationCount = ap.m_allocations.size();
heapSummary.m_allocatedMemory = ap.m_totalAllocatedMemory;
break;
case AllocationCategories::VRAM:
writer.String(ap.m_codePoint->m_file);
vramSummary.m_allocationCount = ap.m_allocations.size();
vramSummary.m_allocatedMemory = ap.m_totalAllocatedMemory;
break;
}
writer.Key("heap");
OutputAllocationInfo(writer, heapSummary);
writer.Key("vram");
OutputAllocationInfo(writer, vramSummary);
writer.EndObject();
}
writer.EndArray();
writer.EndObject();
}
for (const auto& childInfo : asset.m_childAssets)
{
recurse(childInfo, depth + 1);
}
writer.EndArray();
}
writer.EndObject();
};
writer.StartArray();
recurse(analysis->GetRootAsset(), 0);
writer.EndArray();
auto fs = AZ::IO::FileIOBase::GetDirectInstance();
AZ::IO::HandleType hdl;
if (!fs->Open(path, AZ::IO::OpenMode::ModeWrite, hdl))
{
AZ_Assert(false, "Unable to open file for writing: %s", path);
}
fs->Write(hdl, buff.GetString(), buff.GetSize());
fs->Close(hdl);
AZ_Printf("Debug", "Exported asset allocation map to %s", path);
}
}
@@ -0,0 +1,36 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
namespace AssetMemoryAnalyzer
{
class AssetMemoryAnalyzerSystemComponent;
// This class provides the service of exporting a capture of asset memory to a JSON file that is viewable in the web viewer.
class ExportJSON
{
public:
AZ_TYPE_INFO(AssetMemoryAnalyzer::ExportJSON, "{AA85F7E0-8FAF-43BC-9C09-6411270AE3E7}");
AZ_CLASS_ALLOCATOR(ExportJSON, AZ::OSAllocator, 0);
ExportJSON();
~ExportJSON();
void Init(AssetMemoryAnalyzerSystemComponent* owner);
void OutputJSON(const char* path);
private:
AssetMemoryAnalyzerSystemComponent* m_owner;
};
}
@@ -0,0 +1,41 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "AssetMemoryAnalyzer_precompiled.h"
#include "FormatUtils.h"
#include "AssetMemoryAnalyzer.h"
#include <AzFramework/StringFunc/StringFunc.h>
namespace AssetMemoryAnalyzer
{
namespace FormatUtils
{
const char* FormatCodePoint(const Data::CodePoint& cp)
{
static char buff[1024];
azsnprintf(buff, sizeof(buff), "%s:%d", cp.m_file, cp.m_line);
return buff;
}
const char* FormatKB(size_t bytes)
{
static char buff[32];
int len = azsnprintf(buff, sizeof(buff), "%0.2f", bytes / 1024.0f);
AzFramework::StringFunc::NumberFormatting::GroupDigits(buff, sizeof(buff), len - 3);
return buff;
}
}
}
@@ -0,0 +1,31 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
namespace AssetMemoryAnalyzer
{
namespace Data
{
struct CodePoint;
}
namespace FormatUtils
{
// Formats a location in code to a single line of human-readable text. Returns a pointer to the resulting string.
// WARNING: Returns pointer to an internal static buffer for performance. Single-threaded access only!
extern const char* FormatCodePoint(const Data::CodePoint& cp);
// Formats a byte value to be easily read in kilobytes. Returns a pointer to the resulting string.
// WARNING: Returns pointer to an internal static buffer for performance. Single-threaded access only!
extern const char* FormatKB(size_t bytes);
}
}