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 
115         // arithmetic ops
116         opmap["counter"] = OpEntry(Command::COUNTER);
117 
118         // Generic ops
119         opmap["path"] = OpEntry(0xff, "Check the validity of a path");
120     }
121 
122     UIntOption o_iter;
123     StringOption o_path;
124     StringOption o_value;
125     StringOption o_jsfile;
126     StringOption o_cmd;
127     BoolOption o_mkdirp;
128     BoolOption o_txtscan;
129     map<string,OpEntry> opmap;
130     Parser parser;
131     size_t totalBytes;
132 };
133 
134 static void
readJsonFile(string& name, vector<string>& out)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
execOperation(Options& o)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     Operation op;
185     Result res;
186 
187 
188     size_t nquotes = 0;
189     bool is_txtscan = o.o_txtscan.result();
190     size_t itermax = o.o_iter.result();
191 
192     int char_table[256] = { 0 };
193     char_table[static_cast<uint8_t >('"')] = 1;
194     char_table[static_cast<uint8_t >('\\')] = 1;
195     char_table[static_cast<uint8_t >('!')] = 1;
196 
197     for (size_t ii = 0; ii < itermax; ii++) {
198         const string& curInput = inputStrs[ii % inputStrs.size()];
199 
200         if (is_txtscan) {
201             const char *buf = curInput.c_str();
202             size_t nbytes = curInput.size();
203             for (; nbytes; buf++, nbytes--) {
204                 if (char_table[static_cast<unsigned char>(*buf)]) {
205                     nquotes++;
206                 }
207             }
208             continue;
209         }
210 
211         op.clear();
212         res.clear();
213         op.set_value(value);
214         op.set_code(opcode);
215         op.set_doc(curInput);
216         op.set_result_buf(&res);
217 
218         Error rv = op.op_exec(path);
219         if (!rv.success()) {
220             throw rv;
221         }
222     }
223 
224     if (nquotes) {
225         printf("Found %lu quotes!\n", nquotes);
226     }
227 
228     // Print the result.
229     if (opcode == Command::GET || opcode == Command::EXISTS) {
230         string match = res.matchloc().to_string();
231         printf("%s\n", match.c_str());
232     } else {
233         string newdoc;
234         for (auto ii : res.newdoc()) {
235             newdoc.append(ii.at, ii.length);
236         }
237         printf("%s\n", newdoc.c_str());
238     }
239 }
240 
241 static void
execPathParse(Options& o)242 execPathParse(Options& o)
243 {
244     size_t itermax = o.o_iter.result();
245     string path = o.o_path.const_result();
246     Path pth;
247 
248     for (size_t ii = 0; ii < itermax; ii++) {
249         pth.clear();
250         int rv = pth.parse(path);
251 
252         if (rv != 0) {
253             throw string("Failed to parse path!");
254         }
255     }
256 }
257 
runMain(int argc, char **argv)258 void runMain(int argc, char **argv)
259 {
260     using namespace std::chrono;
261 
262     Options o;
263     if (!o.parser.parse(argc, argv)) {
264         throw string("Bad options!");
265     }
266     // Determine the command
267     string cmdStr = o.o_cmd.const_result();
268     auto t_begin = steady_clock::now();
269 
270     if (cmdStr == "help") {
271         map<string,OpEntry>::const_iterator iter = o.opmap.begin();
272         for (; iter != o.opmap.end(); ++iter) {
273             const OpEntry& ent = iter->second;
274             fprintf(stderr, "%s (0x%x): ", iter->first.c_str(), ent.opcode);
275             fprintf(stderr, "%s\n", ent.description);
276         }
277         exit(EXIT_SUCCESS);
278     }
279     if (!o.o_path.passed()) {
280         fprintf(stderr, "Path (-p) required\n");
281         exit(EXIT_FAILURE);
282     }
283 
284     if (o.opmap.find(cmdStr) != o.opmap.end()) {
285         if (!o.o_jsfile.passed()) {
286             throw string("Operation must contain file!");
287         }
288         execOperation(o);
289     } else if (cmdStr == "path") {
290         execPathParse(o);
291     } else {
292         throw string("Unknown command!");
293     }
294 
295     auto total = duration_cast<duration<double>>(steady_clock::now() - t_begin);
296 
297     // Get the number of seconds:
298     double n_seconds = total.count();
299     double ops_per_sec = static_cast<double>(o.o_iter.result()) / n_seconds;
300     double mb_per_sec = (
301             static_cast<double>(o.totalBytes) *
302             static_cast<double>(o.o_iter.result())) /
303                     n_seconds;
304 
305     mb_per_sec /= (1024 * 1024);
306 
307     fprintf(stderr, "DURATION=%.2fs. OPS=%u\n", n_seconds, o.o_iter.result());
308     fprintf(stderr, "%.2f OPS/s\n",  ops_per_sec);
309     fprintf(stderr, "%.2f MB/s\n", mb_per_sec);
310 }
311 
main(int argc, char **argv)312 int main(int argc, char **argv)
313 {
314     try {
315         runMain(argc, argv);
316         return EXIT_SUCCESS;
317     } catch (string& exc) {
318         fprintf(stderr, "%s\n", exc.c_str());
319         return EXIT_FAILURE;
320     } catch (Error& rc) {
321         fprintf(stderr, "Command failed: %s\n", rc.description());
322     } catch (std::exception& ex) {
323         fprintf(stderr, "Command failed: %s\n", ex.what());
324     }
325 }
326