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

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
213 changes: 155 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,172 @@ 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);

DedicatedCache* _this = const_cast<DedicatedCache*>(this);
_this->CleanupCachedNames();
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();

AZStd::string_view name;

if (m_cachedFileRanges[i].IsEntireFile())
{
auto combinedName = AZStd::string::format("%s/%s", m_name.c_str(), m_cachedFileNames[i].GetRelativePath());
name = _this->GetOrAddCachedName(AZStd::hash_string(combinedName.begin(), combinedName.length()), AZStd::move(combinedName));
}
else
{
auto combinedName = AZStd::string::format("%s/%s %llu:%llu", m_name.c_str(), m_cachedFileNames[i].GetRelativePath(),
m_cachedFileRanges[i].GetOffset(), m_cachedFileRanges[i].GetEndPoint());
name = _this->GetOrAddCachedName(AZStd::hash_string(combinedName.begin(), combinedName.length()), AZStd::move(combinedName));
}

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));
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)
{
size_t hashToDelete = 0;
if (m_cachedFileRanges[index].IsEntireFile())
{
auto combinedName = AZStd::string::format("%s/%s", m_name.c_str(), m_cachedFileNames[index].GetRelativePath());
hashToDelete = AZStd::hash_string(combinedName.begin(), combinedName.length());
}
else
{
auto combinedName = AZStd::string::format("%s/%s %llu:%llu", m_name.c_str(), m_cachedFileNames[index].GetRelativePath(),
m_cachedFileRanges[index].GetOffset(), m_cachedFileRanges[index].GetEndPoint());
hashToDelete = AZStd::hash_string(combinedName.begin(), combinedName.length());
}
m_cachedStatNamesToDelete.emplace_back(hashToDelete);

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 +265,40 @@ namespace AZ
}
return s_fileNotFound;
}

AZStd::string_view DedicatedCache::GetOrAddCachedName(size_t index, AZStd::string&& name)
{
auto iter = AZStd::find_if(m_cachedStatNames.begin(), m_cachedStatNames.end(), [index](const auto& element)
{
return element.first == index;
});

if (iter != m_cachedStatNames.end())
{
return AZStd::string_view(iter->second);
}
else
{
m_cachedStatNames.push_back(AZStd::make_pair<size_t, AZStd::string>(index, AZStd::forward<AZStd::string>(name)));
return AZStd::string_view(m_cachedStatNames.back().second);
}
}

void DedicatedCache::CleanupCachedNames()
{
for (const auto fileHash : m_cachedStatNamesToDelete)
{
auto iter = AZStd::find_if(m_cachedStatNames.begin(), m_cachedStatNames.end(), [fileHash](const auto& element)
{
return element.first == fileHash;
});

if (iter != m_cachedStatNames.end())
{
m_cachedStatNames.erase(iter);
}
}
m_cachedStatNamesToDelete.clear();
}
} // namespace IO
} // namespace AZ
7 changes: 7 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,19 @@ 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 GetOrAddCachedName(size_t index, AZStd::string&& name);
void CleanupCachedNames();

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::list<AZStd::pair<size_t, AZStd::string>> m_cachedStatNames;
AZStd::vector<size_t> m_cachedStatNamesToDelete;

mutable AZStd::recursive_mutex m_cacheMutex;

u64 m_cacheSize;
u32 m_blockSize;
Expand Down