// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Management; using System.Threading; using System.Windows; using AgentInterface; namespace Agent { /////////////////////////////////////////////////////////////////////////// /** * A thread-safe reader-writer-locked Dictionary wrapper */ public class ReaderWriterDictionary { /** * The key to protecting the contained dictionary properly */ private ReaderWriterLock AccessLock = new ReaderWriterLock(); /** * The protected dictionary */ private Dictionary ProtectedDictionary = new Dictionary(); /** * Wrappers around each method we need to protect in the dictionary */ public bool Add( TKey K, TValue V ) { // Modifies the collection, use a writer lock AccessLock.AcquireWriterLock( Timeout.Infinite ); bool ReturnValue = false; try { // Add a check for whether we're about to add a duplicate key if( !ProtectedDictionary.ContainsKey( K ) ) { ProtectedDictionary.Add( K, V ); ReturnValue = true; } } finally { AccessLock.ReleaseWriterLock(); } return ReturnValue; } public bool Remove( TKey K ) { // Modifies the collection, use a writer lock AccessLock.AcquireWriterLock( Timeout.Infinite ); bool ReturnValue = false; try { ReturnValue = ProtectedDictionary.Remove( K ); } finally { AccessLock.ReleaseWriterLock(); } return ReturnValue; } public void Clear() { // Modifies the collection, use a writer lock AccessLock.AcquireWriterLock( Timeout.Infinite ); try { ProtectedDictionary.Clear(); } finally { AccessLock.ReleaseWriterLock(); } } public bool ContainsKey( TKey K ) { // Does not modify the collection, use a reader lock AccessLock.AcquireReaderLock( Timeout.Infinite ); bool ReturnValue = false; try { ReturnValue = ProtectedDictionary.ContainsKey( K ); } finally { AccessLock.ReleaseReaderLock(); } return ReturnValue; } public bool TryGetValue( TKey K, out TValue V ) { // Does not modify the collection, use a reader lock AccessLock.AcquireReaderLock( Timeout.Infinite ); bool ReturnValue = false; try { ReturnValue = ProtectedDictionary.TryGetValue( K, out V ); } finally { AccessLock.ReleaseReaderLock(); } return ReturnValue; } public List Values { get { // Does not modify the collection, use a reader lock AccessLock.AcquireReaderLock( Timeout.Infinite ); List CopyOfValues = null; try { CopyOfValues = new List( ProtectedDictionary.Values ); } finally { AccessLock.ReleaseReaderLock(); } return CopyOfValues; } } public List Keys { get { // Does not modify the collection, use a reader lock AccessLock.AcquireReaderLock( Timeout.Infinite ); List CopyOfKeys = null; try { CopyOfKeys = new List( ProtectedDictionary.Keys ); } finally { AccessLock.ReleaseReaderLock(); } return CopyOfKeys; } } public int Count { get { // Does not modify the collection, use a reader lock AccessLock.AcquireReaderLock( Timeout.Infinite ); int ReturnValue = 0; try { ReturnValue = ProtectedDictionary.Count; } finally { AccessLock.ReleaseReaderLock(); } return ReturnValue; } } public Dictionary Copy() { // Does not modify the collection, use a reader lock AccessLock.AcquireReaderLock( Timeout.Infinite ); Dictionary CopyOfDictionary = null; try { CopyOfDictionary = new Dictionary( ProtectedDictionary ); } finally { AccessLock.ReleaseReaderLock(); } return CopyOfDictionary; } } /////////////////////////////////////////////////////////////////////////// /** * A thread-safe reader-writer-locked Queue wrapper */ public class ReaderWriterQueue { /** * The key to protecting the contained queue properly */ private ReaderWriterLock AccessLock = new ReaderWriterLock(); /** * The protected queue */ private Queue ProtectedQueue = new Queue(); /** * Wrappers around each method we need to protect in the queue */ public void Clear() { // Modifies the collection, use a writer lock AccessLock.AcquireWriterLock( Timeout.Infinite ); try { ProtectedQueue.Clear(); } finally { AccessLock.ReleaseWriterLock(); } } public void Enqueue( TValue V ) { // Modifies the collection, use a writer lock AccessLock.AcquireWriterLock( Timeout.Infinite ); try { ProtectedQueue.Enqueue( V ); } finally { AccessLock.ReleaseWriterLock(); } } public TValue Dequeue() { // Modifies the collection, use a writer lock AccessLock.AcquireWriterLock( Timeout.Infinite ); TValue V = default( TValue ); try { V = ProtectedQueue.Dequeue(); } finally { AccessLock.ReleaseWriterLock(); } return ( V ); } public TValue[] ToArray() { // Does not modify the collection, use a reader lock AccessLock.AcquireReaderLock( Timeout.Infinite ); TValue[] ReturnValues; try { ReturnValues = ProtectedQueue.ToArray(); } finally { AccessLock.ReleaseReaderLock(); } return ReturnValues; } public int Count { get { // Does not modify the collection, use a reader lock AccessLock.AcquireReaderLock( Timeout.Infinite ); int ReturnValue = 0; try { ReturnValue = ProtectedQueue.Count; } finally { AccessLock.ReleaseReaderLock(); } return ReturnValue; } } } /////////////////////////////////////////////////////////////////////////// /** * A thread-safe reader-writer-locked Stack wrapper */ public class ReaderWriterStack { /** * The key to protecting the contained queue properly */ private ReaderWriterLock AccessLock = new ReaderWriterLock(); /** * The protected queue */ private Stack ProtectedStack = new Stack(); /** * Wrappers around each method we need to protect in the queue */ public void Clear() { // Modifies the collection, use a writer lock AccessLock.AcquireWriterLock( Timeout.Infinite ); try { ProtectedStack.Clear(); } finally { AccessLock.ReleaseWriterLock(); } } public void Push( TValue V ) { // Modifies the collection, use a writer lock AccessLock.AcquireWriterLock( Timeout.Infinite ); try { ProtectedStack.Push( V ); } finally { AccessLock.ReleaseWriterLock(); } } public TValue Pop() { // Modifies the collection, use a writer lock AccessLock.AcquireWriterLock( Timeout.Infinite ); TValue V = default( TValue ); try { V = ProtectedStack.Pop(); } finally { AccessLock.ReleaseWriterLock(); } return ( V ); } public TValue[] ToArray() { // Does not modify the collection, use a reader lock AccessLock.AcquireReaderLock( Timeout.Infinite ); TValue[] ReturnValues; try { ReturnValues = ProtectedStack.ToArray(); } finally { AccessLock.ReleaseReaderLock(); } return ReturnValues; } public int Count { get { // Does not modify the collection, use a reader lock AccessLock.AcquireReaderLock( Timeout.Infinite ); int ReturnValue = 0; try { ReturnValue = ProtectedStack.Count; } finally { AccessLock.ReleaseReaderLock(); } return ReturnValue; } } } /////////////////////////////////////////////////////////////////////////// public class PerfTimer { private class PerfTiming { public string Name; public Stopwatch StopWatchTimer; public Int32 Count; public bool Accumulate; public Int64 Counter; public PerfTiming( string InName, bool InAccumulate ) { Name = InName; StopWatchTimer = new Stopwatch(); Count = 0; Accumulate = InAccumulate; Counter = 0; } public void Start() { StopWatchTimer.Start(); } public void Stop() { Count++; StopWatchTimer.Stop(); } public void IncrementCounter( Int64 Adder ) { Counter += Adder; } } ReaderWriterDictionary Timings = new ReaderWriterDictionary(); Stack LastTimers = new Stack(); public void Start( string Name, bool Accum, Int64 Adder ) { lock( LastTimers ) { PerfTiming Timing = null; if( !Timings.TryGetValue( Name, out Timing ) ) { Timing = new PerfTiming( Name, Accum ); Timings.Add( Name, Timing ); } LastTimers.Push( Timing ); Timing.IncrementCounter( Adder ); Timing.Start(); } } public void Stop() { lock( LastTimers ) { PerfTiming Timing = LastTimers.Pop(); Timing.Stop(); } } public string DumpTimings() { string Output = "Machine: " + System.Net.Dns.GetHostName() + Environment.NewLine + Environment.NewLine; double TotalTime = 0.0; foreach( PerfTiming Timing in Timings.Values ) { if( Timing.Count > 0 ) { double Elapsed = Timing.StopWatchTimer.Elapsed.TotalSeconds; double Average = ( Elapsed * 1000000.0 ) / Timing.Count; Output += Timing.Name.PadLeft( 30 ) + " : " + Elapsed.ToString( "F3" ) + "s in " + Timing.Count + " calls (" + Average.ToString( "F0" ) + "us per call)"; if( Timing.Counter > 0 ) { Output += " (" + ( Timing.Counter / 1024 ) + "k)"; } Output += Environment.NewLine; if( Timing.Accumulate ) { TotalTime += Elapsed; } } } Output += "\nTotal time inside Swarm: " + TotalTime.ToString( "F3" ) + "s" + Environment.NewLine; return( Output ); } } /////////////////////////////////////////////////////////////////////////// /** * Implementation of Agent-specific utilities */ public partial class Agent : MarshalByRefObject, IAgentInternalInterface, IAgentInterface { /////////////////////////////////////////////////////////////////////////// /* * Logs a line of text to the agent log window */ public Int32 Log( EVerbosityLevel Verbosity, ELogColour TextColour, string Line ) { AgentApplication.Log( Verbosity, TextColour, Line ); return ( Constants.SUCCESS ); } /////////////////////////////////////////////////////////////////////////// private Int32 DirectoryRecreate( string DirectoryName ) { Int32 ErrorCode = Constants.INVALID; // Delete the directory, if it exists int DeleteTryCount = 0; do { try { // If this isn't the first time through, sleep a little before trying again if( DeleteTryCount > 0 ) { Thread.Sleep( 500 ); } DeleteTryCount++; if( Directory.Exists( DirectoryName ) ) { Directory.Delete( DirectoryName, true ); } } catch( Exception Ex ) { Log( EVerbosityLevel.Verbose, ELogColour.Red, "[DirectoryRecreate] Failed to delete the directory " + DirectoryName + ", there may be files open... retrying" ); Log( EVerbosityLevel.Verbose, ELogColour.Red, "[DirectoryRecreate] Exception was: " + Ex.Message.TrimEnd( '\n' ) ); ErrorCode = Constants.ERROR_EXCEPTION; } } while( Directory.Exists( DirectoryName ) && ( DeleteTryCount < 10 ) ); // Create the directory, if it doesn't exist int CreateTryCount = 0; do { try { // If this isn't the first time through, sleep a little before trying again if( CreateTryCount > 0 ) { Thread.Sleep( 500 ); } CreateTryCount++; if( !Directory.Exists( DirectoryName ) ) { Directory.CreateDirectory( DirectoryName ); } } catch( Exception Ex ) { Log( EVerbosityLevel.Verbose, ELogColour.Red, "[DirectoryRecreate] Failed to create the directory " + DirectoryName + ", retrying" ); Log( EVerbosityLevel.Verbose, ELogColour.Red, "[DirectoryRecreate] Exception was: " + Ex.Message.TrimEnd( '\n' ) ); ErrorCode = Constants.ERROR_EXCEPTION; } } while( !Directory.Exists( DirectoryName ) && ( CreateTryCount < 10 ) ); // Determine whether we recreated the directory if( Directory.Exists( DirectoryName ) ) { ErrorCode = Constants.SUCCESS; } return ( ErrorCode ); } /** * Helper classes to sort newest timestamp first. */ public class DirectoryWriteTimestampComparer : IComparer { public int Compare( DirectoryInfo A, DirectoryInfo B ) { return -A.LastWriteTimeUtc.CompareTo( B.LastWriteTimeUtc ); } } public class FileAccessTimestampComparer : IComparer { public int Compare( FileInfo A, FileInfo B ) { return -A.LastAccessTimeUtc.CompareTo( B.LastAccessTimeUtc ); } } private Int64 SizeOfDirectory( string DirectoryPath ) { DirectoryInfo DirInfo = new DirectoryInfo( DirectoryPath ); if( DirInfo.Exists ) { Int64 SummedSize = 0; DirectoryInfo[] SubDirectories = DirInfo.GetDirectories(); foreach( DirectoryInfo NextSubDirectory in SubDirectories ) { // Recur for each subdirectory SummedSize += SizeOfDirectory( NextSubDirectory.FullName ); } // Now, sum the file sizes FileInfo[] SubDirectoryFiles = DirInfo.GetFiles(); foreach( FileInfo NextSubDirectoryFile in SubDirectoryFiles ) { SummedSize += NextSubDirectoryFile.Length; } return SummedSize; } return 0; } /** * Deletes all sub-directories except the newest ones, based on 'Date modified'. * @param DirectoryPath Path to the directory to clean up * @param NumSubDirectoriesToKeep Number of sub-directories to keep * @param OldestJobKeptUtc Output param with the creation DateTime of the oldest job directory that was kept * @return The size, in bytes of the resulting directory */ private void CleanupJobDirectory( string DirectoryPath, Int32 NumSubDirectoriesToKeep, out DateTime OldestJobKeptUtc ) { Log( EVerbosityLevel.Verbose, ELogColour.Green, "[CleanupJobDirectory] Cleaning up jobs directory" ); OldestJobKeptUtc = new DateTime(); DirectoryInfo DirInfo = new DirectoryInfo( DirectoryPath ); if( DirInfo.Exists ) { DirectoryInfo[] SubDirectories = DirInfo.GetDirectories(); if( SubDirectories.Length > NumSubDirectoriesToKeep ) { // Sort with newest directories first. Array.Sort( SubDirectories, new DirectoryWriteTimestampComparer() ); // Delete all directories except the first NumSubDirectoriesToKeep directories // and the special debugging directory used by Lightmass string SpecialDebuggingJobDirectory = Path.Combine( DirectoryPath, "Job-00000123-00004567-000089AB-0000CDEF" ); for( int Index = NumSubDirectoriesToKeep; Index < SubDirectories.Length; ++Index ) { if( SubDirectories[Index].FullName != SpecialDebuggingJobDirectory ) { try { Directory.Delete( SubDirectories[Index].FullName, true ); } catch( Exception Ex ) { Log( EVerbosityLevel.Verbose, ELogColour.Red, "[CleanupJobDirectory] Error cleaning up " + SubDirectories[Index].FullName ); Log( EVerbosityLevel.Verbose, ELogColour.Red, "[CleanupJobDirectory] Exception: " + Ex.Message ); } } } } // Refresh the list and get the creation time of the oldest job directory // Also, add up sizes of the directories SubDirectories = DirInfo.GetDirectories(); if( SubDirectories.Length > 0 ) { Array.Sort( SubDirectories, new DirectoryWriteTimestampComparer() ); OldestJobKeptUtc = SubDirectories[SubDirectories.Length - 1].CreationTimeUtc; } } Log( EVerbosityLevel.Verbose, ELogColour.Green, "[CleanupJobDirectory] Done cleaning up jobs directory" ); } /** * Deletes all sub-directories except the newest ones, based on 'Date modified'. * @param DirectoryPath Path to the directory to clean up * @param OldestToKeepUtc The DateTime (UTC) of the oldest log to keep * @return The size, in bytes of the resulting directory */ private void CleanupLogDirectory( string DirectoryPath, DateTime OldestToKeepUtc ) { Log( EVerbosityLevel.Verbose, ELogColour.Green, "[CleanupJobDirectory] Cleaning up logs directory" ); DirectoryInfo DirInfo = new DirectoryInfo( DirectoryPath ); if( DirInfo.Exists ) { FileInfo[] LogFiles = DirInfo.GetFiles(); if( LogFiles.Length > 0 ) { // For any file older than the specified time, delete it foreach( FileInfo NextLogFile in LogFiles ) { if( NextLogFile.LastWriteTimeUtc < OldestToKeepUtc ) { try { File.Delete( NextLogFile.FullName ); } catch( Exception Ex ) { Log( EVerbosityLevel.Verbose, ELogColour.Red, "[CleanupLogDirectory] Error cleaning up " + NextLogFile.FullName ); Log( EVerbosityLevel.Verbose, ELogColour.Red, "[CleanupLogDirectory] Exception: " + Ex.Message ); } } } } } Log( EVerbosityLevel.Verbose, ELogColour.Green, "[CleanupJobDirectory] Done cleaning up logs directory" ); } private Int32 EnsureFolderExists( string Folder ) { Int32 ErrorCode = Constants.INVALID; try { if( !Directory.Exists( Folder ) ) { // Create the directory Directory.CreateDirectory( Folder ); } } catch( Exception Ex ) { Log( EVerbosityLevel.Verbose, ELogColour.Red, "[EnsureFolderExists] Error: " + Ex.ToString() ); } if( Directory.Exists( Folder ) ) { ErrorCode = Constants.SUCCESS; } return ( ErrorCode ); } /** * An instance of the timing object if timings are being collected */ private ELogFlags LoggingFlags = ELogFlags.LOG_NONE; private PerfTimer PerfTimerInstance = new PerfTimer(); private bool bPerfTimerInstanceActive = false; private void CreateTimings( ELogFlags InLoggingFlags ) { LoggingFlags = InLoggingFlags; if( ( LoggingFlags & ELogFlags.LOG_TIMINGS ) != 0 ) { bPerfTimerInstanceActive = true; } } /* * Start a timer going */ private void StartTiming( string Name, bool Accum, Int64 Adder ) { if( bPerfTimerInstanceActive ) { lock( PerfTimerInstance ) { PerfTimerInstance.Start( Name, Accum, Adder ); } } } private void StartTiming( string Name, bool Accum ) { StartTiming( Name, Accum, 0 ); } /* * Stop a timer */ private void StopTiming() { if( bPerfTimerInstanceActive ) { lock( PerfTimerInstance ) { PerfTimerInstance.Stop(); } } } /* * Print out all the timing info */ private void DumpTimings( Connection Parent ) { lock( PerfTimerInstance ) { if( bPerfTimerInstanceActive ) { string PerfMessage = PerfTimerInstance.DumpTimings(); Log( EVerbosityLevel.Simple, ELogColour.Blue, PerfMessage ); AgentInfoMessage Info = new AgentInfoMessage(); Info.TextMessage = PerfMessage; SendMessageInternal( Parent, Info ); } } } } }