2019-04-30 12:24:15 -04:00
// Copyright (C) Microsoft. All rights reserved.
2019-12-26 23:01:54 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2019-04-30 12:24:15 -04:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using System.Xml.Linq ;
using System.IO ;
using System.Diagnostics ;
using CSVStats ;
2021-03-18 15:20:03 -04:00
using System.Collections ;
using System.Security.Cryptography ;
2022-04-19 00:19:31 -04:00
using System.Threading.Tasks ;
using System.Threading ;
2019-04-30 12:24:15 -04:00
using PerfSummaries ;
2021-07-22 08:07:48 -04:00
using System.Globalization ;
2022-04-19 00:19:31 -04:00
using CSVTools ;
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
using System.Text.Json ;
using System.Text.Json.Serialization ;
2019-04-30 12:24:15 -04:00
namespace PerfReportTool
{
class Version
{
2022-08-22 21:35:02 -04:00
// Format: Major.Minor.Bugfix
2022-11-03 15:02:31 -04:00
private static string VersionString = "4.98.1" ;
2019-04-30 12:24:15 -04:00
public static string Get ( ) { return VersionString ; }
} ;
2021-03-18 15:20:03 -04:00
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 ;
}
}
2021-04-26 14:25:00 -04:00
class SummaryTableCacheStats
2021-03-18 15:20:03 -04:00
{
public int WriteCount = 0 ;
public int HitCount = 0 ;
public int MissCount = 0 ;
public int PurgeCount = 0 ;
2019-04-30 12:24:15 -04:00
2021-03-18 15:20:03 -04:00
public void LogStats ( )
{
2021-04-26 14:25:00 -04:00
Console . WriteLine ( "Summary Table Cache stats:" ) ;
2021-03-18 15:20:03 -04:00
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
2022-06-29 15:44:53 -04:00
{
2019-04-30 12:24:15 -04:00
static string formatString =
"PerfReportTool v" + Version . Get ( ) + "\n" +
"\n" +
"Format: \n" +
2022-04-08 11:08:05 -04:00
" -csv <filename> or -csvdir <directory path> or -summaryTableCacheIn <directory path> or\n" +
" -csvList <comma separated> or -prcList <comma separated>\n" +
2019-04-30 12:24:15 -04:00
" -o <dir name>: output directory (will be created if necessary)\n" +
"\n" +
"Optional Args:\n" +
" -reportType <e.g. flythrough, playthrough, playthroughmemory>\n" +
2022-06-29 15:44:53 -04:00
" -reportTypeCompatCheck : do a compatibility if when specifying a report type (rather than forcing)\n" +
2019-04-30 12:24:15 -04:00
" -graphXML <xmlfilename>\n" +
" -reportXML <xmlfilename>\n" +
" -reportxmlbasedir <folder>\n" +
2022-04-08 11:08:05 -04:00
" -title <name> - title for detailed reports\n" +
" -summaryTitle <name> - title for summary tables\n" +
2021-03-18 15:20:03 -04:00
" -maxy <value> - forces all graphs to use this value\n" +
2022-06-29 15:44:53 -04:00
" -writeSummaryCsv : if specified, a csv file containing summary information will be generated.\n" +
2021-04-26 14:28:01 -04:00
" Not available in bulk mode.\n" +
2022-06-29 15:44:53 -04:00
" -noWatermarks : don't embed the commandline or version in reports\n" +
" -cleanCsvOut <filename> : write a standard format CSV after event stripping with metadata stripped out.\n" +
2021-04-26 14:28:01 -04:00
" Not available in bulk mode.\n" +
2021-08-03 11:56:47 -04:00
" -noSmooth : disable smoothing on all graphs\n" +
2021-11-07 23:43:01 -05:00
" -listSummaryTables: lists available summary tables from the current report XML\n" +
2021-04-26 14:28:01 -04:00
"\n" +
2021-03-18 15:20:03 -04:00
"Performance args:\n" +
" -perfLog : output performance logging information\n" +
2022-06-29 15:44:53 -04:00
" -graphThreads : use with -batchedGraphs to control the number of threads per CsvToSVG instance \n" +
2021-04-26 14:28:01 -04:00
" (default: PC core count/2)\n" +
2022-04-19 00:19:31 -04:00
" -csvToSvgSequential : Run CsvToSvg sequentially\n" +
2022-06-29 15:44:53 -04:00
"Deprecated performance args:\n" +
2022-04-19 00:19:31 -04:00
" -csvToSvgProcesses : Use separate processes for csvToSVG instead of threads (slower)\n" +
" -noBatchedGraphs : disable batched/multithreaded graph generation (use with -csvToSvgProcesses. Default is enabled)\n" +
2021-03-18 15:20:03 -04:00
"\n" +
"Options to truncate or filter source data:\n" +
2021-04-26 14:28:01 -04:00
"Warning: these options disable Summary Table caching\n" +
2021-03-18 15:20:03 -04:00
" -minx <frameNumber>\n" +
" -maxx <frameNumber>\n" +
" -beginEvent <event> : strip data before this event\n" +
" -endEvent <event> : strip data after this event\n" +
" -noStripEvents : if specified, don't strip out samples between excluded events from the stats\n" +
2019-08-13 14:21:55 -04:00
"\n" +
2022-04-08 11:08:05 -04:00
"Optional bulk mode args: (use with -csvdir, -summaryTableCacheIn, -csvList, -prcList)\n" +
2019-04-30 12:24:15 -04:00
" -recurse \n" +
" -searchpattern <pattern>, e.g -searchpattern csvprofile*\n" +
2022-04-08 11:08:05 -04:00
" -customTable <comma separated fields>\n" +
" -customTableSort <comma separated field row sort order> (use with -customTable)\n" +
2019-04-30 12:24:15 -04:00
" -noDetailedReports : skips individual report generation\n" +
" -collateTable : writes a collated table in addition to the main one, merging by row sort\n" +
2022-07-10 12:07:44 -04:00
" -collateTableOnly : as -collateTable, but doesn't write the standard summary table.\n" +
2019-04-30 12:24:15 -04:00
" -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" +
2022-04-08 11:08:05 -04:00
" -summaryTableXML <XML filename>\n" +
2019-04-30 12:24:15 -04:00
" -summaryTable <name> :\n" +
2022-06-29 15:44:53 -04:00
" Selects a custom summary table type from the list in reportTypes.xml \n" +
2019-04-30 12:24:15 -04:00
" (if not specified, 'default' will be used)\n" +
2020-06-23 18:40:00 -04:00
" -condensedSummaryTable <name> :\n" +
2022-06-29 15:44:53 -04:00
" Selects a custom condensed summary table type from the list in reportTypes.xml \n" +
2020-06-23 18:40:00 -04:00
" (if not specified, 'condensed' will be used)\n" +
2022-06-29 15:44:53 -04:00
" -summaryTableFilename <name> : use the specified filename for the summary table (instead of SummaryTable.html)\n" +
" -metadataFilter <query> or <key0=value0,key1=value1...>: filters based on CSV metadata,\n" +
2021-04-26 14:28:01 -04:00
" 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" +
2019-04-30 12:24:15 -04:00
" -externalGraphs : enables external graphs (off by default)\n" +
2020-06-23 18:40:00 -04:00
" -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" +
2022-06-29 15:44:53 -04:00
" -reverseTable [0|1]: Reverses the order of summary tables (set 0 to force off)\n" +
2022-06-15 19:53:12 -04:00
" -scrollableTable [0|1]: makes the summary table scrollable, with frozen first rows and columns (set 0 to force off)\n" +
2022-10-20 13:44:01 -04:00
" -colorizeTable [off|budget|auto]: selects the table colorization mode. If omitted, uses the default in the summary xml table if set.\n" +
2021-03-18 15:20:03 -04:00
" -maxSummaryTableStringLength <n>: strings longer than this will get truncated\n" +
2022-06-29 15:44:53 -04:00
" -allowDuplicateCSVs : doesn't remove duplicate CSVs (Note: can cause summary table cache file locking issues)\n" +
2021-04-26 14:28:01 -04:00
" -requireMetadata : ignores CSVs without metadata\n" +
2021-05-01 15:23:22 -04:00
" -listFiles : just list all files that pass the metadata query. Don't generate any reports.\n" +
2022-01-08 13:13:03 -05:00
" -reportLinkRootPath <path> : Make report links relative to this\n" +
" -csvLinkRootPath <path> : Make CSV file links relative to this\n" +
2022-04-20 14:43:25 -04:00
" -linkTemplates : insert templates in place of relative links that can be replaced later, e.g {{LinkTemplate:Report:<CSV ID>}}\n" +
2021-05-11 01:10:20 -04:00
" -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" +
2022-01-08 13:13:03 -05:00
" -minFrameCount <n> : ignore CSVs without at least this number of valid frames\n" +
" -maxFileAgeDays <n> : max file age in days. CSV or PRC files older than this will be ignored\n" +
2022-07-17 23:54:33 -04:00
" -summaryTableStatThreshold <n> : stat/metric columns in the summarytable will be filtered out if all values < threshold\n" +
2022-07-25 13:50:18 -04:00
" -summaryTableXmlSubst <find1>=<replace1>,<find2>=<replace2>... : replace summarytable XML row and filter entries\n" +
" -transposeTable : write the summary tables transposed\n" +
" -transposeCollatedTable : write the collated summary table transposed (disables min/max columns)\n" +
" -addDiffRows : adds diff rows after the first two rows\n" +
2022-11-01 16:30:42 -04:00
"\n" +
"Optional Column Filters\n" +
" -debugShowFilteredColumns : grays out filtered columns instead of removing them. Column tooltip will show filtered reason.\n" +
" -hideMetadataColumns : filters out metadata columns from the table (excluding those used in row sort).\n" +
" -onlyShowRegressedColumns : only shows columns where the most recent row group (see -regressionJoinRowsByName) is outside the given standard deviation threshold from the mean of the previous rows.\n" +
" -regressionJoinRowsByName <statName> (optional) : the csv stat name to join rows by for aggregation. If not provided each row is treated distinctly and no aggregation occurs.\n" +
" -regressionStdDevThreshold <n> (default = 2) : how many standard deviations from the mean the most recent row group must be to be considered a regression.\n" +
" -regressionOutlierStdDevThreshold <n> (default = 4) : how many standard deviations from the mean a value must be to be considered an outlier. Used in the initial pass to remove outliers to better identify real regressions.\n" +
2021-03-18 15:20:03 -04:00
"\n" +
2022-06-16 16:04:15 -04:00
"Json serialization:\n" +
" -summaryTableToJson <filename> : json filename to write summary table row data to\n" +
" -summaryTableToJsonFastMode : exit after serializing json data (skips making summary tables)\n" +
2022-06-19 23:41:49 -04:00
" -summaryTableToJsonWriteAllElementData : write all element data, including tooltips, flags\n" +
2022-06-16 16:04:15 -04:00
" -summaryTableToJsonMetadataOnly : only write CsvMetadata elements to json\n" +
2022-06-29 18:57:28 -04:00
" -summaryTableToJsonFileStream : use a file stream to write Json. Experimental but can avoid OOMs\n" +
2022-06-29 15:44:53 -04:00
" -jsonToPrcs <json filename> : write PRCs. PRC files will be written to -summaryTableCache folder\n" +
2022-06-16 16:04:15 -04:00
"\n" +
2021-03-18 15:20:03 -04:00
"Performance args for bulk mode:\n" +
" -precacheCount <n> : number of CSV files to precache in the lookahead cache (0 for no precache)\n" +
2021-04-08 14:32:07 -04:00
" -precacheThreads <n> : number of threads to use for the CSV lookahead cache (default 8)\n" +
2022-06-29 15:44:53 -04:00
" -summaryTableCache <dir> : specifies a directory for summary table data to be cached.\n" +
2021-04-26 14:28:01 -04:00
" This avoids processing csvs on subsequent runs when -noDetailedReports is specified\n" +
2022-06-29 15:44:53 -04:00
" Note: Enables -readAllStats implicitly. \n" +
2021-04-26 14:25:00 -04:00
" -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 <dir> : reads data directly from the summary table cache instead of from CSVs\n" +
2022-06-29 15:44:53 -04:00
" -summaryTableCacheUseOnlyCsvID : only use the CSV ID for the summary table cacheID, ignoringthe report type hash\n" +
2021-04-26 14:28:01 -04:00
" Use this if you want to avoid cache data being invalidated by report changes\n" +
2021-04-26 14:25:00 -04:00
" -noCsvCacheFiles: disables usage of .csv.cache files. Cache files can be much faster if filtering on metadata\n" +
2019-04-30 12:24:15 -04:00
"" ;
2022-06-29 15:44:53 -04:00
/ *
"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" +
""
* /
2019-04-30 12:24:15 -04:00
Dictionary < string , string > statDisplaynameMapping ;
ReportXML reportXML ;
string GetBaseDirectory ( )
2022-06-29 15:44:53 -04:00
{
string location = System . Reflection . Assembly . GetEntryAssembly ( ) . Location . ToLower ( ) ;
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
string baseDirectory = location ;
baseDirectory = baseDirectory . Replace ( "perfreporttool.exe" , "" ) ;
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
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 ;
}
2019-04-30 12:24:15 -04:00
void Run ( string [ ] args )
{
2022-05-27 16:49:31 -04:00
System . Globalization . CultureInfo . DefaultThreadCurrentCulture = System . Globalization . CultureInfo . InvariantCulture ;
// Read the command line
if ( args . Length < 1 )
2019-04-30 12:24:15 -04:00
{
WriteLine ( "Invalid args" ) ;
WriteLine ( formatString ) ;
return ;
}
2019-11-20 07:46:12 -05:00
WriteLine ( "PerfReportTool v" + Version . Get ( ) ) ;
2019-04-30 12:24:15 -04:00
ReadCommandLine ( args ) ;
2022-06-29 15:44:53 -04:00
PerfLog perfLog = new PerfLog ( GetBoolArg ( "perfLog" ) ) ;
2021-07-01 13:09:53 -04:00
SummaryFactory . Init ( ) ;
2022-06-29 15:44:53 -04:00
// Handle converting json to PRCs if requested
string jsonToPrcFilename = GetArg ( "jsonToPrcs" , null ) ;
if ( jsonToPrcFilename ! = null )
{
string prcOutputDir = GetArg ( "summaryTableCache" , null ) ;
if ( prcOutputDir = = null )
{
throw new Exception ( "-jsonToPRCs requires -summaryTableCache!" ) ;
}
ConvertJsonToPrcs ( jsonToPrcFilename , prcOutputDir ) ;
return ;
}
2021-05-05 11:51:38 -04:00
string csvDir = null ;
2019-04-30 12:24:15 -04:00
2021-05-05 11:51:38 -04:00
bool bBulkMode = false ;
2021-04-26 14:25:00 -04:00
bool bSummaryTableCacheOnlyMode = false ;
2019-04-30 12:24:15 -04:00
// 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
{
2022-06-29 15:44:53 -04:00
csvDir = GetArg ( "csvDir" ) ;
2022-01-08 13:13:03 -05:00
int maxFileAgeDays = GetIntArg ( "maxFileAgeDays" , - 1 ) ;
2021-04-26 14:25:00 -04:00
string summaryTableCacheInDir = GetArg ( "summaryTableCacheIn" ) ;
2022-04-08 11:08:05 -04:00
string csvListStr = GetArg ( "csvList" ) ;
string prcListStr = GetArg ( "prcList" ) ;
2019-04-30 12:24:15 -04:00
if ( csvDir . Length > 0 )
{
bool recurse = GetBoolArg ( "recurse" ) ;
string searchPattern = GetArg ( "searchPattern" , false ) ;
if ( searchPattern = = "" )
{
2020-06-23 18:40:00 -04:00
searchPattern = "*.csv;*.csv.bin" ;
2019-04-30 12:24:15 -04:00
}
else if ( ! searchPattern . Contains ( '.' ) )
{
2020-06-23 18:40:00 -04:00
searchPattern + = ".csv;*.csv.bin" ;
2019-04-30 12:24:15 -04:00
}
2020-06-23 18:40:00 -04:00
2022-01-08 13:13:03 -05:00
System . IO . FileInfo [ ] files = GetFilesWithSearchPattern ( csvDir , searchPattern , recurse , maxFileAgeDays ) ;
2019-04-30 12:24:15 -04:00
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 ;
2022-04-08 11:08:05 -04:00
perfLog . LogTiming ( "DirectoryScan" ) ;
}
else if ( summaryTableCacheInDir . Length > 0 )
2021-04-26 06:55:45 -04:00
{
bool recurse = GetBoolArg ( "recurse" ) ;
2022-01-08 13:13:03 -05:00
System . IO . FileInfo [ ] files = GetFilesWithSearchPattern ( summaryTableCacheInDir , "*.prc" , recurse , maxFileAgeDays ) ;
2021-04-26 06:55:45 -04:00
csvFilenames = new string [ files . Length ] ;
int i = 0 ;
foreach ( FileInfo csvFile in files )
{
csvFilenames [ i ] = csvFile . FullName ;
i + + ;
}
bBulkMode = true ;
2021-04-26 14:25:00 -04:00
bSummaryTableCacheOnlyMode = true ;
2021-04-26 06:55:45 -04:00
perfLog . LogTiming ( "DirectoryScan" ) ;
}
2022-04-08 11:08:05 -04:00
else if ( csvListStr . Length > 0 )
2019-04-30 12:24:15 -04:00
{
2022-04-08 11:08:05 -04:00
csvFilenames = csvListStr . Split ( ',' ) ;
bBulkMode = true ;
}
else if ( prcListStr . Length > 0 )
{
csvFilenames = prcListStr . Split ( ',' ) ;
bBulkMode = true ;
bSummaryTableCacheOnlyMode = true ;
}
else
2022-06-29 15:44:53 -04:00
{
2019-04-30 12:24:15 -04:00
string csvFilenamesStr = GetArg ( "csv" ) ;
if ( csvFilenamesStr . Length = = 0 )
{
csvFilenamesStr = GetArg ( "csvs" , true ) ;
if ( csvFilenamesStr . Length = = 0 )
{
2022-04-08 11:08:05 -04:00
if ( ! GetBoolArg ( "listSummaryTables" ) )
{
System . Console . Write ( formatString ) ;
return ;
}
2019-04-30 12:24:15 -04:00
}
}
csvFilenames = csvFilenamesStr . Split ( ';' ) ;
}
}
// Load the report + graph XML data
2022-07-23 10:30:52 -04:00
reportXML = new ReportXML ( GetArg ( "graphxml" , false ) , GetArg ( "reportxml" , false ) , GetArg ( "reportxmlbasedir" , false ) , GetArg ( "summaryTableXml" , false ) , GetArg ( "summaryTableXmlSubst" , false ) ) ;
2021-11-07 23:43:01 -05:00
if ( GetBoolArg ( "listSummaryTables" ) )
{
Console . WriteLine ( "Listing summary tables:" ) ;
2022-06-29 15:44:53 -04:00
List < string > summaryTableNames = reportXML . GetSummaryTableNames ( ) ;
2021-11-07 23:43:01 -05:00
foreach ( string name in summaryTableNames )
{
2022-06-29 15:44:53 -04:00
Console . WriteLine ( " " + name ) ;
2021-11-07 23:43:01 -05:00
}
return ;
}
2019-04-30 12:24:15 -04:00
statDisplaynameMapping = reportXML . GetDisplayNameMapping ( ) ;
2022-06-16 16:04:15 -04:00
// If we're outputting row data to json, create the dict
2022-06-29 15:44:53 -04:00
SummaryTableDataJsonWriteHelper summaryTableJsonHelper = null ;
2022-06-16 16:04:15 -04:00
string summaryJsonOutFilename = GetArg ( "summaryTableToJson" , null ) ;
if ( summaryJsonOutFilename ! = null )
{
2022-06-29 15:44:53 -04:00
summaryTableJsonHelper = new SummaryTableDataJsonWriteHelper ( summaryJsonOutFilename , GetBoolArg ( "summaryTableToJsonMetadataOnly" ) , GetBoolArg ( "summaryTableToJsonWriteAllElementData" ) ) ;
2022-06-16 16:04:15 -04:00
}
2021-04-26 14:25:00 -04:00
SummaryTableCacheStats summaryTableCacheStats = new SummaryTableCacheStats ( ) ;
2021-03-18 15:20:03 -04:00
perfLog . LogTiming ( "Initialization" ) ;
2021-04-26 14:25:00 -04:00
string summaryTableCacheDir = null ;
2021-03-18 15:20:03 -04:00
if ( bBulkMode )
{
2021-04-26 14:25:00 -04:00
summaryTableCacheDir = GetArg ( "summaryTableCache" , null ) ;
if ( summaryTableCacheDir ! = null )
2021-03-18 15:20:03 -04:00
{
// 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 < string > badOptions = new List < string > ( ) ;
foreach ( string option in incompatibleOptionsList )
{
2022-06-29 15:44:53 -04:00
if ( GetArg ( option , null ) ! = null )
2021-03-18 15:20:03 -04:00
{
badOptions . Add ( option ) ;
}
}
2022-06-29 15:44:53 -04:00
if ( badOptions . Count > 0 )
2021-03-18 15:20:03 -04:00
{
2022-06-29 15:44:53 -04:00
Console . WriteLine ( "Warning: Summary Table cache disabled due to incompatible options (" + string . Join ( ", " , badOptions ) + "). See help for details." ) ;
2021-04-26 14:25:00 -04:00
summaryTableCacheDir = null ;
2021-03-18 15:20:03 -04:00
}
else
{
2021-04-26 14:25:00 -04:00
Console . WriteLine ( "Using summary table cache: " + summaryTableCacheDir ) ;
Directory . CreateDirectory ( summaryTableCacheDir ) ;
2021-03-18 15:20:03 -04:00
2022-06-29 15:44:53 -04:00
if ( GetBoolArg ( "summaryTableCachePurgeInvalid" ) )
2021-03-18 15:20:03 -04:00
{
2022-06-29 15:44:53 -04:00
Console . WriteLine ( "Purging invalid data from the summary table cache." ) ;
2021-04-26 14:25:00 -04:00
DirectoryInfo di = new DirectoryInfo ( summaryTableCacheDir ) ;
2021-03-18 15:20:03 -04:00
FileInfo [ ] files = di . GetFiles ( "*.prc" , SearchOption . TopDirectoryOnly ) ;
2022-06-29 15:44:53 -04:00
int numFilesDeleted = 0 ;
2021-03-18 15:20:03 -04:00
foreach ( FileInfo file in files )
{
2022-06-29 15:44:53 -04:00
if ( SummaryTableRowData . TryReadFromCache ( summaryTableCacheDir , file . Name . Substring ( 0 , file . Name . Length - 4 ) ) = = null )
2021-03-18 15:20:03 -04:00
{
File . Delete ( file . FullName ) ;
numFilesDeleted + + ;
}
}
2021-04-26 14:25:00 -04:00
summaryTableCacheStats . PurgeCount = numFilesDeleted ;
2022-06-29 15:44:53 -04:00
Console . WriteLine ( numFilesDeleted + " of " + files . Length + " cache entries deleted" ) ;
2021-04-26 14:25:00 -04:00
perfLog . LogTiming ( "PurgeSummaryTableCache" ) ;
2021-03-18 15:20:03 -04:00
}
}
}
}
2022-06-29 15:44:53 -04:00
// Create the output directory if requested
string outputDir = GetArg ( "o" , false ) . ToLower ( ) ;
if ( ! string . IsNullOrEmpty ( outputDir ) )
{
if ( ! Directory . Exists ( outputDir ) )
{
Directory . CreateDirectory ( outputDir ) ;
}
}
2019-04-30 12:24:15 -04:00
2019-11-20 07:46:12 -05:00
int precacheCount = GetIntArg ( "precacheCount" , 8 ) ;
int precacheThreads = GetIntArg ( "precacheThreads" , 8 ) ;
2019-11-27 11:41:46 -05:00
bool bBatchedGraphs = true ;
2022-06-29 15:44:53 -04:00
if ( GetBoolArg ( "noBatchedGraphs" ) )
2019-11-27 11:41:46 -05:00
{
bBatchedGraphs = false ;
}
2019-11-20 07:46:12 -05:00
if ( bBatchedGraphs )
{
WriteLine ( "Batched graph generation enabled." ) ;
}
2019-11-27 11:41:46 -05:00
else
{
WriteLine ( "Batched graph generation disabled." ) ;
}
2019-04-30 12:24:15 -04:00
2021-04-08 14:32:07 -04:00
// Read the metadata filter string
2020-06-23 18:40:00 -04:00
string metadataFilterString = GetArg ( "metadataFilter" , null ) ;
2021-04-08 14:32:07 -04:00
QueryExpression metadataQuery = null ;
if ( metadataFilterString ! = null )
{
metadataQuery = MetadataQueryBuilder . BuildQueryExpressionTree ( metadataFilterString ) ;
}
2019-12-17 09:18:04 -05:00
2021-03-18 15:20:03 -04:00
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" ) ;
2021-04-26 06:55:45 -04:00
2021-04-26 14:25:00 -04:00
bool bSummaryTableCacheReadonly = GetBoolArg ( "summaryTableCacheReadOnly" ) ;
bool bSummaryTableCacheInvalidate = GetBoolArg ( "summaryTableCacheInvalidate" ) ;
2021-04-08 14:32:07 -04:00
string cleanCsvOutputFilename = GetArg ( "cleanCsvOut" , null ) ;
if ( cleanCsvOutputFilename ! = null & & bBulkMode )
{
throw new Exception ( "-cleanCsvOut is not compatible with bulk mode. Pass one csv with -csv <filename>" ) ;
}
2021-04-26 06:55:45 -04:00
bool bShowHiddenStats = GetBoolArg ( "showHiddenStats" ) ;
string customSummaryTableFilter = GetArg ( "customTable" ) ;
if ( customSummaryTableFilter . Length > 0 )
{
bShowHiddenStats = true ;
}
2021-04-26 14:25:00 -04:00
string summaryTableCacheForRead = summaryTableCacheDir ;
2022-06-29 15:44:53 -04:00
if ( bSummaryTableCacheInvalidate | | writeDetailedReports )
2021-03-18 15:20:03 -04:00
{
// Don't read from the summary metadata cache if we're generating full reports
2021-04-26 14:25:00 -04:00
summaryTableCacheForRead = null ;
2021-03-18 15:20:03 -04:00
}
2021-04-26 14:25:00 -04:00
if ( bSummaryTableCacheOnlyMode )
2021-04-26 06:55:45 -04:00
{
2021-04-26 14:25:00 -04:00
// Override these options in summaryTableCacheOnly mode
bSummaryTableCacheReadonly = true ;
summaryTableCacheForRead = null ;
bSummaryTableCacheInvalidate = false ;
2021-04-26 06:55:45 -04:00
}
2021-03-18 15:20:03 -04:00
ReportTypeParams reportTypeParams = new ReportTypeParams
{
reportTypeOverride = GetArg ( "reportType" , false ) . ToLower ( ) ,
forceReportType = ! GetBoolArg ( "reportTypeCompatCheck" )
} ;
2021-04-08 14:32:07 -04:00
bool bRemoveDuplicates = ! GetBoolArg ( "allowDuplicateCSVs" ) ;
2021-04-26 14:27:22 -04:00
bool bSummaryTableCacheUseOnlyCsvID = GetBoolArg ( "summaryTableCacheUseOnlyCsvID" ) ;
2021-04-26 14:28:01 -04:00
bool bRequireMetadata = GetBoolArg ( "requireMetadata" ) ;
2021-05-01 15:23:22 -04:00
bool bListFilesMode = GetBoolArg ( "listFiles" ) ;
2021-10-12 21:21:22 -04:00
int frameCountThreshold = GetIntArg ( "minFrameCount" , 0 ) ;
2021-05-01 15:23:22 -04:00
if ( bListFilesMode )
{
writeDetailedReports = false ;
}
2021-04-26 14:27:22 -04:00
CsvFileCache csvFileCache = new CsvFileCache (
2022-06-29 15:44:53 -04:00
csvFilenames ,
precacheCount ,
precacheThreads ,
! GetBoolArg ( "noCsvCacheFiles" ) ,
metadataQuery ,
reportXML ,
reportTypeParams ,
bBulkMode ,
bSummaryTableCacheOnlyMode ,
bSummaryTableCacheUseOnlyCsvID ,
2021-04-26 14:28:01 -04:00
bRemoveDuplicates ,
bRequireMetadata ,
2021-05-01 15:23:22 -04:00
summaryTableCacheForRead ,
bListFilesMode ) ;
2021-04-26 14:27:22 -04:00
2022-06-29 15:44:53 -04:00
SummaryTable summaryTable = new SummaryTable ( ) ;
2021-04-26 14:25:00 -04:00
bool bWriteToSummaryTableCache = summaryTableCacheDir ! = null & & ! bSummaryTableCacheReadonly ;
2021-03-18 15:20:03 -04:00
2021-04-26 06:55:45 -04:00
int csvCount = csvFilenames . Length ;
2022-06-29 15:44:53 -04:00
for ( int i = 0 ; i < csvCount ; i + + )
2019-04-30 12:24:15 -04:00
{
2022-06-29 15:44:53 -04:00
try
{
CachedCsvFile cachedCsvFile = csvFileCache . GetNextCachedCsvFile ( ) ;
2021-04-26 06:55:45 -04:00
if ( cachedCsvFile = = null )
{
continue ;
}
2022-06-29 15:44:53 -04:00
Console . WriteLine ( "-------------------------------------------------" ) ;
Console . WriteLine ( "CSV " + ( i + 1 ) + "/" + csvFilenames . Length ) ;
Console . WriteLine ( cachedCsvFile . filename ) ;
2019-04-30 12:24:15 -04:00
2020-06-23 18:40:00 -04:00
perfLog . LogTiming ( " CsvCacheRead" ) ;
2021-04-08 14:32:07 -04:00
if ( cachedCsvFile = = null )
{
Console . WriteLine ( "Skipped!" ) ;
}
else
2022-06-29 15:44:53 -04:00
{
2021-04-26 14:25:00 -04:00
SummaryTableRowData rowData = cachedCsvFile . cachedSummaryTableRowData ;
if ( rowData = = null )
2021-03-18 15:20:03 -04:00
{
2021-04-26 14:25:00 -04:00
if ( summaryTableCacheForRead ! = null )
2021-03-18 15:20:03 -04:00
{
2021-04-26 14:25:00 -04:00
summaryTableCacheStats . MissCount + + ;
2021-03-18 15:20:03 -04:00
}
if ( bBulkMode )
{
2021-04-26 14:25:00 -04:00
rowData = new SummaryTableRowData ( ) ;
2021-03-18 15:20:03 -04:00
}
2021-04-08 14:32:07 -04:00
if ( cleanCsvOutputFilename ! = null )
2021-03-18 15:20:03 -04:00
{
2021-04-08 14:32:07 -04:00
WriteCleanCsv ( cachedCsvFile , cleanCsvOutputFilename , cachedCsvFile . reportTypeInfo ) ;
2021-04-26 06:55:45 -04:00
perfLog . LogTiming ( " WriteCleanCsv" ) ;
2021-04-08 14:32:07 -04:00
}
else
{
2021-05-05 11:51:38 -04:00
GenerateReport ( cachedCsvFile , outputDir , bBulkMode , rowData , bBatchedGraphs , writeDetailedReports , bReadAllStats | | bWriteToSummaryTableCache , cachedCsvFile . reportTypeInfo , csvDir ) ;
2021-04-08 14:32:07 -04:00
perfLog . LogTiming ( " GenerateReport" ) ;
2021-10-12 21:21:22 -04:00
2021-04-26 14:25:00 -04:00
if ( rowData ! = null & & bWriteToSummaryTableCache )
2021-03-18 15:20:03 -04:00
{
2021-04-26 14:25:00 -04:00
if ( rowData . WriteToCache ( summaryTableCacheDir , cachedCsvFile . summaryTableCacheId ) )
2021-04-08 14:32:07 -04:00
{
2021-04-26 14:25:00 -04:00
Console . WriteLine ( "Cached summary rowData for CSV: " + cachedCsvFile . filename ) ;
summaryTableCacheStats . WriteCount + + ;
perfLog . LogTiming ( " WriteSummaryTableCache" ) ;
2021-04-08 14:32:07 -04:00
}
2021-03-18 15:20:03 -04:00
}
}
}
else
{
2021-04-26 14:25:00 -04:00
summaryTableCacheStats . HitCount + + ;
2021-03-18 15:20:03 -04:00
}
2021-10-12 21:21:22 -04:00
2021-04-26 14:25:00 -04:00
if ( rowData ! = null )
2022-06-29 15:44:53 -04:00
{
2021-10-12 21:21:22 -04:00
// Filter row based on framecount if minFrameCount is specified
bool bIncludeRowData = true ;
if ( frameCountThreshold > 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 ) ;
2022-06-16 16:04:15 -04:00
if ( summaryTableJsonHelper ! = null )
{
summaryTableJsonHelper . AddRowData ( rowData ) ;
}
2021-10-12 21:21:22 -04:00
}
2021-04-26 14:25:00 -04:00
perfLog . LogTiming ( " AddRowData" ) ;
2021-03-18 15:20:03 -04:00
}
}
2022-06-29 15:44:53 -04:00
}
2019-04-30 12:24:15 -04:00
catch ( Exception e )
{
if ( bBulkMode )
{
2022-06-29 15:44:53 -04:00
Console . Out . WriteLine ( "[ERROR] : " + e . Message ) ;
2019-04-30 12:24:15 -04:00
}
else
{
// If we're not in bulk mode, exceptions are fatal
throw e ;
}
}
}
2022-06-16 16:04:15 -04:00
if ( summaryTableJsonHelper ! = null )
{
2022-06-29 18:57:28 -04:00
summaryTableJsonHelper . WriteJsonFile ( GetBoolArg ( "summaryTableToJsonFileStream" ) ) ;
2022-06-16 16:04:15 -04:00
perfLog . LogTiming ( "WriteSummaryDataJson" ) ;
if ( GetBoolArg ( "summaryTableToJsonFastMode" ) )
{
perfLog . LogTotalTiming ( ) ;
return ;
}
}
2022-06-29 15:44:53 -04:00
Console . WriteLine ( "-------------------------------------------------" ) ;
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
// Write out the summary table, if there is one
if ( summaryTable . Count > 0 )
2019-04-30 12:24:15 -04:00
{
2021-05-25 20:21:55 -04:00
// Pre-sort the summary table to ensure determinism
2022-06-29 15:44:53 -04:00
summaryTable = summaryTable . SortRows ( new List < string > ( new string [ ] { "csvfilename" } ) , true ) ;
2021-05-25 20:21:55 -04:00
perfLog . LogTiming ( "PreSort Summary table" ) ;
2019-08-13 14:21:55 -04:00
string summaryTableFilename = GetArg ( "summaryTableFilename" , "SummaryTable" ) ;
2022-06-29 15:44:53 -04:00
if ( summaryTableFilename . ToLower ( ) . EndsWith ( ".html" ) )
2019-08-13 14:21:55 -04:00
{
summaryTableFilename = summaryTableFilename . Substring ( 0 , summaryTableFilename . Length - 5 ) ;
}
2019-04-30 12:24:15 -04:00
bool bCsvTable = GetBoolArg ( "csvTable" ) ;
bool bCollateTable = GetBoolArg ( "collateTable" ) ;
2022-07-10 12:07:44 -04:00
bool bCollateTableOnly = GetBoolArg ( "collateTableOnly" ) ;
bCollateTable | = bCollateTableOnly ;
string collatedTableFilename = summaryTableFilename + ( bCollateTableOnly ? "" : "_Collated" ) ;
2020-06-23 18:40:00 -04:00
bool bSpreadsheetFriendlyStrings = GetBoolArg ( "spreadsheetFriendly" ) ;
2021-05-11 01:10:20 -04:00
string weightByColumnName = GetArg ( "weightByColumn" , null ) ;
2019-04-30 12:24:15 -04:00
if ( customSummaryTableFilter . Length > 0 )
{
string customSummaryTableRowSort = GetArg ( "customTableSort" ) ;
if ( customSummaryTableRowSort . Length = = 0 )
{
customSummaryTableRowSort = "buildversion,deviceprofile" ;
}
2022-07-10 12:07:44 -04:00
if ( ! bCollateTableOnly )
{
WriteSummaryTableReport ( outputDir , summaryTableFilename , summaryTable , customSummaryTableFilter . Split ( ',' ) . ToList ( ) , customSummaryTableRowSort . Split ( ',' ) . ToList ( ) , false , bCsvTable , bSpreadsheetFriendlyStrings , null , null ) ;
}
2019-04-30 12:24:15 -04:00
if ( bCollateTable )
{
2022-07-10 12:07:44 -04:00
WriteSummaryTableReport ( outputDir , collatedTableFilename , summaryTable , customSummaryTableFilter . Split ( ',' ) . ToList ( ) , customSummaryTableRowSort . Split ( ',' ) . ToList ( ) , true , bCsvTable , bSpreadsheetFriendlyStrings , null , weightByColumnName ) ;
2019-04-30 12:24:15 -04:00
}
}
else
{
string summaryTableName = GetArg ( "summaryTable" ) ;
if ( summaryTableName . Length = = 0 )
{
summaryTableName = "default" ;
}
SummaryTableInfo tableInfo = reportXML . GetSummaryTable ( summaryTableName ) ;
2022-07-10 12:07:44 -04:00
if ( ! bCollateTableOnly )
{
WriteSummaryTableReport ( outputDir , summaryTableFilename , summaryTable , tableInfo , false , bCsvTable , bSpreadsheetFriendlyStrings , null ) ;
}
2019-04-30 12:24:15 -04:00
if ( bCollateTable )
{
2022-07-10 12:07:44 -04:00
WriteSummaryTableReport ( outputDir , collatedTableFilename , summaryTable , tableInfo , true , bCsvTable , bSpreadsheetFriendlyStrings , weightByColumnName ) ;
2019-04-30 12:24:15 -04:00
}
}
2020-06-23 18:40:00 -04:00
// EmailTable is hardcoded to use the condensed type
2022-06-29 15:44:53 -04:00
string condensedSummaryTable = GetArg ( "condensedSummaryTable" , null ) ;
2020-06-23 18:40:00 -04:00
if ( GetBoolArg ( "emailSummary" ) | | GetBoolArg ( "emailTable" ) | | condensedSummaryTable ! = null )
2019-04-30 12:24:15 -04:00
{
2020-06-23 18:40:00 -04:00
SummaryTableInfo tableInfo = reportXML . GetSummaryTable ( condensedSummaryTable = = null ? "condensed" : condensedSummaryTable ) ;
2022-06-29 15:44:53 -04:00
WriteSummaryTableReport ( outputDir , summaryTableFilename + "_Email" , summaryTable , tableInfo , true , false , bSpreadsheetFriendlyStrings , weightByColumnName ) ;
2019-04-30 12:24:15 -04:00
}
2022-06-29 15:44:53 -04:00
perfLog . LogTiming ( "WriteSummaryTable" ) ;
}
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
if ( summaryTableCacheDir ! = null )
2021-03-18 15:20:03 -04:00
{
2021-04-26 14:25:00 -04:00
summaryTableCacheStats . LogStats ( ) ;
2021-03-18 15:20:03 -04:00
}
2021-04-08 14:32:07 -04:00
Console . WriteLine ( "Duplicate CSVs skipped: " + csvFileCache . duplicateCount ) ;
2022-06-29 15:44:53 -04:00
perfLog . LogTotalTiming ( ) ;
}
2019-04-30 12:24:15 -04:00
2021-06-22 10:36:56 -04:00
void WriteSummaryTableReport ( string outputDir , string filenameWithoutExtension , SummaryTable table , List < string > columnFilterList , List < string > rowSortList , bool bCollated , bool bToCSV , bool bSpreadsheetFriendlyStrings , List < SummarySectionBoundaryInfo > sectionBoundaries , string weightByColumnName )
2019-04-30 12:24:15 -04:00
{
2021-07-01 13:12:17 -04:00
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 ;
2021-05-11 01:10:20 -04:00
if ( GetBoolArg ( "noWeightedAvg" ) )
{
weightByColumnName = null ;
}
2022-06-15 19:53:12 -04:00
2022-07-25 13:50:18 -04:00
bool bTransposeFullSummaryTable = GetBoolArg ( "transposeTable" ) ;
bool bTransposeCollatedSummaryTable = bTransposeFullSummaryTable | GetBoolArg ( "transposeCollatedTable" ) ;
2022-06-15 19:53:12 -04:00
// 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 ;
}
2022-10-20 13:44:01 -04:00
// The colorize mode is initially set to whatever is in the summary table xml file.
// We then override that value if -colorizeTable is set to auto, off or budget.
// If -colorizeTable isn't specified then we use the default from the xml.
// If -colorizeTable isn't specified and it's not set in the summary table xml it uses the default set in the class initializer.
2022-07-26 17:00:58 -04:00
TableColorizeMode colorizeMode = tableInfo . tableColorizeMode ;
string colorizeArg = GetArg ( "colorizeTable" , "" ) . ToLower ( ) ;
if ( GetBoolArg ( "autoColorizeTable" ) ) // Legacy support for the -autoColorizeTable arg
2022-06-15 19:53:12 -04:00
{
2022-07-26 17:00:58 -04:00
colorizeMode = TableColorizeMode . Auto ;
}
if ( colorizeArg ! = "" )
{
if ( colorizeArg = = "auto" )
{
colorizeMode = TableColorizeMode . Auto ;
}
else if ( colorizeArg = = "off" )
{
colorizeMode = TableColorizeMode . Off ;
}
2022-10-20 13:44:01 -04:00
else if ( colorizeArg = = "budget" )
{
colorizeMode = TableColorizeMode . Budget ;
}
2022-06-15 19:53:12 -04:00
}
2022-07-25 13:50:18 -04:00
bool addMinMaxColumns = ! GetBoolArg ( "noSummaryMinMax" ) & & ! bTransposeCollatedSummaryTable ;
2022-06-15 19:53:12 -04:00
2020-06-23 18:40:00 -04:00
if ( ! string . IsNullOrEmpty ( outputDir ) )
2022-06-29 15:44:53 -04:00
{
filenameWithoutExtension = Path . Combine ( outputDir , filenameWithoutExtension ) ;
}
2022-07-17 23:54:33 -04:00
2022-11-01 16:30:42 -04:00
IEnumerable < ISummaryTableColumnFilter > additionalColumnFilters = MakeAdditionalColumnFilters ( tableInfo ) ;
bool showFilteredColumns = GetBoolArg ( "debugShowFilteredColumns" ) ;
// Set format info for the columns as some of the info is needed for the filters.
// TODO: would be better if we could determine HighIsBad without the format info and store it directly in the column.
table . SetColumnFormatInfo ( reportXML . columnFormatInfoList ) ;
SummaryTable filteredTable = table . SortAndFilter ( tableInfo . columnFilterList , tableInfo . rowSortList , bReverseTable , weightByColumnName , showFilteredColumns , additionalColumnFilters ) ;
2019-04-30 12:24:15 -04:00
if ( bCollated )
{
2021-07-01 13:12:17 -04:00
filteredTable = filteredTable . CollateSortedTable ( tableInfo . rowSortList , addMinMaxColumns ) ;
2019-04-30 12:24:15 -04:00
}
if ( bToCSV )
{
2022-06-29 15:44:53 -04:00
filteredTable . WriteToCSV ( filenameWithoutExtension + ".csv" ) ;
2019-04-30 12:24:15 -04:00
}
else
{
filteredTable . ApplyDisplayNameMapping ( statDisplaynameMapping ) ;
2021-04-08 14:32:07 -04:00
string VersionString = GetBoolArg ( "noWatermarks" ) ? "" : Version . Get ( ) ;
2022-04-08 11:08:05 -04:00
string summaryTitle = GetArg ( "summaryTitle" , null ) ;
2022-07-25 13:50:18 -04:00
if ( GetBoolArg ( "addDiffRows" ) )
{
filteredTable . AddDiffRows ( ) ;
}
2022-11-01 16:30:42 -04:00
// Run again to add format info for any new columns that were added (eg. count).
filteredTable . SetColumnFormatInfo ( reportXML . columnFormatInfoList ) ;
2022-07-10 14:14:02 -04:00
filteredTable . WriteToHTML (
filenameWithoutExtension + ".html" ,
VersionString ,
bSpreadsheetFriendlyStrings ,
tableInfo . sectionBoundaries ,
2022-07-26 17:00:58 -04:00
bScrollableTable ,
colorizeMode ,
2022-07-10 14:14:02 -04:00
addMinMaxColumns ,
tableInfo . hideStatPrefix ,
2022-07-13 16:25:11 -04:00
GetIntArg ( "maxSummaryTableStringLength" , Int32 . MaxValue ) ,
2022-07-10 14:14:02 -04:00
weightByColumnName ,
2022-07-25 13:50:18 -04:00
summaryTitle ,
2022-11-01 16:30:42 -04:00
bCollated ? bTransposeCollatedSummaryTable : bTransposeFullSummaryTable ,
showFilteredColumns
2022-07-25 13:50:18 -04:00
) ;
2019-04-30 12:24:15 -04:00
}
}
2022-11-01 16:30:42 -04:00
IEnumerable < ISummaryTableColumnFilter > MakeAdditionalColumnFilters ( SummaryTableInfo tableInfo )
{
List < ISummaryTableColumnFilter > additionalColumnFilters = new List < ISummaryTableColumnFilter > ( ) ;
additionalColumnFilters . Add ( new StatThresholdColumnFilter ( GetFloatArg ( "summaryTableStatThreshold" , tableInfo . statThreshold ) ) ) ;
if ( GetBoolArg ( "hideMetadataColumns" ) )
{
additionalColumnFilters . Add ( new MetadataColumnFilter ( tableInfo . rowSortList ) ) ;
}
if ( GetBoolArg ( "onlyShowRegressedColumns" ) )
{
string joinByStatName = GetArg ( "regressionJoinRowsByName" ) ;
float stdDevThreshold = GetFloatArg ( "regressionStdDevThreshold" , 2.0f ) ;
float outlierStdDevThreshold = GetFloatArg ( "regressionOutlierStdDevThreshold" , 4.0f ) ;
additionalColumnFilters . Add ( new RegressionColumnFilter ( joinByStatName , stdDevThreshold , outlierStdDevThreshold ) ) ;
}
return additionalColumnFilters ;
}
2022-06-29 15:44:53 -04:00
string ReplaceFileExtension ( string path , string newExtension )
{
2020-06-23 18:40:00 -04:00
// Special case for .bin.csv
if ( path . ToLower ( ) . EndsWith ( ".csv.bin" ) )
{
2022-06-29 15:44:53 -04:00
return path . Substring ( 0 , path . Length - 8 ) + newExtension ;
2020-06-23 18:40:00 -04:00
}
2022-06-29 15:44:53 -04:00
int lastDotIndex = path . LastIndexOf ( '.' ) ;
if ( path . EndsWith ( "\"" ) )
{
newExtension = newExtension + "\"" ;
if ( lastDotIndex = = - 1 )
{
lastDotIndex = path . Length - 1 ;
}
}
else if ( lastDotIndex = = - 1 )
{
lastDotIndex = path . Length ;
}
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
return path . Substring ( 0 , lastDotIndex ) + newExtension ;
}
2019-04-30 12:24:15 -04:00
static Dictionary < string , bool > UniqueHTMLFilemameLookup = new Dictionary < string , bool > ( ) ;
2021-04-08 14:32:07 -04:00
void WriteCleanCsv ( CachedCsvFile csvFile , string outCsvFilename , ReportTypeInfo reportTypeInfo )
{
2022-06-29 15:44:53 -04:00
if ( File . Exists ( outCsvFilename ) )
2021-04-08 14:32:07 -04:00
{
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 ) ;
2021-05-25 20:21:55 -04:00
// Check if we're stripping stats
bool bStripStatsByEvents = reportTypeInfo . bStripEvents ;
if ( GetBoolArg ( "noStripEvents" ) )
{
bStripStatsByEvents = false ;
}
2021-04-08 14:32:07 -04:00
int numFramesStripped ;
2022-05-22 16:34:09 -04:00
CsvStats csvStatsUnstripped ;
CsvStats csvStats = ProcessCsv ( csvFile , out numFramesStripped , out csvStatsUnstripped , minX , maxX , null , bStripStatsByEvents ) ;
2021-04-08 14:32:07 -04:00
csvStats . WriteToCSV ( outCsvFilename , false ) ;
}
2022-06-29 15:44:53 -04:00
CsvStats ProcessCsv ( CachedCsvFile csvFile , out int numFramesStripped , out CsvStats csvStatsUnstripped , int minX = 0 , int maxX = Int32 . MaxValue , PerfLog perfLog = null , bool bStripStatsByEvents = true )
2021-04-08 14:32:07 -04:00
{
numFramesStripped = 0 ;
CsvStats csvStats = ReadCsvStats ( csvFile , minX , maxX ) ;
2022-05-22 16:34:09 -04:00
csvStatsUnstripped = csvStats ;
2021-04-08 14:32:07 -04:00
if ( perfLog ! = null )
{
perfLog . LogTiming ( " ReadCsvStats" ) ;
}
2021-05-25 20:21:55 -04:00
if ( bStripStatsByEvents )
2021-04-08 14:32:07 -04:00
{
2022-05-22 16:34:09 -04:00
CsvStats strippedCsvStats = StripCsvStatsByEvents ( csvStatsUnstripped , out numFramesStripped ) ;
2021-04-08 14:32:07 -04:00
csvStats = strippedCsvStats ;
}
if ( perfLog ! = null )
{
perfLog . LogTiming ( " FilterStats" ) ;
}
return csvStats ;
}
2021-05-05 11:51:38 -04:00
void GenerateReport ( CachedCsvFile csvFile , string outputDir , bool bBulkMode , SummaryTableRowData rowData , bool bBatchedGraphs , bool writeDetailedReport , bool bReadAllStats , ReportTypeInfo reportTypeInfo , string csvDir )
2022-06-29 15:44:53 -04:00
{
PerfLog perfLog = new PerfLog ( GetBoolArg ( "perfLog" ) ) ;
string shortName = ReplaceFileExtension ( MakeShortFilename ( csvFile . filename ) , "" ) ;
string title = GetArg ( "title" , false ) ;
if ( title . Length = = 0 )
{
title = shortName ;
}
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
char c = title [ 0 ] ;
c = char . ToUpper ( c ) ;
title = c + title . Substring ( 1 ) ;
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
int minX = GetIntArg ( "minx" , 0 ) ;
int maxX = GetIntArg ( "maxx" , Int32 . MaxValue ) ;
2019-04-30 12:24:15 -04:00
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" ;
2022-06-29 15:44:53 -04:00
}
2019-04-30 12:24:15 -04:00
2022-04-19 00:19:31 -04:00
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 ;
2019-11-20 07:46:12 -05:00
List < string > csvToSvgCommandlines = new List < string > ( ) ;
List < string > svgFilenames = new List < string > ( ) ;
string responseFilename = null ;
List < Process > csvToSvgProcesses = new List < Process > ( ) ;
2022-04-19 00:19:31 -04:00
List < Task > csvToSvgTasks = new List < Task > ( ) ;
2022-06-29 15:44:53 -04:00
if ( writeDetailedReport )
{
2022-04-19 00:19:31 -04:00
GraphGenerator graphGenerator = null ;
if ( ! bCsvToSvgProcesses )
{
graphGenerator = new GraphGenerator ( csvFile . GetFinalCsv ( ) , csvFile . filename ) ;
}
2019-11-20 07:46:12 -05:00
// Generate all the graphs asyncronously
2022-04-19 00:19:31 -04:00
foreach ( ReportGraph graph in reportTypeInfo . graphs )
{
2019-11-20 07:46:12 -05:00
string svgFilename = String . Empty ;
if ( graph . isExternal & & ! GetBoolArg ( "externalGraphs" ) )
{
svgFilenames . Add ( svgFilename ) ;
continue ;
}
bool bFoundStat = false ;
2021-04-29 19:32:06 -04:00
foreach ( string statString in graph . settings . statString . value . Split ( ',' ) )
2022-04-19 00:19:31 -04:00
{
List < StatSamples > matchingStats = csvFile . dummyCsvStats . GetStatsMatchingString ( statString ) ;
if ( matchingStats . Count > 0 )
{
bFoundStat = true ;
break ;
}
}
2019-11-20 07:46:12 -05:00
if ( bFoundStat )
2019-04-30 12:24:15 -04:00
{
2019-11-20 07:46:12 -05:00
svgFilename = GetTempFilename ( csvFile . filename ) + ".svg" ;
2022-04-19 00:19:31 -04:00
if ( graphGenerator ! = null )
2019-11-20 07:46:12 -05:00
{
2022-06-29 15:44:53 -04:00
GraphParams graphParams = GetCsvToSvgGraphParams ( csvFile . filename , graph , thickness , minX , maxX , false , svgFilenames . Count ) ;
2022-04-19 00:19:31 -04:00
if ( bCsvToSvgMultiThreaded )
{
2022-06-29 15:44:53 -04:00
csvToSvgTasks . Add ( graphGenerator . MakeGraphAsync ( graphParams , svgFilename , true , false ) ) ;
2022-04-19 00:19:31 -04:00
}
else
{
graphGenerator . MakeGraph ( graphParams , svgFilename , true , false ) ;
2022-06-29 15:44:53 -04:00
}
2019-11-20 07:46:12 -05:00
}
else
{
2022-04-19 00:19:31 -04:00
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 ) ;
}
2019-11-20 07:46:12 -05:00
}
2019-04-30 12:24:15 -04:00
}
svgFilenames . Add ( svgFilename ) ;
2022-04-19 00:19:31 -04:00
}
2019-11-20 07:46:12 -05:00
2022-04-19 00:19:31 -04:00
if ( bCsvToSvgProcesses & & bBatchedGraphs )
2019-11-20 07:46:12 -05:00
{
// Save the response file
responseFilename = GetTempFilename ( csvFile . filename ) + "_response.txt" ;
System . IO . File . WriteAllLines ( responseFilename , csvToSvgCommandlines ) ;
2022-06-29 15:44:53 -04:00
Process csvToSvgProcess = LaunchCsvToSvgAsync ( "-batchCommands \"" + responseFilename + "\" -mt " + GetIntArg ( "graphThreads" , Environment . ProcessorCount / 2 ) . ToString ( ) ) ;
2019-11-20 07:46:12 -05:00
csvToSvgProcesses . Add ( csvToSvgProcess ) ;
}
}
2022-06-29 15:44:53 -04:00
perfLog . LogTiming ( " Initial Processing" ) ;
2019-04-30 12:24:15 -04:00
2021-05-25 20:21:55 -04:00
// Check if we're stripping stats
bool bStripStatsByEvents = reportTypeInfo . bStripEvents ;
if ( GetBoolArg ( "noStripEvents" ) )
{
bStripStatsByEvents = false ;
}
2022-04-19 00:19:31 -04:00
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)
2021-04-08 14:32:07 -04:00
int numFramesStripped ;
2022-05-22 16:34:09 -04:00
CsvStats csvStatsUnstripped ;
2022-06-29 15:44:53 -04:00
CsvStats csvStats = ProcessCsv ( csvFile , out numFramesStripped , out csvStatsUnstripped , minX , maxX , perfLog , bStripStatsByEvents ) ;
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
if ( writeDetailedReport & & csvToSvgProcesses . Count > 0 )
{
// wait on the graph processes to complete
foreach ( Process process in csvToSvgProcesses )
2019-04-30 12:24:15 -04:00
{
2019-11-20 07:46:12 -05:00
process . WaitForExit ( ) ;
2019-04-30 12:24:15 -04:00
}
2022-06-29 15:44:53 -04:00
perfLog . LogTiming ( " WaitForAsyncGraphs" ) ;
}
2019-04-30 12:24:15 -04:00
2021-03-18 15:20:03 -04:00
// Generate CSV metadata
2021-04-26 14:25:00 -04:00
if ( rowData ! = null )
2019-04-30 12:24:15 -04:00
{
2022-06-29 15:44:53 -04:00
Uri currentDirUri = new Uri ( Directory . GetCurrentDirectory ( ) + "/" , UriKind . Absolute ) ;
if ( outputDir . Length > 0 & & ! outputDir . EndsWith ( "/" ) )
{
outputDir + = "/" ;
}
Uri optionalDirUri = new Uri ( outputDir , UriKind . RelativeOrAbsolute ) ;
2021-05-01 15:23:22 -04:00
// Make a Csv URI that's relative to the report directory
2022-06-29 15:44:53 -04:00
Uri finalDirUri ;
if ( optionalDirUri . IsAbsoluteUri )
{
finalDirUri = optionalDirUri ;
}
else
{
finalDirUri = new Uri ( currentDirUri , outputDir ) ;
}
Uri csvFileUri = new Uri ( csvFile . filename , UriKind . Absolute ) ;
2021-05-01 15:23:22 -04:00
Uri relativeCsvUri = finalDirUri . MakeRelativeUri ( csvFileUri ) ;
string csvPath = relativeCsvUri . ToString ( ) ;
2019-04-30 12:24:15 -04:00
2022-04-08 11:08:05 -04:00
bool bLinkTemplates = GetBoolArg ( "linkTemplates" ) ;
string csvId = null ;
if ( csvStats . metaData ! = null )
{
2022-06-29 15:44:53 -04:00
csvId = csvStats . metaData . GetValue ( "csvid" , null ) ;
2022-04-08 11:08:05 -04:00
}
2021-05-05 11:51:38 -04:00
// re-root the CSV path if requested
2021-05-01 15:23:22 -04:00
string csvLinkRootPath = GetArg ( "csvLinkRootPath" , null ) ;
2022-06-29 15:44:53 -04:00
if ( csvDir ! = null & & csvLinkRootPath ! = null )
2021-05-01 15:23:22 -04:00
{
2021-05-05 11:51:38 -04:00
string csvDirFinal = csvDir . Replace ( "\\" , "/" ) ;
csvDirFinal + = csvDirFinal . EndsWith ( "/" ) ? "" : "/" ;
Uri csvDirUri = new Uri ( csvDirFinal , UriKind . Absolute ) ;
Uri csvRelativeToCsvDirUri = csvDirUri . MakeRelativeUri ( csvFileUri ) ;
2022-06-29 15:44:53 -04:00
csvPath = Path . Combine ( csvLinkRootPath , csvRelativeToCsvDirUri . ToString ( ) ) ;
2021-05-05 11:51:38 -04:00
csvPath = new Uri ( csvPath , UriKind . Absolute ) . ToString ( ) ;
2021-05-01 15:23:22 -04:00
}
2021-05-05 11:51:38 -04:00
2022-04-08 11:08:05 -04:00
string csvLink = "<a href='" + csvPath + "'>" + shortName + ".csv" + "</a>" ;
if ( bLinkTemplates )
{
2022-06-29 15:44:53 -04:00
if ( ! csvPath . StartsWith ( "http://" ) & & ! csvPath . StartsWith ( "https://" ) )
2022-04-20 14:43:25 -04:00
{
csvLink = "{LinkTemplate:Csv:" + ( csvId ? ? "0" ) + "}" ;
}
2022-04-08 11:08:05 -04:00
}
rowData . Add ( SummaryTableElement . Type . ToolMetadata , "Csv File" , csvLink , null , csvPath ) ;
2021-04-26 14:27:22 -04:00
rowData . Add ( SummaryTableElement . Type . ToolMetadata , "ReportType" , reportTypeInfo . name ) ;
rowData . Add ( SummaryTableElement . Type . ToolMetadata , "ReportTypeID" , reportTypeInfo . summaryTableCacheID ) ;
2019-04-30 12:24:15 -04:00
if ( htmlFilename ! = null )
{
2021-05-01 15:23:22 -04:00
string htmlUrl = htmlFilename ;
string reportLinkRootPath = GetArg ( "reportLinkRootPath" , null ) ;
if ( reportLinkRootPath ! = null )
{
htmlUrl = reportLinkRootPath + htmlFilename ;
}
2022-04-08 11:08:05 -04:00
string reportLink = "<a href='" + htmlUrl + "'>Link</a>" ;
if ( bLinkTemplates )
{
2022-04-20 14:43:25 -04:00
if ( ! htmlUrl . StartsWith ( "http://" ) & & ! htmlUrl . StartsWith ( "https://" ) )
{
reportLink = "{LinkTemplate:Report:" + ( csvId ? ? "0" ) + "}" ;
}
2022-06-29 15:44:53 -04:00
}
2022-04-08 11:08:05 -04:00
rowData . Add ( SummaryTableElement . Type . ToolMetadata , "Report" , reportLink ) ;
2019-04-30 12:24:15 -04:00
}
// Pass through all the metadata from the CSV
if ( csvStats . metaData ! = null )
{
foreach ( KeyValuePair < string , string > pair in csvStats . metaData . Values . ToList ( ) )
{
2021-04-26 14:25:00 -04:00
rowData . Add ( SummaryTableElement . Type . CsvMetadata , pair . Key . ToLower ( ) , pair . Value ) ;
2019-04-30 12:24:15 -04:00
}
}
2021-03-18 15:20:03 -04:00
if ( bReadAllStats )
2019-06-22 04:33:09 -04:00
{
2021-03-18 15:20:03 -04:00
// Add every stat avg value to the metadata
2022-06-29 15:44:53 -04:00
foreach ( StatSamples stat in csvStats . Stats . Values )
2019-06-22 04:33:09 -04:00
{
2021-05-25 20:21:55 -04:00
rowData . Add ( SummaryTableElement . Type . CsvStatAverage , stat . Name , ( double ) stat . average ) ;
2019-06-22 04:33:09 -04:00
}
}
}
if ( htmlFilename ! = null & & ! string . IsNullOrEmpty ( outputDir ) )
2022-06-29 15:44:53 -04:00
{
htmlFilename = Path . Combine ( outputDir , htmlFilename ) ;
}
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
// Write the report
WriteReport ( htmlFilename , title , svgFilenames , reportTypeInfo , csvStats , csvStatsUnstripped , numFramesStripped , minX , maxX , bBulkMode , rowData ) ;
perfLog . LogTiming ( " WriteReport" ) ;
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
// Delete the temp files
foreach ( string svgFilename in svgFilenames )
{
if ( svgFilename ! = String . Empty & & File . Exists ( svgFilename ) )
2019-04-30 12:24:15 -04:00
{
File . Delete ( svgFilename ) ;
}
2019-11-20 07:46:12 -05:00
}
if ( responseFilename ! = null & & File . Exists ( responseFilename ) )
{
File . Delete ( responseFilename ) ;
}
}
2019-11-17 21:03:32 -05:00
CsvStats ReadCsvStats ( CachedCsvFile csvFile , int minX , int maxX )
{
2020-06-23 18:40:00 -04:00
CsvStats csvStats = csvFile . GetFinalCsv ( ) ;
2019-05-21 10:50:29 -04:00
reportXML . ApplyDerivedMetadata ( csvStats . metaData ) ;
2019-04-30 12:24:15 -04:00
2019-11-17 21:03:32 -05:00
if ( csvStats . metaData = = null )
2019-07-31 13:44:37 -04:00
{
csvStats . metaData = new CsvMetadata ( ) ;
}
2019-07-17 11:06:38 -04:00
csvStats . metaData . Values . Add ( "csvfilename" , csvFile . filename ) ;
2019-08-13 14:21:55 -04:00
// 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 ( ) ;
2019-11-17 21:03:32 -05:00
if ( endEventStr ! = "" )
{
2019-08-13 14:21:55 -04:00
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 ;
}
}
}
2019-11-17 21:03:32 -05:00
// Strip out all stats with a zero total
List < StatSamples > allStats = new List < StatSamples > ( ) ;
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 ) ;
}
}
2019-05-21 10:50:29 -04:00
// Crop the stats to the range
csvStats . CropStats ( minX , maxX ) ;
2019-11-17 21:03:32 -05:00
return csvStats ;
}
2019-04-30 12:24:15 -04:00
2019-11-17 21:03:32 -05:00
CsvStats StripCsvStatsByEvents ( CsvStats csvStats , out int numFramesStripped )
{
numFramesStripped = 0 ;
2022-06-29 15:44:53 -04:00
List < CsvEventStripInfo > eventsToStrip = reportXML . GetCsvEventsToStrip ( ) ;
2021-03-18 15:20:03 -04:00
// We want to run the mask apply in parallel if -nodetailedreports is specified. Otherwise leave cores free for graph generation
bool doParallelMaskApply = GetBoolArg ( "noDetailedReports" ) ;
2019-11-17 21:03:32 -05:00
CsvStats strippedStats = csvStats ;
2021-03-18 15:20:03 -04:00
if ( eventsToStrip ! = null )
2022-06-29 15:44:53 -04:00
{
2021-03-18 15:20:03 -04:00
BitArray sampleMask = null ;
2019-11-17 21:03:32 -05:00
foreach ( CsvEventStripInfo eventStripInfo in eventsToStrip )
2021-03-18 15:20:03 -04:00
{
csvStats . ComputeEventStripSampleMask ( eventStripInfo . beginName , eventStripInfo . endName , ref sampleMask ) ;
2019-11-17 21:03:32 -05:00
}
2021-03-18 15:20:03 -04:00
if ( sampleMask ! = null )
{
numFramesStripped = sampleMask . Cast < bool > ( ) . Count ( l = > ! l ) ;
strippedStats = csvStats . ApplySampleMask ( sampleMask , doParallelMaskApply ) ;
}
}
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
if ( numFramesStripped > 0 )
{
Console . WriteLine ( "CSV frames excluded : " + numFramesStripped ) ;
}
return strippedStats ;
}
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
void WriteReport ( string htmlFilename , string title , List < string > 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 ;
2019-04-30 12:24:15 -04:00
if ( htmlFilename ! = null )
{
htmlFile = new System . IO . StreamWriter ( htmlFilename ) ;
htmlFile . WriteLine ( "<html>" ) ;
htmlFile . WriteLine ( " <head>" ) ;
htmlFile . WriteLine ( " <meta http-equiv='X-UA-Compatible' content='IE=edge'/>" ) ;
2022-06-29 15:44:53 -04:00
if ( GetBoolArg ( "noWatermarks" ) )
2019-11-20 07:46:12 -05:00
{
2022-06-29 15:44:53 -04:00
htmlFile . WriteLine ( " <![CDATA[ \nCreated with PerfReportTool" ) ;
2019-11-20 07:46:12 -05:00
}
else
{
htmlFile . WriteLine ( " <![CDATA[ \nCreated with PerfReportTool " + Version . Get ( ) + " with commandline:" ) ;
htmlFile . WriteLine ( commandLine . GetCommandLine ( ) ) ;
}
2019-04-30 12:24:15 -04:00
htmlFile . WriteLine ( " ]]>" ) ;
htmlFile . WriteLine ( " <title>" + titleStr + "</title>" ) ;
2021-03-18 15:20:03 -04:00
htmlFile . WriteLine ( " <style type='text/css'>" ) ;
htmlFile . WriteLine ( " table, th, td { border: 2px solid black; border-collapse: collapse; padding: 3px; vertical-align: top; font-family: 'Verdana', Times, serif; font-size: 12px;}" ) ;
htmlFile . WriteLine ( " p { font-family: 'Verdana', Times, serif; font-size: 12px }" ) ;
htmlFile . WriteLine ( " h1 { font-family: 'Verdana', Times, serif; font-size: 20px; padding-top:10px }" ) ;
htmlFile . WriteLine ( " h2 { font-family: 'Verdana', Times, serif; font-size: 18px; padding-top:20px }" ) ;
htmlFile . WriteLine ( " h3 { font-family: 'Verdana', Times, serif; font-size: 16px; padding-top:20px }" ) ;
htmlFile . WriteLine ( " </style>" ) ;
2019-04-30 12:24:15 -04:00
htmlFile . WriteLine ( " </head>" ) ;
htmlFile . WriteLine ( " <body><font face='verdana'>" ) ;
htmlFile . WriteLine ( " <h1>" + titleStr + "</h1>" ) ;
// show the range
if ( minX > 0 | | maxX < Int32 . MaxValue )
{
htmlFile . WriteLine ( "<br><br><font size='1.5'>(CSV cropped to range " + minX + "-" ) ;
if ( maxX < Int32 . MaxValue )
{
htmlFile . WriteLine ( maxX ) ;
}
htmlFile . WriteLine ( ")</font>" ) ;
}
htmlFile . WriteLine ( " <h2>Summary</h2>" ) ;
2021-03-18 15:20:03 -04:00
htmlFile . WriteLine ( "<table style='width:800'>" ) ;
2019-07-17 11:06:38 -04:00
2022-06-29 15:44:53 -04:00
if ( reportTypeInfo . metadataToShowList ! = null )
2019-07-17 11:06:38 -04:00
{
2022-06-29 15:44:53 -04:00
Dictionary < string , string > 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 ( "<tr><td bgcolor='#F0F0F0'>" + friendlyName + "</td><td><b>" + value + "</b></td></tr>" ) ;
}
}
2019-07-17 11:06:38 -04:00
}
2021-03-18 15:20:03 -04:00
htmlFile . WriteLine ( "<tr><td bgcolor='#F0F0F0'>Frame count</td><td>" + csvStats . SampleCount + " (" + numFramesStripped + " excluded)</td></tr>" ) ;
2019-07-17 11:06:38 -04:00
htmlFile . WriteLine ( "</table>" ) ;
}
2021-04-26 14:25:00 -04:00
if ( summaryRowData ! = null )
2022-06-29 15:44:53 -04:00
{
summaryRowData . Add ( SummaryTableElement . Type . ToolMetadata , "framecount" , csvStats . SampleCount . ToString ( ) ) ;
if ( numFramesStripped > 0 )
{
summaryRowData . Add ( SummaryTableElement . Type . ToolMetadata , "framecountExcluded" , numFramesStripped . ToString ( ) ) ;
}
2021-03-18 15:20:03 -04:00
}
2019-04-30 12:24:15 -04:00
2021-05-25 20:21:55 -04:00
bool bWriteSummaryCsv = GetBoolArg ( "writeSummaryCsv" ) & & ! bBulkMode ;
2019-04-30 12:24:15 -04:00
2021-05-13 13:54:13 -04:00
List < Summary > summaries = new List < Summary > ( reportTypeInfo . summaries ) ;
bool bExtraLinksSummary = GetBoolArg ( "extraLinksSummary" ) ;
if ( bExtraLinksSummary )
{
2022-04-08 11:08:05 -04:00
bool bLinkTemplates = GetBoolArg ( "linkTemplates" ) ;
2022-06-29 15:44:53 -04:00
summaries . Insert ( 0 , new ExtraLinksSummary ( null , null , bLinkTemplates ) ) ;
2021-05-13 13:54:13 -04:00
}
// If the reporttype has summary info, then write out the summary]
PeakSummary peakSummary = null ;
2022-06-29 15:44:53 -04:00
foreach ( Summary summary in summaries )
{
summary . WriteSummaryData ( htmlFile , summary . useUnstrippedCsvStats ? csvStatsUnstripped : csvStats , csvStatsUnstripped , bWriteSummaryCsv , summaryRowData , htmlFilename ) ;
if ( summary . GetType ( ) = = typeof ( PeakSummary ) )
{
peakSummary = ( PeakSummary ) summary ;
}
}
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
if ( htmlFile ! = null )
2019-04-30 12:24:15 -04:00
{
// Output the list of graphs
2021-03-18 15:20:03 -04:00
htmlFile . WriteLine ( "<h2>Graphs</h2>" ) ;
2019-04-30 12:24:15 -04:00
2021-05-25 20:21:55 -04:00
// TODO: support sections for graphs
List < string > sections = new List < string > ( ) ;
2019-04-30 12:24:15 -04:00
2021-05-25 20:21:55 -04:00
//// We have to at least have the empty string in this array so that we can print the list of links.
2022-06-29 15:44:53 -04:00
if ( sections . Count ( ) = = 0 )
{
sections . Add ( "" ) ;
2021-05-25 20:21:55 -04:00
}
2019-04-30 12:24:15 -04:00
for ( int index = 0 ; index < sections . Count ; index + + )
{
htmlFile . WriteLine ( "<ul>" ) ;
string currentCategory = sections [ index ] ;
2019-11-17 11:23:12 -05:00
if ( currentCategory . Length > 0 )
{
htmlFile . WriteLine ( "<h4>" + currentCategory + " Graphs</h4>" ) ;
}
2019-04-30 12:24:15 -04:00
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 ( "<li><a href='#" + StripSpaces ( svgTitle ) + "'>" + svgTitle + "</a></li>" ) ;
}
}
htmlFile . WriteLine ( "</ul>" ) ;
}
// Output the Graphs
2022-06-29 15:44:53 -04:00
for ( int svgFileIndex = 0 ; svgFileIndex < svgFilenames . Count ; svgFileIndex + + )
2019-04-30 12:24:15 -04:00
{
string svgFilename = svgFilenames [ svgFileIndex ] ;
if ( String . IsNullOrEmpty ( svgFilename ) )
{
continue ;
}
ReportGraph graph = graphs [ svgFileIndex ] ;
string svgTitle = graph . title ;
2021-03-18 15:20:03 -04:00
htmlFile . WriteLine ( " <br><a name='" + StripSpaces ( svgTitle ) + "'></a> <h2>" + svgTitle + "</h2>" ) ;
2019-04-30 12:24:15 -04:00
if ( graph . isExternal )
{
string outFilename = htmlFilename . Replace ( ".html" , "_" + svgTitle . Replace ( " " , "_" ) + ".svg" ) ;
File . Copy ( svgFilename , outFilename , true ) ;
htmlFile . WriteLine ( "<a href='" + outFilename + "'>" + svgTitle + " (external)</a>" ) ;
}
else
{
string [ ] svgLines = ReadLinesFromFile ( svgFilename ) ;
foreach ( string line in svgLines )
{
string modLine = line . Replace ( "__MAKEUNIQUE__" , "U_" + svgFileIndex . ToString ( ) ) ;
htmlFile . WriteLine ( modLine ) ;
}
}
}
2021-04-08 14:32:07 -04:00
if ( GetBoolArg ( "noWatermarks" ) )
{
htmlFile . WriteLine ( "<p style='font-size:8'>Created with PerfReportTool</p>" ) ;
}
else
{
htmlFile . WriteLine ( "<p style='font-size:8'>Created with PerfReportTool " + Version . Get ( ) + "</p>" ) ;
}
2019-04-30 12:24:15 -04:00
htmlFile . WriteLine ( " </font>" ) ;
htmlFile . WriteLine ( " </body>" ) ;
htmlFile . WriteLine ( "</html>" ) ;
htmlFile . Close ( ) ;
string ForEmail = GetArg ( "foremail" , false ) ;
if ( ForEmail ! = "" )
{
2022-05-22 16:34:09 -04:00
WriteEmail ( htmlFilename , title , svgFilenames , reportTypeInfo , csvStats , csvStatsUnstripped , minX , maxX , bBulkMode ) ;
2019-04-30 12:24:15 -04:00
}
}
2022-06-29 15:44:53 -04:00
}
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
void WriteEmail ( string htmlFilename , string title , List < string > svgFilenames , ReportTypeInfo reportTypeInfo , CsvStats csvStats , CsvStats csvStatsUnstripped , int minX , int maxX , bool bBulkMode )
{
if ( htmlFilename = = null )
2019-04-30 12:24:15 -04:00
{
return ;
}
2022-06-29 15:44:53 -04:00
ReportGraph [ ] graphs = reportTypeInfo . graphs . ToArray ( ) ;
string titleStr = reportTypeInfo . title + " : " + title ;
System . IO . StreamWriter htmlFile ;
htmlFile = new System . IO . StreamWriter ( htmlFilename + "email" ) ;
htmlFile . WriteLine ( "<html>" ) ;
htmlFile . WriteLine ( " <head>" ) ;
htmlFile . WriteLine ( " <meta http-equiv='X-UA-Compatible' content='IE=edge'/>" ) ;
2021-04-08 14:32:07 -04:00
if ( GetBoolArg ( "noWatermarks" ) )
{
htmlFile . WriteLine ( " <![CDATA[ \nCreated with PerfReportTool" ) ;
}
else
{
htmlFile . WriteLine ( " <![CDATA[ \nCreated with PerfReportTool " + Version . Get ( ) + " with commandline:" ) ;
htmlFile . WriteLine ( commandLine . GetCommandLine ( ) ) ;
}
htmlFile . WriteLine ( " ]]>" ) ;
2022-06-29 15:44:53 -04:00
htmlFile . WriteLine ( " <title>" + titleStr + "</title>" ) ;
htmlFile . WriteLine ( " </head>" ) ;
htmlFile . WriteLine ( " <body><font face='verdana'>" ) ;
htmlFile . WriteLine ( " <h1>" + titleStr + "</h1>" ) ;
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
// show the range
if ( minX > 0 | | maxX < Int32 . MaxValue )
{
htmlFile . WriteLine ( "<br><br><font size='1.5'>(CSV cropped to range " + minX + "-" ) ;
if ( maxX < Int32 . MaxValue )
{
htmlFile . WriteLine ( maxX ) ;
}
htmlFile . WriteLine ( ")</font>" ) ;
}
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
htmlFile . WriteLine ( "<a href=\"[Report Link Here]\">Click here for Report w/ interactive SVGs.</a>" ) ;
htmlFile . WriteLine ( " <h2>Summary</h2>" ) ;
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
htmlFile . WriteLine ( "Overall Runtime: [Replace Me With Runtime]" ) ;
2019-04-30 12:24:15 -04:00
2021-05-25 20:21:55 -04:00
bool bWriteSummaryCsv = GetBoolArg ( "writeSummaryCsv" ) & & ! bBulkMode ;
2019-04-30 12:24:15 -04:00
// If the reporttype has summary info, then write out the summary]
PeakSummary peakSummary = null ;
2022-06-29 15:44:53 -04:00
foreach ( Summary summary in reportTypeInfo . summaries )
{
summary . WriteSummaryData ( htmlFile , csvStats , csvStatsUnstripped , bWriteSummaryCsv , null , htmlFilename ) ;
if ( summary . GetType ( ) = = typeof ( PeakSummary ) )
{
peakSummary = ( PeakSummary ) summary ;
}
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
}
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
htmlFile . WriteLine ( " </font></body>" ) ;
htmlFile . WriteLine ( "</html>" ) ;
htmlFile . Close ( ) ;
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
}
string StripSpaces ( string str )
{
return str . Replace ( " " , "" ) ;
}
2019-04-30 12:24:15 -04:00
2019-11-20 07:46:12 -05:00
string GetTempFilename ( string csvFilename )
{
string shortFileName = MakeShortFilename ( csvFilename ) . Replace ( " " , "_" ) ;
2022-06-29 15:44:53 -04:00
return Path . Combine ( Path . GetTempPath ( ) , shortFileName + "_" + Guid . NewGuid ( ) . ToString ( ) . Substring ( 26 ) ) ;
2019-11-20 07:46:12 -05:00
}
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 ;
2019-04-30 12:24:15 -04:00
2019-11-20 07:46:12 -05:00
GraphSettings graphSettings = graph . settings ;
2021-04-29 19:32:06 -04:00
string [ ] statStringTokens = graphSettings . statString . value . Split ( ',' ) ;
IEnumerable < string > quoteWrappedStatStrings = statStringTokens . Select ( token = > '"' + token + '"' ) ;
string statString = String . Join ( " " , quoteWrappedStatStrings ) ;
2019-11-20 07:46:12 -05:00
double thickness = graphSettings . thickness . value * thicknessMultiplier ;
float maxy = GetFloatArg ( "maxy" , ( float ) graphSettings . maxy . value ) ;
2021-08-03 11:56:47 -04:00
bool smooth = graphSettings . smooth . value & & ! GetBoolArg ( "nosmooth" ) ;
2019-11-20 07:46:12 -05:00
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 ;
2022-04-19 00:19:31 -04:00
string additionalArgs = "" ;
2019-11-20 07:46:12 -05:00
bool stacked = graphSettings . stacked . value ;
bool showAverages = graphSettings . showAverages . value ;
bool filterOutZeros = graphSettings . filterOutZeros . value ;
2019-11-17 11:23:12 -05:00
bool snapToPeaks = false ;
if ( graphSettings . snapToPeaks . isSet )
{
snapToPeaks = graphSettings . snapToPeaks . value ;
}
2019-04-30 12:24:15 -04:00
2019-11-17 15:25:55 -05:00
int lineDecimalPlaces = graphSettings . lineDecimalPlaces . isSet ? graphSettings . lineDecimalPlaces . value : 1 ;
2019-11-17 11:23:12 -05:00
int maxHierarchyDepth = graphSettings . maxHierarchyDepth . value ;
2019-11-20 07:46:12 -05:00
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 ( ) ;
}
}
2019-04-30 12:24:15 -04:00
2019-11-20 07:46:12 -05:00
string highlightEventRegions = "" ;
if ( ! GetBoolArg ( "noStripEvents" ) )
{
List < CsvEventStripInfo > eventsToStrip = reportXML . GetCsvEventsToStrip ( ) ;
2019-04-30 12:24:15 -04:00
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 ;
}
2019-11-20 07:46:12 -05:00
highlightEventRegions + = "\"" ;
2019-04-30 12:24:15 -04:00
}
2019-11-20 07:46:12 -05:00
}
2019-04-30 12:24:15 -04:00
OptionalDouble minFilterStatValueSetting = graph . minFilterStatValue . isSet ? graph . minFilterStatValue : graphSettings . minFilterStatValue ;
2019-11-20 07:46:12 -05:00
string args =
" -csvs \"" + csvFilename + "\"" +
" -title \"" + title + "\"" +
2022-04-19 00:19:31 -04:00
" -o \"" + svgFilename + "\"" +
2019-11-20 07:46:12 -05:00
" -stats " + statString +
" -width " + ( width * scaleby ) . ToString ( ) +
" -height " + ( height * scaleby ) . ToString ( ) +
2022-04-19 00:19:31 -04:00
OptionalHelper . GetDoubleSetting ( graph . budget , " -budget " ) +
2019-11-20 07:46:12 -05:00
" -maxy " + maxy . ToString ( ) +
" -uniqueID Graph_" + graphIndex . ToString ( ) +
" -lineDecimalPlaces " + lineDecimalPlaces . ToString ( ) +
2022-04-19 00:19:31 -04:00
" -nocommandlineEmbed " +
2019-04-30 12:24:15 -04:00
2019-11-20 07:46:12 -05:00
( ( statMultiplier ! = 1.0 ) ? " -statMultiplier " + statMultiplier . ToString ( "0.0000000000000000000000" ) : "" ) +
( hideEventNames ? " -hideeventNames 1" : "" ) +
( ( minx > 0 ) ? ( " -minx " + minx . ToString ( ) ) : "" ) +
( ( maxx ! = Int32 . MaxValue ) ? ( " -maxx " + maxx . ToString ( ) ) : "" ) +
2019-04-30 12:24:15 -04:00
OptionalHelper . GetDoubleSetting ( graphSettings . miny , " -miny " ) +
OptionalHelper . GetDoubleSetting ( graphSettings . threshold , " -threshold " ) +
OptionalHelper . GetDoubleSetting ( graphSettings . averageThreshold , " -averageThreshold " ) +
OptionalHelper . GetDoubleSetting ( minFilterStatValueSetting , " -minFilterStatValue " ) +
OptionalHelper . GetStringSetting ( graphSettings . minFilterStatName , " -minFilterStatName " ) +
2019-11-20 07:46:12 -05:00
( 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 : "" ) +
2019-04-30 12:24:15 -04:00
( graphSettings . legendAverageThreshold . isSet ? " -legendAverageThreshold " + graphSettings . legendAverageThreshold . value : "" ) +
( graphSettings . ignoreStats . isSet ? " -ignoreStats " + graphSettings . ignoreStats . value : "" ) +
2019-11-20 07:46:12 -05:00
" " + additionalArgs ;
return args ;
}
2019-04-30 12:24:15 -04:00
2022-04-19 00:19:31 -04:00
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 ;
2022-06-29 15:44:53 -04:00
graphParams . statNames . AddRange ( graphSettings . statString . value . Split ( ',' ) ) ;
2022-04-19 00:19:31 -04:00
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 ) ;
2022-06-29 15:44:53 -04:00
if ( graphSettings . stacked . isSet )
2022-04-19 00:19:31 -04:00
{
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 )
{
2022-06-29 15:44:53 -04:00
graphParams . hideStatPrefixes . AddRange ( graphSettings . hideStatPrefix . value . Split ( ' ' , ';' ) ) ;
2022-04-19 00:19:31 -04:00
}
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 < CsvEventStripInfo > eventsToStrip = reportXML . GetCsvEventsToStrip ( ) ;
if ( eventsToStrip ! = null )
{
for ( int i = 0 ; i < eventsToStrip . Count ; i + + )
{
2022-06-29 15:44:53 -04:00
graphParams . highlightEventRegions . Add ( ( eventsToStrip [ i ] . beginName = = null ) ? "" : eventsToStrip [ i ] . beginName ) ;
2022-04-19 00:19:31 -04:00
graphParams . highlightEventRegions . Add ( ( eventsToStrip [ i ] . endName = = null ) ? "{NULL}" : eventsToStrip [ i ] . endName ) ;
}
}
}
2022-06-29 15:44:53 -04:00
if ( graph . minFilterStatValue . isSet )
2022-04-19 00:19:31 -04:00
{
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 ( ) ;
2022-06-29 15:44:53 -04:00
if ( minx > 0 )
2022-04-19 00:19:31 -04:00
{
graphParams . minX = minx ;
}
if ( maxx ! = Int32 . MaxValue )
{
graphParams . maxX = maxx ;
}
2022-06-29 15:44:53 -04:00
if ( graphSettings . miny . isSet )
2022-04-19 00:19:31 -04:00
{
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 ;
}
2019-11-20 07:46:12 -05:00
Process LaunchCsvToSvgAsync ( string args )
{
string csvToolPath = GetBaseDirectory ( ) + "/CSVToSVG.exe" ;
2019-04-30 12:24:15 -04:00
string binary = csvToolPath ;
// run mono on non-Windows hosts
if ( Host ! = HostPlatform . Windows )
{
2019-08-01 16:53:35 -04:00
// note, on Mac mono will not be on path
binary = Host = = HostPlatform . Linux ? "mono" : "/Library/Frameworks/Mono.framework/Versions/Current/Commands/mono" ;
2019-04-30 12:24:15 -04:00
args = csvToolPath + " " + args ;
}
2022-06-29 15:44:53 -04:00
// Generate the SVGs, multithreaded
ProcessStartInfo startInfo = new ProcessStartInfo ( binary ) ;
startInfo . Arguments = args ;
startInfo . CreateNoWindow = true ;
startInfo . UseShellExecute = false ;
Process process = Process . Start ( startInfo ) ;
2019-11-20 07:46:12 -05:00
return process ;
2022-06-29 15:44:53 -04:00
}
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
int CountCSVs ( CsvStats csvStats )
{
// Count the CSVs
int csvCount = 0 ;
foreach ( CsvEvent ev in csvStats . Events )
{
string eventName = ev . Name ;
if ( eventName . Length > 0 )
{
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
if ( eventName . Contains ( "CSV:" ) & & eventName . ToLower ( ) . Contains ( ".csv" ) )
{
csvCount + + ;
}
}
}
if ( csvCount = = 0 )
{
csvCount = 1 ;
}
return csvCount ;
}
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
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 ;
}
}
2019-04-30 12:24:15 -04:00
2022-06-29 15:44:53 -04:00
return 0 ;
}
2020-06-23 18:40:00 -04:00
2021-03-18 15:20:03 -04:00
bool matchesPattern ( string str , string pattern )
{
2022-06-29 15:44:53 -04:00
string [ ] patternSections = pattern . ToLower ( ) . Split ( '*' ) ;
2021-03-18 15:20:03 -04:00
// Check the substrings appear in order
string remStr = str . ToLower ( ) ;
2022-06-29 15:44:53 -04:00
for ( int i = 0 ; i < patternSections . Length ; i + + )
2021-03-18 15:20:03 -04:00
{
int idx = remStr . IndexOf ( patternSections [ i ] ) ;
2022-06-29 15:44:53 -04:00
if ( idx = = - 1 )
2021-03-18 15:20:03 -04:00
{
return false ;
}
2022-06-29 15:44:53 -04:00
remStr = remStr . Substring ( idx + patternSections [ i ] . Length ) ;
2021-03-18 15:20:03 -04:00
}
2021-04-08 14:32:07 -04:00
return remStr . Length = = 0 ;
2021-03-18 15:20:03 -04:00
}
2022-06-29 15:44:53 -04:00
System . IO . FileInfo [ ] GetFilesWithSearchPattern ( string directory , string searchPatternStr , bool recurse , int maxFileAgeDays = - 1 )
2020-06-23 18:40:00 -04:00
{
List < System . IO . FileInfo > fileList = new List < FileInfo > ( ) ;
string [ ] searchPatterns = searchPatternStr . Split ( ';' ) ;
DirectoryInfo di = new DirectoryInfo ( directory ) ;
foreach ( string searchPattern in searchPatterns )
{
2021-03-18 15:20:03 -04:00
System . IO . FileInfo [ ] files = di . GetFiles ( "*.*" , recurse ? SearchOption . AllDirectories : SearchOption . TopDirectoryOnly ) ;
foreach ( FileInfo file in files )
{
2022-06-29 15:44:53 -04:00
if ( maxFileAgeDays > = 0 )
2022-01-08 13:13:03 -05:00
{
DateTime fileModifiedTime = file . LastWriteTimeUtc ;
DateTime currentTime = DateTime . UtcNow ;
TimeSpan elapsed = currentTime . Subtract ( fileModifiedTime ) ;
2022-06-29 15:44:53 -04:00
if ( elapsed . TotalHours > ( double ) maxFileAgeDays * 24.0 )
2022-01-08 13:13:03 -05:00
{
continue ;
}
}
2021-03-18 15:20:03 -04:00
if ( matchesPattern ( file . FullName , searchPattern ) )
{
fileList . Add ( file ) ;
}
}
2020-06-23 18:40:00 -04:00
}
return fileList . Distinct ( ) . ToArray ( ) ;
}
2022-06-29 15:44:53 -04:00
void ConvertJsonToPrcs ( string jsonFilename , string prcOutputDir )
{
Console . WriteLine ( "Converting " + jsonFilename + " to PRCs. Output folder: " + prcOutputDir ) ;
if ( ! Directory . Exists ( prcOutputDir ) )
{
Directory . CreateDirectory ( prcOutputDir ) ;
}
Console . WriteLine ( "Reading " + jsonFilename ) ;
string jsonText = File . ReadAllText ( jsonFilename ) ;
Console . WriteLine ( "Parsing json" ) ;
Dictionary < string , dynamic > jsonDict = JsonToDynamicDict ( jsonText ) ;
Console . WriteLine ( "Writing PRCs" ) ;
foreach ( string csvId in jsonDict . Keys )
{
Dictionary < string , dynamic > srcDict = jsonDict [ csvId ] ;
SummaryTableRowData rowData = new SummaryTableRowData ( srcDict ) ;
rowData . WriteToCache ( prcOutputDir , csvId ) ;
}
}
Dictionary < string , dynamic > JsonToDynamicDict ( string jsonStr )
{
JsonElement RootElement = JsonSerializer . Deserialize < JsonElement > ( ( string ) jsonStr , null ) ;
Dictionary < string , dynamic > RootElementValue = GetJsonValue ( RootElement ) ;
return RootElementValue ;
}
// .net Json support is poor, so we have to do stuff like this if we just want to read a json file to a dictionary
dynamic GetJsonValue ( JsonElement jsonElement )
{
string jsonStr = jsonElement . GetRawText ( ) ;
switch ( jsonElement . ValueKind )
{
case JsonValueKind . Number :
return jsonElement . GetDouble ( ) ;
case JsonValueKind . Null :
return null ;
case JsonValueKind . True :
return true ;
case JsonValueKind . False :
return false ;
case JsonValueKind . String :
return jsonElement . GetString ( ) ;
case JsonValueKind . Undefined :
return null ;
case JsonValueKind . Array :
List < dynamic > ArrayValue = new List < dynamic > ( ) ;
foreach ( JsonElement element in jsonElement . EnumerateArray ( ) )
{
ArrayValue . Add ( GetJsonValue ( element ) ) ;
}
return ArrayValue ;
case JsonValueKind . Object :
Dictionary < string , dynamic > DictValue = new Dictionary < string , dynamic > ( ) ;
foreach ( JsonProperty property in jsonElement . EnumerateObject ( ) )
{
DictValue [ property . Name ] = GetJsonValue ( property . Value ) ;
}
return DictValue ;
}
return null ;
}
}
2019-04-30 12:24:15 -04:00
static class Extensions
{
public static T GetSafeAttibute < T > ( this XElement element , string attributeName , T defaultValue = default ( T ) )
{
XAttribute attribute = element . Attribute ( attributeName ) ;
2021-07-22 08:07:48 -04:00
if ( attribute = = null )
2019-04-30 12:24:15 -04:00
{
2021-07-22 08:07:48 -04:00
return defaultValue ;
}
try
{
switch ( Type . GetTypeCode ( typeof ( T ) ) )
2019-04-30 12:24:15 -04:00
{
2021-07-22 08:07:48 -04:00
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 ) ) ;
2019-04-30 12:24:15 -04:00
}
}
2021-07-22 08:07:48 -04:00
catch ( FormatException e )
{
Console . WriteLine ( string . Format ( "[Warning] Failed to convert XML attribute '{0}' ({1})" , attributeName , e . Message ) ) ;
return defaultValue ;
}
2019-04-30 12:24:15 -04:00
}
} ;
}