Files
UnrealEngineUWP/Engine/Source/Programs/UnrealTraceServer/src/Store.cpp
Martin Ridgers 49906a5874 Delay slightly before reading modified timestamps.
2f30438 / Martin Ridgers / 2021-08-17 15:51:52 +0200

[CL 17229421 by Martin Ridgers in ue5-main branch]
2021-08-19 04:14:53 -04:00

388 lines
8.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Pch.h"
#include "Store.h"
////////////////////////////////////////////////////////////////////////////////
// Pre-C++20 there is now way to convert between clocks. C++20 onwards it is
// possible to create a clock with an Unreal epoch and convert file times to it.
// But at time of writing, C++20 isn't complete enough across the board.
static const uint64 UnrealEpochYear = 1;
#if TS_USING(TS_PLATFORM_WINDOWS)
static const uint64 FsEpochYear = 1601;
#else
static const uint64 FsEpochYear = 1970;
#endif
static int64 FsToUnrealEpochBiasSeconds = uint64(double(FsEpochYear - UnrealEpochYear) * 365.2425) * 86400;
#if TS_USING(TS_WITH_DIR_WATCHER)
////////////////////////////////////////////////////////////////////////////////
#if TS_USING(TS_PLATFORM_WINDOWS)
class FStore::FDirWatcher
: public asio::windows::object_handle
{
public:
using asio::windows::object_handle::object_handle;
};
#else
class FStore::FDirWatcher
//: public asio::posix::stream_descriptor
{
public:
void async_wait(...) {}
void cancel() {}
void close() {}
bool is_open() { return false; }
};
#endif // PLATFORM_WINDOWS
#endif // TS_WITH_DIR_WATCHER
////////////////////////////////////////////////////////////////////////////////
FStore::FTrace::FTrace(const char* InPath)
: Path(InPath)
{
// Extract the trace's name
const char* Dot = std::strrchr(*Path, '.');
if (Dot == nullptr)
{
Dot = *Path;
}
for (const char* c = Dot; c > *Path; --c)
{
if (c[-1] == '\\' || c[-1] == '/')
{
Name = FStringView(c, int32(Dot - c));
break;
}
}
Id = QuickStoreHash(Name);
// Calculate that trace's timestamp. Bias in seconds then convert to 0.1us.
std::filesystem::file_time_type LastWriteTime = std::filesystem::last_write_time(InPath);
auto LastWriteDuration = LastWriteTime.time_since_epoch();
Timestamp = std::chrono::duration_cast<std::chrono::seconds>(LastWriteDuration).count();
Timestamp += FsToUnrealEpochBiasSeconds;
Timestamp *= 10'000'000;
}
////////////////////////////////////////////////////////////////////////////////
const FStringView& FStore::FTrace::GetName() const
{
return Name;
}
////////////////////////////////////////////////////////////////////////////////
uint32 FStore::FTrace::GetId() const
{
return Id;
}
////////////////////////////////////////////////////////////////////////////////
uint64 FStore::FTrace::GetSize() const
{
return std::filesystem::file_size(*Path);
}
////////////////////////////////////////////////////////////////////////////////
uint64 FStore::FTrace::GetTimestamp() const
{
return Timestamp;
}
////////////////////////////////////////////////////////////////////////////////
FStore::FStore(asio::io_context& InIoContext, const char* InStoreDir)
: IoContext(InIoContext)
, StoreDir(InStoreDir)
{
StoreDir += "/001";
std::filesystem::create_directories(*StoreDir);
Refresh();
#if TS_USING(TS_WITH_DIR_WATCHER)
#if TS_USING(TS_PLATFORM_WINDOWS)
FWinApiStr StoreDirW(*StoreDir);
HANDLE DirWatchHandle = FindFirstChangeNotificationW(StoreDirW, false, FILE_NOTIFY_CHANGE_FILE_NAME);
if (DirWatchHandle == INVALID_HANDLE_VALUE)
{
DirWatchHandle = 0;
}
DirWatcher = new FDirWatcher(IoContext, DirWatchHandle);
#else
/* NOTE TO SELF - DELETE FWinApiStr NOW! */
#endif // TS_PLATFORM_WINDOWS
WatchDir();
#endif // TS_WITH_DIR_WATCHER
}
////////////////////////////////////////////////////////////////////////////////
FStore::~FStore()
{
#if TS_USING(TS_WITH_DIR_WATCHER)
if (DirWatcher != nullptr)
{
check(!DirWatcher->is_open());
delete DirWatcher;
}
#endif // TS_WITH_DIR_WATCHER
}
////////////////////////////////////////////////////////////////////////////////
void FStore::Close()
{
#if TS_USING(TS_WITH_DIR_WATCHER)
if (DirWatcher != nullptr)
{
DirWatcher->cancel();
DirWatcher->close();
}
#endif // TS_WITH_DIR_WATCHER
ClearTraces();
}
////////////////////////////////////////////////////////////////////////////////
void FStore::ClearTraces()
{
for (FTrace* Trace : Traces)
{
delete Trace;
}
Traces.Empty();
ChangeSerial = 0;
}
#if TS_USING(TS_WITH_DIR_WATCHER)
////////////////////////////////////////////////////////////////////////////////
void FStore::WatchDir()
{
if (DirWatcher == nullptr)
{
return;
}
DirWatcher->async_wait([this] (asio::error_code ErrorCode)
{
if (ErrorCode)
{
return;
}
#if TS_USING(TS_PLATFORM_WINDOWS)
// Windows doesn't update modified timestamps in a timely fashion when
// copying files (or it could be Explorer that doesn't update it until
// later). This is a not-so-pretty "wait for a little bit" workaround.
auto* DelayTimer = new asio::steady_timer(IoContext);
DelayTimer->expires_after(std::chrono::seconds(2));
DelayTimer->async_wait([this, DelayTimer] (const asio::error_code& ErrorCode)
{
delete DelayTimer;
Refresh();
FindNextChangeNotification(DirWatcher->native_handle());
WatchDir();
});
#else
Refresh();
WatchDir();
#endif
});
}
#endif // TS_WITH_DIR_WATCHER
////////////////////////////////////////////////////////////////////////////////
const char* FStore::GetStoreDir() const
{
return *StoreDir;
}
////////////////////////////////////////////////////////////////////////////////
uint32 FStore::GetChangeSerial() const
{
return ChangeSerial;
}
////////////////////////////////////////////////////////////////////////////////
uint32 FStore::GetTraceCount() const
{
return Traces.Num();
}
////////////////////////////////////////////////////////////////////////////////
const FStore::FTrace* FStore::GetTraceInfo(uint32 Index) const
{
if (Index >= uint32(Traces.Num()))
{
return nullptr;
}
return Traces[Index];
}
////////////////////////////////////////////////////////////////////////////////
FStore::FTrace* FStore::GetTrace(uint32 Id) const
{
for (FTrace* Trace : Traces)
{
if (Trace->GetId() == Id)
{
return Trace;
}
}
return nullptr;
}
////////////////////////////////////////////////////////////////////////////////
FStore::FTrace* FStore::AddTrace(const char* Path)
{
FTrace NewTrace(Path);
uint32 Id = NewTrace.GetId();
if (FTrace* Existing = GetTrace(Id))
{
return Existing;
}
ChangeSerial += Id;
FTrace* Trace = new FTrace(MoveTemp(NewTrace));
Traces.Add(Trace);
return Trace;
}
////////////////////////////////////////////////////////////////////////////////
FStore::FNewTrace FStore::CreateTrace()
{
FString TracePath;
#if 0
bool bOk = false;
for (int i = 0; i < 256; ++i)
{
uint32 TraceId = ++LastTraceId;
TracePath = StoreDir;
TracePath.Appendf("/%05d", TraceId);
if (!std::filesystem::is_directory(*TracePath) && std::filesystem::create_directories(*TracePath))
{
bOk = true;
break;
}
}
if (!bOk)
{
return {};
}
TracePath += "/data.utrace";
#else
// N.B. Not thread safe!?
char Prefix[24];
std::time_t Now = std::time(nullptr);
std::tm* LocalNow = std::localtime(&Now);
std::strftime(Prefix, TS_ARRAY_COUNT(Prefix), "%Y%m%d_%H%M%S", LocalNow);
TracePath = StoreDir;
TracePath += "/";
TracePath += Prefix;
TracePath += ".utrace";
for (uint32 Index = 0; std::filesystem::is_regular_file(*TracePath); ++Index)
{
char Suffix[64];
std::snprintf(Suffix, TS_ARRAY_COUNT(Suffix), "/%s_%02d.utrace", Prefix, Index);
TracePath = StoreDir;
TracePath += *Suffix;
}
#endif // 0
FAsioWriteable* File = FAsioFile::WriteFile(IoContext, *TracePath);
if (File == nullptr)
{
return {};
}
FTrace* Trace = AddTrace(*TracePath);
if (Trace == nullptr)
{
delete File;
return {};
}
return { Trace->GetId(), File };
}
////////////////////////////////////////////////////////////////////////////////
bool FStore::HasTrace(uint32 Id) const
{
return GetTrace(Id) != nullptr;
}
////////////////////////////////////////////////////////////////////////////////
FAsioReadable* FStore::OpenTrace(uint32 Id)
{
FTrace* Trace = GetTrace(Id);
if (Trace == nullptr)
{
return nullptr;
}
FString TracePath;
TracePath = StoreDir;
#if 0
TracePath.Appendf("/%05d/data.utrace", Id);
#else
TracePath += "/";
TracePath += Trace->GetName();
TracePath += ".utrace";
#endif // 0
return FAsioFile::ReadFile(IoContext, *TracePath);
}
////////////////////////////////////////////////////////////////////////////////
void FStore::Refresh()
{
ClearTraces();
for (auto& DirItem : std::filesystem::directory_iterator(*StoreDir))
{
#if 0
if (!DirItem.is_directory())
{
continue;
}
int32 Id = FCString::Atoi(Path);
LastTraceId = (Id < LastTraceId) ? Id : LastTraceId;
#else
if (DirItem.is_directory())
{
continue;
}
std::filesystem::path Extension = DirItem.path().extension();
if (Extension != ".utrace")
{
continue;
}
AddTrace(DirItem.path().string().c_str());
#endif // 0
};
}
/* vim: set noexpandtab : */