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