// Copyright (C) Microsoft. All rights reserved. // Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Xml.Linq; using System.IO; using System.Diagnostics; using CSVStats; using System.Collections; using System.Security.Cryptography; using System.Threading.Tasks; using System.Threading; using PerfSummaries; using System.Globalization; using CSVTools; namespace PerfReportTool { class Version { private static string VersionString = "4.78"; public static string Get() { return VersionString; } }; class HashHelper { public static string StringToHashStr(string strIn, int maxCharsOut=-1) { HashAlgorithm algorithm = SHA256.Create(); StringBuilder sb = new StringBuilder(); byte[] hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(strIn)); StringBuilder sbOut = new StringBuilder(); foreach (byte b in hash) { sbOut.Append(b.ToString("X2")); } string strOut = sbOut.ToString(); if (maxCharsOut > 0) { return strOut.Substring(0, maxCharsOut); } return strOut; } } class SummaryTableCacheStats { public int WriteCount = 0; public int HitCount = 0; public int MissCount = 0; public int PurgeCount = 0; public void LogStats() { Console.WriteLine("Summary Table Cache stats:"); Console.WriteLine(" Cache hits : " + HitCount); Console.WriteLine(" Cache misses : " + MissCount); Console.WriteLine(" Cache writes : " + WriteCount); if (PurgeCount > 0) { Console.WriteLine(" Files purged : " + PurgeCount); } if (HitCount > 0 || MissCount > 0) { Console.WriteLine(" Hit percentage : " + ((float)HitCount * 100.0f / ((float)MissCount+(float)HitCount)).ToString("0.0") + "%"); } } }; class Program : CommandLineTool { static string formatString = "PerfReportTool v" + Version.Get() + "\n" + "\n" + "Format: \n" + " -csv or -csvdir or -summaryTableCacheIn or\n" + " -csvList or -prcList \n" + " -o : output directory (will be created if necessary)\n" + "\n" + "Optional Args:\n" + " -reportType \n" + " -reportTypeCompatCheck : do a compatibility if when specifying a report type (rather than forcing)\n"+ " -graphXML \n" + " -reportXML \n" + " -reportxmlbasedir \n" + " -title - title for detailed reports\n" + " -summaryTitle - title for summary tables\n" + " -maxy - forces all graphs to use this value\n" + " -writeSummaryCsv : if specified, a csv file containing summary information will be generated.\n"+ " Not available in bulk mode.\n" + " -noWatermarks : don't embed the commandline or version in reports\n"+ " -cleanCsvOut : write a standard format CSV after event stripping with metadata stripped out.\n"+ " Not available in bulk mode.\n" + " -noSmooth : disable smoothing on all graphs\n" + " -listSummaryTables: lists available summary tables from the current report XML\n" + "\n" + "Performance args:\n" + " -perfLog : output performance logging information\n" + " -graphThreads : use with -batchedGraphs to control the number of threads per CsvToSVG instance \n"+ " (default: PC core count/2)\n" + " -csvToSvgSequential : Run CsvToSvg sequentially\n" + "Deprecated performance args:\n"+ " -csvToSvgProcesses : Use separate processes for csvToSVG instead of threads (slower)\n" + " -noBatchedGraphs : disable batched/multithreaded graph generation (use with -csvToSvgProcesses. Default is enabled)\n" + "\n" + "Options to truncate or filter source data:\n" + "Warning: these options disable Summary Table caching\n" + " -minx \n" + " -maxx \n" + " -beginEvent : strip data before this event\n" + " -endEvent : strip data after this event\n" + " -noStripEvents : if specified, don't strip out samples between excluded events from the stats\n" + "\n" + "Optional bulk mode args: (use with -csvdir, -summaryTableCacheIn, -csvList, -prcList)\n" + " -recurse \n" + " -searchpattern , e.g -searchpattern csvprofile*\n" + " -customTable \n" + " -customTableSort (use with -customTable)\n" + " -noDetailedReports : skips individual report generation\n" + " -collateTable : writes a collated table in addition to the main one, merging by row sort\n" + " -emailTable : writes a condensed email-friendly table (see the 'condensed' summary table)\n" + " -csvTable : writes the summary table in CSV format instead of html\n" + " -summaryTableXML \n" + " -summaryTable :\n" + " Selects a custom summary table type from the list in reportTypes.xml \n"+ " (if not specified, 'default' will be used)\n" + " -condensedSummaryTable :\n" + " Selects a custom condensed summary table type from the list in reportTypes.xml \n"+ " (if not specified, 'condensed' will be used)\n" + " -summaryTableFilename : use the specified filename for the summary table (instead of SummaryTable.html)\n"+ " -metadataFilter or : filters based on CSV metadata,\n"+ " e.g \"platform=ps4 AND deviceprofile=ps4_60\" \n" + " -readAllStats : allows any CSV stat avg to appear in the summary table, not just those referenced in summaries\n" + " -showHiddenStats : shows stats which have been automatically hidden (typically duplicate csv unit stats)\n" + " -externalGraphs : enables external graphs (off by default)\n" + " -spreadsheetfriendly: outputs a single quote before non-numeric entries in summary tables\n" + " -noSummaryMinMax: don't make min/max columns for each stat in a condensed summary\n" + " -reverseTable [0|1]: Reverses the order of summary tables (set 0 to force off)\n"+ " -scrollableTable [0|1]: makes the summary table scrollable, with frozen first rows and columns (set 0 to force off)\n" + " -autoColorizeTable [0|1]: enables summary table autocolorization. (set 0 to force off) \n" + " -maxSummaryTableStringLength : strings longer than this will get truncated\n" + " -allowDuplicateCSVs : doesn't remove duplicate CSVs (Note: can cause summary table cache file locking issues)\n"+ " -requireMetadata : ignores CSVs without metadata\n" + " -listFiles : just list all files that pass the metadata query. Don't generate any reports.\n" + " -reportLinkRootPath : Make report links relative to this\n" + " -csvLinkRootPath : Make CSV file links relative to this\n" + " -linkTemplates : insert templates in place of relative links that can be replaced later, e.g {{LinkTemplate:Report:}}\n" + " -weightByColumn : weight collated table averages by this column (overrides value specified in the report XML)\n" + " -noWeightedAvg : Don't use weighted averages for the collated table\n" + " -minFrameCount : ignore CSVs without at least this number of valid frames\n" + " -maxFileAgeDays : max file age in days. CSV or PRC files older than this will be ignored\n" + "\n" + "Performance args for bulk mode:\n" + " -precacheCount : number of CSV files to precache in the lookahead cache (0 for no precache)\n" + " -precacheThreads : number of threads to use for the CSV lookahead cache (default 8)\n" + " -summaryTableCache : specifies a directory for summary table data to be cached.\n"+ " This avoids processing csvs on subsequent runs when -noDetailedReports is specified\n" + " Note: Enables -readAllStats implicitly. \n"+ " -summaryTableCacheInvalidate : regenerates summary table disk cache entries (ie write only)\n" + " -summaryTableCacheReadOnly : only read from the cache, never write\n" + " -summaryTableCachePurgeInvalid : Purges invalid PRCs from the cache folder\n" + " -summaryTableCacheIn : reads data directly from the summary table cache instead of from CSVs\n" + " -summaryTableCacheUseOnlyCsvID : only use the CSV ID for the summary table cacheID, ignoringthe report type hash\n"+ " Use this if you want to avoid cache data being invalidated by report changes\n" + " -noCsvCacheFiles: disables usage of .csv.cache files. Cache files can be much faster if filtering on metadata\n" + ""; /* "Note on custom tables:\n" + " The -customTable and -customTableSort args allow you to generate a custom summary table\n" + " This is an alternative to using preset summary tables (see -summarytable)\n" + " Example:\n"+ " -customTableSort \"deviceprofile,buildversion\" -customTable \"deviceprofile,buildversion,memoryfreeMB*\" \n"+ " This outputs a table containing deviceprofile, buildversion and memoryfree stats, sorted by deviceprofile and then buildversion\n" + "" */ Dictionary statDisplaynameMapping; ReportXML reportXML; string GetBaseDirectory() { string location = System.Reflection.Assembly.GetEntryAssembly().Location.ToLower(); string baseDirectory = location; baseDirectory = baseDirectory.Replace("perfreporttool.exe", ""); string debugSubDir = "\\bin\\debug\\"; if (baseDirectory.ToLower().EndsWith(debugSubDir)) { // Might be best to use the CSVToSVG from source, but that might not be built, so use the one checked into binaries instead baseDirectory = baseDirectory.Substring(0, baseDirectory.Length - debugSubDir.Length); baseDirectory += "\\..\\..\\..\\..\\Binaries\\DotNET\\CsvTools"; } return baseDirectory; } void Run(string[] args) { System.Globalization.CultureInfo.DefaultThreadCurrentCulture = System.Globalization.CultureInfo.InvariantCulture; // Read the command line if (args.Length < 1) { WriteLine("Invalid args"); WriteLine(formatString); return; } WriteLine("PerfReportTool v" + Version.Get()); ReadCommandLine(args); PerfLog perfLog = new PerfLog(GetBoolArg("perfLog")); SummaryFactory.Init(); string csvDir = null; bool bBulkMode = false; bool bSummaryTableCacheOnlyMode = false; // Read CSV filenames from a directory or list string[] csvFilenames; if (args.Length == 1) { // Simple mode: just pass a csv name csvFilenames = new string[] { args[0] }; } else { csvDir=GetArg("csvDir"); int maxFileAgeDays = GetIntArg("maxFileAgeDays", -1); string summaryTableCacheInDir = GetArg("summaryTableCacheIn"); string csvListStr = GetArg("csvList"); string prcListStr = GetArg("prcList"); if (csvDir.Length > 0) { bool recurse = GetBoolArg("recurse"); string searchPattern = GetArg("searchPattern", false); if (searchPattern == "") { searchPattern = "*.csv;*.csv.bin"; } else if (!searchPattern.Contains('.')) { searchPattern += ".csv;*.csv.bin"; } System.IO.FileInfo[] files = GetFilesWithSearchPattern(csvDir, searchPattern, recurse, maxFileAgeDays); csvFilenames = new string[files.Length]; int i = 0; foreach (FileInfo csvFile in files) { csvFilenames[i] = csvFile.FullName; i++; } // We don't write summary CSVs in bulk mode bBulkMode = true; perfLog.LogTiming("DirectoryScan"); } else if (summaryTableCacheInDir.Length > 0) { bool recurse = GetBoolArg("recurse"); System.IO.FileInfo[] files = GetFilesWithSearchPattern(summaryTableCacheInDir, "*.prc", recurse, maxFileAgeDays); csvFilenames = new string[files.Length]; int i = 0; foreach (FileInfo csvFile in files) { csvFilenames[i] = csvFile.FullName; i++; } bBulkMode = true; bSummaryTableCacheOnlyMode = true; perfLog.LogTiming("DirectoryScan"); } else if (csvListStr.Length > 0) { csvFilenames = csvListStr.Split(','); bBulkMode = true; } else if (prcListStr.Length > 0) { csvFilenames = prcListStr.Split(','); bBulkMode = true; bSummaryTableCacheOnlyMode = true; } else { string csvFilenamesStr = GetArg("csv"); if (csvFilenamesStr.Length == 0) { csvFilenamesStr = GetArg("csvs", true); if (csvFilenamesStr.Length == 0) { if (!GetBoolArg("listSummaryTables")) { System.Console.Write(formatString); return; } } } csvFilenames = csvFilenamesStr.Split(';'); } } // Load the report + graph XML data reportXML = new ReportXML(GetArg("graphxml", false), GetArg("reportxml", false), GetArg("reportxmlbasedir", false), GetArg("summaryTableXml", false)); if (GetBoolArg("listSummaryTables")) { Console.WriteLine("Listing summary tables:"); List summaryTableNames=reportXML.GetSummaryTableNames(); foreach (string name in summaryTableNames) { Console.WriteLine(" "+name); } return; } statDisplaynameMapping = reportXML.GetDisplayNameMapping(); SummaryTableCacheStats summaryTableCacheStats = new SummaryTableCacheStats(); perfLog.LogTiming("Initialization"); string summaryTableCacheDir = null; if (bBulkMode) { summaryTableCacheDir = GetArg("summaryTableCache", null); if (summaryTableCacheDir != null) { // Check for incompatible options. Could just feed these into the metadata key eventually string incompatibleOptionsStr = "minx,maxx,beginevent,endevent,noStripEvents"; string[] incompatibleOptionsList = incompatibleOptionsStr.Split(','); List badOptions = new List(); foreach (string option in incompatibleOptionsList) { if ( GetArg(option, null) != null) { badOptions.Add(option); } } if (badOptions.Count>0) { Console.WriteLine("Warning: Summary Table cache disabled due to incompatible options ("+ string.Join(", ", badOptions) + "). See help for details."); summaryTableCacheDir = null; } else { Console.WriteLine("Using summary table cache: " + summaryTableCacheDir); Directory.CreateDirectory(summaryTableCacheDir); if ( GetBoolArg("summaryTableCachePurgeInvalid")) { Console.WriteLine("Purging invalid data from the summary table cache." ); DirectoryInfo di = new DirectoryInfo(summaryTableCacheDir); FileInfo[] files = di.GetFiles("*.prc", SearchOption.TopDirectoryOnly); int numFilesDeleted=0; foreach (FileInfo file in files) { if ( SummaryTableRowData.TryReadFromCache(summaryTableCacheDir, file.Name.Substring(0,file.Name.Length-4))==null ) { File.Delete(file.FullName); numFilesDeleted++; } } summaryTableCacheStats.PurgeCount = numFilesDeleted; Console.WriteLine(numFilesDeleted+" of "+files.Length+" cache entries deleted"); perfLog.LogTiming("PurgeSummaryTableCache"); } } } } // Create the output directory if requested string outputDir = GetArg("o", false).ToLower(); if (!string.IsNullOrEmpty(outputDir)) { if (!Directory.Exists(outputDir)) { Directory.CreateDirectory(outputDir); } } int precacheCount = GetIntArg("precacheCount", 8); int precacheThreads = GetIntArg("precacheThreads", 8); bool bBatchedGraphs = true; if ( GetBoolArg("noBatchedGraphs") ) { bBatchedGraphs = false; } if (bBatchedGraphs) { WriteLine("Batched graph generation enabled."); } else { WriteLine("Batched graph generation disabled."); } // Read the metadata filter string string metadataFilterString = GetArg("metadataFilter", null); QueryExpression metadataQuery = null; if (metadataFilterString != null) { metadataQuery = MetadataQueryBuilder.BuildQueryExpressionTree(metadataFilterString); } bool writeDetailedReports = !GetBoolArg("noDetailedReports"); // A csv hash for now can just be a filename/size. Replace with metadata later // Make PerfSummaryCache from: CSV hash + reporttype hash. If the cache is enabled then always -readAllStats bool bReadAllStats = GetBoolArg("readAllStats"); bool bSummaryTableCacheReadonly = GetBoolArg("summaryTableCacheReadOnly"); bool bSummaryTableCacheInvalidate = GetBoolArg("summaryTableCacheInvalidate"); string cleanCsvOutputFilename = GetArg("cleanCsvOut", null); if (cleanCsvOutputFilename != null && bBulkMode) { throw new Exception("-cleanCsvOut is not compatible with bulk mode. Pass one csv with -csv "); } bool bShowHiddenStats = GetBoolArg("showHiddenStats"); string customSummaryTableFilter = GetArg("customTable"); if (customSummaryTableFilter.Length > 0) { bShowHiddenStats = true; } string summaryTableCacheForRead = summaryTableCacheDir; if (bSummaryTableCacheInvalidate || writeDetailedReports ) { // Don't read from the summary metadata cache if we're generating full reports summaryTableCacheForRead = null; } if (bSummaryTableCacheOnlyMode) { // Override these options in summaryTableCacheOnly mode bSummaryTableCacheReadonly = true; summaryTableCacheForRead = null; bSummaryTableCacheInvalidate = false; } ReportTypeParams reportTypeParams = new ReportTypeParams { reportTypeOverride = GetArg("reportType", false).ToLower(), forceReportType = !GetBoolArg("reportTypeCompatCheck") }; bool bRemoveDuplicates = !GetBoolArg("allowDuplicateCSVs"); bool bSummaryTableCacheUseOnlyCsvID = GetBoolArg("summaryTableCacheUseOnlyCsvID"); bool bRequireMetadata = GetBoolArg("requireMetadata"); bool bListFilesMode = GetBoolArg("listFiles"); int frameCountThreshold = GetIntArg("minFrameCount", 0); if (bListFilesMode) { writeDetailedReports = false; } CsvFileCache csvFileCache = new CsvFileCache( csvFilenames, precacheCount, precacheThreads, !GetBoolArg("noCsvCacheFiles"), metadataQuery, reportXML, reportTypeParams, bBulkMode, bSummaryTableCacheOnlyMode, bSummaryTableCacheUseOnlyCsvID, bRemoveDuplicates, bRequireMetadata, summaryTableCacheForRead, bListFilesMode); SummaryTable summaryTable = new SummaryTable(); bool bWriteToSummaryTableCache = summaryTableCacheDir != null && !bSummaryTableCacheReadonly; int csvCount = csvFilenames.Length; for ( int i=0; i 0 && rowData.GetFrameCount() < frameCountThreshold) { Console.WriteLine("CSV frame count below the threshold. Excluding from summary table:" + cachedCsvFile.filename); bIncludeRowData = false; } if (bIncludeRowData) { summaryTable.AddRowData(rowData, bReadAllStats, bShowHiddenStats); } perfLog.LogTiming(" AddRowData"); } } } catch (Exception e) { if (bBulkMode) { Console.Out.WriteLine("[ERROR] : "+ e.Message); } else { // If we're not in bulk mode, exceptions are fatal throw e; } } } Console.WriteLine("-------------------------------------------------"); // Write out the summary table, if there is one if (summaryTable.Count > 0) { // Pre-sort the summary table to ensure determinism summaryTable = summaryTable.SortRows(new List(new string[] { "csvfilename" }),true); perfLog.LogTiming("PreSort Summary table"); string summaryTableFilename = GetArg("summaryTableFilename", "SummaryTable"); if ( summaryTableFilename.ToLower().EndsWith(".html")) { summaryTableFilename = summaryTableFilename.Substring(0, summaryTableFilename.Length - 5); } bool bCsvTable = GetBoolArg("csvTable"); bool bCollateTable = GetBoolArg("collateTable"); bool bSpreadsheetFriendlyStrings = GetBoolArg("spreadsheetFriendly"); string weightByColumnName = GetArg("weightByColumn", null); if (customSummaryTableFilter.Length > 0) { string customSummaryTableRowSort = GetArg("customTableSort"); if (customSummaryTableRowSort.Length == 0) { customSummaryTableRowSort = "buildversion,deviceprofile"; } WriteSummaryTableReport(outputDir, summaryTableFilename, summaryTable, customSummaryTableFilter.Split(',').ToList(), customSummaryTableRowSort.Split(',').ToList(), false, bCsvTable, bSpreadsheetFriendlyStrings, null, null); if (bCollateTable) { WriteSummaryTableReport(outputDir, summaryTableFilename+"_Collated", summaryTable, customSummaryTableFilter.Split(',').ToList(), customSummaryTableRowSort.Split(',').ToList(), true, bCsvTable, bSpreadsheetFriendlyStrings, null, weightByColumnName); } } else { string summaryTableName = GetArg("summaryTable"); if (summaryTableName.Length == 0) { summaryTableName = "default"; } SummaryTableInfo tableInfo = reportXML.GetSummaryTable(summaryTableName); WriteSummaryTableReport(outputDir, summaryTableFilename, summaryTable, tableInfo, false, bCsvTable, bSpreadsheetFriendlyStrings, null ); if (bCollateTable) { WriteSummaryTableReport(outputDir, summaryTableFilename, summaryTable, tableInfo, true, bCsvTable, bSpreadsheetFriendlyStrings, weightByColumnName); } } // EmailTable is hardcoded to use the condensed type string condensedSummaryTable = GetArg("condensedSummaryTable",null); if (GetBoolArg("emailSummary") || GetBoolArg("emailTable") || condensedSummaryTable != null) { SummaryTableInfo tableInfo = reportXML.GetSummaryTable(condensedSummaryTable == null ? "condensed" : condensedSummaryTable); WriteSummaryTableReport(outputDir, summaryTableFilename+"_Email", summaryTable, tableInfo, true, false, bSpreadsheetFriendlyStrings, weightByColumnName); } perfLog.LogTiming("WriteSummaryTable"); } if ( summaryTableCacheDir != null ) { summaryTableCacheStats.LogStats(); } Console.WriteLine("Duplicate CSVs skipped: " + csvFileCache.duplicateCount); perfLog.LogTotalTiming(); } void WriteSummaryTableReport(string outputDir, string filenameWithoutExtension, SummaryTable table, List columnFilterList, List rowSortList, bool bCollated, bool bToCSV, bool bSpreadsheetFriendlyStrings, List sectionBoundaries, string weightByColumnName) { SummaryTableInfo tableInfo = new SummaryTableInfo(); tableInfo.columnFilterList = columnFilterList; tableInfo.rowSortList = rowSortList; WriteSummaryTableReport(outputDir, filenameWithoutExtension, table, tableInfo, bCollated, bToCSV, bSpreadsheetFriendlyStrings, weightByColumnName); } void WriteSummaryTableReport(string outputDir, string filenameWithoutExtension, SummaryTable table, SummaryTableInfo tableInfo, bool bCollated, bool bToCSV, bool bSpreadsheetFriendlyStrings, string weightByColumnNameOverride) { string weightByColumnName = weightByColumnNameOverride != null ? weightByColumnNameOverride : tableInfo.weightByColumn; if (GetBoolArg("noWeightedAvg")) { weightByColumnName = null; } // Check params and any commandline overrides bool bReverseTable = tableInfo.bReverseSortRows; bool? bReverseTableOption = GetOptionalBoolArg("reverseTable"); if (bReverseTableOption != null) { bReverseTable = (bool)bReverseTableOption; } bool bScrollableTable = tableInfo.bScrollableFormatting; bool? bScrollableTableOption = GetOptionalBoolArg("scrollableTable"); if (bScrollableTableOption != null) { bScrollableTable = (bool)bScrollableTableOption; } bool bAutoColorizeTable = tableInfo.bAutoColorize; bool? bAutoColorizeTableOption = GetOptionalBoolArg("autoColorizeTable"); if (bAutoColorizeTableOption != null) { bAutoColorizeTable = (bool)bAutoColorizeTableOption; } bool addMinMaxColumns = !GetBoolArg("noSummaryMinMax"); if (!string.IsNullOrEmpty(outputDir)) { filenameWithoutExtension = Path.Combine(outputDir, filenameWithoutExtension); } SummaryTable filteredTable = table.SortAndFilter(tableInfo.columnFilterList, tableInfo.rowSortList, bReverseTable, weightByColumnName); if (bCollated) { filteredTable = filteredTable.CollateSortedTable(tableInfo.rowSortList, addMinMaxColumns); } if (bToCSV) { filteredTable.WriteToCSV(filenameWithoutExtension+".csv"); } else { filteredTable.ApplyDisplayNameMapping(statDisplaynameMapping); string VersionString = GetBoolArg("noWatermarks") ? "" : Version.Get(); string summaryTitle = GetArg("summaryTitle", null); filteredTable.WriteToHTML(filenameWithoutExtension+".html", VersionString, bSpreadsheetFriendlyStrings, tableInfo.sectionBoundaries, bScrollableTable, bAutoColorizeTable, addMinMaxColumns, GetIntArg("maxSummaryTableStringLength", -1), reportXML.columnFormatInfoList, weightByColumnName, summaryTitle); } } string ReplaceFileExtension( string path, string newExtension ) { // Special case for .bin.csv if (path.ToLower().EndsWith(".csv.bin")) { return path.Substring(0, path.Length - 8)+ newExtension; } int lastDotIndex = path.LastIndexOf('.'); if ( path.EndsWith("\"")) { newExtension = newExtension + "\""; if (lastDotIndex == -1) { lastDotIndex = path.Length - 1; } } else if ( lastDotIndex == -1 ) { lastDotIndex = path.Length; } return path.Substring(0, lastDotIndex) + newExtension; } static Dictionary UniqueHTMLFilemameLookup = new Dictionary(); void WriteCleanCsv(CachedCsvFile csvFile, string outCsvFilename, ReportTypeInfo reportTypeInfo) { if ( File.Exists(outCsvFilename) ) { throw new Exception("Clean csv file " + outCsvFilename + " already exists!"); } Console.WriteLine("Writing clean (standard format, event stripped) csv file to " + outCsvFilename); int minX = GetIntArg("minx", 0); int maxX = GetIntArg("maxx", Int32.MaxValue); // Check if we're stripping stats bool bStripStatsByEvents = reportTypeInfo.bStripEvents; if (GetBoolArg("noStripEvents")) { bStripStatsByEvents = false; } int numFramesStripped; CsvStats csvStatsUnstripped; CsvStats csvStats = ProcessCsv(csvFile, out numFramesStripped, out csvStatsUnstripped, minX, maxX, null, bStripStatsByEvents); csvStats.WriteToCSV(outCsvFilename, false); } CsvStats ProcessCsv(CachedCsvFile csvFile, out int numFramesStripped, out CsvStats csvStatsUnstripped, int minX=0, int maxX=Int32.MaxValue, PerfLog perfLog=null, bool bStripStatsByEvents = true) { numFramesStripped = 0; CsvStats csvStats = ReadCsvStats(csvFile, minX, maxX); csvStatsUnstripped = csvStats; if (perfLog != null) { perfLog.LogTiming(" ReadCsvStats"); } if (bStripStatsByEvents) { CsvStats strippedCsvStats = StripCsvStatsByEvents(csvStatsUnstripped, out numFramesStripped); csvStats = strippedCsvStats; } if (perfLog != null) { perfLog.LogTiming(" FilterStats"); } return csvStats; } void GenerateReport(CachedCsvFile csvFile, string outputDir, bool bBulkMode, SummaryTableRowData rowData, bool bBatchedGraphs, bool writeDetailedReport, bool bReadAllStats, ReportTypeInfo reportTypeInfo, string csvDir) { PerfLog perfLog = new PerfLog(GetBoolArg("perfLog")); string shortName = ReplaceFileExtension(MakeShortFilename(csvFile.filename), ""); string title = GetArg("title", false); if (title.Length == 0) { title = shortName; } char c = title[0]; c = char.ToUpper(c); title = c + title.Substring(1); int minX = GetIntArg("minx", 0); int maxX = GetIntArg("maxx", Int32.MaxValue); string htmlFilename = null; if (writeDetailedReport) { htmlFilename = shortName; // Make sure the HTML filename is unique if (bBulkMode) { int index = 0; while (UniqueHTMLFilemameLookup.ContainsKey(htmlFilename.Trim().ToLower())) { if (htmlFilename.EndsWith("]") && htmlFilename.Contains('[')) { int strIndex = htmlFilename.LastIndexOf('['); htmlFilename = htmlFilename.Substring(0, strIndex); } htmlFilename += "[" + index.ToString() + "]"; index++; } UniqueHTMLFilemameLookup.Add(htmlFilename.Trim().ToLower(), true); } htmlFilename += ".html"; } bool bCsvToSvgProcesses = GetBoolArg("csvToSvgProcesses"); bool bCsvToSvgMultiThreaded = !GetBoolArg("csvToSvgSequential"); int minWorker, minIOC; // Get the current settings. ThreadPool.GetMinThreads(out minWorker, out minIOC); float thickness = 1.0f; List csvToSvgCommandlines = new List(); List svgFilenames = new List(); string responseFilename = null; List csvToSvgProcesses = new List(); List csvToSvgTasks = new List(); if (writeDetailedReport) { GraphGenerator graphGenerator = null; if (!bCsvToSvgProcesses) { graphGenerator = new GraphGenerator(csvFile.GetFinalCsv(), csvFile.filename); } // Generate all the graphs asyncronously foreach (ReportGraph graph in reportTypeInfo.graphs) { string svgFilename = String.Empty; if (graph.isExternal && !GetBoolArg("externalGraphs")) { svgFilenames.Add(svgFilename); continue; } bool bFoundStat = false; foreach (string statString in graph.settings.statString.value.Split(',')) { List matchingStats = csvFile.dummyCsvStats.GetStatsMatchingString(statString); if (matchingStats.Count > 0) { bFoundStat = true; break; } } if (bFoundStat) { svgFilename = GetTempFilename(csvFile.filename) + ".svg"; if (graphGenerator != null) { GraphParams graphParams=GetCsvToSvgGraphParams(csvFile.filename, graph, thickness, minX, maxX, false, svgFilenames.Count); if (bCsvToSvgMultiThreaded) { csvToSvgTasks.Add( graphGenerator.MakeGraphAsync(graphParams, svgFilename, true, false) ); } else { graphGenerator.MakeGraph(graphParams, svgFilename, true, false); } } else { string args = GetCsvToSvgArgs(csvFile.filename, svgFilename, graph, thickness, minX, maxX, false, svgFilenames.Count); if (bBatchedGraphs) { csvToSvgCommandlines.Add(args); } else { Process csvToSvgProcess = LaunchCsvToSvgAsync(args); csvToSvgProcesses.Add(csvToSvgProcess); } } } svgFilenames.Add(svgFilename); } if (bCsvToSvgProcesses && bBatchedGraphs) { // Save the response file responseFilename = GetTempFilename(csvFile.filename) + "_response.txt"; System.IO.File.WriteAllLines(responseFilename, csvToSvgCommandlines); Process csvToSvgProcess = LaunchCsvToSvgAsync("-batchCommands \""+responseFilename +"\" -mt " + GetIntArg("graphThreads", Environment.ProcessorCount/2).ToString() ); csvToSvgProcesses.Add(csvToSvgProcess); } } perfLog.LogTiming(" Initial Processing"); // Check if we're stripping stats bool bStripStatsByEvents = reportTypeInfo.bStripEvents; if (GetBoolArg("noStripEvents")) { bStripStatsByEvents = false; } if (writeDetailedReport && csvToSvgTasks.Count > 0) { // wait on the graph tasks to complete // Note that we have to do this before we can call ProcessCSV, since this modifies the CsvStats object the graph tasks are reading foreach (Task task in csvToSvgTasks) { task.Wait(); } perfLog.LogTiming(" WaitForAsyncGraphs"); } // Read the full csv while we wait for the graph processes to complete (this is only safe for CsvToSVG processes, not task threads) int numFramesStripped; CsvStats csvStatsUnstripped; CsvStats csvStats=ProcessCsv(csvFile, out numFramesStripped, out csvStatsUnstripped, minX, maxX, perfLog, bStripStatsByEvents); if ( writeDetailedReport && csvToSvgProcesses.Count > 0) { // wait on the graph processes to complete foreach (Process process in csvToSvgProcesses) { process.WaitForExit(); } perfLog.LogTiming(" WaitForAsyncGraphs"); } // Generate CSV metadata if (rowData != null) { Uri currentDirUri = new Uri(Directory.GetCurrentDirectory() + "/", UriKind.Absolute); if ( outputDir.Length > 0 && !outputDir.EndsWith("/")) { outputDir += "/"; } Uri optionalDirUri = new Uri(outputDir, UriKind.RelativeOrAbsolute); // Make a Csv URI that's relative to the report directory Uri finalDirUri; if (optionalDirUri.IsAbsoluteUri) { finalDirUri = optionalDirUri; } else { finalDirUri = new Uri(currentDirUri,outputDir); } Uri csvFileUri = new Uri(csvFile.filename, UriKind.Absolute); Uri relativeCsvUri = finalDirUri.MakeRelativeUri(csvFileUri); string csvPath = relativeCsvUri.ToString(); bool bLinkTemplates = GetBoolArg("linkTemplates"); string csvId = null; if (csvStats.metaData != null) { csvId=csvStats.metaData.GetValue("csvid", null); } // re-root the CSV path if requested string csvLinkRootPath = GetArg("csvLinkRootPath", null); if ( csvDir != null && csvLinkRootPath != null) { string csvDirFinal = csvDir.Replace("\\", "/"); csvDirFinal += csvDirFinal.EndsWith("/") ? "" : "/"; Uri csvDirUri = new Uri(csvDirFinal, UriKind.Absolute); Uri csvRelativeToCsvDirUri = csvDirUri.MakeRelativeUri(csvFileUri); csvPath = Path.Combine(csvLinkRootPath,csvRelativeToCsvDirUri.ToString()); csvPath = new Uri(csvPath, UriKind.Absolute).ToString(); } string csvLink = "" + shortName + ".csv" + ""; if (bLinkTemplates) { if ( !csvPath.StartsWith("http://") && !csvPath.StartsWith("https://") ) { csvLink = "{LinkTemplate:Csv:" + (csvId ?? "0") + "}"; } } rowData.Add(SummaryTableElement.Type.ToolMetadata, "Csv File", csvLink, null, csvPath); rowData.Add(SummaryTableElement.Type.ToolMetadata, "ReportType", reportTypeInfo.name); rowData.Add(SummaryTableElement.Type.ToolMetadata, "ReportTypeID", reportTypeInfo.summaryTableCacheID); if (htmlFilename != null) { string htmlUrl = htmlFilename; string reportLinkRootPath = GetArg("reportLinkRootPath", null); if (reportLinkRootPath != null) { htmlUrl = reportLinkRootPath + htmlFilename; } string reportLink = "Link"; if (bLinkTemplates) { if (!htmlUrl.StartsWith("http://") && !htmlUrl.StartsWith("https://")) { reportLink = "{LinkTemplate:Report:" + (csvId ?? "0") + "}"; } } rowData.Add(SummaryTableElement.Type.ToolMetadata, "Report", reportLink); } // Pass through all the metadata from the CSV if (csvStats.metaData != null) { foreach (KeyValuePair pair in csvStats.metaData.Values.ToList()) { rowData.Add(SummaryTableElement.Type.CsvMetadata, pair.Key.ToLower(), pair.Value); } } if (bReadAllStats) { // Add every stat avg value to the metadata foreach ( StatSamples stat in csvStats.Stats.Values ) { rowData.Add(SummaryTableElement.Type.CsvStatAverage, stat.Name, (double)stat.average); } } } if (htmlFilename != null && !string.IsNullOrEmpty(outputDir)) { htmlFilename = Path.Combine(outputDir, htmlFilename); } // Write the report WriteReport(htmlFilename, title, svgFilenames, reportTypeInfo, csvStats, csvStatsUnstripped, numFramesStripped, minX, maxX, bBulkMode, rowData); perfLog.LogTiming(" WriteReport"); // Delete the temp files foreach (string svgFilename in svgFilenames) { if(svgFilename != String.Empty && File.Exists(svgFilename)) { File.Delete(svgFilename); } } if (responseFilename != null && File.Exists(responseFilename)) { File.Delete(responseFilename); } } CsvStats ReadCsvStats(CachedCsvFile csvFile, int minX, int maxX) { CsvStats csvStats = csvFile.GetFinalCsv(); reportXML.ApplyDerivedMetadata(csvStats.metaData); if (csvStats.metaData == null) { csvStats.metaData = new CsvMetadata(); } csvStats.metaData.Values.Add("csvfilename", csvFile.filename); // Adjust min/max x based on the event delimiters string beginEventStr = GetArg("beginEvent").ToLower(); if (beginEventStr != "") { foreach (CsvEvent ev in csvStats.Events) { if (ev.Name.ToLower() == beginEventStr) { minX = Math.Max(minX, ev.Frame); break; } } } string endEventStr = GetArg("endEvent").ToLower(); if (endEventStr != "") { for (int i = csvStats.Events.Count - 1; i >= 0; i--) { CsvEvent ev = csvStats.Events[i]; if (ev.Name.ToLower() == endEventStr) { maxX = Math.Min(maxX, ev.Frame); break; } } } // Strip out all stats with a zero total List allStats = new List(); foreach (StatSamples stat in csvStats.Stats.Values) { allStats.Add(stat); } csvStats.Stats.Clear(); foreach (StatSamples stat in allStats) { if (stat.total != 0.0f) { csvStats.AddStat(stat); } } // Crop the stats to the range csvStats.CropStats(minX, maxX); return csvStats; } CsvStats StripCsvStatsByEvents(CsvStats csvStats, out int numFramesStripped) { numFramesStripped = 0; List eventsToStrip = reportXML.GetCsvEventsToStrip(); // We want to run the mask apply in parallel if -nodetailedreports is specified. Otherwise leave cores free for graph generation bool doParallelMaskApply = GetBoolArg("noDetailedReports"); CsvStats strippedStats = csvStats; if (eventsToStrip != null) { BitArray sampleMask = null; foreach (CsvEventStripInfo eventStripInfo in eventsToStrip) { csvStats.ComputeEventStripSampleMask(eventStripInfo.beginName, eventStripInfo.endName, ref sampleMask); } if (sampleMask != null) { numFramesStripped = sampleMask.Cast().Count(l => !l); strippedStats = csvStats.ApplySampleMask(sampleMask, doParallelMaskApply); } } if (numFramesStripped > 0 ) { Console.WriteLine("CSV frames excluded : " + numFramesStripped); } return strippedStats; } void WriteReport(string htmlFilename, string title, List svgFilenames, ReportTypeInfo reportTypeInfo, CsvStats csvStats, CsvStats csvStatsUnstripped, int numFramesStripped, int minX, int maxX, bool bBulkMode, SummaryTableRowData summaryRowData) { ReportGraph[] graphs = reportTypeInfo.graphs.ToArray(); string titleStr = reportTypeInfo.title + " : " + title; System.IO.StreamWriter htmlFile = null; if (htmlFilename != null) { htmlFile = new System.IO.StreamWriter(htmlFilename); htmlFile.WriteLine(""); htmlFile.WriteLine(" "); htmlFile.WriteLine(" "); if ( GetBoolArg("noWatermarks")) { htmlFile.WriteLine(" "); htmlFile.WriteLine(" " + titleStr + ""); htmlFile.WriteLine(" "); htmlFile.WriteLine(" "); htmlFile.WriteLine(" "); htmlFile.WriteLine("

" + titleStr + "

"); // show the range if (minX > 0 || maxX < Int32.MaxValue) { htmlFile.WriteLine("

(CSV cropped to range " + minX + "-"); if (maxX < Int32.MaxValue) { htmlFile.WriteLine(maxX); } htmlFile.WriteLine(")"); } htmlFile.WriteLine("

Summary

"); htmlFile.WriteLine(""); if ( reportTypeInfo.metadataToShowList != null ) { Dictionary displayNameMapping = reportXML.GetDisplayNameMapping(); foreach (string metadataStr in reportTypeInfo.metadataToShowList) { string value = csvStats.metaData.GetValue(metadataStr, null); if (value != null) { string friendlyName = metadataStr; if (displayNameMapping.ContainsKey(metadataStr.ToLower())) { friendlyName = displayNameMapping[metadataStr]; } htmlFile.WriteLine(""); } } } htmlFile.WriteLine(""); htmlFile.WriteLine("
" + friendlyName + "" + value + "
Frame count" + csvStats.SampleCount + " (" + numFramesStripped + " excluded)
"); } if (summaryRowData != null) { summaryRowData.Add(SummaryTableElement.Type.ToolMetadata, "framecount", csvStats.SampleCount.ToString()); if (numFramesStripped > 0) { summaryRowData.Add(SummaryTableElement.Type.ToolMetadata, "framecountExcluded", numFramesStripped.ToString()); } } bool bWriteSummaryCsv = GetBoolArg("writeSummaryCsv") && !bBulkMode; List
summaries = new List(reportTypeInfo.summaries); bool bExtraLinksSummary = GetBoolArg("extraLinksSummary"); if (bExtraLinksSummary) { bool bLinkTemplates = GetBoolArg("linkTemplates"); summaries.Insert(0,new ExtraLinksSummary(null, null, bLinkTemplates)); } // If the reporttype has summary info, then write out the summary] PeakSummary peakSummary = null; foreach (Summary summary in summaries) { summary.WriteSummaryData(htmlFile, summary.useUnstrippedCsvStats ? csvStatsUnstripped : csvStats, csvStatsUnstripped, bWriteSummaryCsv, summaryRowData, htmlFilename); if ( summary.GetType() == typeof(PeakSummary) ) { peakSummary = (PeakSummary)summary; } } if (htmlFile != null) { // Output the list of graphs htmlFile.WriteLine("

Graphs

"); // TODO: support sections for graphs List sections = new List(); //// We have to at least have the empty string in this array so that we can print the list of links. if (sections.Count() == 0) { sections.Add(""); } for (int index = 0; index < sections.Count; index++) { htmlFile.WriteLine("
    "); string currentCategory = sections[index]; if (currentCategory.Length > 0) { htmlFile.WriteLine("

    " + currentCategory + " Graphs

    "); } for (int i = 0; i < svgFilenames.Count(); i++) { string svgFilename = svgFilenames[i]; if (string.IsNullOrEmpty(svgFilename)) { continue; } ReportGraph graph = graphs[i]; string svgTitle = graph.title; //if (reportTypeInfo.summary.stats[i].ToLower().StartsWith(currentCategory)) { htmlFile.WriteLine("
  • " + svgTitle + "
  • "); } } htmlFile.WriteLine("
"); } // Output the Graphs for(int svgFileIndex = 0; svgFileIndex < svgFilenames.Count; svgFileIndex++) { string svgFilename = svgFilenames[svgFileIndex]; if (String.IsNullOrEmpty(svgFilename)) { continue; } ReportGraph graph = graphs[svgFileIndex]; string svgTitle = graph.title; htmlFile.WriteLine("

" + svgTitle + "

"); if (graph.isExternal) { string outFilename = htmlFilename.Replace(".html", "_" + svgTitle.Replace(" ", "_") + ".svg"); File.Copy(svgFilename, outFilename, true); htmlFile.WriteLine("" + svgTitle + " (external)"); } else { string[] svgLines = ReadLinesFromFile(svgFilename); foreach (string line in svgLines) { string modLine = line.Replace("__MAKEUNIQUE__", "U_" + svgFileIndex.ToString()); htmlFile.WriteLine(modLine); } } } if (GetBoolArg("noWatermarks")) { htmlFile.WriteLine("

Created with PerfReportTool

"); } else { htmlFile.WriteLine("

Created with PerfReportTool " + Version.Get() + "

"); } htmlFile.WriteLine("
"); htmlFile.WriteLine(" "); htmlFile.WriteLine(""); htmlFile.Close(); string ForEmail = GetArg("foremail", false); if (ForEmail != "") { WriteEmail(htmlFilename, title, svgFilenames, reportTypeInfo, csvStats, csvStatsUnstripped, minX, maxX, bBulkMode); } } } void WriteEmail(string htmlFilename, string title, List svgFilenames, ReportTypeInfo reportTypeInfo, CsvStats csvStats, CsvStats csvStatsUnstripped, int minX, int maxX, bool bBulkMode) { if (htmlFilename==null) { return; } ReportGraph[] graphs = reportTypeInfo.graphs.ToArray(); string titleStr = reportTypeInfo.title + " : " + title; System.IO.StreamWriter htmlFile; htmlFile = new System.IO.StreamWriter(htmlFilename + "email"); htmlFile.WriteLine(""); htmlFile.WriteLine(" "); htmlFile.WriteLine(" "); if (GetBoolArg("noWatermarks")) { htmlFile.WriteLine(" "); htmlFile.WriteLine(" " + titleStr + ""); htmlFile.WriteLine(" "); htmlFile.WriteLine(" "); htmlFile.WriteLine("

" + titleStr + "

"); // show the range if (minX > 0 || maxX < Int32.MaxValue) { htmlFile.WriteLine("

(CSV cropped to range " + minX + "-"); if (maxX < Int32.MaxValue) { htmlFile.WriteLine(maxX); } htmlFile.WriteLine(")"); } htmlFile.WriteLine("Click here for Report w/ interactive SVGs."); htmlFile.WriteLine("

Summary

"); htmlFile.WriteLine("Overall Runtime: [Replace Me With Runtime]"); bool bWriteSummaryCsv = GetBoolArg("writeSummaryCsv") && !bBulkMode; // If the reporttype has summary info, then write out the summary] PeakSummary peakSummary = null; foreach (Summary summary in reportTypeInfo.summaries) { summary.WriteSummaryData(htmlFile, csvStats, csvStatsUnstripped, bWriteSummaryCsv, null, htmlFilename); if (summary.GetType() == typeof(PeakSummary)) { peakSummary = (PeakSummary)summary; } } htmlFile.WriteLine("
"); htmlFile.WriteLine(""); htmlFile.Close(); } string StripSpaces( string str ) { return str.Replace(" ", ""); } string GetTempFilename(string csvFilename) { string shortFileName = MakeShortFilename(csvFilename).Replace(" ", "_"); return Path.Combine( Path.GetTempPath(), shortFileName+"_"+Guid.NewGuid().ToString().Substring(26)); } string GetCsvToSvgArgs(string csvFilename, string svgFilename, ReportGraph graph, double thicknessMultiplier, int minx, int maxx, bool multipleCSVs, int graphIndex, float scaleby = 1.0f) { string title = graph.title; GraphSettings graphSettings = graph.settings; string[] statStringTokens = graphSettings.statString.value.Split(','); IEnumerable quoteWrappedStatStrings = statStringTokens.Select(token => '"' + token + '"'); string statString = String.Join(" ", quoteWrappedStatStrings); double thickness = graphSettings.thickness.value * thicknessMultiplier; float maxy = GetFloatArg("maxy", (float)graphSettings.maxy.value); bool smooth = graphSettings.smooth.value && !GetBoolArg("nosmooth"); double smoothKernelPercent = graphSettings.smoothKernelPercent.value; double smoothKernelSize = graphSettings.smoothKernelSize.value; double compression = graphSettings.compression.value; int width = graphSettings.width.value; int height = graphSettings.height.value; string additionalArgs = ""; bool stacked = graphSettings.stacked.value; bool showAverages = graphSettings.showAverages.value; bool filterOutZeros = graphSettings.filterOutZeros.value; bool snapToPeaks = false; if (graphSettings.snapToPeaks.isSet) { snapToPeaks = graphSettings.snapToPeaks.value; } int lineDecimalPlaces = graphSettings.lineDecimalPlaces.isSet ? graphSettings.lineDecimalPlaces.value : 1; int maxHierarchyDepth = graphSettings.maxHierarchyDepth.value; string hideStatPrefix = graphSettings.hideStatPrefix.value; string showEvents = graphSettings.showEvents.value; double statMultiplier = graphSettings.statMultiplier.isSet ? graphSettings.statMultiplier.value : 1.0; bool hideEventNames = false; if (multipleCSVs) { showEvents = "CSV:*"; hideEventNames = true; } bool interactive = true; string smoothParams = ""; if (smooth) { smoothParams = " -smooth"; if (smoothKernelPercent >= 0.0f) { smoothParams += " -smoothKernelPercent " + smoothKernelPercent.ToString(); } if (smoothKernelSize >= 0.0f) { smoothParams += " -smoothKernelSize " + smoothKernelSize.ToString(); } } string highlightEventRegions = ""; if (!GetBoolArg("noStripEvents")) { List eventsToStrip = reportXML.GetCsvEventsToStrip(); if (eventsToStrip != null) { highlightEventRegions += "\""; for (int i = 0; i < eventsToStrip.Count; i++) { if (i > 0) { highlightEventRegions += ","; } string endEvent = (eventsToStrip[i].endName == null) ? "{NULL}" : eventsToStrip[i].endName; highlightEventRegions += eventsToStrip[i].beginName + "," + endEvent; } highlightEventRegions += "\""; } } OptionalDouble minFilterStatValueSetting = graph.minFilterStatValue.isSet ? graph.minFilterStatValue : graphSettings.minFilterStatValue; string args = " -csvs \"" + csvFilename + "\"" + " -title \"" + title + "\"" + " -o \"" + svgFilename + "\"" + " -stats " + statString + " -width " + (width * scaleby).ToString() + " -height " + (height * scaleby).ToString() + OptionalHelper.GetDoubleSetting(graph.budget, " -budget ") + " -maxy " + maxy.ToString() + " -uniqueID Graph_" + graphIndex.ToString() + " -lineDecimalPlaces " + lineDecimalPlaces.ToString() + " -nocommandlineEmbed " + ((statMultiplier != 1.0) ? " -statMultiplier " + statMultiplier.ToString("0.0000000000000000000000") : "") + (hideEventNames ? " -hideeventNames 1" : "") + ((minx > 0) ? (" -minx " + minx.ToString()) : "") + ((maxx != Int32.MaxValue) ? (" -maxx " + maxx.ToString()) : "") + OptionalHelper.GetDoubleSetting(graphSettings.miny, " -miny ") + OptionalHelper.GetDoubleSetting(graphSettings.threshold, " -threshold ") + OptionalHelper.GetDoubleSetting(graphSettings.averageThreshold, " -averageThreshold ") + OptionalHelper.GetDoubleSetting(minFilterStatValueSetting, " -minFilterStatValue ") + OptionalHelper.GetStringSetting(graphSettings.minFilterStatName, " -minFilterStatName ") + (compression > 0.0 ? " -compression " + compression.ToString() : "") + (thickness > 0.0 ? " -thickness " + thickness.ToString() : "") + smoothParams + (interactive ? " -interactive" : "") + (stacked ? " -stacked -forceLegendSort" : "") + (showAverages ? " -showAverages" : "") + (snapToPeaks ? "" : " -nosnap") + (filterOutZeros ? " -filterOutZeros" : "") + (maxHierarchyDepth >= 0 ? " -maxHierarchyDepth " + maxHierarchyDepth.ToString() : "") + (hideStatPrefix.Length > 0 ? " -hideStatPrefix " + hideStatPrefix : "") + (graphSettings.mainStat.isSet ? " -stacktotalstat " + graphSettings.mainStat.value : "") + (showEvents.Length > 0 ? " -showevents " + showEvents : "") + (highlightEventRegions.Length > 0 ? " -highlightEventRegions " + highlightEventRegions : "") + (graphSettings.legendAverageThreshold.isSet ? " -legendAverageThreshold " + graphSettings.legendAverageThreshold.value : "") + (graphSettings.ignoreStats.isSet ? " -ignoreStats " + graphSettings.ignoreStats.value : "") + " " + additionalArgs; return args; } GraphParams GetCsvToSvgGraphParams(string csvFilename, ReportGraph graph, double thicknessMultiplier, int minx, int maxx, bool multipleCSVs, int graphIndex, float scaleby = 1.0f) { GraphParams graphParams = new GraphParams(); graphParams.title = graph.title; GraphSettings graphSettings = graph.settings; graphParams.statNames.AddRange( graphSettings.statString.value.Split(',') ); graphParams.lineThickness = (float)(graphSettings.thickness.value * thicknessMultiplier); graphParams.smooth = graphSettings.smooth.value && !GetBoolArg("nosmooth"); if (graphParams.smooth) { if (graphSettings.smoothKernelPercent.isSet && graphSettings.smoothKernelPercent.value > 0) { graphParams.smoothKernelPercent = (float)graphSettings.smoothKernelPercent.value; } if (graphSettings.smoothKernelSize.isSet && graphSettings.smoothKernelSize.value > 0) { graphParams.smoothKernelSize = (int)(graphSettings.smoothKernelSize.value); } } if (graphSettings.compression.isSet) { graphParams.compression = (float)graphSettings.compression.value; } graphParams.width = (int)(graphSettings.width.value * scaleby); graphParams.height = (int)(graphSettings.height.value * scaleby); if ( graphSettings.stacked.isSet ) { graphParams.stacked = graphSettings.stacked.value; if (graphParams.stacked) { graphParams.forceLegendSort = true; if (graphSettings.mainStat.isSet) { graphParams.stackTotalStat = graphSettings.mainStat.value; } } } if (graphSettings.showAverages.isSet) { graphParams.showAverages = graphSettings.showAverages.value; } if (graphSettings.filterOutZeros.isSet) { graphParams.filterOutZeros = graphSettings.filterOutZeros.value; } graphParams.snapToPeaks = false; if (graphSettings.snapToPeaks.isSet) { graphParams.snapToPeaks = graphSettings.snapToPeaks.value; } graphParams.lineDecimalPlaces = graphSettings.lineDecimalPlaces.isSet ? graphSettings.lineDecimalPlaces.value : 1; if (graphSettings.maxHierarchyDepth.isSet) { graphParams.maxHierarchyDepth = graphSettings.maxHierarchyDepth.value; } if (graphSettings.hideStatPrefix.isSet && graphSettings.hideStatPrefix.value.Length > 0) { graphParams.hideStatPrefixes.AddRange(graphSettings.hideStatPrefix.value.Split(' ',';')); } if (multipleCSVs) { graphParams.showEventNames.Add("CSV:*"); graphParams.showEventNameText = false; } else { if (graphSettings.showEvents.isSet && graphSettings.showEvents.value.Length > 0) { graphParams.showEventNames.AddRange(graphSettings.showEvents.value.Split(' ', ';')); } } if (graphSettings.statMultiplier.isSet) { graphParams.statMultiplier = (float)graphSettings.statMultiplier.value; } graphParams.interactive = true; if (!GetBoolArg("noStripEvents")) { List eventsToStrip = reportXML.GetCsvEventsToStrip(); if (eventsToStrip != null) { for (int i = 0; i < eventsToStrip.Count; i++) { graphParams.highlightEventRegions.Add((eventsToStrip[i].beginName==null) ? "" : eventsToStrip[i].beginName); graphParams.highlightEventRegions.Add((eventsToStrip[i].endName == null) ? "{NULL}" : eventsToStrip[i].endName); } } } if ( graph.minFilterStatValue.isSet ) { graphParams.minFilterStatValue = (float)graph.minFilterStatValue.value; } if (graphSettings.minFilterStatName.isSet) { graphParams.minFilterStatName = graphSettings.minFilterStatName.value; } if (graph.budget.isSet) { graphParams.budget = (float)graph.budget.value; } graphParams.uniqueId = "Graph_" + graphIndex.ToString(); if (minx>0) { graphParams.minX = minx; } if (maxx != Int32.MaxValue) { graphParams.maxX = maxx; } if ( graphSettings.miny.isSet ) { graphParams.minY = (float)graphSettings.miny.value; } graphParams.maxY = GetFloatArg("maxy", (float)graphSettings.maxy.value); if (graphSettings.threshold.isSet) { graphParams.threshold = (float)graphSettings.threshold.value; } if (graphSettings.averageThreshold.isSet) { graphParams.averageThreshold = (float)graphSettings.averageThreshold.value; } if (graphSettings.legendAverageThreshold.isSet) { graphParams.legendAverageThreshold = (float)graphSettings.legendAverageThreshold.value; } if (graphSettings.ignoreStats.isSet && graphSettings.ignoreStats.value.Length > 0) { graphParams.ignoreStats.AddRange(graphSettings.ignoreStats.value.Split(' ', ';')); } return graphParams; } Process LaunchCsvToSvgAsync(string args) { string csvToolPath = GetBaseDirectory() + "/CSVToSVG.exe"; string binary = csvToolPath; // run mono on non-Windows hosts if (Host != HostPlatform.Windows) { // note, on Mac mono will not be on path binary = Host == HostPlatform.Linux ? "mono" : "/Library/Frameworks/Mono.framework/Versions/Current/Commands/mono"; args = csvToolPath + " " + args; } // Generate the SVGs, multithreaded ProcessStartInfo startInfo = new ProcessStartInfo(binary); startInfo.Arguments = args; startInfo.CreateNoWindow = true; startInfo.UseShellExecute = false; Process process = Process.Start(startInfo); return process; } int CountCSVs( CsvStats csvStats) { // Count the CSVs int csvCount = 0; foreach (CsvEvent ev in csvStats.Events) { string eventName = ev.Name; if (eventName.Length > 0) { if (eventName.Contains("CSV:") && eventName.ToLower().Contains(".csv")) { csvCount++; } } } if (csvCount == 0) { csvCount = 1; } return csvCount; } static int Main(string[] args) { Program program = new Program(); if (Debugger.IsAttached) { program.Run(args); } else { try { program.Run(args); } catch (System.Exception e) { Console.WriteLine("[ERROR] " + e.Message); return 1; } } return 0; } bool matchesPattern(string str, string pattern) { string [] patternSections=pattern.ToLower().Split('*'); // Check the substrings appear in order string remStr = str.ToLower(); for (int i = 0;i fileList = new List(); string[] searchPatterns = searchPatternStr.Split(';'); DirectoryInfo di = new DirectoryInfo(directory); foreach (string searchPattern in searchPatterns) { System.IO.FileInfo[] files = di.GetFiles("*.*", recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); foreach (FileInfo file in files) { if (maxFileAgeDays >=0) { DateTime fileModifiedTime = file.LastWriteTimeUtc; DateTime currentTime = DateTime.UtcNow; TimeSpan elapsed = currentTime.Subtract(fileModifiedTime); if ( elapsed.TotalHours > (double)maxFileAgeDays*24.0 ) { continue; } } if (matchesPattern(file.FullName, searchPattern)) { fileList.Add(file); } } } return fileList.Distinct().ToArray(); } } static class Extensions { public static T GetSafeAttibute(this XElement element, string attributeName, T defaultValue = default(T)) { XAttribute attribute = element.Attribute(attributeName); if (attribute == null) { return defaultValue; } try { switch (Type.GetTypeCode(typeof(T))) { case TypeCode.Boolean: return (T)Convert.ChangeType(Convert.ChangeType(attribute.Value, typeof(int)), typeof(bool)); case TypeCode.Single: case TypeCode.Double: case TypeCode.Decimal: return (T)Convert.ChangeType(attribute.Value, typeof(T), CultureInfo.InvariantCulture.NumberFormat); default: return (T)Convert.ChangeType(attribute.Value, typeof(T)); } } catch (FormatException e) { Console.WriteLine(string.Format("[Warning] Failed to convert XML attribute '{0}' ({1})", attributeName, e.Message)); return defaultValue; } } }; }