2021-12-02 23:30:16 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
2021-12-02 23:29:52 -05:00
using System ;
using System.IO ;
using System.Collections.Generic ;
using System.Linq ;
using System.Text ;
using AutomationTool ;
using Ionic.Zip ;
using EpicGames.Core ;
using UnrealBuildBase ;
using SheetsHelper ;
2023-03-08 12:43:35 -05:00
using Microsoft.Extensions.Logging ;
2021-12-02 23:29:52 -05:00
[Help("MemreportToHelper is used to take a memreport file, extract the CSV sections and generate a GSheet from them (i.e:RunUAT.bat MemReportToGSheet -report=myFile.memreport -name=SheetName (optional)")]
public class MemreportHelper : BuildCommand
{
class MRCsvBlock
{
public int LineIndex ;
public string Title ;
public string SizeKey ;
public List < Dictionary < string , string > > SortedRows = new List < Dictionary < string , string > > ( ) ;
public StringBuilder sb = new StringBuilder ( ) ;
}
private string [ ] InputFileAllLines ;
private List < MRCsvBlock > CsvBlocks = new List < MRCsvBlock > ( ) ;
private bool ReadCsvListAndAdd ( string ListHeader , int LineOffset , string SizeKey , bool SkipCsvRequired = false )
{
bool FoundCsv = false ;
int StartIndex = InputFileAllLines . FindIndex ( s = > s . StartsWith ( ListHeader ) ) ;
if ( StartIndex ! = - 1 )
{
if ( InputFileAllLines [ StartIndex ] . Contains ( "-csv" ) | | SkipCsvRequired )
{
// hackily remove duped list (these come through as alphasort + resourcesizesort)
2021-12-02 23:57:52 -05:00
if ( InputFileAllLines [ StartIndex ] . Contains ( "-alphasort" ) = = false )
2021-12-02 23:29:52 -05:00
{
int HeaderIndex = StartIndex + LineOffset ;
int DataIndex = HeaderIndex + 1 ;
string [ ] HeaderValues = InputFileAllLines [ HeaderIndex ] . Split ( ',' ) ;
2021-12-02 23:57:52 -05:00
if ( HeaderValues . Length > 1 )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:57:52 -05:00
List < Dictionary < string , string > > Rows = new List < Dictionary < string , string > > ( ) ;
MRCsvBlock block = new MRCsvBlock ( ) ;
2021-12-02 23:29:52 -05:00
2021-12-02 23:57:52 -05:00
block . LineIndex = StartIndex ;
block . Title = InputFileAllLines [ StartIndex ] ;
block . SizeKey = SizeKey ;
// Parse the rest of the data
while ( DataIndex < InputFileAllLines . Length )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:57:52 -05:00
Dictionary < string , string > OneRow = new Dictionary < string , string > ( ) ;
string Line = InputFileAllLines [ DataIndex ] ;
DataIndex + + ;
string [ ] Values = Line . Split ( ',' ) ;
if ( Values . Length > = HeaderValues . Length )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:57:52 -05:00
// used to remove the starting blank column on obj lists
int startindex = SkipCsvRequired ? 0 : 1 ;
for ( int i = startindex ; i < HeaderValues . Length ; i + + )
{
OneRow [ HeaderValues [ i ] . Trim ( ) ] = Values [ i ] . Trim ( ) ;
}
Rows . Add ( OneRow ) ;
}
else
{
break ;
2021-12-02 23:29:52 -05:00
}
}
2021-12-02 23:57:52 -05:00
if ( Rows . Count > 0 )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:57:52 -05:00
block . SortedRows = Rows . OrderByDescending ( x = > float . Parse ( x [ SizeKey ] ) ) . ToList ( ) ;
2021-12-02 23:29:52 -05:00
}
2021-12-02 23:57:52 -05:00
CsvBlocks . Add ( block ) ;
2021-12-02 23:29:52 -05:00
}
}
// replace the line so subsequent calls do not find the same array
InputFileAllLines [ StartIndex ] = InputFileAllLines [ StartIndex ] . Replace ( ListHeader , "ListReplaced" ) ;
FoundCsv = true ;
}
}
return FoundCsv ;
}
public override ExitCode Execute ( )
{
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "************************ STARTING MemreportHelper ************************" ) ;
2021-12-02 23:29:52 -05:00
2021-12-02 23:34:38 -05:00
if ( Params . Length < 1 )
2021-12-02 23:29:52 -05:00
{
2023-03-08 12:43:35 -05:00
Logger . LogError ( "Invalid number of arguments {Arg0}" , Params . Length ) ;
2021-12-02 23:29:52 -05:00
return ExitCode . Error_Arguments ;
}
2021-12-02 23:34:38 -05:00
string MemreportFileorPath = ParseParamValue ( "report" , "" ) ;
string InOutputDir = ParseParamValue ( "outcsvdir" , "" ) ;
2021-12-02 23:29:52 -05:00
string MaxOutputString = ParseParamValue ( "maxoutputcount" , "" ) ;
string MinKBLim = ParseParamValue ( "minkblim" , "" ) ;
int MaxOutputCount = 75 ;
float MinOutputKBLimit = 1 * 1024 ;
2021-12-02 23:34:38 -05:00
if ( string . IsNullOrWhiteSpace ( MemreportFileorPath ) )
2021-12-02 23:29:52 -05:00
{
2023-03-08 12:43:35 -05:00
Logger . LogError ( "No memreport file or directory specified" ) ;
2021-12-02 23:29:52 -05:00
return ExitCode . Error_Arguments ;
}
2021-12-02 23:34:38 -05:00
2021-12-02 23:29:52 -05:00
if ( string . IsNullOrEmpty ( MaxOutputString ) = = false )
{
MaxOutputCount = int . Parse ( MaxOutputString ) ;
}
if ( string . IsNullOrEmpty ( MinKBLim ) = = false )
{
MinOutputKBLimit = int . Parse ( MinKBLim ) ;
}
float MeshMin = MinOutputKBLimit / 2 ;
float SkelMeshMin = MinOutputKBLimit / 2 ;
2021-12-02 23:34:38 -05:00
List < string > FilesToProcess = new List < string > ( ) ;
if ( Directory . Exists ( MemreportFileorPath ) )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
var FilesReturned = Directory . EnumerateFiles ( MemreportFileorPath , "*.memreport" ) ;
2021-12-02 23:29:52 -05:00
2021-12-02 23:34:38 -05:00
foreach ( string file in FilesReturned )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
FilesToProcess . Add ( file ) ;
}
}
else
{
if ( File . Exists ( MemreportFileorPath ) = = false )
{
2023-03-08 12:43:35 -05:00
Logger . LogError ( "{Text}" , "Memreport file not found: " + MemreportFileorPath ) ;
2021-12-02 23:34:38 -05:00
return ExitCode . Error_Arguments ;
2021-12-02 23:29:52 -05:00
}
2021-12-02 23:34:38 -05:00
FilesToProcess . Add ( MemreportFileorPath ) ;
}
2021-12-02 23:29:52 -05:00
2021-12-02 23:34:38 -05:00
foreach ( string MRFile in FilesToProcess )
{
string OutputDir = InOutputDir ;
if ( string . IsNullOrEmpty ( OutputDir ) )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
OutputDir = Path . Combine ( Path . GetDirectoryName ( MRFile ) , Path . GetFileName ( MRFile ) . Replace ( '.' , '_' ) ) ;
}
2021-12-02 23:29:52 -05:00
2021-12-02 23:34:38 -05:00
// Pull the file into a buffer of lines (this will be stomped by the process)
InputFileAllLines = File . ReadAllLines ( MRFile ) ;
CsvBlocks . Clear ( ) ;
2021-12-02 23:29:52 -05:00
2021-12-02 23:34:38 -05:00
bool CsvFound = ReadCsvListAndAdd ( "Obj List:" , 3 , "ResExcKB" ) ;
if ( CsvFound )
{
// look for other 'obj list' entries only if we found the main Obj List
while ( CsvFound )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
CsvFound = ReadCsvListAndAdd ( "Obj List:" , 3 , "ResExcKB" ) ;
}
// search for 'Listing NONVT textures.' and its ilk, these are ad-hoc and not explicitly set to -csv
// order here is important for latest badness filtering to ensure badness from "all" is unique from NONVT/UNCOMP list
ReadCsvListAndAdd ( "Listing NONVT textures" , 1 , "Current Size (KB)" , true ) ;
ReadCsvListAndAdd ( "Listing uncompressed textures." , 1 , "Current Size (KB)" , true ) ;
ReadCsvListAndAdd ( "Listing all textures." , 1 , "Current Size (KB)" , true ) ;
foreach ( var csvBlock in CsvBlocks )
{
csvBlock . sb . AppendLine ( csvBlock . Title ) ;
// write out headers
if ( csvBlock . Title . Contains ( "-csv" ) )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
foreach ( var key in csvBlock . SortedRows [ 0 ] . Keys )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
csvBlock . sb . Append ( key + "," ) ;
2021-12-02 23:29:52 -05:00
}
csvBlock . sb . Append ( Environment . NewLine ) ;
2021-12-02 23:34:38 -05:00
foreach ( var row in csvBlock . SortedRows )
{
foreach ( var ele in row . Values )
{
csvBlock . sb . Append ( ele + "," ) ;
}
csvBlock . sb . Append ( Environment . NewLine ) ;
}
csvBlock . sb . Append ( Environment . NewLine ) ;
csvBlock . sb . Append ( Environment . NewLine ) ;
2021-12-02 23:29:52 -05:00
}
2021-12-02 23:34:38 -05:00
else
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
// Max Height Max Size(KB) Bias Authored Current Width Current Height Current Size(KB) Format LODGroup Name Streaming UnknownRef VT Usage Count NumMips Uncompressed
// These are the explicit textures lists, slightly different formating
var headers = new [ ]
{
2021-12-02 23:29:52 -05:00
"Name" ,
"Format" ,
"Current Size (KB)" ,
"Current Width" ,
"Current Height" ,
"Max Size (KB)" ,
"Max Width" ,
"Max Height" ,
"LODGroup" ,
"Uncompressed" ,
"VT" ,
"Streaming" ,
"UnknownRef" ,
"Usage Count" ,
"NumMips" ,
} ;
2021-12-02 23:34:38 -05:00
csvBlock . sb . AppendLine ( string . Join ( "," , headers ) ) ;
2021-12-02 23:29:52 -05:00
2021-12-02 23:34:38 -05:00
foreach ( var row in csvBlock . SortedRows )
{
csvBlock . sb . AppendLine ( string . Join ( "," , headers . Select ( h = > row [ h ] ) ) ) ;
}
csvBlock . sb . AppendLine ( ) ;
csvBlock . sb . AppendLine ( ) ;
2021-12-02 23:29:52 -05:00
}
}
2021-12-02 23:34:38 -05:00
if ( ! DirectoryExists ( OutputDir ) )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
CreateDirectory ( OutputDir ) ;
}
string OutputRootFileName = Path . Combine ( OutputDir , Path . GetFileName ( MRFile ) ) ;
File . WriteAllText ( OutputRootFileName + ".csv" , "" ) ;
// output the WHOLE set to 1 file + each set to individual files
foreach ( var csvBlock in CsvBlocks )
{
File . AppendAllText ( OutputRootFileName + ".csv" , csvBlock . sb . ToString ( ) ) ;
string SanitizedFileName = OutputRootFileName + "." + csvBlock . Title . Replace ( "-csv" , "" ) . Replace ( ' ' , '_' ) . Replace ( '-' , '_' ) . Replace ( ':' , '_' ) ;
File . WriteAllText ( SanitizedFileName + ".csv" , csvBlock . sb . ToString ( ) ) ;
}
{
// Now output a 'badness' file, this is heavily tailored to workflows in frosty right now (9/20/21 andrew.firth)
List < string > PreOutput = new List < string > ( ) ;
StringBuilder sb = new StringBuilder ( ) ;
// Output "bad" textures
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
var TextureHeaders = new [ ]
{
2021-12-02 23:29:52 -05:00
"Name" ,
"Format" ,
"Current Size (KB)" ,
"Current Width" ,
"Current Height" ,
"Uncompressed" ,
"VT" ,
"Streaming" ,
} ;
2021-12-02 23:34:38 -05:00
sb . AppendLine ( "Type," + string . Join ( "," , TextureHeaders ) ) ;
2021-12-02 23:29:52 -05:00
2021-12-02 23:34:38 -05:00
foreach ( var csvBlock in CsvBlocks )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
// limit this to just "all textures"
//if (csvBlock.Title.Contains("Listing NONVT textures") || csvBlock.Title.Contains("Listing uncompressed textures") || csvBlock.Title.Contains("Listing all textures."))
if ( csvBlock . Title . Contains ( "Listing all textures." ) )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
sb . AppendLine ( csvBlock . Title ) ;
int OutputCount = 0 ;
// output top 10 enties, limited info
foreach ( var row in csvBlock . SortedRows )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
if ( PreOutput . Contains ( row [ "Name" ] ) = = false )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
sb . AppendLine ( "," + string . Join ( "," , TextureHeaders . Select ( h = > row [ h ] ) ) ) ;
2021-12-02 23:29:52 -05:00
2021-12-02 23:34:38 -05:00
PreOutput . Add ( row [ "Name" ] ) ;
// handle limiting the badness output based on user options
OutputCount + + ;
if ( OutputCount > MaxOutputCount )
{
break ;
}
if ( float . Parse ( row [ "Current Size (KB)" ] ) < MinOutputKBLimit )
{
break ;
}
2021-12-02 23:29:52 -05:00
}
}
2021-12-02 23:34:38 -05:00
sb . AppendLine ( "" ) ;
2021-12-02 23:29:52 -05:00
}
}
}
2021-12-02 23:34:38 -05:00
// Output "bad" meshes
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
var MeshHeaders = new [ ]
{
2021-12-02 23:29:52 -05:00
"Object" ,
"ResExcKB"
} ;
2021-12-02 23:34:38 -05:00
sb . AppendLine ( "Type," + string . Join ( "," , MeshHeaders ) ) ;
2021-12-02 23:29:52 -05:00
2021-12-02 23:34:38 -05:00
foreach ( var csvBlock in CsvBlocks )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
if ( csvBlock . Title . Contains ( "Obj List: class=StaticMesh -alphasort" ) )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
sb . AppendLine ( csvBlock . Title ) ;
int OutputCount = 0 ;
// output top 10 enties, limited info
foreach ( var row in csvBlock . SortedRows )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
if ( PreOutput . Contains ( row [ "Object" ] ) = = false )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
sb . AppendLine ( "," + string . Join ( "," , MeshHeaders . Select ( h = > row [ h ] ) ) ) ;
2021-12-02 23:29:52 -05:00
2021-12-02 23:34:38 -05:00
PreOutput . Add ( row [ "Object" ] ) ;
// handle limiting the badness output based on user options
OutputCount + + ;
if ( OutputCount > MaxOutputCount )
{
break ;
}
if ( float . Parse ( row [ "ResExcKB" ] ) < MeshMin )
{
break ;
}
2021-12-02 23:29:52 -05:00
}
}
2021-12-02 23:34:38 -05:00
sb . AppendLine ( "" ) ;
2021-12-02 23:29:52 -05:00
}
}
}
2021-12-02 23:34:38 -05:00
// Output "bad" skelmesh
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
var MeshHeaders = new [ ]
{
2021-12-02 23:29:52 -05:00
"Object" ,
"ResExcKB"
} ;
2021-12-02 23:34:38 -05:00
sb . AppendLine ( "Type," + string . Join ( "," , MeshHeaders ) ) ;
2021-12-02 23:29:52 -05:00
2021-12-02 23:34:38 -05:00
foreach ( var csvBlock in CsvBlocks )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
if ( csvBlock . Title . Contains ( "Obj List: class=SkeletalMesh" ) )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
sb . AppendLine ( csvBlock . Title ) ;
int OutputCount = 0 ;
// output top 10 enties, limited info
foreach ( var row in csvBlock . SortedRows )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
if ( PreOutput . Contains ( row [ "Object" ] ) = = false )
2021-12-02 23:29:52 -05:00
{
2021-12-02 23:34:38 -05:00
sb . AppendLine ( "," + string . Join ( "," , MeshHeaders . Select ( h = > row [ h ] ) ) ) ;
2021-12-02 23:29:52 -05:00
2021-12-02 23:34:38 -05:00
PreOutput . Add ( row [ "Object" ] ) ;
// handle limiting the badness output based on user options
OutputCount + + ;
if ( OutputCount > MaxOutputCount )
{
break ;
}
if ( float . Parse ( row [ "ResExcKB" ] ) < SkelMeshMin )
{
break ;
}
2021-12-02 23:29:52 -05:00
}
}
2021-12-02 23:34:38 -05:00
sb . AppendLine ( "" ) ;
2021-12-02 23:29:52 -05:00
}
}
}
2021-12-02 23:34:38 -05:00
File . WriteAllText ( OutputRootFileName + ".badness.csv" , sb . ToString ( ) ) ;
2021-12-02 23:29:52 -05:00
}
}
}
2023-03-08 12:43:35 -05:00
Logger . LogInformation ( "************************ MemreportHelper WORK COMPLETED ************************" ) ;
2021-12-02 23:29:52 -05:00
return ExitCode . Success ;
}
}