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