xref: /6.0.3/subjson/bench.cc (revision a80d9184)
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <assert.h>
4 #include <string.h>
5 #include <limits.h>
6 #include <errno.h>
7 #include <stdexcept>
8 
9 #include <map>
10 #include <vector>
11 #include <string>
12 #include <fstream>
13 #include <iostream>
14 #include <istream>
15 #include <ostream>
16 #include <sstream>
17 
18 #define CLIOPTS_ENABLE_CXX
19 #define INCLUDE_SUBDOC_NTOHLL
20 #include "subdoc/subdoc-api.h"
21 #include "subdoc/subdoc-util.h"
22 #include "subdoc/path.h"
23 #include "subdoc/match.h"
24 #include "subdoc/operations.h"
25 #include "contrib/cliopts/cliopts.h"
26 
27 using std::string;
28 using std::vector;
29 using std::map;
30 using namespace cliopts;
31 
32 struct OpEntry {
33     uint8_t opcode;
34     const char *description;
35     OpEntry(uint8_t opcode = 0, const char *description = NULL) :
36         opcode(opcode),
37         description(description) {}
38 
39     operator uint8_t() const { return opcode; }
40 };
41 
42 class Options {
43 public:
44     Options() :
45         o_iter('i', "iterations", 1000),
46         o_path('p', "docpath"),
47         o_value('v', "value"),
48         o_jsfile('f', "json"),
49         o_cmd('c', "command"),
50         o_mkdirp('M', "--create-intermediate"),
51         parser("subdoc-bench")
52     {
53         o_iter.description("Number of iterations to run");
54         o_path.description("Document path to manipulate");
55         o_value.description("Document value to insert");
56         o_jsfile.description("JSON files to operate on. If passing multiple files, each file should be delimited by a comma");
57         o_cmd.description("Command to use. Use -c help to show all the commands").mandatory();
58         o_mkdirp.description("Create intermediate paths for mutation operations");
59 
60         parser.addOption(o_iter);
61         parser.addOption(o_path);
62         parser.addOption(o_value);
63         parser.addOption(o_jsfile);
64         parser.addOption(o_cmd);
65         parser.addOption(o_mkdirp);
66 
67         totalBytes = 0;
68         // Set the opmap
69         initOpmap();
70     }
71 
72     void initOpmap() {
73         // generic ops:
74         opmap["replace"] = OpEntry(SUBDOC_CMD_REPLACE, "Replace a value");
75         opmap["delete"] = OpEntry(SUBDOC_CMD_DELETE, "Delete a value");
76         opmap["get"] = OpEntry(SUBDOC_CMD_GET, "Retrieve a value");
77         opmap["exists"] = OpEntry(SUBDOC_CMD_EXISTS, "Check if a value exists");
78 
79         // dict ops
80         opmap["add"] = OpEntry(SUBDOC_CMD_DICT_ADD, "Create a new dictionary value");
81         opmap["upsert"] = OpEntry(SUBDOC_CMD_DICT_UPSERT, "Create or replace a dictionary value");
82 
83         // list ops
84         opmap["append"] = OpEntry(SUBDOC_CMD_ARRAY_APPEND, "Insert values to the end of an array");
85         opmap["prepend"] = OpEntry(SUBDOC_CMD_ARRAY_PREPEND, "Insert values to the beginning of an array");
86         opmap["addunique"] = OpEntry(SUBDOC_CMD_ARRAY_ADD_UNIQUE, "Add a unique value to an array");
87 
88         // arithmetic ops
89         opmap["incr"] = OpEntry(SUBDOC_CMD_INCREMENT, "Increment a value");
90         opmap["decr"] = OpEntry(SUBDOC_CMD_DECREMENT, "Decrement a value");
91         opmap["path"] = OpEntry(0xff, "Check the validity of a path");
92     }
93 
94     UIntOption o_iter;
95     StringOption o_path;
96     StringOption o_value;
97     StringOption o_jsfile;
98     StringOption o_cmd;
99     BoolOption o_mkdirp;
100     map<string,OpEntry> opmap;
101     Parser parser;
102     size_t totalBytes;
103 };
104 
105 #ifdef _WIN32
106 #include <windows.h>
107 static uint64_t
108 get_nstime(void) {
109     double ret;
110     static LARGE_INTEGER pf = { 0 };
111     static double freq;
112     LARGE_INTEGER currtime;
113 
114     if (pf.QuadPart == 0) {
115         QueryPerformanceFrequency(&pf);
116         freq = 1.0e9 / (double)pf.QuadPart;
117     }
118 
119     QueryPerformanceCounter(&currtime);
120 
121     ret = (double)currtime.QuadPart * freq ;
122     return (uint64_t)ret;
123 }
124 #else
125 #include <sys/time.h>
126 static uint64_t
127 get_nstime(void) {
128     struct timeval tv;
129     gettimeofday(&tv, NULL);
130     return (tv.tv_sec * 1000000000) + (tv.tv_usec * 1000);
131 }
132 #endif
133 
134 static void
135 readJsonFile(string& name, vector<string>& out)
136 {
137     std::ifstream input(name.c_str());
138     if (input.fail()) {
139         throw name + ": " + strerror(errno);
140     }
141     fprintf(stderr, "Reading %s\n", name.c_str());
142     std::stringstream ss;
143     ss << input.rdbuf();
144     out.push_back(ss.str());
145     input.close();
146 }
147 
148 static void
149 execOperation(Options& o)
150 {
151     vector<string> fileNames;
152     vector<string> inputStrs;
153     string flist = o.o_jsfile.const_result();
154     if (flist.find(',') == string::npos) {
155         fileNames.push_back(flist);
156     } else {
157         while (true) {
158             size_t pos = flist.find_first_of(',');
159             if (pos == string::npos) {
160                 fileNames.push_back(flist);
161                 break;
162             } else {
163                 string curName = flist.substr(0, pos);
164                 fileNames.push_back(curName);
165                 flist = flist.substr(pos+1);
166             }
167         }
168     }
169     if (fileNames.empty()) {
170         throw string("At least one file must be passed!");
171     }
172     for (size_t ii = 0; ii < fileNames.size(); ii++) {
173         readJsonFile(fileNames[ii], inputStrs);
174         o.totalBytes += inputStrs.back().length();
175     }
176 
177     uint8_t opcode = o.opmap[o.o_cmd.result()];
178     if (o.o_mkdirp.passed()) {
179         opcode |= 0x80;
180     }
181 
182     string value = o.o_value.const_result();
183     string path = o.o_path.const_result();
184     const char *vbuf = value.c_str();
185     size_t nvbuf = value.length();
186     uint64_t dummy;
187 
188     switch (opcode) {
189     case SUBDOC_CMD_INCREMENT:
190     case SUBDOC_CMD_INCREMENT_P:
191     case SUBDOC_CMD_DECREMENT:
192     case SUBDOC_CMD_DECREMENT_P: {
193         int64_t ctmp = (uint64_t)strtoll(vbuf, NULL, 10);
194         if (ctmp == LLONG_MAX && errno == ERANGE) {
195             throw string("Invalid delta for arithmetic operation!");
196         }
197         dummy = ctmp;
198         dummy = htonll(dummy);
199         vbuf = (const char *)&dummy;
200         nvbuf = sizeof dummy;
201         break;
202     }
203     }
204 
205     subdoc_OPERATION *op = subdoc_op_alloc();
206 
207 
208     size_t itermax = o.o_iter.result();
209     for (size_t ii = 0; ii < itermax; ii++) {
210         subdoc_op_clear(op);
211         const string& curInput = inputStrs[ii % inputStrs.size()];
212         SUBDOC_OP_SETCODE(op, opcode);
213         SUBDOC_OP_SETDOC(op, curInput.c_str(), curInput.size());
214         SUBDOC_OP_SETVALUE(op, vbuf, nvbuf);
215 
216         uint16_t rv = subdoc_op_exec(op, path.c_str(), path.size());
217         if (rv != SUBDOC_STATUS_SUCCESS) {
218             throw uint16_t(rv);
219         }
220     }
221 
222     // Print the result.
223     if (opcode == SUBDOC_CMD_GET || opcode == SUBDOC_CMD_EXISTS) {
224         string match(op->match.loc_match.at, op->match.loc_match.length);
225         printf("%s\n", match.c_str());
226     } else {
227         string newdoc;
228         for (size_t ii = 0; ii < op->doc_new_len; ii++) {
229             const subdoc_LOC *loc = &op->doc_new[ii];
230             newdoc.append(loc->at, loc->length);
231         }
232         printf("%s\n", newdoc.c_str());
233     }
234 
235     subdoc_op_free(op);
236 }
237 
238 static void
239 execPathParse(Options& o)
240 {
241     size_t itermax = o.o_iter.result();
242     string path = o.o_path.const_result();
243     subdoc_PATH *pth = subdoc_path_alloc();
244 
245     for (size_t ii = 0; ii < itermax; ii++) {
246         subdoc_path_clear(pth);
247         int rv = subdoc_path_parse(pth, path.c_str(), path.size());
248 
249         if (rv != 0) {
250             throw string("Failed to parse path!");
251         }
252     }
253 
254     subdoc_path_free(pth);
255 }
256 
257 void runMain(int argc, char **argv)
258 {
259     Options o;
260     if (!o.parser.parse(argc, argv)) {
261         throw string("Bad options!");
262     }
263     // Determine the command
264     string cmdStr = o.o_cmd.const_result();
265 
266     uint64_t t_begin = get_nstime();
267 
268     if (cmdStr == "help") {
269         map<string,OpEntry>::const_iterator iter = o.opmap.begin();
270         for (; iter != o.opmap.end(); ++iter) {
271             const OpEntry& ent = iter->second;
272             fprintf(stderr, "%s (0x%x): ", iter->first.c_str(), ent.opcode);
273             fprintf(stderr, "%s\n", ent.description);
274         }
275         exit(EXIT_SUCCESS);
276     }
277     if (!o.o_path.passed()) {
278         fprintf(stderr, "Path (-p) required\n");
279         exit(EXIT_FAILURE);
280     }
281 
282     if (o.opmap.find(cmdStr) != o.opmap.end()) {
283         if (!o.o_jsfile.passed()) {
284             throw string("Operation must contain file!");
285         }
286         execOperation(o);
287     } else if (cmdStr == "path") {
288         execPathParse(o);
289     } else {
290         throw string("Unknown command!");
291     }
292 
293     uint64_t t_total = get_nstime() - t_begin;
294     // Get the number of seconds:
295     double n_seconds = t_total / 1000000000.0;
296     double ops_per_sec = (double)o.o_iter.result() / n_seconds;
297     double mb_per_sec = ((double)o.totalBytes * (double)o.o_iter.result()) / n_seconds;
298     mb_per_sec /= (1024 * 1024);
299 
300     fprintf(stderr, "DURATION=%.2lfs. OPS=%lu\n", n_seconds, o.o_iter.result());
301     fprintf(stderr, "%.2lf OPS/s\n",  ops_per_sec);
302     fprintf(stderr, "%.2lf MB/s\n", mb_per_sec);
303 }
304 
305 int main(int argc, char **argv)
306 {
307     try {
308         runMain(argc, argv);
309         return EXIT_SUCCESS;
310     } catch (string& exc) {
311         fprintf(stderr, "%s\n", exc.c_str());
312         return EXIT_FAILURE;
313     } catch (uint16_t& rc) {
314         fprintf(stderr, "Command failed: %s\n", subdoc_strerror(rc));
315     }
316 }
317