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