Skip to content
This repository was archived by the owner on Oct 4, 2022. It is now read-only.

Statistics collection and thread safety fixes for AZ::IO::DedicatedCache #494

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 122 additions & 58 deletions dev/Code/Framework/AzCore/AzCore/IO/Streamer/DedicatedCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,14 @@
#include <AzCore/IO/Streamer/FileRequest.h>
#include <AzCore/IO/Streamer/DedicatedCache.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/parallel/lock.h>

namespace AZ
{
namespace IO
{
using RecursiveLockGuard = AZStd::lock_guard<AZStd::recursive_mutex>;

DedicatedCache::DedicatedCache(u64 cacheSize, u32 blockSize, bool onlyEpilogWrites)
: StreamStackEntry("Dedicated cache")
, m_cacheSize(cacheSize)
Expand All @@ -29,6 +32,9 @@ namespace AZ
void DedicatedCache::SetNext(AZStd::shared_ptr<StreamStackEntry> next)
{
m_next = AZStd::move(next);

RecursiveLockGuard lock(m_cacheMutex);

for (AZStd::unique_ptr<BlockCache>& cache : m_cachedFileCaches)
{
cache->SetNext(m_next);
Expand All @@ -38,6 +44,9 @@ namespace AZ
void DedicatedCache::SetContext(StreamerContext& context)
{
StreamStackEntry::SetContext(context);

RecursiveLockGuard lock(m_cacheMutex);

for (AZStd::unique_ptr<BlockCache>& cache : m_cachedFileCaches)
{
cache->SetContext(context);
Expand All @@ -62,119 +71,160 @@ namespace AZ
bool DedicatedCache::ExecuteRequests()
{
bool hasProcessedRequest = false;
for (AZStd::unique_ptr<BlockCache>& cache : m_cachedFileCaches)

{
hasProcessedRequest = cache->ExecuteRequests() || hasProcessedRequest;
RecursiveLockGuard lock(m_cacheMutex);

for (AZStd::unique_ptr<BlockCache>& cache : m_cachedFileCaches)
{
hasProcessedRequest = cache->ExecuteRequests() || hasProcessedRequest;
}
}

return StreamStackEntry::ExecuteRequests() || hasProcessedRequest;
}

void DedicatedCache::ReadFile(FileRequest* request)
{
FileRequest::ReadData& data = request->GetReadData();

size_t index = FindCache(*data.m_path, data.m_offset);
if (index == s_fileNotFound)
{
if (m_next)
RecursiveLockGuard lock(m_cacheMutex);

size_t index = FindCache(*data.m_path, data.m_offset);
if (index != s_fileNotFound)
{
m_next->PrepareRequest(request);
m_cachedFileCaches[index]->PrepareRequest(request);
return;
}
}
else

if (m_next)
{
m_cachedFileCaches[index]->PrepareRequest(request);
m_next->PrepareRequest(request);
}
}

void DedicatedCache::FlushCache(const RequestPath& filePath)
{
size_t count = m_cachedFileNames.size();
for (size_t i = 0; i < count; ++i)
{
if (m_cachedFileNames[i] == filePath)
RecursiveLockGuard lock(m_cacheMutex);

size_t count = m_cachedFileNames.size();
for (size_t i = 0; i < count; ++i)
{
m_cachedFileCaches[i]->FlushCache(filePath);
if (m_cachedFileNames[i] == filePath)
{
m_cachedFileCaches[i]->FlushCache(filePath);
}
}
}

StreamStackEntry::FlushCache(filePath);
}

void DedicatedCache::FlushEntireCache()
{
for (AZStd::unique_ptr<BlockCache>& cache : m_cachedFileCaches)
{
cache->FlushEntireCache();
RecursiveLockGuard lock(m_cacheMutex);

for (AZStd::unique_ptr<BlockCache>& cache : m_cachedFileCaches)
{
cache->FlushEntireCache();
}
}

StreamStackEntry::FlushEntireCache();
}

void DedicatedCache::CollectStatistics(AZStd::vector<Statistic>& statistics) const
{
size_t count = m_cachedFileNames.size();
for (size_t i = 0; i < count; ++i)
{
double hitRate = m_cachedFileCaches[i]->CalculateHitRatePercentage();
double cacheable = m_cachedFileCaches[i]->CalculateCacheableRatePercentage();
s32 slots = m_cachedFileCaches[i]->CalculateAvailableRequestSlots();

AZStd::string name;
if (m_cachedFileRanges[i].IsEntireFile())
{
name = AZStd::string::format("%s/%s", m_name.c_str(), m_cachedFileNames[i].GetRelativePath());
}
else
RecursiveLockGuard lock(m_cacheMutex);

size_t count = m_cachedFileNames.size();
for (size_t i = 0; i < count; ++i)
{
name = AZStd::string::format("%s/%s %llu:%llu", m_name.c_str(), m_cachedFileNames[i].GetRelativePath(),
m_cachedFileRanges[i].GetOffset(), m_cachedFileRanges[i].GetEndPoint());
}
double hitRate = m_cachedFileCaches[i]->CalculateHitRatePercentage();
double cacheable = m_cachedFileCaches[i]->CalculateCacheableRatePercentage();
s32 slots = m_cachedFileCaches[i]->CalculateAvailableRequestSlots();

DedicatedCache* _this = const_cast<DedicatedCache*>(this);
AZStd::string_view name;

statistics.push_back(Statistic::CreatePercentage(name, "Cache Hit Rate", hitRate));
statistics.push_back(Statistic::CreatePercentage(name, "Cacheable", cacheable));
statistics.push_back(Statistic::CreateInteger(name, "Available slots", slots));
if (m_cachedFileRanges[i].IsEntireFile())
{
name = _this->AddOrUpdateCachedName(i, AZStd::string::format("%s/%s", m_name.c_str(), m_cachedFileNames[i].GetRelativePath()));
}
else
{
name = _this->AddOrUpdateCachedName(i, AZStd::string::format("%s/%s %llu:%llu", m_name.c_str(), m_cachedFileNames[i].GetRelativePath(),
m_cachedFileRanges[i].GetOffset(), m_cachedFileRanges[i].GetEndPoint()));
}

statistics.push_back(Statistic::CreatePercentage(name, "Cache Hit Rate", hitRate));
statistics.push_back(Statistic::CreatePercentage(name, "Cacheable", cacheable));
statistics.push_back(Statistic::CreateInteger(name, "Available slots", slots));
}
}

StreamStackEntry::CollectStatistics(statistics);
}

void DedicatedCache::CreateDedicatedCache(const RequestPath& filename, const FileRange& range)
{
size_t index = FindCache(filename, range);
if (index == s_fileNotFound)
{
index = m_cachedFileCaches.size();
m_cachedFileNames.push_back(filename);
m_cachedFileRanges.push_back(range);
m_cachedFileCaches.push_back(AZStd::make_unique<BlockCache>(m_cacheSize, m_blockSize, m_onlyEpilogWrites));
m_cachedFileCaches[index]->SetNext(m_next);
m_cachedFileCaches[index]->SetContext(*m_context);
m_cachedFileRefCounts.push_back(1);
}
else
{
++m_cachedFileRefCounts[index];
RecursiveLockGuard lock(m_cacheMutex);

size_t index = FindCache(filename, range);
if (index == s_fileNotFound)
{
index = m_cachedFileCaches.size();
m_cachedFileNames.push_back(filename);
m_cachedFileRanges.push_back(range);
m_cachedFileCaches.push_back(AZStd::make_unique<BlockCache>(m_cacheSize, m_blockSize, m_onlyEpilogWrites));
m_cachedFileCaches[index]->SetNext(m_next);
m_cachedFileCaches[index]->SetContext(*m_context);
m_cachedFileRefCounts.push_back(1);
}
else
{
++m_cachedFileRefCounts[index];
}
}

StreamStackEntry::CreateDedicatedCache(filename, range);
}

void DedicatedCache::DestroyDedicatedCache(const RequestPath& filename, const FileRange& range)
{
size_t index = FindCache(filename, range);
if (index != s_fileNotFound)
{
AZ_Assert(m_cachedFileRefCounts[index] > 0, "A dedicated cache entry without references was left.");
--m_cachedFileRefCounts[index];
if (m_cachedFileRefCounts[index] == 0)
RecursiveLockGuard lock(m_cacheMutex);

size_t index = FindCache(filename, range);
if (index != s_fileNotFound)
{
m_cachedFileNames.erase(m_cachedFileNames.begin() + index);
m_cachedFileRanges.erase(m_cachedFileRanges.begin() + index);
m_cachedFileCaches.erase(m_cachedFileCaches.begin() + index);
m_cachedFileRefCounts.erase(m_cachedFileRefCounts.begin() + index);
AZ_Assert(m_cachedFileRefCounts[index] > 0, "A dedicated cache entry without references was left.");
--m_cachedFileRefCounts[index];
if (m_cachedFileRefCounts[index] == 0)
{
if (m_cachedStatNames.size() > index)
{
m_cachedStatNames.erase(m_cachedStatNames.begin() + index);
}

m_cachedFileNames.erase(m_cachedFileNames.begin() + index);
m_cachedFileRanges.erase(m_cachedFileRanges.begin() + index);
m_cachedFileCaches.erase(m_cachedFileCaches.begin() + index);
m_cachedFileRefCounts.erase(m_cachedFileRefCounts.begin() + index);
}
}
else
{
AZ_Assert(false, "Attempting to destroy a dedicated cache that doesn't exist or was already destroyed.");
}
}
else
{
AZ_Assert(false, "Attempting to destroy a dedicated cache that doesn't exist or was already destroyed.");
}

StreamStackEntry::DestroyDedicatedCache(filename, range);
}

Expand Down Expand Up @@ -203,5 +253,19 @@ namespace AZ
}
return s_fileNotFound;
}

AZStd::string_view DedicatedCache::AddOrUpdateCachedName(size_t index, AZStd::string&& name)
{
if (m_cachedStatNames.size() <= index)
{
m_cachedStatNames.push_back(AZStd::forward<AZStd::string>(name));
}
else
{
m_cachedStatNames[index].assign(AZStd::forward<AZStd::string>(name));
}

return m_cachedStatNames.at(index);
}
} // namespace IO
} // namespace AZ
5 changes: 5 additions & 0 deletions dev/Code/Framework/AzCore/AzCore/IO/Streamer/DedicatedCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <AzCore/IO/Streamer/StreamStackEntry.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/parallel/mutex.h>

namespace AZ
{
Expand Down Expand Up @@ -52,13 +53,17 @@ namespace AZ
void ReadFile(FileRequest* request);
size_t FindCache(const RequestPath& filename, FileRange range);
size_t FindCache(const RequestPath& filename, u64 offset);
AZStd::string_view AddOrUpdateCachedName(size_t index, AZStd::string&& name);

static constexpr size_t s_fileNotFound = static_cast<size_t>(-1);

AZStd::vector<RequestPath> m_cachedFileNames;
AZStd::vector<FileRange> m_cachedFileRanges;
AZStd::vector<AZStd::unique_ptr<BlockCache>> m_cachedFileCaches;
AZStd::vector<size_t> m_cachedFileRefCounts;
AZStd::vector<AZStd::string> m_cachedStatNames;

mutable AZStd::recursive_mutex m_cacheMutex;

u64 m_cacheSize;
u32 m_blockSize;
Expand Down