]> git.proxmox.com Git - ceph.git/blob - ceph/src/tools/ceph_kvstore_tool.cc
e9f31091c1c9d5837e8389cea64f99a694075a6e
[ceph.git] / ceph / src / tools / ceph_kvstore_tool.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3 /*
4 * Ceph - scalable distributed file system
5 *
6 * Copyright (C) 2012 Inktank, Inc.
7 *
8 * This is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License version 2.1, as published by the Free Software
11 * Foundation. See file COPYING.
12 */
13 #include <map>
14 #include <set>
15 #include <string>
16
17 #include <boost/scoped_ptr.hpp>
18
19 #include "common/ceph_argparse.h"
20 #include "common/config.h"
21 #include "common/errno.h"
22 #include "common/strtol.h"
23 #include "global/global_context.h"
24 #include "global/global_init.h"
25 #include "include/stringify.h"
26 #include "include/utime.h"
27 #include "common/Clock.h"
28 #include "kv/KeyValueDB.h"
29 #include "common/url_escape.h"
30
31 #ifdef HAVE_LIBAIO
32 #include "os/bluestore/BlueStore.h"
33 #endif
34
35 using namespace std;
36
37 class StoreTool
38 {
39 boost::scoped_ptr<BlueStore> bluestore;
40
41 // TODO: make KeyValueDB enable_shared_from_this
42 // bluestore will hold *db* also, use unique_ptr/shared_ptr will
43 // double free.
44 KeyValueDB* db;
45
46 string store_path;
47
48 public:
49 StoreTool(string type, const string &path) : store_path(path) {
50 KeyValueDB *db_ptr;
51 if (type == "bluestore-kv") {
52 #ifdef HAVE_LIBAIO
53 // note: we'll leak this! the only user is ceph-kvstore-tool and
54 // we don't care.
55 bluestore.reset(new BlueStore(g_ceph_context, path));
56 int r = bluestore->start_kv_only(&db_ptr);
57 if (r < 0) {
58 exit(1);
59 }
60 #else
61 cerr << "bluestore not compiled in" << std::endl;
62 exit(1);
63 #endif
64 } else {
65 db_ptr = KeyValueDB::create(g_ceph_context, type, path);
66 int r = db_ptr->open(std::cerr);
67 if (r < 0) {
68 cerr << "failed to open type " << type << " path " << path << ": "
69 << cpp_strerror(r) << std::endl;
70 exit(1);
71 }
72 }
73 db = db_ptr;
74 }
75
76 ~StoreTool() {
77 if (bluestore) {
78 bluestore->umount();
79 }
80 else {
81 if (db) {
82 delete db;
83 }
84 }
85 }
86
87 uint32_t traverse(const string &prefix,
88 const bool do_crc,
89 ostream *out) {
90 KeyValueDB::WholeSpaceIterator iter = db->get_iterator();
91
92 if (prefix.empty())
93 iter->seek_to_first();
94 else
95 iter->seek_to_first(prefix);
96
97 uint32_t crc = -1;
98
99 while (iter->valid()) {
100 pair<string,string> rk = iter->raw_key();
101 if (!prefix.empty() && (rk.first != prefix))
102 break;
103
104 if (out)
105 *out << url_escape(rk.first) << "\t" << url_escape(rk.second);
106 if (do_crc) {
107 bufferlist bl;
108 bl.append(rk.first);
109 bl.append(rk.second);
110 bl.append(iter->value());
111
112 crc = bl.crc32c(crc);
113 if (out) {
114 *out << "\t" << bl.crc32c(0);
115 }
116 }
117 if (out)
118 *out << std::endl;
119 iter->next();
120 }
121
122 return crc;
123 }
124
125 void list(const string &prefix, const bool do_crc) {
126 traverse(prefix, do_crc, &std::cout);
127 }
128
129 bool exists(const string &prefix) {
130 assert(!prefix.empty());
131 KeyValueDB::WholeSpaceIterator iter = db->get_iterator();
132 iter->seek_to_first(prefix);
133 return (iter->valid() && (iter->raw_key().first == prefix));
134 }
135
136 bool exists(const string &prefix, const string &key) {
137 assert(!prefix.empty());
138
139 if (key.empty()) {
140 return exists(prefix);
141 }
142
143 bool exists = false;
144 get(prefix, key, exists);
145 return exists;
146 }
147
148 bufferlist get(const string &prefix, const string &key, bool &exists) {
149 assert(!prefix.empty() && !key.empty());
150
151 map<string,bufferlist> result;
152 std::set<std::string> keys;
153 keys.insert(key);
154 db->get(prefix, keys, &result);
155
156 if (result.count(key) > 0) {
157 exists = true;
158 return result[key];
159 }
160 exists = false;
161 return bufferlist();
162 }
163
164 uint64_t get_size() {
165 map<string,uint64_t> extras;
166 uint64_t s = db->get_estimated_size(extras);
167 for (map<string,uint64_t>::iterator p = extras.begin();
168 p != extras.end(); ++p) {
169 std::cout << p->first << " - " << p->second << std::endl;
170 }
171 std::cout << "total: " << s << std::endl;
172 return s;
173 }
174
175 bool set(const string &prefix, const string &key, bufferlist &val) {
176 assert(!prefix.empty());
177 assert(!key.empty());
178 assert(val.length() > 0);
179
180 KeyValueDB::Transaction tx = db->get_transaction();
181 tx->set(prefix, key, val);
182 int ret = db->submit_transaction_sync(tx);
183
184 return (ret == 0);
185 }
186
187 bool rm(const string& prefix, const string& key) {
188 assert(!prefix.empty());
189 assert(!key.empty());
190
191 KeyValueDB::Transaction tx = db->get_transaction();
192 tx->rmkey(prefix, key);
193 int ret = db->submit_transaction_sync(tx);
194
195 return (ret == 0);
196 }
197
198 bool rm_prefix(const string& prefix) {
199 assert(!prefix.empty());
200
201 KeyValueDB::Transaction tx = db->get_transaction();
202 tx->rmkeys_by_prefix(prefix);
203 int ret = db->submit_transaction_sync(tx);
204
205 return (ret == 0);
206 }
207
208 int copy_store_to(string type, const string &other_path,
209 const int num_keys_per_tx) {
210
211 if (num_keys_per_tx <= 0) {
212 std::cerr << "must specify a number of keys/tx > 0" << std::endl;
213 return -EINVAL;
214 }
215
216 // open or create a leveldb store at @p other_path
217 KeyValueDB *other = KeyValueDB::create(g_ceph_context, type, other_path);
218 int err = other->create_and_open(std::cerr);
219 if (err < 0)
220 return err;
221
222 KeyValueDB::WholeSpaceIterator it = db->get_iterator();
223 it->seek_to_first();
224 uint64_t total_keys = 0;
225 uint64_t total_size = 0;
226 uint64_t total_txs = 0;
227
228 utime_t started_at = ceph_clock_now();
229
230 do {
231 int num_keys = 0;
232
233 KeyValueDB::Transaction tx = other->get_transaction();
234
235
236 while (it->valid() && num_keys < num_keys_per_tx) {
237 pair<string,string> k = it->raw_key();
238 bufferlist v = it->value();
239 tx->set(k.first, k.second, v);
240
241 num_keys ++;
242 total_size += v.length();
243
244 it->next();
245 }
246
247 total_txs ++;
248 total_keys += num_keys;
249
250 if (num_keys > 0)
251 other->submit_transaction_sync(tx);
252
253 utime_t cur_duration = ceph_clock_now() - started_at;
254 std::cout << "ts = " << cur_duration << "s, copied " << total_keys
255 << " keys so far (" << stringify(si_t(total_size)) << ")"
256 << std::endl;
257
258 } while (it->valid());
259
260 utime_t time_taken = ceph_clock_now() - started_at;
261
262 std::cout << "summary:" << std::endl;
263 std::cout << " copied " << total_keys << " keys" << std::endl;
264 std::cout << " used " << total_txs << " transactions" << std::endl;
265 std::cout << " total size " << stringify(si_t(total_size)) << std::endl;
266 std::cout << " from '" << store_path << "' to '" << other_path << "'"
267 << std::endl;
268 std::cout << " duration " << time_taken << " seconds" << std::endl;
269
270 return 0;
271 }
272
273 void compact() {
274 db->compact();
275 }
276 void compact_prefix(string prefix) {
277 db->compact_prefix(prefix);
278 }
279 void compact_range(string prefix, string start, string end) {
280 db->compact_range(prefix, start, end);
281 }
282 };
283
284 void usage(const char *pname)
285 {
286 std::cerr << "Usage: " << pname << " <leveldb|rocksdb|bluestore-kv> <store path> command [args...]\n"
287 << "\n"
288 << "Commands:\n"
289 << " list [prefix]\n"
290 << " list-crc [prefix]\n"
291 << " exists <prefix> [key]\n"
292 << " get <prefix> <key> [out <file>]\n"
293 << " crc <prefix> <key>\n"
294 << " get-size [<prefix> <key>]\n"
295 << " set <prefix> <key> [ver <N>|in <file>]\n"
296 << " rm <prefix> <key>\n"
297 << " rm-prefix <prefix>\n"
298 << " store-copy <path> [num-keys-per-tx]\n"
299 << " store-crc <path>\n"
300 << " compact\n"
301 << " compact-prefix <prefix>\n"
302 << " compact-range <prefix> <start> <end>\n"
303 << std::endl;
304 }
305
306 int main(int argc, const char *argv[])
307 {
308 vector<const char*> args;
309 argv_to_vec(argc, argv, args);
310 env_to_vec(args);
311
312 auto cct = global_init(
313 NULL, args,
314 CEPH_ENTITY_TYPE_CLIENT, CODE_ENVIRONMENT_UTILITY, 0);
315 common_init_finish(g_ceph_context);
316
317
318 if (args.size() < 3) {
319 usage(argv[0]);
320 return 1;
321 }
322
323 string type(args[0]);
324 string path(args[1]);
325 string cmd(args[2]);
326
327 StoreTool st(type, path);
328
329 if (cmd == "list" || cmd == "list-crc") {
330 string prefix;
331 if (argc > 4)
332 prefix = url_unescape(argv[4]);
333
334 bool do_crc = (cmd == "list-crc");
335
336 st.list(prefix, do_crc);
337
338 } else if (cmd == "exists") {
339 string key;
340 if (argc < 5) {
341 usage(argv[0]);
342 return 1;
343 }
344 string prefix(url_unescape(argv[4]));
345 if (argc > 5)
346 key = url_unescape(argv[5]);
347
348 bool ret = st.exists(prefix, key);
349 std::cout << "(" << url_escape(prefix) << ", " << url_escape(key) << ") "
350 << (ret ? "exists" : "does not exist")
351 << std::endl;
352 return (ret ? 0 : 1);
353
354 } else if (cmd == "get") {
355 if (argc < 6) {
356 usage(argv[0]);
357 return 1;
358 }
359 string prefix(url_unescape(argv[4]));
360 string key(url_unescape(argv[5]));
361
362 bool exists = false;
363 bufferlist bl = st.get(prefix, key, exists);
364 std::cout << "(" << url_escape(prefix) << ", " << url_escape(key) << ")";
365 if (!exists) {
366 std::cout << " does not exist" << std::endl;
367 return 1;
368 }
369 std::cout << std::endl;
370
371 if (argc >= 7) {
372 string subcmd(argv[6]);
373 if (subcmd != "out") {
374 std::cerr << "unrecognized subcmd '" << subcmd << "'"
375 << std::endl;
376 return 1;
377 }
378 if (argc < 8) {
379 std::cerr << "output path not specified" << std::endl;
380 return 1;
381 }
382 string out(argv[7]);
383
384 if (out.empty()) {
385 std::cerr << "unspecified out file" << std::endl;
386 return 1;
387 }
388
389 int err = bl.write_file(argv[7], 0644);
390 if (err < 0) {
391 std::cerr << "error writing value to '" << out << "': "
392 << cpp_strerror(err) << std::endl;
393 return 1;
394 }
395 } else {
396 ostringstream os;
397 bl.hexdump(os);
398 std::cout << os.str() << std::endl;
399 }
400
401 } else if (cmd == "crc") {
402 if (argc < 6) {
403 usage(argv[0]);
404 return 1;
405 }
406 string prefix(url_unescape(argv[4]));
407 string key(url_unescape(argv[5]));
408
409 bool exists = false;
410 bufferlist bl = st.get(prefix, key, exists);
411 std::cout << "(" << url_escape(prefix) << ", " << url_escape(key) << ") ";
412 if (!exists) {
413 std::cout << " does not exist" << std::endl;
414 return 1;
415 }
416 std::cout << " crc " << bl.crc32c(0) << std::endl;
417
418 } else if (cmd == "get-size") {
419 std::cout << "estimated store size: " << st.get_size() << std::endl;
420
421 if (argc < 5)
422 return 0;
423
424 if (argc < 6) {
425 usage(argv[0]);
426 return 1;
427 }
428 string prefix(url_unescape(argv[4]));
429 string key(url_unescape(argv[5]));
430
431 bool exists = false;
432 bufferlist bl = st.get(prefix, key, exists);
433 if (!exists) {
434 std::cerr << "(" << url_escape(prefix) << "," << url_escape(key)
435 << ") does not exist" << std::endl;
436 return 1;
437 }
438 std::cout << "(" << url_escape(prefix) << "," << url_escape(key)
439 << ") size " << si_t(bl.length()) << std::endl;
440
441 } else if (cmd == "set") {
442 if (argc < 8) {
443 usage(argv[0]);
444 return 1;
445 }
446 string prefix(url_unescape(argv[4]));
447 string key(url_unescape(argv[5]));
448 string subcmd(argv[6]);
449
450 bufferlist val;
451 string errstr;
452 if (subcmd == "ver") {
453 version_t v = (version_t) strict_strtoll(argv[7], 10, &errstr);
454 if (!errstr.empty()) {
455 std::cerr << "error reading version: " << errstr << std::endl;
456 return 1;
457 }
458 ::encode(v, val);
459 } else if (subcmd == "in") {
460 int ret = val.read_file(argv[7], &errstr);
461 if (ret < 0 || !errstr.empty()) {
462 std::cerr << "error reading file: " << errstr << std::endl;
463 return 1;
464 }
465 } else {
466 std::cerr << "unrecognized subcommand '" << subcmd << "'" << std::endl;
467 usage(argv[0]);
468 return 1;
469 }
470
471 bool ret = st.set(prefix, key, val);
472 if (!ret) {
473 std::cerr << "error setting ("
474 << url_escape(prefix) << "," << url_escape(key) << ")" << std::endl;
475 return 1;
476 }
477 } else if (cmd == "rm") {
478 if (argc < 6) {
479 usage(argv[0]);
480 return 1;
481 }
482 string prefix(url_unescape(argv[4]));
483 string key(url_unescape(argv[5]));
484
485 bool ret = st.rm(prefix, key);
486 if (!ret) {
487 std::cerr << "error removing ("
488 << url_escape(prefix) << "," << url_escape(key) << ")"
489 << std::endl;
490 return 1;
491 }
492 } else if (cmd == "rm-prefix") {
493 if (argc < 5) {
494 usage(argv[0]);
495 return 1;
496 }
497 string prefix(url_unescape(argv[4]));
498
499 bool ret = st.rm_prefix(prefix);
500 if (!ret) {
501 std::cerr << "error removing prefix ("
502 << url_escape(prefix) << ")"
503 << std::endl;
504 return 1;
505 }
506 } else if (cmd == "store-copy") {
507 int num_keys_per_tx = 128; // magic number that just feels right.
508 if (argc < 5) {
509 usage(argv[0]);
510 return 1;
511 } else if (argc > 5) {
512 string err;
513 num_keys_per_tx = strict_strtol(argv[5], 10, &err);
514 if (!err.empty()) {
515 std::cerr << "invalid num_keys_per_tx: " << err << std::endl;
516 return 1;
517 }
518 }
519
520 int ret = st.copy_store_to(argv[1], argv[4], num_keys_per_tx);
521 if (ret < 0) {
522 std::cerr << "error copying store to path '" << argv[4]
523 << "': " << cpp_strerror(ret) << std::endl;
524 return 1;
525 }
526
527 } else if (cmd == "store-crc") {
528 uint32_t crc = st.traverse(string(), true, NULL);
529 std::cout << "store at '" << path << "' crc " << crc << std::endl;
530
531 } else if (cmd == "compact") {
532 st.compact();
533 } else if (cmd == "compact-prefix") {
534 if (argc < 5) {
535 usage(argv[0]);
536 return 1;
537 }
538 string prefix(url_unescape(argv[4]));
539 st.compact_prefix(prefix);
540 } else if (cmd == "compact-range") {
541 if (argc < 7) {
542 usage(argv[0]);
543 return 1;
544 }
545 string prefix(url_unescape(argv[4]));
546 string start(url_unescape(argv[5]));
547 string end(url_unescape(argv[6]));
548 st.compact_range(prefix, start, end);
549 } else {
550 std::cerr << "Unrecognized command: " << cmd << std::endl;
551 return 1;
552 }
553
554 return 0;
555 }