xref: /6.0.3/subjson/bench.cc (revision a76064ec)
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
44using std::string;
45using std::vector;
46using std::map;
47using namespace cliopts;
48
49using Subdoc::Path;
50using Subdoc::Operation;
51using Subdoc::Command;
52using Subdoc::Error;
53using Subdoc::Result;
54
55struct OpEntry {
56    uint8_t opcode;
57    const char *description;
58    OpEntry(uint8_t opcode = 0, const char *description = NULL) :
59        opcode(opcode),
60        description(description) {}
61
62    operator uint8_t() const { return opcode; }
63};
64
65class Options {
66public:
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
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
135static void
136readJsonFile(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
149static void
150execOperation(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
244static void
245execPathParse(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
261void 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
315int 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