xref: /6.0.3/subjson/bench.cc (revision 10cb006b)
1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3 *     Copyright 2015 Couchbase, Inc
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17 
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <assert.h>
21 #include <string.h>
22 #include <limits.h>
23 #include <errno.h>
24 #include <stdexcept>
25 
26 #include <map>
27 #include <vector>
28 #include <string>
29 #include <fstream>
30 #include <iostream>
31 #include <istream>
32 #include <ostream>
33 #include <sstream>
34 #include <chrono>
35 
36 #define CLIOPTS_ENABLE_CXX
37 #define INCLUDE_SUBDOC_NTOHLL
38 #include "subdoc/subdoc-api.h"
39 #include "subdoc/path.h"
40 #include "subdoc/match.h"
41 #include "subdoc/operations.h"
42 #include "contrib/cliopts/cliopts.h"
43 
44 using std::string;
45 using std::vector;
46 using std::map;
47 using namespace cliopts;
48 
49 using Subdoc::Path;
50 using Subdoc::Operation;
51 using Subdoc::Command;
52 using Subdoc::Error;
53 
54 struct OpEntry {
55     uint8_t opcode;
56     const char *description;
57     OpEntry(uint8_t opcode = 0, const char *description = NULL) :
58         opcode(opcode),
59         description(description) {}
60 
61     operator uint8_t() const { return opcode; }
62 };
63 
64 class Options {
65 public:
66     Options() :
67         o_iter('i', "iterations", 1000),
68         o_path('p', "docpath"),
69         o_value('v', "value"),
70         o_jsfile('f', "json"),
71         o_cmd('c', "command"),
72         o_mkdirp('M', "create-intermediate"),
73         parser("subdoc-bench")
74     {
75         o_iter.description("Number of iterations to run");
76         o_path.description("Document path to manipulate");
77         o_value.description("Document value to insert");
78         o_jsfile.description("JSON files to operate on. If passing multiple files, each file should be delimited by a comma");
79         o_cmd.description("Command to use. Use -c help to show all the commands").mandatory();
80         o_mkdirp.description("Create intermediate paths for mutation operations");
81 
82         parser.addOption(o_iter);
83         parser.addOption(o_path);
84         parser.addOption(o_value);
85         parser.addOption(o_jsfile);
86         parser.addOption(o_cmd);
87         parser.addOption(o_mkdirp);
88 
89         totalBytes = 0;
90         // Set the opmap
91         initOpmap();
92     }
93 
94     void initOpmap() {
95         // generic ops:
96         opmap["replace"] = OpEntry(Command::REPLACE, "Replace a value");
97         opmap["delete"] = OpEntry(Command::REMOVE, "Delete a value");
98         opmap["get"] = OpEntry(Command::GET, "Retrieve a value");
99         opmap["exists"] = OpEntry(Command::EXISTS, "Check if a value exists");
100 
101         // dict ops
102         opmap["add"] = OpEntry(Command::DICT_ADD, "Create a new dictionary value");
103         opmap["upsert"] = OpEntry(Command::DICT_UPSERT, "Create or replace a dictionary value");
104 
105         // list ops
106         opmap["append"] = OpEntry(Command::ARRAY_APPEND, "Insert values to the end of an array");
107         opmap["prepend"] = OpEntry(Command::ARRAY_PREPEND, "Insert values to the beginning of an array");
108         opmap["addunique"] = OpEntry(Command::ARRAY_ADD_UNIQUE, "Add a unique value to an array");
109         opmap["insert"] = OpEntry(Command::ARRAY_INSERT, "Insert value at given array index");
110 
111         // arithmetic ops
112         opmap["incr"] = OpEntry(Command::INCREMENT, "Increment a value");
113         opmap["decr"] = OpEntry(Command::DECREMENT, "Decrement a value");
114         opmap["path"] = OpEntry(0xff, "Check the validity of a path");
115     }
116 
117     UIntOption o_iter;
118     StringOption o_path;
119     StringOption o_value;
120     StringOption o_jsfile;
121     StringOption o_cmd;
122     BoolOption o_mkdirp;
123     map<string,OpEntry> opmap;
124     Parser parser;
125     size_t totalBytes;
126 };
127 
128 static void
129 readJsonFile(string& name, vector<string>& out)
130 {
131     std::ifstream input(name.c_str());
132     if (input.fail()) {
133         throw name + ": " + strerror(errno);
134     }
135     fprintf(stderr, "Reading %s\n", name.c_str());
136     std::stringstream ss;
137     ss << input.rdbuf();
138     out.push_back(ss.str());
139     input.close();
140 }
141 
142 static void
143 execOperation(Options& o)
144 {
145     vector<string> fileNames;
146     vector<string> inputStrs;
147     string flist = o.o_jsfile.const_result();
148     if (flist.find(',') == string::npos) {
149         fileNames.push_back(flist);
150     } else {
151         while (true) {
152             size_t pos = flist.find_first_of(',');
153             if (pos == string::npos) {
154                 fileNames.push_back(flist);
155                 break;
156             } else {
157                 string curName = flist.substr(0, pos);
158                 fileNames.push_back(curName);
159                 flist = flist.substr(pos+1);
160             }
161         }
162     }
163     if (fileNames.empty()) {
164         throw string("At least one file must be passed!");
165     }
166     for (size_t ii = 0; ii < fileNames.size(); ii++) {
167         readJsonFile(fileNames[ii], inputStrs);
168         o.totalBytes += inputStrs.back().length();
169     }
170 
171     uint8_t opcode = o.opmap[o.o_cmd.result()];
172     if (o.o_mkdirp.passed()) {
173         opcode |= 0x80;
174     }
175 
176     string value = o.o_value.const_result();
177     string path = o.o_path.const_result();
178     const char *vbuf = value.c_str();
179     size_t nvbuf = value.length();
180     uint64_t delta = 0;
181     bool has_delta = false;
182 
183     switch (opcode) {
184     case Command::INCREMENT:
185     case Command::INCREMENT_P:
186     case Command::DECREMENT:
187     case Command::DECREMENT_P: {
188         // Exceptions are caught and printed by main()
189         size_t tmppos = 0;
190         delta = std::stoul(value, &tmppos);
191         if (tmppos != value.size()) {
192             throw string("Invalid numeric value: " + value);
193         }
194         has_delta = true;
195         break;
196     }
197     }
198 
199     Operation op;
200 
201 
202     size_t itermax = o.o_iter.result();
203     for (size_t ii = 0; ii < itermax; ii++) {
204         op.clear();
205         const string& curInput = inputStrs[ii % inputStrs.size()];
206         op.set_code(opcode);
207         op.set_doc(curInput);
208         if (has_delta) {
209             op.set_delta(delta);
210         } else {
211             op.set_value(vbuf, nvbuf);
212         }
213 
214         Error rv = op.op_exec(path);
215         if (!rv.success()) {
216             throw rv;
217         }
218     }
219 
220     // Print the result.
221     if (opcode == Command::GET || opcode == Command::EXISTS) {
222         string match = op.matchloc().to_string();
223         printf("%s\n", match.c_str());
224     } else {
225         string newdoc;
226         for (auto ii : op.newdoc()) {
227             newdoc.append(ii.at, ii.length);
228         }
229         printf("%s\n", newdoc.c_str());
230     }
231 }
232 
233 static void
234 execPathParse(Options& o)
235 {
236     size_t itermax = o.o_iter.result();
237     string path = o.o_path.const_result();
238     Path pth;
239 
240     for (size_t ii = 0; ii < itermax; ii++) {
241         pth.clear();
242         int rv = pth.parse(path);
243 
244         if (rv != 0) {
245             throw string("Failed to parse path!");
246         }
247     }
248 }
249 
250 void runMain(int argc, char **argv)
251 {
252     using namespace std::chrono;
253 
254     Options o;
255     if (!o.parser.parse(argc, argv)) {
256         throw string("Bad options!");
257     }
258     // Determine the command
259     string cmdStr = o.o_cmd.const_result();
260     auto t_begin = steady_clock::now();
261 
262     if (cmdStr == "help") {
263         map<string,OpEntry>::const_iterator iter = o.opmap.begin();
264         for (; iter != o.opmap.end(); ++iter) {
265             const OpEntry& ent = iter->second;
266             fprintf(stderr, "%s (0x%x): ", iter->first.c_str(), ent.opcode);
267             fprintf(stderr, "%s\n", ent.description);
268         }
269         exit(EXIT_SUCCESS);
270     }
271     if (!o.o_path.passed()) {
272         fprintf(stderr, "Path (-p) required\n");
273         exit(EXIT_FAILURE);
274     }
275 
276     if (o.opmap.find(cmdStr) != o.opmap.end()) {
277         if (!o.o_jsfile.passed()) {
278             throw string("Operation must contain file!");
279         }
280         execOperation(o);
281     } else if (cmdStr == "path") {
282         execPathParse(o);
283     } else {
284         throw string("Unknown command!");
285     }
286 
287     auto total = duration_cast<duration<double>>(steady_clock::now() - t_begin);
288 
289     // Get the number of seconds:
290     double n_seconds = total.count();
291     double ops_per_sec = static_cast<double>(o.o_iter.result()) / n_seconds;
292     double mb_per_sec = (
293             static_cast<double>(o.totalBytes) *
294             static_cast<double>(o.o_iter.result())) /
295                     n_seconds;
296 
297     mb_per_sec /= (1024 * 1024);
298 
299     fprintf(stderr, "DURATION=%.2lfs. OPS=%u\n", n_seconds, o.o_iter.result());
300     fprintf(stderr, "%.2lf OPS/s\n",  ops_per_sec);
301     fprintf(stderr, "%.2lf MB/s\n", mb_per_sec);
302 }
303 
304 int main(int argc, char **argv)
305 {
306     try {
307         runMain(argc, argv);
308         return EXIT_SUCCESS;
309     } catch (string& exc) {
310         fprintf(stderr, "%s\n", exc.c_str());
311         return EXIT_FAILURE;
312     } catch (Error& rc) {
313         fprintf(stderr, "Command failed: %s\n", rc.description());
314     } catch (std::exception& ex) {
315         fprintf(stderr, "Command failed: %s\n", ex.what());
316     }
317 }
318