1 
2 module dyaml.yaml_bench;
3 //Benchmark that loads, and optionally extracts data from and/or emits a YAML file.
4 
5 import std.algorithm;
6 import std.conv;
7 import std.datetime.systime;
8 import std.datetime.stopwatch;
9 import std.file;
10 import std.getopt;
11 import std.range;
12 import std.stdio;
13 import std.string;
14 import dyaml;
15 
16 ///Get data out of every node.
17 void extract(ref Node document) @safe
18 {
19     void crawl(ref Node root) @safe
20     {
21         final switch (root.nodeID)
22         {
23             case NodeID.scalar:
24                  switch(root.tag)
25                 {
26                     case "tag:yaml.org,2002:null":      auto value = root.as!YAMLNull;  break;
27                     case "tag:yaml.org,2002:bool":      auto value = root.as!bool;      break;
28                     case "tag:yaml.org,2002:int":       auto value = root.as!long;      break;
29                     case "tag:yaml.org,2002:float":     auto value = root.as!real;      break;
30                     case "tag:yaml.org,2002:binary":    auto value = root.as!(ubyte[]); break;
31                     case "tag:yaml.org,2002:timestamp": auto value = root.as!SysTime;   break;
32                     case "tag:yaml.org,2002:str":       auto value = root.as!string;    break;
33                     default: writeln("Unrecognozed tag: ", root.tag);
34                 }
35                 break;
36             case NodeID.sequence:
37                 foreach(ref Node node; root)
38                 {
39                     crawl(node);
40                 }
41                 break;
42             case NodeID.mapping:
43                 foreach(ref Node key, ref Node value; root)
44                 {
45                     crawl(key);
46                     crawl(value);
47                 }
48                 break;
49             case NodeID.invalid:
50                 assert(0);
51         }
52     }
53 
54     crawl(document);
55 }
56 
57 void main(string[] args) //@safe
58 {
59     import std.array : array;
60     bool get = false;
61     bool dump = false;
62     bool reload = false;
63     bool quiet = false;
64     bool verbose = false;
65     bool scanOnly = false;
66     uint runs = 1;
67 
68     auto help = getopt(
69         args,
70         "get|g", "Extract data from the file (using Node.as()).", &get,
71         "dump|d", "Dump the loaded data (to YAML_FILE.dump).", &dump,
72         "runs|r", "Repeat parsing the file NUM times.", &runs,
73         "reload", "Reload the file from the diskl on every repeat By default,"~
74             " the file is loaded to memory once and repeatedly parsed from memory.", &reload,
75         "quiet|q", "Don't print anything.", &quiet,
76         "verbose|v", "Print even more.", &verbose,
77         "scan-only|s", "Do not execute the entire parsing process, only scanning. Overrides '--dump'", &scanOnly
78     );
79 
80     if (help.helpWanted || (args.length < 2))
81     {
82         defaultGetoptPrinter(
83             "D:YAML benchmark\n"~
84             "Copyright (C) 2011-2018 Ferdinand Majerech, Cameron \"Herringway\" Ross\n"~
85             "Usage: yaml_bench [OPTION ...] [YAML_FILE]\n\n"~
86             "Loads and optionally extracts data and/or dumps a YAML file.\n",
87             help.options
88         );
89         return;
90     }
91 
92     string file = args[1];
93 
94     auto stopWatch = StopWatch(AutoStart.yes);
95     void[] fileInMemory;
96     if(!reload) { fileInMemory = std.file.read(file); }
97     void[] fileWorkingCopy = fileInMemory.dup;
98     auto loadTime = stopWatch.peek();
99     stopWatch.reset();
100     try
101     {
102         // Instead of constructing a resolver/constructor with each Loader,
103         // construct them once to remove noise when profiling.
104         auto resolver    = Resolver.withDefaultResolvers;
105 
106         auto constructTime = stopWatch.peek();
107 
108         Node[] nodes;
109 
110         void runLoaderBenchmark() //@safe
111         {
112             // Loading the file rewrites the loaded buffer, so if we don't reload from
113             // disk, we need to use a copy of the originally loaded file.
114             if(reload) { fileInMemory = std.file.read(file); }
115             else       { fileWorkingCopy[] = fileInMemory[]; }
116             void[] fileToLoad = reload ? fileInMemory : fileWorkingCopy;
117 
118             auto loader        = Loader.fromBuffer(fileToLoad);
119             if(scanOnly)
120             {
121                 loader.scanBench();
122                 return;
123             }
124 
125             loader.resolver = resolver;
126             nodes = loader.array;
127         }
128         void runDumpBenchmark() @safe
129         {
130             if(dump)
131             {
132                 dumper().dump(File(file ~ ".dump", "w").lockingTextWriter, nodes);
133             }
134         }
135         void runGetBenchmark() @safe
136         {
137             if(get) foreach(ref node; nodes)
138             {
139                 extract(node);
140             }
141         }
142         auto totalTime = benchmark!(runLoaderBenchmark, runDumpBenchmark, runGetBenchmark)(runs);
143         if (!quiet)
144         {
145             auto enabledOptions =
146                 only(
147                     get ? "Get" : "",
148                     dump ? "Dump" : "",
149                     reload ? "Reload" : "",
150                     scanOnly ? "Scan Only":  ""
151                 ).filter!(x => x != "");
152             if (!enabledOptions.empty)
153             {
154                 writefln!"Options enabled: %-(%s, %)"(enabledOptions);
155             }
156             if (verbose)
157             {
158                 if (!reload)
159                 {
160                     writeln("Time to load file: ", loadTime);
161                 }
162                 writeln("Time to set up resolver: ", constructTime);
163             }
164             writeln("Runs: ", runs);
165             foreach(time, func, enabled; lockstep(totalTime[], only("Loader", "Dumper", "Get"), only(true, dump, get)))
166             {
167                 if (enabled)
168                 {
169                     writeln("Average time spent on ", func, ": ", time / runs);
170                     writeln("Total time spent on ", func, ": ", time);
171                 }
172             }
173         }
174     }
175     catch(YAMLException e)
176     {
177         writeln("ERROR: ", e.msg);
178     }
179 }