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