Skip to content

Commit 52c0635

Browse files
committed
support performance profiling information in console runner
1 parent b6bd0a3 commit 52c0635

File tree

3 files changed

+255
-14
lines changed

3 files changed

+255
-14
lines changed

tools/UglyToad.PdfPig.ConsoleRunner/Program.cs

Lines changed: 252 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,124 @@
11
using System;
2+
using System.Collections.Generic;
3+
using System.Diagnostics;
4+
using System.Diagnostics.CodeAnalysis;
5+
using System.Globalization;
26
using System.IO;
7+
using System.Linq;
38
using System.Text;
49
using Console = System.Console;
510

611
namespace UglyToad.PdfPig.ConsoleRunner
712
{
813
public static class Program
914
{
15+
private class OptionalArg
16+
{
17+
public required string ShortSymbol { get; init; }
18+
19+
public required string Symbol { get; init; }
20+
21+
public required bool SupportsValue { get; init; }
22+
23+
public string? Value { get; set; }
24+
}
25+
26+
private class ParsedArgs
27+
{
28+
public required IReadOnlyList<OptionalArg> SuppliedArgs { get; init; }
29+
30+
public required string SuppliedDirectoryPath { get; init; }
31+
}
32+
33+
private static IReadOnlyList<OptionalArg> GetSupportedArgs() =>
34+
[
35+
new OptionalArg
36+
{
37+
SupportsValue = false,
38+
ShortSymbol = "nr",
39+
Symbol = "no-recursion"
40+
},
41+
new OptionalArg
42+
{
43+
SupportsValue = true,
44+
ShortSymbol = "o",
45+
Symbol = "output"
46+
},
47+
new OptionalArg
48+
{
49+
SupportsValue = true,
50+
ShortSymbol = "l",
51+
Symbol = "limit"
52+
}
53+
];
54+
55+
private static bool TryParseArgs(
56+
string[] args,
57+
[NotNullWhen(true)] out ParsedArgs? parsed)
58+
{
59+
parsed = null;
60+
string? path = null;
61+
var suppliedOpts = new List<OptionalArg>();
62+
63+
var opts = GetSupportedArgs();
64+
65+
for (var i = 0; i < args.Length; i++)
66+
{
67+
var str = args[i];
68+
69+
var isOptFlag = str.StartsWith('-');
70+
71+
if (!isOptFlag)
72+
{
73+
if (path == null)
74+
{
75+
path = str;
76+
}
77+
else
78+
{
79+
return false;
80+
}
81+
}
82+
else
83+
{
84+
var item = opts.SingleOrDefault(x =>
85+
string.Equals("-" + x.ShortSymbol, str, StringComparison.OrdinalIgnoreCase)
86+
|| string.Equals("--" + x.Symbol, str, StringComparison.OrdinalIgnoreCase));
87+
88+
if (item == null)
89+
{
90+
return false;
91+
}
92+
93+
if (item.SupportsValue)
94+
{
95+
if (i == args.Length - 1)
96+
{
97+
return false;
98+
}
99+
100+
i++;
101+
item.Value = args[i];
102+
}
103+
104+
suppliedOpts.Add(item);
105+
}
106+
}
107+
108+
if (path == null)
109+
{
110+
return false;
111+
}
112+
113+
parsed = new ParsedArgs
114+
{
115+
SuppliedArgs = suppliedOpts,
116+
SuppliedDirectoryPath = path
117+
};
118+
119+
return true;
120+
}
121+
10122
public static int Main(string[] args)
11123
{
12124
if (args.Length == 0)
@@ -15,30 +127,47 @@ public static int Main(string[] args)
15127
return 7;
16128
}
17129

18-
var path = args[0];
19-
20-
if (!Directory.Exists(path))
130+
if (!TryParseArgs(args, out var parsed))
21131
{
22-
Console.WriteLine($"The provided path is not a valid directory: {path}.");
132+
var strJoined = string.Join(" ", args);
133+
Console.WriteLine($"Unrecognized arguments passed: {strJoined}");
23134
return 7;
24135
}
25136

26-
var maxCount = default(int?);
137+
if (!Directory.Exists(parsed.SuppliedDirectoryPath))
138+
{
139+
Console.WriteLine($"The provided path is not a valid directory: {parsed.SuppliedDirectoryPath}.");
140+
return 7;
141+
}
27142

28-
if (args.Length > 1 && int.TryParse(args[1], out var countIn))
143+
int? maxCount = null;
144+
var limit = parsed.SuppliedArgs.SingleOrDefault(x => x.ShortSymbol == "l");
145+
if (limit?.Value != null && int.TryParse(limit.Value, CultureInfo.InvariantCulture, out var maxCountArg))
29146
{
30-
maxCount = countIn;
147+
Console.WriteLine($"Limiting input files to first: {maxCountArg}");
148+
maxCount = maxCountArg;
31149
}
150+
151+
var noRecursionMode = parsed.SuppliedArgs.Any(x => x.ShortSymbol == "nr");
152+
var outputOpt = parsed.SuppliedArgs.SingleOrDefault(x => x.ShortSymbol == "o" && x.Value != null);
32153

33154
var hasError = false;
34155
var errorBuilder = new StringBuilder();
35-
var fileList = Directory.GetFiles(path, "*.pdf", SearchOption.AllDirectories);
156+
var fileList = Directory.GetFiles(
157+
parsed.SuppliedDirectoryPath,
158+
"*.pdf",
159+
noRecursionMode ? SearchOption.TopDirectoryOnly : SearchOption.AllDirectories)
160+
.OrderBy(x => x).ToList();
36161
var runningCount = 0;
37162

38-
Console.WriteLine($"Found {fileList.Length} files.");
163+
Console.WriteLine($"Found {fileList.Count} files.");
164+
Console.WriteLine();
165+
166+
PrintTableColumns("File", "Size", "Words", "Pages", "Open cost (μs)", "Total cost (μs)", "Page cost (μs)");
39167

40-
Console.WriteLine($"{GetCleanFilename("File")}| Size\t| Words\t| Pages");
168+
var dataList = new List<DataRecord>();
41169

170+
var sw = new Stopwatch();
42171
foreach (var file in fileList)
43172
{
44173
if (maxCount.HasValue && runningCount >= maxCount)
@@ -50,8 +179,20 @@ public static int Main(string[] args)
50179
{
51180
var numWords = 0;
52181
var numPages = 0;
182+
long openMicros;
183+
long totalPageMicros;
184+
185+
sw.Reset();
186+
sw.Start();
187+
53188
using (var pdfDocument = PdfDocument.Open(file))
54189
{
190+
sw.Stop();
191+
192+
openMicros = sw.Elapsed.Microseconds;
193+
194+
sw.Start();
195+
55196
foreach (var page in pdfDocument.GetPages())
56197
{
57198
numPages++;
@@ -63,13 +204,36 @@ public static int Main(string[] args)
63204
}
64205
}
65206
}
207+
208+
sw.Stop();
209+
totalPageMicros = sw.Elapsed.Microseconds;
66210
}
67211

68212
var filename = Path.GetFileName(file);
69213

70214
var size = new FileInfo(file);
71215

72-
Console.WriteLine($"{GetCleanFilename(filename)}| {size.Length}\t| {numWords}\t| {numPages}");
216+
var item = new DataRecord
217+
{
218+
FileName = filename,
219+
OpenCostMicros = openMicros,
220+
Pages = numPages,
221+
Size = size.Length,
222+
Words = numWords,
223+
TotalCostMicros = totalPageMicros + openMicros,
224+
PerPageMicros = Math.Round(totalPageMicros / (double)Math.Max(numPages, 1), 2)
225+
};
226+
227+
dataList.Add(item);
228+
229+
PrintTableColumns(
230+
item.FileName,
231+
item.Size,
232+
item.Words,
233+
item.Pages,
234+
item.OpenCostMicros,
235+
item.TotalCostMicros,
236+
item.PerPageMicros);
73237
}
74238
catch (Exception ex)
75239
{
@@ -88,12 +252,71 @@ public static int Main(string[] args)
88252
return 5;
89253
}
90254

255+
if (outputOpt != null && outputOpt.Value != null)
256+
{
257+
WriteOutput(outputOpt.Value, dataList);
258+
}
259+
91260
Console.WriteLine("Complete! :)");
92261

93262
return 0;
94263
}
95264

96-
private static string GetCleanFilename(string name, int maxLength = 30)
265+
private static void WriteOutput(string outPath, IReadOnlyList<DataRecord> records)
266+
{
267+
using var fs = File.OpenWrite(outPath);
268+
using var sw = new StreamWriter(fs);
269+
270+
sw.WriteLine("File,Size,Words,Pages,Open Cost,Total Cost,Per Page");
271+
foreach (var record in records)
272+
{
273+
var sizeStr = record.Size.ToString("D", CultureInfo.InvariantCulture);
274+
var wordsStr = record.Words.ToString("D", CultureInfo.InvariantCulture);
275+
var pagesStr = record.Pages.ToString("D", CultureInfo.InvariantCulture);
276+
var openCostStr = record.OpenCostMicros.ToString("D", CultureInfo.InvariantCulture);
277+
var totalCostStr = record.TotalCostMicros.ToString("D", CultureInfo.InvariantCulture);
278+
var ppcStr = record.PerPageMicros.ToString("F2", CultureInfo.InvariantCulture);
279+
280+
var numericPartsStr = string.Join(",",
281+
[
282+
sizeStr,
283+
wordsStr,
284+
pagesStr,
285+
openCostStr,
286+
totalCostStr,
287+
ppcStr
288+
]);
289+
290+
sw.WriteLine($"\"{record.FileName}\",{numericPartsStr}");
291+
}
292+
293+
sw.Flush();
294+
}
295+
296+
private static void PrintTableColumns(params object[] values)
297+
{
298+
for (var i = 0; i < values.Length; i++)
299+
{
300+
var value = values[i];
301+
var valueStr = value.ToString();
302+
303+
var cleaned = GetCleanStr(valueStr ?? string.Empty);
304+
305+
var padChars = 16 - cleaned.Length;
306+
307+
var padding = padChars > 0 ? new string(' ', padChars) : string.Empty;
308+
309+
var padded = cleaned + padding;
310+
311+
Console.Write("| ");
312+
313+
Console.Write(padded);
314+
}
315+
316+
Console.WriteLine();
317+
}
318+
319+
private static string GetCleanStr(string name, int maxLength = 16)
97320
{
98321
if (name.Length <= maxLength)
99322
{
@@ -105,4 +328,21 @@ private static string GetCleanFilename(string name, int maxLength = 30)
105328
return name.Substring(0, maxLength);
106329
}
107330
}
331+
332+
internal class DataRecord
333+
{
334+
public required string FileName { get; init; }
335+
336+
public required long Size { get; init; }
337+
338+
public required int Words { get; init; }
339+
340+
public required int Pages { get; init; }
341+
342+
public required long OpenCostMicros { get; init; }
343+
344+
public required long TotalCostMicros { get; init; }
345+
346+
public required double PerPageMicros { get; init; }
347+
}
108348
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"profiles": {
33
"UglyToad.PdfPig.ConsoleRunner": {
4-
"commandName": "Project",
5-
"commandLineArgs": "\"C:\\temp\\pdfs\\archive\""
4+
"commandName": "Project",
5+
"commandLineArgs": "\"C:\\temp\\pdfs\\archive\""
66
}
77
}
88
}

tools/UglyToad.PdfPig.ConsoleRunner/UglyToad.PdfPig.ConsoleRunner.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<LangVersion>latest</LangVersion>
55
<OutputType>Exe</OutputType>
66
<TargetFramework>net8</TargetFramework>
7+
<Nullable>enable</Nullable>
78
</PropertyGroup>
89

910
<ItemGroup>

0 commit comments

Comments
 (0)