1 /* ========================================================================
2 $File: tools/ctime/ctime.c $
3 $Date: 2016/05/08 04:16:55PM $
4 $Revision: 7 $
5 $Creator: Casey Muratori $
6 $Notice:
7
8 The author of this software MAKES NO WARRANTY as to the RELIABILITY,
9 SUITABILITY, or USABILITY of this software. USE IT AT YOUR OWN RISK.
10
11 This is a simple timing utility. It is in the public domain.
12 Anyone can use it, modify it, roll'n'smoke hardcopies of the source
13 code, sell it to the terrorists, etc.
14
15 But the author makes absolutely no warranty as to the reliability,
16 suitability, or usability of the software. There might be bad bugs
17 in here. It could delete all your files. It could format your
18 hard drive. I have no idea. If you lose all your files from using
19 it, it is your fault.
20
21 $
22
23 ctime is a simple utility that helps you keep track of how much time
24 you spend building your projects. You use it the same way you would
25 use a begin/end block profiler in your normal code, only instead of
26 profiling your code, you profile your build.
27
28 BASIC INSTRUCTIONS
29 ------------------
30
31 On the very first line of your build script, you do something like this:
32
33 ctime -begin timings_file_for_this_build.ctm
34
35 and then on the very last line of your build script, you do
36
37 ctime -end timings_file_for_this_build.ctm
38
39 That's all there is to it! ctime will keep track of every build you
40 do, when you did it, and how long it took. Later, when you'd like to
41 get a feel for how your build times have evolved, you can type
42
43 ctime -stats timings_file_for_this_build.ctm
44
45 and it will tell you a number of useful statistics!
46
47
48 ADVANCED INSTRUCTIONS
49 ---------------------
50
51 ctime has the ability to track the difference between _failed_ builds
52 and _successful_ builds. If you would like it to do so, you can capture
53 the error status in your build script at whatever point you want,
54 for example:
55
56 set LastError=%ERRORLEVEL%
57
58 and then when you eventually call ctime to end the profiling, you simply
59 pass that error code to it:
60
61 ctime -end timings_file_for_this_build.ctm %LastError%
62
63 ctime can also dump all timings from a timing file into a textual
64 format for use in other types of tools. To get a CSV you can import
65 into a graphing program or database, use:
66
67 ctime -csv timings_file_for_this_build.ctm
68
69 Also, you may want to do things like timing multiple builds separately,
70 or timing builds based on what compiler flags are active. To do this,
71 you can use separate timing files for each configuration by using
72 the shell variables for the build at the filename, eg.:
73
74 ctime -begin timings_for_%BUILD_NAME%.ctm
75 ...
76 ctime -end timings_for_%BUILD_NAME%.ctm
77
78 ======================================================================== */
79
80 #define _CRT_SECURE_NO_DEPRECATE
81 #include <stdio.h>
82 #include <stdlib.h>
83 #include <time.h>
84 #include <string.h>
85
86 #pragma pack(push,1)
87
88 #define MAGIC_VALUE 0xCA5E713F
89 typedef struct timing_file_header
90 {
91 int unsigned MagicValue;
92 } timing_file_header;
93
94 typedef struct timing_file_date
95 {
96 long long unsigned E;
97 } timing_file_date;
98
99 enum timing_file_entry_flag
100 {
101 TFEF_Complete = 0x1,
102 TFEF_NoErrors = 0x2,
103 };
104 typedef struct timing_file_entry
105 {
106 timing_file_date StartDate;
107 int unsigned Flags;
108 int unsigned MillisecondsElapsed;
109 } timing_file_entry;
110
111 #pragma pack(pop)
112
113 typedef struct timing_entry_array
114 {
115 int EntryCount;
116 timing_file_entry *Entries;
117 } timing_entry_array;
118
119 //
120 // TODO(casey): More platforms? Sadly, ANSI C doesn't support high-resolution timing across runs of a process AFAICT :(
121 //
122
123 #ifdef _WIN32
124
125 #include <windows.h>
126
127 static int unsigned
GetClock(void)128 GetClock(void)
129 {
130 if(sizeof(int unsigned) != sizeof(DWORD))
131 {
132 fprintf(stderr, "ERROR: Unexpected integer size - timing will not work on this platform!\n");
133 }
134
135 return(timeGetTime());
136 }
137
138 #elif __APPLE__
139 #include "TargetConditionals.h"
140 # if TARGET_OS_MAC
141 #include <mach/mach_time.h>
142
143 static int unsigned
GetClock(void)144 GetClock(void)
145 {
146 static mach_timebase_info_data_t ToNanoseconds;
147 if (ToNanoseconds.denom == 0)
148 {
149 mach_timebase_info(&ToNanoseconds);
150 }
151 uint64_t Ticks = mach_absolute_time();
152 int unsigned Result = Ticks *
153 ToNanoseconds.numer / 1000000 / ToNanoseconds.denom;
154 return Result;
155 }
156 # else
157 # error "Unknown Apple target"
158 # endif
159 #else
160
161 // This was written for Linux
162
163 static int unsigned
GetClock(void)164 GetClock(void)
165 {
166 struct timespec TimeSpec;
167 int unsigned Result;
168
169 clock_gettime(CLOCK_REALTIME, &TimeSpec);
170 Result = TimeSpec.tv_sec * 1000 + TimeSpec.tv_nsec / 1000000;
171
172 return Result;
173 }
174
175 #endif
176
177 //
178 //
179 //
180
181 static timing_file_date
GetDate(void)182 GetDate(void)
183 {
184 timing_file_date Result = {0};
185 Result.E = time(NULL);
186 return(Result);
187 }
188
189 static void
PrintDate(timing_file_date Date)190 PrintDate(timing_file_date Date)
191 {
192 time_t Time;
193 struct tm *LocalTime;
194 char Str[256];
195
196 Time = Date.E;
197 LocalTime = localtime(&Time);
198 strftime(Str, 256, "%Y-%m-%d %H:%M:%S", LocalTime);
199 fprintf(stdout, "%s", Str);
200 }
201
202 static long long unsigned
SecondDifference(timing_file_date A,timing_file_date B)203 SecondDifference(timing_file_date A, timing_file_date B)
204 {
205 long long unsigned Result = A.E - B.E;
206 return Result;
207 }
208
209 static int unsigned
DayIndex(timing_file_date A)210 DayIndex(timing_file_date A)
211 {
212 time_t Time;
213 struct tm *LocalTime;
214
215 Time = A.E;
216 LocalTime = localtime(&Time);
217 return LocalTime->tm_yday;
218 }
219
220 static void
Usage(void)221 Usage(void)
222 {
223 fprintf(stderr, "CTime v1.0 by Casey Muratori\n");
224 fprintf(stderr, "Usage:\n");
225 fprintf(stderr, " ctime -begin <timing file>\n");
226 fprintf(stderr, " ctime -end <timing file> [error level]\n");
227 fprintf(stderr, " ctime -stats <timing file>\n");
228 fprintf(stderr, " ctime -csv <timing file>\n");
229 }
230
231 static timing_entry_array
ReadAllEntries(FILE * Handle)232 ReadAllEntries(FILE* Handle)
233 {
234 timing_entry_array Result = {0};
235
236 int EntriesBegin = sizeof(timing_file_header);
237 int FileSize;
238 if((fseek(Handle, 0, SEEK_END) == 0) && ((FileSize = ftell(Handle)) >= 0))
239 {
240 int EntriesSize = FileSize - EntriesBegin;
241 Result.Entries = (timing_file_entry *)malloc(EntriesSize);
242 if(Result.Entries)
243 {
244 fseek(Handle, EntriesBegin, SEEK_SET);
245 int ReadSize = (int)fread(Result.Entries, 1, EntriesSize, Handle);
246 if(ReadSize == EntriesSize)
247 {
248 Result.EntryCount = EntriesSize / sizeof(timing_file_entry);
249 }
250 else
251 {
252 fprintf(stderr, "ERROR: Unable to read timing entries from file.\n");
253 }
254 }
255 else
256 {
257 fprintf(stderr, "ERROR: Unable to allocate %d for storing timing entries.\n", EntriesSize);
258 }
259 }
260 else
261 {
262 fprintf(stderr, "ERROR: Unable to determine file size of timing file.\n");
263 }
264
265 return(Result);
266 }
267
268 static void
FreeAllEntries(timing_entry_array Array)269 FreeAllEntries(timing_entry_array Array)
270 {
271 if(Array.Entries)
272 {
273 free(Array.Entries);
274 Array.EntryCount = 0;
275 Array.Entries = 0;
276 }
277 }
278
279 static void
CSV(timing_entry_array Array,char * TimingFileName)280 CSV(timing_entry_array Array, char *TimingFileName)
281 {
282 int EntryIndex;
283 timing_file_entry *Entry = Array.Entries;
284
285 fprintf(stdout, "%s Timings\n", TimingFileName);
286 fprintf(stdout, "ordinal, date, duration, status\n");
287 {for(EntryIndex = 0;
288 EntryIndex < Array.EntryCount;
289 ++EntryIndex, ++Entry)
290 {
291 fprintf(stdout, "%d, ", EntryIndex);
292 PrintDate(Entry->StartDate);
293 if(Entry->Flags & TFEF_Complete)
294 {
295 fprintf(stdout, ", %0.3fs, %s", (double)Entry->MillisecondsElapsed / 1000.0,
296 (Entry->Flags & TFEF_NoErrors) ? "succeeded" : "failed");
297 }
298 else
299 {
300 fprintf(stdout, ", (never completed), failed");
301 }
302
303 fprintf(stdout, "\n");
304 }}
305 }
306
307 typedef struct time_part
308 {
309 char *Name;
310 double MillisecondsPer;
311 } time_part;
312
313 static void
PrintTime(double Milliseconds)314 PrintTime(double Milliseconds)
315 {
316 double MillisecondsPerSecond = 1000;
317 double MillisecondsPerMinute = 60*MillisecondsPerSecond;
318 double MillisecondsPerHour = 60*MillisecondsPerMinute;
319 double MillisecondsPerDay = 24*MillisecondsPerHour;
320 double MillisecondsPerWeek = 7*MillisecondsPerDay;
321 time_part Parts[] =
322 {
323 {"week", MillisecondsPerWeek},
324 {"day", MillisecondsPerDay},
325 {"hour", MillisecondsPerHour},
326 {"minute", MillisecondsPerMinute},
327 };
328 int unsigned PartIndex;
329 double Q = Milliseconds;
330
331 for(PartIndex = 0;
332 PartIndex < (sizeof(Parts)/sizeof(Parts[0]));
333 ++PartIndex)
334 {
335 double MsPer = Parts[PartIndex].MillisecondsPer;
336 double This = (double)(int)(Q / MsPer);
337
338 if(This > 0)
339 {
340 fprintf(stdout, "%d %s%s, ", (int)This, Parts[PartIndex].Name,
341 (This != 1) ? "s" : "");
342 }
343 Q -= This*MsPer;
344 }
345
346 fprintf(stdout, "%0.3f seconds", (double)Q / 1000.0);
347 }
348
349 static void
PrintTimeStat(char * Name,int unsigned Milliseconds)350 PrintTimeStat(char *Name, int unsigned Milliseconds)
351 {
352 fprintf(stdout, "%s: ", Name);
353 PrintTime((double)Milliseconds);
354 fprintf(stdout, "\n");
355 }
356
357 typedef struct stat_group
358 {
359 int unsigned Count;
360
361 int unsigned SlowestMs;
362 int unsigned FastestMs;
363 double TotalMs;
364
365 } stat_group;
366
367 #define GRAPH_HEIGHT 10
368 #define GRAPH_WIDTH 30
369 typedef struct graph
370 {
371 stat_group Buckets[GRAPH_WIDTH];
372 } graph;
373
374 static void
PrintStatGroup(char * Title,stat_group * Group)375 PrintStatGroup(char *Title, stat_group *Group)
376 {
377 int unsigned AverageMs = 0;
378 if(Group->Count >= 1)
379 {
380 AverageMs = (int unsigned)(Group->TotalMs / (double)Group->Count);
381 }
382
383 if(Group->Count > 0)
384 {
385 fprintf(stdout, "%s (%d):\n", Title, Group->Count);
386 PrintTimeStat(" Slowest", Group->SlowestMs);
387 PrintTimeStat(" Fastest", Group->FastestMs);
388 PrintTimeStat(" Average", AverageMs);
389 PrintTimeStat(" Total", (int unsigned)Group->TotalMs);
390 }
391 }
392
393 static void
UpdateStatGroup(stat_group * Group,timing_file_entry * Entry)394 UpdateStatGroup(stat_group *Group, timing_file_entry *Entry)
395 {
396 if(Group->SlowestMs < Entry->MillisecondsElapsed)
397 {
398 Group->SlowestMs = Entry->MillisecondsElapsed;
399 }
400
401 if(Group->FastestMs > Entry->MillisecondsElapsed)
402 {
403 Group->FastestMs = Entry->MillisecondsElapsed;
404 }
405
406 Group->TotalMs += (double)Entry->MillisecondsElapsed;
407
408 ++Group->Count;
409 }
410
411 static int
MapToDiscrete(double Value,double InMax,double OutMax)412 MapToDiscrete(double Value, double InMax, double OutMax)
413 {
414 int Result;
415
416 if(InMax == 0)
417 {
418 InMax = 1;
419 }
420
421 Result = (int)((Value / InMax) * OutMax);
422
423 return(Result);
424 }
425
426 static void
PrintGraph(char * Title,double DaySpan,graph * Graph)427 PrintGraph(char *Title, double DaySpan, graph *Graph)
428 {
429 int BucketIndex;
430 int LineIndex;
431 int unsigned MaxCountInBucket = 0;
432 int unsigned SlowestMs = 0;
433 double DPB = DaySpan / (double)GRAPH_WIDTH;
434
435 for(BucketIndex = 0;
436 BucketIndex < GRAPH_WIDTH;
437 ++BucketIndex)
438 {
439 stat_group *Group = Graph->Buckets + BucketIndex;
440
441 if(Group->Count)
442 {
443 // double AverageMs = Group->TotalMs / (double)Group->Count;
444 if(MaxCountInBucket < Group->Count)
445 {
446 MaxCountInBucket = Group->Count;
447 }
448
449 if(SlowestMs < Group->SlowestMs)
450 {
451 SlowestMs = Group->SlowestMs;
452 }
453 }
454 }
455
456 fprintf(stdout, "\n%s (%f day%s/bucket):\n", Title, DPB, (DPB == 1) ? "" : "s");
457 for(LineIndex = GRAPH_HEIGHT - 1;
458 LineIndex >= 0;
459 --LineIndex)
460 {
461 fputc('|', stdout);
462 for(BucketIndex = 0;
463 BucketIndex < GRAPH_WIDTH;
464 ++BucketIndex)
465 {
466 stat_group *Group = Graph->Buckets + BucketIndex;
467 int This = -1;
468 if(Group->Count)
469 {
470 // double AverageMs = Group->TotalMs / (double)Group->Count;
471 This = MapToDiscrete(Group->SlowestMs, SlowestMs, GRAPH_HEIGHT - 1);
472 }
473 fputc((This >= LineIndex) ? '*' : ' ', stdout);
474 }
475 if(LineIndex == (GRAPH_HEIGHT - 1))
476 {
477 fputc(' ', stdout);
478 PrintTime(SlowestMs);
479 }
480 fputc('\n', stdout);
481 }
482 fputc('+', stdout);
483 for(BucketIndex = 0; BucketIndex < GRAPH_WIDTH; ++BucketIndex) {fputc('-', stdout);}
484 fputc(' ', stdout);
485 PrintTime(0);
486 fputc('\n', stdout);
487 fputc('\n', stdout);
488 for(LineIndex = GRAPH_HEIGHT - 1;
489 LineIndex >= 0;
490 --LineIndex)
491 {
492 fputc('|', stdout);
493 for(BucketIndex = 0;
494 BucketIndex < GRAPH_WIDTH;
495 ++BucketIndex)
496 {
497 stat_group *Group = Graph->Buckets + BucketIndex;
498 int This = -1;
499 if(Group->Count)
500 {
501 This = MapToDiscrete(Group->Count, MaxCountInBucket, GRAPH_HEIGHT - 1);
502 }
503 fputc((This >= LineIndex) ? '*' : ' ', stdout);
504 }
505 if(LineIndex == (GRAPH_HEIGHT - 1))
506 {
507 fprintf(stdout, " %u", MaxCountInBucket);
508 }
509 fputc('\n', stdout);
510 }
511 fputc('+', stdout);
512 for(BucketIndex = 0; BucketIndex < GRAPH_WIDTH; ++BucketIndex) {fputc('-', stdout);}
513 fprintf(stdout, " 0\n");
514 }
515
516 static void
Stats(timing_entry_array Array,char * TimingFileName)517 Stats(timing_entry_array Array, char *TimingFileName)
518 {
519 stat_group WithErrors = {0};
520 stat_group NoErrors = {0};
521 stat_group AllStats = {0};
522
523 int unsigned IncompleteCount = 0;
524 int unsigned DaysWithTimingCount = 0;
525 int unsigned DaySpanCount = 0;
526
527 int EntryIndex;
528
529 timing_file_entry *Entry = Array.Entries;
530 int unsigned LastDayIndex = 0;
531
532 double AllMs = 0;
533
534 double FirstDayAt = 0;
535 double LastDayAt = 0;
536 double DaySpan = 0;
537
538 graph TotalGraph = {0};
539 graph RecentGraph = {0};
540
541 WithErrors.FastestMs = 0xFFFFFFFF;
542 NoErrors.FastestMs = 0xFFFFFFFF;
543
544 if(Array.EntryCount >= 2)
545 {
546 long long unsigned SecondD = SecondDifference(Array.Entries[Array.EntryCount - 1].StartDate, Array.Entries[0].StartDate);
547 DaySpanCount = (int unsigned)(SecondD / (60 * 60 * 24));
548
549 FirstDayAt = (double)DayIndex(Array.Entries[0].StartDate);
550 LastDayAt = (double)DayIndex(Array.Entries[Array.EntryCount - 1].StartDate);
551 DaySpan = (LastDayAt - FirstDayAt);
552 }
553 DaySpan += 1;
554
555 for(EntryIndex = 0;
556 EntryIndex < Array.EntryCount;
557 ++EntryIndex, ++Entry)
558 {
559 if(Entry->Flags & TFEF_Complete)
560 {
561 stat_group *Group = (Entry->Flags & TFEF_NoErrors) ? &NoErrors : &WithErrors;
562
563 int unsigned ThisDayIndex = DayIndex(Entry->StartDate);
564 if(LastDayIndex != ThisDayIndex)
565 {
566 LastDayIndex = ThisDayIndex;
567 ++DaysWithTimingCount;
568 }
569
570 UpdateStatGroup(Group, Entry);
571 UpdateStatGroup(&AllStats, Entry);
572
573 AllMs += (double)Entry->MillisecondsElapsed;
574
575 {
576 int GraphIndex = (int)(((double)(ThisDayIndex-FirstDayAt)/DaySpan)*(double)GRAPH_WIDTH);
577 UpdateStatGroup(TotalGraph.Buckets + GraphIndex, Entry);
578 }
579
580 {
581 int GraphIndex = (int)(ThisDayIndex - (LastDayAt - GRAPH_WIDTH + 1));
582 if(GraphIndex >= 0)
583 {
584 UpdateStatGroup(RecentGraph.Buckets + GraphIndex, Entry);
585 }
586 }
587 }
588 else
589 {
590 ++IncompleteCount;
591 }
592 }
593
594 fprintf(stdout, "\n%s Statistics\n\n", TimingFileName);
595 fprintf(stdout, "Total complete timings: %d\n", WithErrors.Count + NoErrors.Count);
596 fprintf(stdout, "Total incomplete timings: %d\n", IncompleteCount);
597 fprintf(stdout, "Days with timings: %d\n", DaysWithTimingCount);
598 fprintf(stdout, "Days between first and last timing: %d\n", DaySpanCount);
599 PrintStatGroup("Timings marked successful", &NoErrors);
600 PrintStatGroup("Timings marked failed", &WithErrors);
601
602 PrintGraph("All", (LastDayAt - FirstDayAt), &TotalGraph);
603 PrintGraph("Recent", GRAPH_WIDTH, &RecentGraph);
604
605 fprintf(stdout, "\nTotal time spent: ");
606 PrintTime(AllMs);
607 fprintf(stdout, "\n");
608 }
609
610 int
main(int ArgCount,char ** Args)611 main(int ArgCount, char **Args)
612 {
613 // TODO(casey): It would be nice if this supported 64-bit file sizes, but I can't really
614 // tell right now if "ANSI C" supports this. I feel like it should by now, but the
615 // MSVC docs seem to suggest you have to use __int64 to do 64-bit stuff with the CRT
616 // low-level IO routines, and I'm pretty sure that isn't a portable type :(
617
618 // NOTE(casey): We snap the clock time right on entry, to minimize any overhead on
619 // "end" times that might occur from opening the file.
620 int unsigned EntryClock = GetClock();
621
622 if((ArgCount == 3) || (ArgCount == 4))
623 {
624 char *Mode = Args[1];
625 int ModeIsBegin = (strcmp(Mode, "-begin") == 0);
626 char *TimingFileName = Args[2];
627 timing_file_header Header = {0};
628
629 FILE* Handle = fopen(TimingFileName, "r+b");
630 if(Handle != NULL)
631 {
632 // NOTE(casey): The file exists - check the magic value
633 fread(&Header, sizeof(Header), 1, Handle);
634 if(Header.MagicValue == MAGIC_VALUE)
635 {
636 // NOTE(casey): The file is at least nominally valid.
637 }
638 else
639 {
640 fprintf(stderr, "ERROR: Unable to verify that \"%s\" is actually a ctime-compatible file.\n", TimingFileName);
641
642 fclose(Handle);
643 Handle = NULL;
644 }
645 }
646 else if(ModeIsBegin)
647 {
648 // NOTE(casey): The file doesn't exist and we're starting a new timing, so create it.
649
650 Handle = fopen(TimingFileName, "w+b");
651 if(Handle != NULL)
652 {
653 Header.MagicValue = MAGIC_VALUE;
654 if(fwrite(&Header, sizeof(Header), 1, Handle) == 1)
655 {
656 // NOTE(casey): File creation was (presumably) successful.
657 }
658 else
659 {
660 fprintf(stderr, "ERROR: Unable to write header to \"%s\".\n", TimingFileName);
661 }
662 }
663 else
664 {
665 fprintf(stderr, "ERROR: Unable to create timing file \"%s\".\n", TimingFileName);
666 }
667 }
668
669 if(Handle != NULL)
670 {
671 if(ModeIsBegin)
672 {
673 timing_file_entry NewEntry = {0};
674 NewEntry.StartDate = GetDate();
675 NewEntry.MillisecondsElapsed = GetClock();
676 if((fseek(Handle, 0, SEEK_END) == 0) &&
677 (fwrite(&NewEntry, sizeof(NewEntry), 1, Handle) == 1))
678 {
679 // NOTE(casey): Timer begin entry was written successfully.
680 }
681 else
682 {
683 fprintf(stderr, "ERROR: Unable to append new entry to file \"%s\".\n", TimingFileName);
684 }
685 }
686 else if(strcmp(Mode, "-end") == 0)
687 {
688 timing_file_entry LastEntry = {0};
689 if((fseek(Handle, -(int)sizeof(timing_file_entry), SEEK_END) == 0) &&
690 (fread(&LastEntry, sizeof(LastEntry), 1, Handle) == 1))
691 {
692 if(!(LastEntry.Flags & TFEF_Complete))
693 {
694 int unsigned StartClockD = LastEntry.MillisecondsElapsed;
695 int unsigned EndClockD = EntryClock;
696 LastEntry.Flags |= TFEF_Complete;
697 LastEntry.MillisecondsElapsed = 0;
698 if(StartClockD < EndClockD)
699 {
700 LastEntry.MillisecondsElapsed = (EndClockD - StartClockD);
701 }
702
703 if((ArgCount == 3) ||
704 ((ArgCount == 4) && (atoi(Args[3]) == 0)))
705 {
706 LastEntry.Flags |= TFEF_NoErrors;
707 }
708
709 if((fseek(Handle, -(int)sizeof(timing_file_entry), SEEK_END) == 0) &&
710 (fwrite(&LastEntry, sizeof(LastEntry), 1, Handle) == 1))
711 {
712 fprintf(stdout, "CTIME: ");
713 PrintTime(LastEntry.MillisecondsElapsed);
714 fprintf(stdout, " (%s)\n", TimingFileName);
715 }
716 else
717 {
718 fprintf(stderr, "ERROR: Unable to rewrite last entry to file \"%s\".\n", TimingFileName);
719 }
720 }
721 else
722 {
723 fprintf(stderr, "ERROR: Last entry in file \"%s\" is already closed - unbalanced/overlapped calls?\n", TimingFileName);
724 }
725 }
726 else
727 {
728 fprintf(stderr, "ERROR: Unable to read last entry from file \"%s\".\n", TimingFileName);
729 }
730 }
731 else if(strcmp(Mode, "-stats") == 0)
732 {
733 timing_entry_array Array = ReadAllEntries(Handle);
734 Stats(Array, TimingFileName);
735 FreeAllEntries(Array);
736 }
737 else if(strcmp(Mode, "-csv") == 0)
738 {
739 timing_entry_array Array = ReadAllEntries(Handle);
740 CSV(Array, TimingFileName);
741 FreeAllEntries(Array);
742 }
743 else
744 {
745 fprintf(stderr, "ERROR: Unrecognized command \"%s\".\n", Mode);
746 }
747
748 fclose(Handle);
749 Handle = NULL;
750 }
751 else
752 {
753 fprintf(stderr, "ERROR: Cannnot open file \"%s\".\n", TimingFileName);
754 }
755 }
756 else
757 {
758 Usage();
759 }
760 }
761