/// <summary> /// Gather compile time telemetry for the given files /// </summary> /// <param name="FileToCompileEnvironment">Mapping of source file to the environment used to compile it</param> /// <param name="WorkingDir">The working directory for output files</param> /// <param name="NumSamples">Number of samples to take</param> /// <param name="MaxParallel">Maximum number of tasks to run in parallel.</param> /// <param name="Log">Log writer</param> public static void Generate(DirectoryReference InputDir, DirectoryReference WorkingDir, Dictionary <SourceFile, CompileEnvironment> FileToCompileEnvironment, int NumSamples, int Shard, int NumShards, int MaxParallel, LineBasedTextWriter Log) { Stopwatch Timer = Stopwatch.StartNew(); // Create an intermediate directory DirectoryReference IntermediateDir = DirectoryReference.Combine(WorkingDir, "Timing"); IntermediateDir.CreateDirectory(); // Map of unique fragment to timing data Dictionary <SourceFragment, FragmentTimingData> FragmentToTimingData = new Dictionary <SourceFragment, FragmentTimingData>(); // Map of unique fragment key to timing data Dictionary <string, FragmentTimingData> DigestToTimingData = new Dictionary <string, FragmentTimingData>(); // List of all the sequences to time HashSet <string> UniqueNames = new HashSet <string>(); foreach (KeyValuePair <SourceFile, CompileEnvironment> Pair in FileToCompileEnvironment) { // Find all the fragments in this file List <SourceFragment> Fragments = new List <SourceFragment>(); List <Tuple <int, SourceFile> > IncludeHistory = new List <Tuple <int, SourceFile> >(); Pair.Key.FindIncludedFragments(Fragments, IncludeHistory, new HashSet <SourceFile>()); // Create a sequence for each unique fragment FragmentTimingData PrevTimingData = null; for (int Idx = 0; Idx < Fragments.Count; Idx++) { FragmentTimingData TimingData = null; if (!FragmentToTimingData.ContainsKey(Fragments[Idx]) || (Idx + 1 < Fragments.Count && !FragmentToTimingData.ContainsKey(Fragments[Idx + 1]))) { // Create a sequence for this fragment SourceFragment LastFragment = Fragments[Idx]; // Create a unique key for this sequence by concatenating all the fragment names string Digest = Utility.ComputeDigest(String.Join("\n", Fragments.Take(Idx + 1).Select(x => x.Location.FullName))); // Try to get an existing sequence for this key, otherwise create a new one; if (!DigestToTimingData.TryGetValue(Digest, out TimingData)) { // Find a unique name for this sequence string UniqueName = LastFragment.Location.GetFileName(); for (int NameIdx = 2; !UniqueNames.Add(UniqueName); NameIdx++) { UniqueName = String.Format("{0}_{1}{2}", LastFragment.Location.GetFileNameWithoutExtension(), NameIdx, LastFragment.Location.GetExtension()); } // Add the object for this sequence FileReference IntermediateFile = FileReference.Combine(IntermediateDir, UniqueName); TimingData = new FragmentTimingData(UniqueName, Digest, PrevTimingData, Fragments.Take(Idx + 1).ToArray(), IncludeHistory, IntermediateFile, Pair.Value); DigestToTimingData.Add(Digest, TimingData); } // Add it to the unique mapping of fragments if (!FragmentToTimingData.ContainsKey(LastFragment)) { FragmentToTimingData[LastFragment] = TimingData; } } PrevTimingData = TimingData; } } // Read any existing shard timing data in the output folder foreach (FileReference IntermediateFile in IntermediateDir.EnumerateFileReferences("*.csv")) { string[] Lines = File.ReadAllLines(IntermediateFile.FullName); foreach (string Line in Lines.Skip(1)) { string[] Tokens = Line.Split(','); if (Tokens.Length == 5) { FragmentTimingData TimingData; if (DigestToTimingData.TryGetValue(Tokens[1], out TimingData) && TimingData.Samples.Count < NumSamples) { FragmentTimingSample Sample = new FragmentTimingSample(); Sample.TotalTime = Double.Parse(Tokens[2]); Sample.FrontendTime = Double.Parse(Tokens[3]); Sample.BackendTime = Double.Parse(Tokens[4]); TimingData.Samples.Add(Sample); } } } } // Find all the remaining fragments, and repeat each one by the number of times it has to be executed List <FragmentTimingData> FilteredFragments = DigestToTimingData.Values.ToList(); FilteredFragments.RemoveAll(x => (int)(Math.Abs((long)x.Digest.GetHashCode()) % NumShards) != (Shard - 1)); // Get the initial number of compile times for each fragment. We avoid saving before this number. List <int> InitialCompileCount = FilteredFragments.Select(x => x.Samples.Count).ToList(); // Create all the actions to execute List <Action> Actions = new List <Action>(); foreach (FragmentTimingData Fragment in FilteredFragments) { FragmentTimingData FragmentCopy = Fragment; for (int SampleIdx = Fragment.Samples.Count; SampleIdx < NumSamples; SampleIdx++) { int SampleIdxCopy = SampleIdx; Actions.Add(() => FragmentCopy.Compile(IntermediateDir, SampleIdxCopy)); } } // Randomize the order to ensure that compile times are not consistently affected by other files being compiled simultaneously. Random Random = new Random(); Actions = Actions.OrderBy(x => Random.Next()).ToList(); // Compile them all if (Actions.Count > 0) { Utility.ParallelForWithStatus("Compiling fragments...", 0, Actions.Count, new ParallelOptions { MaxDegreeOfParallelism = MaxParallel }, Idx => Actions[Idx](), Log); } // Write out the results if (NumShards > 1) { // If we're running a sharded build, write out intermediate files containing the results FileReference OutputFile = FileReference.Combine(IntermediateDir, String.Format("Shard{0}.csv", Shard)); using (StreamWriter Writer = new StreamWriter(OutputFile.FullName)) { Writer.WriteLine("Name,Digest,TotalTime,FrontendTime,BackendTime"); for (int Idx = 0; Idx < FilteredFragments.Count; Idx++) { FragmentTimingData FilteredFragment = FilteredFragments[Idx]; for (int SampleIdx = InitialCompileCount[Idx]; SampleIdx < FilteredFragment.Samples.Count; SampleIdx++) { FragmentTimingSample Sample = FilteredFragment.Samples[SampleIdx]; Writer.WriteLine("{0},{1},{2},{3},{4}", FilteredFragment.UniqueName, FilteredFragment.Digest, Sample.TotalTime, Sample.FrontendTime, Sample.BackendTime); } } } } else { // Write out the fragment report FileReference FragmentReport = FileReference.Combine(WorkingDir, "Timing.csv"); Log.WriteLine("Writing {0}...", FragmentReport); using (StreamWriter Writer = new StreamWriter(FragmentReport.FullName)) { // Write the header Writer.Write("Fragment,MinLine,MaxLine"); // Write the labels for each sample type string[] Types = new string[] { "Total", "Frontend", "Backend" }; for (int Idx = 0; Idx < Types.Length; Idx++) { for (int SampleIdx = 0; SampleIdx < NumSamples; SampleIdx++) { Writer.Write(",{0}{1}", Types[Idx], SampleIdx + 1); } Writer.Write(",{0}Min,{0}Max,{0}Avg,{0}Exc", Types[Idx]); } Writer.WriteLine(); // Write all the results Func <FragmentTimingSample, double>[] TimeFieldDelegates = new Func <FragmentTimingSample, double>[] { x => x.TotalTime, x => x.FrontendTime, x => x.BackendTime }; foreach (FragmentTimingData TimingData in FragmentToTimingData.Values) { Writer.Write("{0},{1},{2}", TimingData.Fragment.Location.GetFileName(), TimingData.Fragment.MarkupMin + 1, TimingData.Fragment.MarkupMax + 1); foreach (Func <FragmentTimingSample, double> TimeFieldDelegate in TimeFieldDelegates) { foreach (FragmentTimingSample Sample in TimingData.Samples) { Writer.Write(",{0:0.000}", TimeFieldDelegate(Sample)); } Writer.Write(",{0:0.000}", TimingData.Samples.Min(x => TimeFieldDelegate(x))); Writer.Write(",{0:0.000}", TimingData.Samples.Max(x => TimeFieldDelegate(x))); Writer.Write(",{0:0.000}", TimingData.Samples.Average(x => TimeFieldDelegate(x))); if (TimingData.PrevFragmentData == null) { Writer.Write(",{0:0.000}", TimingData.Samples.Average(x => TimeFieldDelegate(x))); } else { Writer.Write(",{0:0.000}", TimingData.Samples.Average(x => TimeFieldDelegate(x)) - TimingData.PrevFragmentData.Samples.Average(x => TimeFieldDelegate(x))); } } Writer.WriteLine(); } } } }
/// <summary> /// Compiles the fragment and adds the compile time to the list /// </summary> public void Compile(DirectoryReference IntermediateDir, int SampleIdx) { FileReference LogFile = FileReference.Combine(IntermediateDir, String.Format("{0}.sample{1}.txt", UniqueName, SampleIdx + 1)); using (StreamWriter LogWriter = new StreamWriter(LogFile.FullName, true)) { FileReference ResponseFile = FileReference.Combine(IntermediateDir, String.Format("{0}.sample{1}.response", UniqueName, SampleIdx + 1)); // Write the response file CompileEnvironment WorkerCompileEnvironment = new CompileEnvironment(CompileEnvironment); if (WorkerCompileEnvironment.CompilerType == CompilerType.Clang) { WorkerCompileEnvironment.Options.Add(new CompileOption("-o", FileReference.Combine(IntermediateDir, UniqueName + ".o").FullName)); } else if (WorkerCompileEnvironment.CompilerType == CompilerType.VisualC) { WorkerCompileEnvironment.Options.RemoveAll(x => x.Name == "/Z7" || x.Name == "/Zi" || x.Name == "/ZI"); WorkerCompileEnvironment.Options.Add(new CompileOption("/Fo", IntermediateFile.FullName + ".obj")); WorkerCompileEnvironment.Options.Add(new CompileOption("/WX", null)); WorkerCompileEnvironment.Options.Add(new CompileOption("/Bt", null)); } else { throw new NotImplementedException(); } WorkerCompileEnvironment.WriteResponseFile(ResponseFile, IntermediateFile); // Spawn the compiler using (Process NewProcess = new Process()) { List <string> LogLines = new List <string>(); DataReceivedEventHandler OutputHandler = (x, y) => { if (!String.IsNullOrEmpty(y.Data)) { LogWriter.WriteLine(y.Data); LogLines.Add(y.Data); } }; NewProcess.StartInfo.FileName = CompileEnvironment.Compiler.FullName; NewProcess.StartInfo.Arguments = String.Format("\"@{0}\"", ResponseFile); NewProcess.StartInfo.UseShellExecute = false; NewProcess.StartInfo.RedirectStandardOutput = true; NewProcess.StartInfo.RedirectStandardError = true; NewProcess.OutputDataReceived += OutputHandler; NewProcess.ErrorDataReceived += OutputHandler; Stopwatch Timer = Stopwatch.StartNew(); NewProcess.Start(); NewProcess.BeginOutputReadLine(); NewProcess.BeginErrorReadLine(); if (NewProcess.WaitForExit(10 * 60 * 1000)) { // WaitForExit with a timeout does not wait for output data to be flushed, so issue a normal WaitForExit call here too NewProcess.WaitForExit(); Timer.Stop(); FragmentTimingSample Sample = new FragmentTimingSample(); Sample.TotalTime = Timer.Elapsed.TotalSeconds; if (WorkerCompileEnvironment.CompilerType == CompilerType.VisualC) { foreach (string LogLine in LogLines) { if (TryParseCompileTime(LogLine, "c1xx.dll", out Sample.FrontendTime)) { break; } } foreach (string LogLine in LogLines) { if (TryParseCompileTime(LogLine, "c2.dll", out Sample.BackendTime)) { break; } } } Samples.Add(Sample); LogWriter.WriteLine("Compile finished in {0}s", Timer.Elapsed.TotalSeconds); } else { NewProcess.Kill(); NewProcess.WaitForExit(); Timer.Stop(); Samples.Add(new FragmentTimingSample()); LogWriter.WriteLine("Timeout; terminating process after {0}s", Timer.Elapsed.TotalSeconds); } LogWriter.WriteLine(); } } }