You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Previously, we only added the new directory and not the contents. The new AssetDataGatherer has this assumption now though: // Note that we AddDiscovered but do not scan the directory // Any files and paths under it will be added by their own event from the directory watcher, so a scan is unnecessary. // The directory may also be scanned in the future because a parent directory is still yet pending to scan, // we do not try to prevent that wasteful rescan because this is a rare event and it does not cause a behavior problem When we iterate the contents of a new directory we now add them to the FileChanges array to match the above. #review-17900756 @Brandon.Schaefer, @Will.Damon #jira UE-131552 [CL 17907948 by Michael Sartain in ue5-main branch]
692 lines
19 KiB
C++
692 lines
19 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Linux/DirectoryWatchRequestLinux.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "DirectoryWatcherPrivate.h"
|
|
|
|
// To see inotify watch events:
|
|
// TestPAL dirwatcher -LogCmds="LogDirectoryWatcher VeryVerbose"
|
|
|
|
#define EVENT_SIZE ( sizeof(struct inotify_event) )
|
|
#define EVENT_BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )
|
|
|
|
static bool GDumpStats = false;
|
|
static bool GDumpedError = false;
|
|
static FString GINotifyErrorMsg;
|
|
|
|
static uint32 GetPathNameHash(const FString& Key)
|
|
{
|
|
const TCHAR* Str = &Key[0];
|
|
uint32 StrLen = sizeof(TCHAR) * Key.Len();
|
|
|
|
return CityHash64(reinterpret_cast<const char*>(Str), StrLen);
|
|
}
|
|
|
|
FDirectoryWatchRequestLinux::FDirectoryWatchRequestLinux()
|
|
: bWatchSubtree(false)
|
|
, bEndWatchRequestInvoked(false)
|
|
, FileDescriptor(-1)
|
|
{
|
|
}
|
|
|
|
FDirectoryWatchRequestLinux::~FDirectoryWatchRequestLinux()
|
|
{
|
|
Shutdown();
|
|
}
|
|
|
|
void FDirectoryWatchRequestLinux::Shutdown()
|
|
{
|
|
// Go through and inotify_rm_watch all our watch descriptors
|
|
for (auto MapIt = WatchDescriptorsToPaths.CreateConstIterator(); MapIt; ++MapIt)
|
|
{
|
|
inotify_rm_watch(FileDescriptor, MapIt->Key);
|
|
}
|
|
|
|
WatchDescriptorsToPaths.Empty();
|
|
PathNameHashSet.Empty();
|
|
|
|
if (FileDescriptor != -1)
|
|
{
|
|
close(FileDescriptor);
|
|
FileDescriptor = -1;
|
|
}
|
|
}
|
|
|
|
bool FDirectoryWatchRequestLinux::Init(const FString& InDirectory, uint32 Flags)
|
|
{
|
|
if (InDirectory.Len() == 0)
|
|
{
|
|
// Verify input
|
|
return false;
|
|
}
|
|
|
|
Shutdown();
|
|
|
|
bWatchSubtree = (Flags & IDirectoryWatcher::WatchOptions::IgnoreChangesInSubtree) == 0;
|
|
|
|
bEndWatchRequestInvoked = false;
|
|
|
|
// Make sure the path is absolute
|
|
WatchDirectory = FPaths::ConvertRelativePathToFull(InDirectory);
|
|
|
|
UE_LOG(LogDirectoryWatcher, Verbose, TEXT("Adding watch for directory tree '%s'"), *WatchDirectory);
|
|
|
|
FileDescriptor = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
|
|
if (FileDescriptor == -1)
|
|
{
|
|
if (errno == EMFILE)
|
|
{
|
|
SetINotifyErrorMsg(TEXT("Failed to init inotify (ran out of inotify instances)"));
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogDirectoryWatcher, Error, TEXT("Failed to init inotify (errno=%d, %s)"), errno, UTF8_TO_TCHAR(strerror(errno)));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Find all subdirs and add inotify watch requests
|
|
WatchDirectoryTree(WatchDirectory, nullptr);
|
|
|
|
return true;
|
|
}
|
|
|
|
FDelegateHandle FDirectoryWatchRequestLinux::AddDelegate(const IDirectoryWatcher::FDirectoryChanged& InDelegate, uint32 Flags)
|
|
{
|
|
Delegates.Emplace(InDelegate, Flags);
|
|
return Delegates.Last().Key.GetHandle();
|
|
}
|
|
|
|
bool FDirectoryWatchRequestLinux::RemoveDelegate(FDelegateHandle InHandle)
|
|
{
|
|
return Delegates.RemoveAll([=](const FWatchDelegate& Delegate) {
|
|
return Delegate.Key.GetHandle() == InHandle;
|
|
}) != 0;
|
|
}
|
|
|
|
bool FDirectoryWatchRequestLinux::HasDelegates() const
|
|
{
|
|
return Delegates.Num() > 0;
|
|
}
|
|
|
|
void FDirectoryWatchRequestLinux::EndWatchRequest()
|
|
{
|
|
bEndWatchRequestInvoked = true;
|
|
}
|
|
|
|
void FDirectoryWatchRequestLinux::ProcessNotifications(TMap<FString, FDirectoryWatchRequestLinux*>& RequestMap)
|
|
{
|
|
// Trigger any file change notification delegates
|
|
for (auto MapIt = RequestMap.CreateConstIterator(); MapIt; ++MapIt)
|
|
{
|
|
FDirectoryWatchRequestLinux &WatchRequest = *MapIt.Value();
|
|
|
|
WatchRequest.ProcessPendingNotifications();
|
|
}
|
|
|
|
DumpINotifyErrorDetails(RequestMap);
|
|
}
|
|
|
|
void FDirectoryWatchRequestLinux::DumpStats(TMap<FString, FDirectoryWatchRequestLinux*>& RequestMap)
|
|
{
|
|
GDumpStats = true;
|
|
DumpINotifyErrorDetails(RequestMap);
|
|
}
|
|
|
|
void FDirectoryWatchRequestLinux::ProcessPendingNotifications()
|
|
{
|
|
/** Each FFileChangeData tracks whether it is a directory or not */
|
|
TArray<TPair<FFileChangeData, bool>> FileChanges;
|
|
|
|
ProcessChanges(FileChanges);
|
|
|
|
// Trigger all listening delegates with the files that have changed
|
|
if (FileChanges.Num() > 0)
|
|
{
|
|
TMap<uint32, TArray<FFileChangeData>> FileChangeCache;
|
|
|
|
for (const FWatchDelegate& Delegate : Delegates)
|
|
{
|
|
// Filter list of all file changes down to ones that just match this delegate's flags
|
|
TArray<FFileChangeData>* CachedChanges = FileChangeCache.Find(Delegate.Value);
|
|
|
|
if (CachedChanges)
|
|
{
|
|
Delegate.Key.Execute(*CachedChanges);
|
|
}
|
|
else
|
|
{
|
|
const bool bIncludeDirs = (Delegate.Value & IDirectoryWatcher::WatchOptions::IncludeDirectoryChanges) != 0;
|
|
TArray<FFileChangeData>& Changes = FileChangeCache.Add(Delegate.Value);
|
|
|
|
for (const TPair<FFileChangeData, bool>& FileChangeData : FileChanges)
|
|
{
|
|
if (!FileChangeData.Value || bIncludeDirs)
|
|
{
|
|
Changes.Add(FileChangeData.Key);
|
|
}
|
|
}
|
|
Delegate.Key.Execute(Changes);
|
|
}
|
|
}
|
|
|
|
FileChanges.Empty();
|
|
}
|
|
}
|
|
|
|
void FDirectoryWatchRequestLinux::WatchDirectoryTree(const FString & RootAbsolutePath, TArray<TPair<FFileChangeData, bool>>* FileChanges)
|
|
{
|
|
if (bEndWatchRequestInvoked || (FileDescriptor == -1))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If this isn't our root watch directory or under it, don't watch
|
|
if (!RootAbsolutePath.StartsWith(WatchDirectory, ESearchCase::CaseSensitive) ||
|
|
(!bWatchSubtree && (RootAbsolutePath != WatchDirectory)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
UE_LOG(LogDirectoryWatcher, VeryVerbose, TEXT("Watching tree '%s'"), *RootAbsolutePath);
|
|
|
|
if (FileChanges)
|
|
{
|
|
FileChanges->Emplace(FFileChangeData(RootAbsolutePath, FFileChangeData::FCA_Added), true);
|
|
}
|
|
|
|
TArray<FString> AllFiles;
|
|
if (bWatchSubtree)
|
|
{
|
|
IPlatformFile::GetPlatformPhysical().IterateDirectoryRecursively(*RootAbsolutePath,
|
|
[&AllFiles, FileChanges](const TCHAR* Name, bool bIsDirectory)
|
|
{
|
|
if (bIsDirectory)
|
|
{
|
|
AllFiles.Add(Name);
|
|
}
|
|
|
|
if (FileChanges)
|
|
{
|
|
FileChanges->Emplace(FFileChangeData(Name, FFileChangeData::FCA_Added), bIsDirectory);
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
|
|
// Add root path
|
|
AllFiles.Add(RootAbsolutePath);
|
|
|
|
for (const FString& FolderName: AllFiles)
|
|
{
|
|
uint32 PathNameHash = GetPathNameHash(FolderName);
|
|
|
|
if (PathNameHashSet.Find(PathNameHash) == nullptr)
|
|
{
|
|
int32 NotifyFilter = IN_CREATE | IN_MOVE | IN_MODIFY | IN_DELETE;
|
|
int32 WatchDescriptor = inotify_add_watch(FileDescriptor, TCHAR_TO_UTF8(*FolderName), NotifyFilter);
|
|
|
|
if (WatchDescriptor == -1)
|
|
{
|
|
if (errno == ENOSPC)
|
|
{
|
|
FString ErrorMsg = FString::Printf(
|
|
TEXT("inotify_add_watch cannot watch folder %s (Out of inotify watches)"), *FolderName);
|
|
SetINotifyErrorMsg(ErrorMsg);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogDirectoryWatcher, Warning, TEXT("inotify_add_watch cannot watch folder %s (errno = %d, %s)"),
|
|
*FolderName, errno, UTF8_TO_TCHAR(strerror(errno)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogDirectoryWatcher, VeryVerbose, TEXT("+ Added a watch %d for '%s'"), WatchDescriptor, *FolderName);
|
|
|
|
// Set the inotify watch descriptor -> folder name mapping
|
|
WatchDescriptorsToPaths.Add(WatchDescriptor, FolderName);
|
|
|
|
// Add hashed directory path
|
|
PathNameHashSet.Add(PathNameHash);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FDirectoryWatchRequestLinux::UnwatchDirectoryTree(const FString& RootAbsolutePath)
|
|
{
|
|
UE_LOG(LogDirectoryWatcher, VeryVerbose, TEXT("Unwatching tree '%s'"), *RootAbsolutePath);
|
|
|
|
for (auto MapIt = WatchDescriptorsToPaths.CreateIterator(); MapIt; ++MapIt)
|
|
{
|
|
int WatchDescriptor = MapIt->Key;
|
|
const FString& Path = MapIt->Value;
|
|
|
|
if (Path.StartsWith(RootAbsolutePath, ESearchCase::CaseSensitive))
|
|
{
|
|
UE_LOG(LogDirectoryWatcher, VeryVerbose, TEXT("- Removing a watch %d for '%s'"), WatchDescriptor, *Path);
|
|
|
|
// delete the descriptor
|
|
int RetVal = inotify_rm_watch(FileDescriptor, WatchDescriptor);
|
|
|
|
// This function may be called when root path has been deleted, and inotify_rm_watch() will fail
|
|
// with an EINVAL when removing a watch on a deleted file.
|
|
if (RetVal == -1 && errno != EINVAL)
|
|
{
|
|
UE_LOG(LogDirectoryWatcher, Warning, TEXT("inotify_rm_watch cannot remove descriptor %d for folder '%s' (errno = %d, %s)"),
|
|
WatchDescriptor, *Path, errno, ANSI_TO_TCHAR(strerror(errno)));
|
|
}
|
|
|
|
// Safe version of:
|
|
// WatchDescriptorsToPaths.Remove(WatchDescriptor);
|
|
MapIt.RemoveCurrent();
|
|
|
|
PathNameHashSet.Remove(GetPathNameHash(Path));
|
|
}
|
|
}
|
|
}
|
|
|
|
static FString INotifyFlagsToStr(uint32 INotifyFlags)
|
|
{
|
|
#if UE_BUILD_SHIPPING
|
|
return FString();
|
|
#else
|
|
FString Ret = TEXT("[");
|
|
|
|
#define _XTAG(_x) if (INotifyFlags & _x) Ret += FString(TEXT(" ")) + TEXT(#_x)
|
|
_XTAG(IN_ACCESS);
|
|
_XTAG(IN_MODIFY);
|
|
_XTAG(IN_ATTRIB);
|
|
_XTAG(IN_CLOSE_WRITE);
|
|
_XTAG(IN_CLOSE_NOWRITE);
|
|
_XTAG(IN_OPEN);
|
|
_XTAG(IN_MOVED_FROM);
|
|
_XTAG(IN_MOVED_TO);
|
|
_XTAG(IN_CREATE);
|
|
_XTAG(IN_DELETE);
|
|
_XTAG(IN_DELETE_SELF);
|
|
_XTAG(IN_MOVE_SELF);
|
|
_XTAG(IN_UNMOUNT);
|
|
_XTAG(IN_Q_OVERFLOW);
|
|
_XTAG(IN_IGNORED);
|
|
_XTAG(IN_ISDIR);
|
|
#undef _XTAG
|
|
|
|
Ret += TEXT(" ]");
|
|
return Ret;
|
|
#endif
|
|
}
|
|
|
|
void FDirectoryWatchRequestLinux::ProcessChanges(TArray<TPair<FFileChangeData, bool>>& FileChanges)
|
|
{
|
|
uint8_t Buffer[EVENT_BUF_LEN] __attribute__ ((aligned(__alignof__(struct inotify_event))));
|
|
|
|
if (FileDescriptor == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Loop while events can be read from inotify file descriptor
|
|
for (;;)
|
|
{
|
|
// Read event stream
|
|
ssize_t Len = read(FileDescriptor, Buffer, EVENT_BUF_LEN);
|
|
|
|
// If the non-blocking read() found no events to read, then it returns -1 with errno set to EAGAIN.
|
|
if (Len == -1 && errno != EAGAIN)
|
|
{
|
|
UE_LOG(LogDirectoryWatcher, Error, TEXT("FDirectoryWatchRequestLinux::ProcessChanges() read() error getting events for path '%s' (errno = %d, %s)"),
|
|
*WatchDirectory, errno, ANSI_TO_TCHAR(strerror(errno)));
|
|
break;
|
|
}
|
|
|
|
if (Len <= 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (bEndWatchRequestInvoked)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Loop over all events in the buffer
|
|
uint8_t* Ptr = Buffer;
|
|
while (Ptr < Buffer + Len)
|
|
{
|
|
const struct inotify_event* Event;
|
|
FFileChangeData::EFileChangeAction Action = FFileChangeData::FCA_Unknown;
|
|
|
|
Event = reinterpret_cast<const struct inotify_event *>(Ptr);
|
|
Ptr += EVENT_SIZE + Event->len;
|
|
|
|
// Skip if overflowed
|
|
if ((Event->wd != -1) && (Event->mask & IN_Q_OVERFLOW) == 0)
|
|
{
|
|
const FString *EventPathPtr = WatchDescriptorsToPaths.Find(Event->wd);
|
|
|
|
UE_LOG(LogDirectoryWatcher, VeryVerbose, TEXT("Event: watch descriptor %d, mask 0x%08x, EventPath: '%s' Event Name: '%s' %s"),
|
|
Event->wd, Event->mask, EventPathPtr ? *(*EventPathPtr) : TEXT("nullptr"), ANSI_TO_TCHAR(Event->name), *INotifyFlagsToStr(Event->mask));
|
|
|
|
// If we're geting multiple events (e.g. DELETE, IGNORED) we could have removed descriptor on previous iteration,
|
|
// so we need to handle inability to find it in the map
|
|
if (EventPathPtr)
|
|
{
|
|
const FString& EventPath = *EventPathPtr;
|
|
FString AffectedFile = EventPath / UTF8_TO_TCHAR(Event->name); // By default, some events report about the file itself
|
|
|
|
bool bIsDir = (Event->mask & IN_ISDIR) != 0;
|
|
|
|
if ((Event->mask & IN_CREATE) || (Event->mask & IN_MOVED_TO))
|
|
{
|
|
// IN_CREATE: File/directory created in watched directory
|
|
// IN_MOVED_TO: Generated for the directory containing the new filename when a file is renamed
|
|
if (bIsDir)
|
|
{
|
|
// If a directory was created/moved, watch it and add changes to FileChanges.
|
|
// Leave Action as FCA_Unknown so nothing gets added down below.
|
|
WatchDirectoryTree(AffectedFile, &FileChanges);
|
|
}
|
|
else
|
|
{
|
|
Action = FFileChangeData::FCA_Added;
|
|
}
|
|
}
|
|
else if (Event->mask & IN_MODIFY)
|
|
{
|
|
// IN_MODIFY: File was modified
|
|
// If a directory was modified, we expect to get events from already watched files in it
|
|
Action = FFileChangeData::FCA_Modified;
|
|
}
|
|
// Check if the file/directory itself has been deleted (IGNORED can also be sent on delete)
|
|
else if ((Event->mask & IN_DELETE_SELF) || (Event->mask & IN_IGNORED) || (Event->mask & IN_UNMOUNT))
|
|
{
|
|
// IN_DELETE_SELF: Watched file/directory was itself deleted.
|
|
// In addition, an IN_IGNORED event will subsequently be generated for the watch descriptor
|
|
// IN_UNMOUNT: Filesystem containing watched object was unmounted.
|
|
// In addition, an IN_IGNORED event will subsequently be generated for the watch descriptor
|
|
// IN_IGNORED: Watch was removed explicitly (inotify_rm_watch(2)) or
|
|
// automatically (file was deleted, or filesystem was unmounted).
|
|
|
|
// If a directory was deleted, we expect to get events from already watched files in it
|
|
AffectedFile = EventPath;
|
|
|
|
if (bIsDir)
|
|
{
|
|
UnwatchDirectoryTree(EventPath);
|
|
}
|
|
else
|
|
{
|
|
// NOTE: I don't *believe* this code should ever get called? We only watch directories.
|
|
// But to be on the safe side, still handling this case...
|
|
|
|
// inotify_rm_watch() may fail here as watch descriptor is potentially no longer valid, so ignore return
|
|
inotify_rm_watch(FileDescriptor, Event->wd);
|
|
|
|
// Remove from both mappings
|
|
WatchDescriptorsToPaths.Remove(Event->wd);
|
|
PathNameHashSet.Remove(GetPathNameHash(EventPath));
|
|
}
|
|
|
|
Action = FFileChangeData::FCA_Removed;
|
|
}
|
|
else if ((Event->mask & IN_DELETE) || (Event->mask & IN_MOVED_FROM))
|
|
{
|
|
// IN_DELETE: File/directory deleted from watched directory
|
|
// IN_MOVED_FROM: Generated for the directory containing the old filename when a file is renamed
|
|
|
|
// If a directory was deleted/moved, unwatch it
|
|
if (bIsDir)
|
|
{
|
|
UnwatchDirectoryTree(AffectedFile);
|
|
}
|
|
|
|
Action = FFileChangeData::FCA_Removed;
|
|
}
|
|
|
|
if (Event->len && (Action != FFileChangeData::FCA_Unknown))
|
|
{
|
|
FileChanges.Emplace(FFileChangeData(AffectedFile, Action), bIsDir);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static uint32 get_inotify_procfs_value(const char *FileName)
|
|
{
|
|
char Buf[256];
|
|
uint32 InterfaceVal = 0;
|
|
|
|
snprintf(Buf, sizeof(Buf), "/proc/sys/fs/inotify/%s", FileName);
|
|
Buf[sizeof(Buf) - 1] = 0;
|
|
|
|
FILE* FilePtr = fopen(Buf, "r");
|
|
if (FilePtr)
|
|
{
|
|
if (fscanf(FilePtr, "%u", &InterfaceVal) != 1)
|
|
{
|
|
InterfaceVal = 0;
|
|
}
|
|
fclose(FilePtr);
|
|
}
|
|
|
|
return InterfaceVal;
|
|
}
|
|
|
|
void FDirectoryWatchRequestLinux::SetINotifyErrorMsg(const FString &ErrorMsg)
|
|
{
|
|
if (!GINotifyErrorMsg.Len())
|
|
{
|
|
GINotifyErrorMsg = ErrorMsg;
|
|
}
|
|
}
|
|
|
|
static FString GetLinkName(const char *Pathname)
|
|
{
|
|
FString Result;
|
|
char Filename[PATH_MAX + 1];
|
|
|
|
ssize_t Ret = readlink(Pathname, Filename, sizeof(Filename));
|
|
if ((Ret > 0) && (Ret < sizeof(Filename)))
|
|
{
|
|
Filename[Ret] = 0;
|
|
Result = Filename;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
static uint32 INotifyParseFDInfoFile(const FString& Executable, int Pid, const char *d_name)
|
|
{
|
|
uint32 INotifyCount = 0;
|
|
|
|
FILE* FilePtr = fopen(d_name, "r");
|
|
if (FilePtr)
|
|
{
|
|
char line_buf[256];
|
|
|
|
for (;;)
|
|
{
|
|
if (!fgets(line_buf, sizeof(line_buf), FilePtr))
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (!strncmp(line_buf, "inotify ", 8))
|
|
{
|
|
INotifyCount++;
|
|
}
|
|
}
|
|
|
|
fclose(FilePtr);
|
|
}
|
|
|
|
return INotifyCount;
|
|
}
|
|
|
|
static void INotifyParseFDDir(const FString& Executable, int Pid, uint32 &INotifyCountTotal, uint32& INotifyInstancesTotal)
|
|
{
|
|
char Buf[256];
|
|
uint32 INotifyCount = 0;
|
|
uint32 INotifyInstances = 0;
|
|
|
|
snprintf(Buf, sizeof(Buf), "/proc/%d/fd", Pid);
|
|
Buf[sizeof(Buf) - 1] = 0;
|
|
|
|
DIR* dir_fd = opendir(Buf);
|
|
if (dir_fd)
|
|
{
|
|
for (;;)
|
|
{
|
|
struct dirent* dp_fd = readdir(dir_fd);
|
|
if (!dp_fd)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ((dp_fd->d_type == DT_LNK) && isdigit(dp_fd->d_name[0]))
|
|
{
|
|
snprintf(Buf, sizeof(Buf), "/proc/%d/fd/%s", Pid, dp_fd->d_name);
|
|
Buf[sizeof(Buf) - 1] = 0;
|
|
|
|
FString Filename = GetLinkName(Buf);
|
|
if (Filename == TEXT("anon_inode:inotify"))
|
|
{
|
|
snprintf(Buf, sizeof(Buf), "/proc/%d/fdinfo/%s", Pid, dp_fd->d_name);
|
|
Buf[sizeof(Buf) - 1] = 0;
|
|
|
|
uint32 Count = INotifyParseFDInfoFile(Executable, Pid, Buf);
|
|
if (Count)
|
|
{
|
|
INotifyInstances++;
|
|
INotifyCount += Count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
closedir(dir_fd);
|
|
}
|
|
|
|
if (INotifyCount)
|
|
{
|
|
FString ExeName = FPaths::GetCleanFilename(Executable);
|
|
|
|
UE_LOG(LogDirectoryWatcher, Warning, TEXT(" %s (pid %d) watches:%u instances:%u"), *ExeName, Pid, INotifyCount, INotifyInstances);
|
|
|
|
INotifyCountTotal += INotifyCount;
|
|
INotifyInstancesTotal += INotifyInstances;
|
|
}
|
|
}
|
|
|
|
static void INotifyDumpProcessStats()
|
|
{
|
|
uint32 INotifyCountTotal = 0;
|
|
uint32 INotifyInstancesTotal = 0;
|
|
|
|
DIR* dir_proc = opendir("/proc");
|
|
if (dir_proc)
|
|
{
|
|
for (;;)
|
|
{
|
|
struct dirent* dp_proc = readdir(dir_proc);
|
|
if (!dp_proc)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if ((dp_proc->d_type == DT_DIR) && isdigit(dp_proc->d_name[0]))
|
|
{
|
|
char Buf[256];
|
|
int32 Pid = atoi(dp_proc->d_name);
|
|
|
|
snprintf(Buf, sizeof(Buf), "/proc/%d/exe", Pid);
|
|
Buf[sizeof(Buf) - 1] = 0;
|
|
|
|
FString Executable = GetLinkName(Buf);
|
|
if (Executable.Len())
|
|
{
|
|
INotifyParseFDDir(Executable, Pid, INotifyCountTotal, INotifyInstancesTotal);
|
|
}
|
|
}
|
|
}
|
|
|
|
closedir(dir_proc);
|
|
}
|
|
|
|
UE_LOG(LogDirectoryWatcher, Warning, TEXT("Total inotify Watches:%u Instances:%u"), INotifyCountTotal, INotifyInstancesTotal);
|
|
}
|
|
|
|
void FDirectoryWatchRequestLinux::DumpINotifyErrorDetails(TMap<FString, FDirectoryWatchRequestLinux*>& RequestMap)
|
|
{
|
|
if (!GDumpStats)
|
|
{
|
|
if (GDumpedError || !GINotifyErrorMsg.Len())
|
|
{
|
|
return;
|
|
}
|
|
GDumpedError = true;
|
|
|
|
UE_LOG(LogDirectoryWatcher, Warning, TEXT("inotify error: %s"), *GINotifyErrorMsg);
|
|
}
|
|
GDumpStats = false;
|
|
|
|
uint32 MaxQueuedEvents = get_inotify_procfs_value("max_queued_events");
|
|
uint32 MaxUserInstances = get_inotify_procfs_value("max_user_instances");
|
|
uint32 MaxUserWatches = get_inotify_procfs_value("max_user_watches");
|
|
|
|
UE_LOG(LogDirectoryWatcher, Warning, TEXT("inotify limits"));
|
|
UE_LOG(LogDirectoryWatcher, Warning, TEXT(" max_queued_events: %u"), MaxQueuedEvents);
|
|
UE_LOG(LogDirectoryWatcher, Warning, TEXT(" max_user_instances: %u"), MaxUserInstances);
|
|
UE_LOG(LogDirectoryWatcher, Warning, TEXT(" max_user_watches: %u"), MaxUserWatches);
|
|
|
|
UE_LOG(LogDirectoryWatcher, Warning, TEXT("inotify per-process stats"));
|
|
INotifyDumpProcessStats();
|
|
|
|
uint32 Count = 0;
|
|
UE_LOG(LogDirectoryWatcher, Warning, TEXT("Current watch requests"));
|
|
|
|
for (auto MapIt = RequestMap.CreateConstIterator(); MapIt; ++MapIt)
|
|
{
|
|
uint32 DirCount = 1;
|
|
FDirectoryWatchRequestLinux &WatchRequest = *MapIt.Value();
|
|
|
|
// Get actual count of subdirectories
|
|
if (WatchRequest.bWatchSubtree)
|
|
{
|
|
IPlatformFile::GetPlatformPhysical().IterateDirectoryRecursively(*WatchRequest.WatchDirectory,
|
|
[&DirCount](const TCHAR* Name, bool bIsDirectory)
|
|
{
|
|
DirCount += bIsDirectory;
|
|
return true;
|
|
});
|
|
}
|
|
|
|
UE_LOG(LogDirectoryWatcher, Warning, TEXT(" %s: %u watches (%u total dirs)"),
|
|
*WatchRequest.WatchDirectory, WatchRequest.WatchDescriptorsToPaths.Num(), DirCount);
|
|
|
|
Count += WatchRequest.WatchDescriptorsToPaths.Num();
|
|
}
|
|
UE_LOG(LogDirectoryWatcher, Warning, TEXT("Total UE inotify Watches:%u Instances:%u"), Count, RequestMap.Num());
|
|
}
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
|
|
static bool INotifyCommandHandler(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar)
|
|
{
|
|
if (FParse::Command(&Cmd, TEXT("DumpINotifyStats")))
|
|
{
|
|
UE_LOG(LogDirectoryWatcher, Warning, TEXT("Dumping inotify stats"));
|
|
GDumpStats = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FStaticSelfRegisteringExec FDirectoryWatchRequestLinuxExecs(INotifyCommandHandler);
|
|
|
|
#endif
|