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 using Subdoc::Result;
54 
55 struct OpEntry {
56     uint8_t opcode;
57     const char *description;
OpEntryOpEntry58     OpEntry(uint8_t opcode = 0, const char *description = NULL) :
59         opcode(opcode),
60         description(description) {}
61 
operator uint8_tOpEntry62     operator uint8_t() const { return opcode; }
63 };
64 
65 class Options {
66 public:
Options()67     Options() :
68         o_iter('i', "iterations", 1000),
69         o_path('p', "docpath"),
70         o_value('v', "value"),
71         o_jsfile('f', "json"),
72         o_cmd('c', "command"),
73         o_mkdirp('M', "create-intermediate"),
74         o_txtscan('T', "text-scan"),
75         parser("subdoc-bench")
76     {
77         o_iter.description("Number of iterations to run");
78         o_path.description("Document path to manipulate");
79         o_value.description("Document value to insert");
80         o_jsfile.description("JSON files to operate on. If passing multiple files, each file should be delimited by a comma");
81         o_cmd.description("Command to use. Use -c help to show all the commands").mandatory();
82         o_mkdirp.description("Create intermediate paths for mutation operations");
83         o_txtscan.description("Simply scan the text using a naive approach. Used to see how much actual overhead jsonsl induces");
84 
85         parser.addOption(o_iter);
86         parser.addOption(o_path);
87         parser.addOption(o_value);
88         parser.addOption(o_jsfile);
89         parser.addOption(o_cmd);
90         parser.addOption(o_mkdirp);
91         parser.addOption(o_txtscan);
92 
93         totalBytes = 0;
94         // Set the opmap
95         initOpmap();
96     }
97 
initOpmap()98     void initOpmap() {
99         // generic ops:
100         opmap["replace"] = OpEntry(Command::REPLACE, "Replace a value");
101         opmap["delete"] = OpEntry(Command::REMOVE, "Delete a value");
102         opmap["get"] = OpEntry(Command::GET, "Retrieve a value");
103         opmap["exists"] = OpEntry(Command::EXISTS, "Check if a value exists");
104 
105         // dict ops
106         opmap["add"] = OpEntry(Command::DICT_ADD, "Create a new dictionary value");
107         opmap["upsert"] = OpEntry(Command::DICT_UPSERT, "Create or replace a dictionary value");
108 
109         // list ops
110         opmap["append"] = OpEntry(Command::ARRAY_APPEND, "Insert values to the end of an array");
111         opmap["prepend"] = OpEntry(Command::ARRAY_PREPEND, "Insert values to the beginning of an array");
112         opmap["addunique"] = OpEntry(Command::ARRAY_ADD_UNIQUE, "Add a unique value to an array");
113         opmap["insert"] = OpEntry(Command::ARRAY_INSERT, "Insert value at given array index");
114         opmap["size"] = OpEntry(Command::GET_COUNT, "Count the number of items in an array or dict");
115 
116         // arithmetic ops
117         opmap["counter"] = OpEntry(Command::COUNTER);
118 
119         // Generic ops
120         opmap["path"] = OpEntry(0xff, "Check the validity of a path");
121     }
122 
123     UIntOption o_iter;
124     StringOption o_path;
125     StringOption o_value;
126     StringOption o_jsfile;
127     StringOption o_cmd;
128     BoolOption o_mkdirp;
129     BoolOption o_txtscan;
130     map<string,OpEntry> opmap;
131     Parser parser;
132     size_t totalBytes;
133 };
134 
135 static void
readJsonFile(string& name, vector<string>& out)136 readJsonFile(string& name, vector<string>& out)
137 {
138     std::ifstream input(name.c_str());
139     if (input.fail()) {
140         throw name + ": " + strerror(errno);
141     }
142     fprintf(stderr, "Reading %s\n", name.c_str());
143     std::stringstream ss;
144     ss << input.rdbuf();
145     out.push_back(ss.str());
146     input.close();
147 }
148 
149 static void
execOperation(Options& o)150 execOperation(Options& o)
151 {
152     vector<string> fileNames;
153     vector<string> inputStrs;
154     string flist = o.o_jsfile.const_result();
155     if (flist.find(',') == string::npos) {
156         fileNames.push_back(flist);
157     } else {
158         while (true) {
159             size_t pos = flist.find_first_of(',');
160             if (pos == string::npos) {
161                 fileNames.push_back(flist);
162                 break;
163             } else {
164                 string curName = flist.substr(0, pos);
165                 fileNames.push_back(curName);
166                 flist = flist.substr(pos+1);
167             }
168         }
169     }
170     if (fileNames.empty()) {
171         throw string("At least one file must be passed!");
172     }
173     for (size_t ii = 0; ii < fileNames.size(); ii++) {
174         readJsonFile(fileNames[ii], inputStrs);
175         o.totalBytes += inputStrs.back().length();
176     }
177 
178     uint8_t opcode = o.opmap[o.o_cmd.result()];
179     if (o.o_mkdirp.passed()) {
180         opcode |= 0x80;
181     }
182 
183     string value = o.o_value.const_result();
184     string path = o.o_path.const_result();
185     Operation op;
186     Result res;
187 
188 
189     size_t nquotes = 0;
190     bool is_txtscan = o.o_txtscan.result();
191     size_t itermax = o.o_iter.result();
192 
193     int char_table[256] = { 0 };
194     char_table[static_cast<uint8_t >('"')] = 1;
195     char_table[static_cast<uint8_t >('\\')] = 1;
196     char_table[static_cast<uint8_t >('!')] = 1;
197 
198     for (size_t ii = 0; ii < itermax; ii++) {
199         const string& curInput = inputStrs[ii % inputStrs.size()];
200 
201         if (is_txtscan) {
202             const char *buf = curInput.c_str();
203             size_t nbytes = curInput.size();
204             for (; nbytes; buf++, nbytes--) {
205                 if (char_table[static_cast<unsigned char>(*buf)]) {
206                     nquotes++;
207                 }
208             }
209             continue;
210         }
211 
212         op.clear();
213         res.clear();
214         op.set_value(value);
215         op.set_code(opcode);
216         op.set_doc(curInput);
217         op.set_result_buf(&res);
218 
219         Error rv = op.op_exec(path);
220         if (!rv.success()) {
221             throw rv;
222         }
223     }
224 
225     if (nquotes) {
226         printf("Found %lu quotes!\n", nquotes);
227     }
228 
229     // Print the result.
230     if (opcode == Command::GET ||
231             opcode == Command::EXISTS ||
232             opcode == Command::GET_COUNT) {
233         string match = res.matchloc().to_string();
234         printf("%s\n", match.c_str());
235     } else {
236         string newdoc;
237         for (auto ii : res.newdoc()) {
238             newdoc.append(ii.at, ii.length);
239         }
240         printf("%s\n", newdoc.c_str());
241     }
242 }
243 
244 static void
execPathParse(Options& o)245 execPathParse(Options& o)
246 {
247     size_t itermax = o.o_iter.result();
248     string path = o.o_path.const_result();
249     Path pth;
250 
251     for (size_t ii = 0; ii < itermax; ii++) {
252         pth.clear();
253         int rv = pth.parse(path);
254 
255         if (rv != 0) {
256             throw string("Failed to parse path!");
257         }
258     }
259 }
260 
runMain(int argc, char **argv)261 void runMain(int argc, char **argv)
262 {
263     using namespace std::chrono;
264 
265     Options o;
266     if (!o.parser.parse(argc, argv)) {
267         throw string("Bad options!");
268     }
269     // Determine the command
270     string cmdStr = o.o_cmd.const_result();
271     auto t_begin = steady_clock::now();
272 
273     if (cmdStr == "help") {
274         map<string,OpEntry>::const_iterator iter = o.opmap.begin();
275         for (; iter != o.opmap.end(); ++iter) {
276             const OpEntry& ent = iter->second;
277             fprintf(stderr, "%s (0x%x): ", iter->first.c_str(), ent.opcode);
278             fprintf(stderr, "%s\n", ent.description);
279         }
280         exit(EXIT_SUCCESS);
281     }
282     if (!o.o_path.passed()) {
283         fprintf(stderr, "Path (-p) required\n");
284         exit(EXIT_FAILURE);
285     }
286 
287     if (o.opmap.find(cmdStr) != o.opmap.end()) {
288         if (!o.o_jsfile.passed()) {
289             throw string("Operation must contain file!");
290         }
291         execOperation(o);
292     } else if (cmdStr == "path") {
293         execPathParse(o);
294     } else {
295         throw string("Unknown command!");
296     }
297 
298     auto total = duration_cast<duration<double>>(steady_clock::now() - t_begin);
299 
300     // Get the number of seconds:
301     double n_seconds = total.count();
302     double ops_per_sec = static_cast<double>(o.o_iter.result()) / n_seconds;
303     double mb_per_sec = (
304             static_cast<double>(o.totalBytes) *
305             static_cast<double>(o.o_iter.result())) /
306                     n_seconds;
307 
308     mb_per_sec /= (1024 * 1024);
309 
310     fprintf(stderr, "DURATION=%.2fs. OPS=%u\n", n_seconds, o.o_iter.result());
311     fprintf(stderr, "%.2f OPS/s\n",  ops_per_sec);
312     fprintf(stderr, "%.2f MB/s\n", mb_per_sec);
313 }
314 
main(int argc, char **argv)315 int main(int argc, char **argv)
316 {
317     try {
318         runMain(argc, argv);
319         return EXIT_SUCCESS;
320     } catch (string& exc) {
321         fprintf(stderr, "%s\n", exc.c_str());
322         return EXIT_FAILURE;
323     } catch (Error& rc) {
324         fprintf(stderr, "Command failed: %s\n", rc.description());
325         return EXIT_FAILURE;
326     } catch (std::exception& ex) {
327         fprintf(stderr, "Command failed: %s\n", ex.what());
328         return EXIT_FAILURE;
329     }
330 }
331