main.cpp 19.2 KB
Newer Older
Q
qinzuoyan 已提交
1 2 3 4 5
// Copyright (c) 2017, Xiaomi, Inc.  All rights reserved.
// This source code is licensed under the Apache License Version 2.0, which
// can be found in the LICENSE file in the root directory of this source tree.

#include <pegasus/version.h>
6
#include <dsn/utility/strings.h>
Q
qinzuoyan 已提交
7 8 9 10 11 12 13
#include <setjmp.h>
#include <signal.h>
#include <algorithm>
#include "args.h"
#include "command_executor.h"
#include "commands.h"

14 15 16 17
std::map<std::string, command_executor *> s_commands_map;
shell_context s_global_context;
size_t s_max_name_length = 0;
size_t s_option_width = 70;
Q
qinzuoyan 已提交
18

19 20 21 22 23 24 25
void print_help();
bool help_info(command_executor *e, shell_context *sc, arguments args)
{
    print_help();
    return true;
}

26
static command_executor commands[] = {
27 28 29
    {
        "help", "print help info", "", help_info,
    },
Q
qinzuoyan 已提交
30 31 32 33 34 35 36 37 38
    {
        "version", "get the shell version", "", version,
    },
    {
        "cluster_info", "get the informations for the cluster", "", query_cluster_info,
    },
    {
        "app",
        "get the partition information for some specific app",
39
        "<app_name> [-d|--detailed] [-o|--output file_name]",
Q
qinzuoyan 已提交
40 41 42 43 44
        query_app,
    },
    {
        "app_disk",
        "get the disk usage information for some specific app",
45
        "<app_name> [-d|--detailed] [-o|--output file_name]",
Q
qinzuoyan 已提交
46 47 48 49 50
        app_disk,
    },
    {
        "ls",
        "list all apps",
51 52
        "[-a|-all] [-d|--detailed] [-o|--output file_name] "
        "[-s|--status all|available|creating|dropping|dropped]",
Q
qinzuoyan 已提交
53 54 55 56 57
        ls_apps,
    },
    {
        "nodes",
        "get the node status for this cluster",
58
        "[-d|--detailed] [-o|--output file_name] [-s|--status all|alive|unalive]",
Q
qinzuoyan 已提交
59 60 61 62 63
        ls_nodes,
    },
    {
        "create",
        "create an app",
64 65
        "<app_name> [-p|--partition_count num] [-r|--replica_count num] "
        "[-e|--envs k1=v1,k2=v2...]",
Q
qinzuoyan 已提交
66 67 68
        create_app,
    },
    {
69
        "drop", "drop an app", "<app_name> [-r|--reserve_seconds num]", drop_app,
Q
qinzuoyan 已提交
70 71 72 73 74 75 76
    },
    {
        "recall", "recall an app", "<app_id> [new_app_name]", recall_app,
    },
    {
        "set_meta_level",
        "set the meta function level: stopped, blind, freezed, steady, lively",
77
        "<stopped|blind|freezed|steady|lively>",
Q
qinzuoyan 已提交
78 79 80 81 82 83 84 85
        set_meta_level,
    },
    {
        "get_meta_level", "get the meta function level", "", get_meta_level,
    },
    {
        "balance",
        "send explicit balancer request for the cluster",
86 87
        "<-g|--gpid appid.pidx> <-p|--type move_pri|copy_pri|copy_sec> <-f|--from from_address> "
        "<-t|--to to_address>",
Q
qinzuoyan 已提交
88 89 90 91 92 93
        balance,
    },
    {
        "propose",
        "send configuration proposals to cluster",
        "[-f|--force] "
94 95
        "<-g|--gpid appid.pidx> <-p|--type ASSIGN_PRIMARY|ADD_SECONDARY|DOWNGRADE_TO_INACTIVE...> "
        "<-t|--target node_to_exec_command> <-n|--node node_to_be_affected> ",
Q
qinzuoyan 已提交
96 97 98 99 100 101 102 103
        propose,
    },
    {
        "use",
        "set the current app used for the data access commands",
        "[app_name]",
        use_app_as_current,
    },
Q
qinzuoyan 已提交
104 105 106 107 108 109
    {
        "escape_all",
        "if escape all characters when printing key/value bytes",
        "[true|false]",
        process_escape_all,
    },
110 111 112 113 114 115
    {
        "timeout",
        "default timeout in milliseconds for read/write operations",
        "[time_in_ms]",
        process_timeout,
    },
Q
qinzuoyan 已提交
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
    {
        "hash",
        "calculate the hash result for some hash key",
        "<hash_key> <sort_key>",
        calculate_hash_value,
    },
    {
        "set", "set value", "<hash_key> <sort_key> <value> [ttl_in_seconds]", data_operations,
    },
    {
        "multi_set",
        "set multiple values for a single hash key",
        "<hash_key> <sort_key> <value> [sort_key value...]",
        data_operations,
    },
    {
        "get", "get value", "<hash_key> <sort_key>", data_operations,
    },
    {
        "multi_get",
        "get multiple values for a single hash key",
        "<hash_key> [sort_key...]",
        data_operations,
    },
140 141 142 143
    {
        "multi_get_range",
        "get multiple values under sort key range for a single hash key",
        "<hash_key> <start_sort_key> <stop_sort_key> "
144 145 146 147
        "[-a|--start_inclusive true|false] [-b|--stop_inclusive true|false] "
        "[-s|--sort_key_filter_type anywhere|prefix|postfix] "
        "[-y|--sort_key_filter_pattern str] "
        "[-n|--max_count num] [-i|--no_value] [-r|--reverse]",
148 149
        data_operations,
    },
Q
qinzuoyan 已提交
150 151 152 153 154 155 156
    {
        "multi_get_sortkeys",
        "get multiple sort keys for a single hash key",
        "<hash_key>",
        data_operations,
    },
    {
157
        "del", "delete a key", "<hash_key> <sort_key>", data_operations,
Q
qinzuoyan 已提交
158 159 160 161 162 163 164
    },
    {
        "multi_del",
        "delete multiple values for a single hash key",
        "<hash_key> <sort_key> [sort_key...]",
        data_operations,
    },
165 166 167 168
    {
        "multi_del_range",
        "delete multiple values under sort key range for a single hash key",
        "<hash_key> <start_sort_key> <stop_sort_key> "
169 170 171 172
        "[-a|--start_inclusive true|false] [-b|--stop_inclusive true|false] "
        "[-s|--sort_key_filter_type anywhere|prefix|postfix] "
        "[-y|--sort_key_filter_pattern str] "
        "[-o|--output file_name] [-i|--silent]",
173 174
        data_operations,
    },
Q
QinZuoyan 已提交
175
    {
Q
QinZuoyan 已提交
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
        "incr",
        "atomically increment value of a key",
        "<hash_key> <sort_key> [increment]",
        data_operations,
    },
    {
        "check_and_set",
        "atomically check and set value",
        "<hash_key> "
        "[-c|--check_sort_key str] "
        "[-t|--check_type not_exist|not_exist_or_empty|exist|not_empty] "
        "[match_anywhere|match_prefix|match_postfix] "
        "[bytes_less|bytes_less_or_equal|bytes_equal|bytes_greater_or_equal|bytes_greater] "
        "[int_less|int_less_or_equal|int_equal|int_greater_or_equal|int_greater] "
        "[-o|--check_operand str] "
        "[-s|--set_sort_key str] "
        "[-v|--set_value str] "
        "[-l|--set_value_ttl_seconds num] "
        "[-r|--return_check_value]",
        data_operations,
Q
QinZuoyan 已提交
196
    },
197 198 199 200 201 202 203 204 205 206 207 208 209
    {
        "check_and_mutate",
        "atomically check and mutate",
        "<hash_key> "
        "[-c|--check_sort_key str] "
        "[-t|--check_type not_exist|not_exist_or_empty|exist|not_empty] "
        "[match_anywhere|match_prefix|match_postfix] "
        "[bytes_less|bytes_less_or_equal|bytes_equal|bytes_greater_or_equal|bytes_greater] "
        "[int_less|int_less_or_equal|int_equal|int_greater_or_equal|int_greater] "
        "[-o|--check_operand str] "
        "[-r|--return_check_value]",
        data_operations,
    },
Q
qinzuoyan 已提交
210 211 212 213 214 215 216 217 218 219
    {
        "exist", "check value exist", "<hash_key> <sort_key>", data_operations,
    },
    {
        "count", "get sort key count for a single hash key", "<hash_key>", data_operations,
    },
    {
        "ttl", "query ttl for a specific key", "<hash_key> <sort_key>", data_operations,
    },
    {
220
        "hash_scan",
Q
qinzuoyan 已提交
221
        "scan all sorted keys for a single hash key",
222 223 224 225
        "<hash_key> <start_sort_key> <stop_sort_key> "
        "[-a|--start_inclusive true|false] [-b|--stop_inclusive true|false] "
        "[-s|--sort_key_filter_type anywhere|prefix|postfix] "
        "[-y|--sort_key_filter_pattern str] "
226 227
        "[-o|--output file_name] [-z|--batch_size num] [-n|--max_count num] "
        "[-t|--timeout_ms num] [-d|--detailed] [-i|--no_value]",
Q
qinzuoyan 已提交
228 229 230
        data_operations,
    },
    {
231 232
        "full_scan",
        "scan all hash keys",
233 234 235 236
        "[-h|--hash_key_filter_type anywhere|prefix|postfix] "
        "[-x|--hash_key_filter_pattern str] "
        "[-s|--sort_key_filter_type anywhere|prefix|postfix] "
        "[-y|--sort_key_filter_pattern str] "
237 238
        "[-o|--output file_name] [-z|--batch_size num] [-n|--max_count num] "
        "[-t|--timeout_ms num] [-d|--detailed] [-i|--no_value] [-p|--partition num]",
Q
qinzuoyan 已提交
239 240 241 242 243
        data_operations,
    },
    {
        "copy_data",
        "copy app data",
244
        "<-c|--target_cluster_name str> <-a|--target_app_name str> "
Q
QinZuoyan 已提交
245 246
        "[-s|--max_split_count num] [-b|--max_batch_count num] [-t|--timeout_ms num] "
        "[-g|--geo_data]",
Q
qinzuoyan 已提交
247 248 249 250 251
        data_operations,
    },
    {
        "clear_data",
        "clear app data",
252 253
        "[-f|--force] [-s|--max_split_count num] [-b|--max_batch_count num] "
        "[-t|--timeout_ms num]",
Q
qinzuoyan 已提交
254 255 256 257 258
        data_operations,
    },
    {
        "count_data",
        "get app row count",
259
        "[-s|--max_split_count num] [-b|--max_batch_count num] "
260 261
        "[-t|--timeout_ms num] [-z|--stat_size] [-c|--top_count num] "
        "[-r|--run_seconds num]",
Q
qinzuoyan 已提交
262 263 264 265 266
        data_operations,
    },
    {
        "remote_command",
        "send remote command to servers",
267 268
        "[-t all|meta-server|replica-server] [-l ip:port,ip:port...] "
        "<command> [arguments...]",
Q
qinzuoyan 已提交
269 270 271 272 273
        remote_command,
    },
    {
        "server_info",
        "get info of servers",
274
        "[-t all|meta-server|replica-server] [-l ip:port,ip:port...]",
Q
qinzuoyan 已提交
275 276 277 278 279
        server_info,
    },
    {
        "server_stat",
        "get stat of servers",
280
        "[-t all|meta-server|replica-server] [-l ip:port,ip:port...]",
Q
qinzuoyan 已提交
281 282 283
        server_stat,
    },
    {
284 285 286 287
        "app_stat",
        "get stat of apps",
        "[-a|--app_name str] [-q|--only_qps] [-o|--output file_name]",
        app_stat,
Q
qinzuoyan 已提交
288 289 290 291
    },
    {
        "flush_log",
        "flush log of servers",
292
        "[-t all|meta-server|replica-server] [-l ip:port,ip:port...]",
Q
qinzuoyan 已提交
293 294 295 296 297 298 299 300
        flush_log,
    },
    {
        "local_get", "get value from local db", "<db_path> <hash_key> <sort_key>", local_get,
    },
    {
        "sst_dump",
        "dump sstable dir or files",
301 302
        "[--command=check|scan|none|raw] <--file=data_dir_OR_sst_file> "
        "[--from=user_key] [--to=user_key] [--read_num=num] [--show_properties]",
Q
qinzuoyan 已提交
303 304 305 306 307
        sst_dump,
    },
    {
        "mlog_dump",
        "dump mutation log dir",
308
        "<-i|--input log_dir> [-o|--output file_name] [-d|--detailed]",
Q
qinzuoyan 已提交
309 310 311 312 313
        mlog_dump,
    },
    {
        "recover",
        "control the meta to recover the system from given nodes",
314 315 316
        "[-f|--node_list_file file_name] [-s|--node_list_str str] "
        "[-w|--wait_seconds num] "
        "[-b|--skip_bad_nodes] [-l|--skip_lost_partitions] [-o|--output file_name]",
Q
qinzuoyan 已提交
317 318
        recover,
    },
C
cailiuyang 已提交
319 320 321
    {
        "add_backup_policy",
        "add new cold backup policy",
322 323 324
        "<-p|--policy_name str> <-b|--backup_provider_type str> <-a|--app_ids 1,2...> "
        "<-i|--backup_interval_seconds num> <-s|--start_time hour:minute> "
        "<-c|--backup_history_cnt num>",
C
cailiuyang 已提交
325 326
        add_backup_policy,
    },
327
    {"ls_backup_policy", "list the names of the subsistent backup policies", "", ls_backup_policy},
C
cailiuyang 已提交
328 329
    {
        "query_backup_policy",
330
        "query subsistent backup policy and last backup infos",
331
        "<-p|--policy_name p1,p2...> [-b|--backup_info_cnt num]",
C
cailiuyang 已提交
332 333 334 335 336
        query_backup_policy,
    },
    {
        "modify_backup_policy",
        "modify the backup policy",
337 338
        "<-p|--policy_name str> [-a|--add_app 1,2...] [-r|--remove_app 1,2...] "
        "[-i|--backup_interval_seconds num] [-c|--backup_history_count num] "
C
cailiuyang 已提交
339
        "[-s|--start_time hour:minute]",
C
cailiuyang 已提交
340 341 342 343 344
        modify_backup_policy,
    },
    {
        "disable_backup_policy",
        "stop policy continue backup",
345
        "<-p|--policy_name str>",
C
cailiuyang 已提交
346 347 348 349 350
        disable_backup_policy,
    },
    {
        "enable_backup_policy",
        "start backup policy to backup again",
351
        "<-p|--policy_name str>",
C
cailiuyang 已提交
352 353 354 355 356
        enable_backup_policy,
    },
    {
        "restore_app",
        "restore app from backup media",
357
        "<-c|--old_cluster_name str> <-p|--old_policy_name str> <-a|--old_app_name str> "
C
cailiuyang 已提交
358
        "<-i|--old_app_id id> <-t|--timestamp/backup_id timestamp> "
359
        "<-b|--backup_provider_type str> [-n|--new_app_name str] [-s|--skip_bad_partition]",
C
cailiuyang 已提交
360 361 362
        restore,
    },
    {
363 364
        "query_restore_status",
        "query restore status",
C
cailiuyang 已提交
365
        "<restore_app_id> [-d|--detailed]",
366
        query_restore_status,
C
cailiuyang 已提交
367
    },
368 369 370
    {
        "get_app_envs", "get current app envs", "", get_app_envs,
    },
371
    {
372
        "set_app_envs", "set current app envs", "<key> <value> [key value...]", set_app_envs,
373 374
    },
    {
375
        "del_app_envs", "delete current app envs", "<key> [key...]", del_app_envs,
376 377 378 379
    },
    {
        "clear_app_envs", "clear current app envs", "<-a|--all> <-p|--prefix str>", clear_app_envs,
    },
Q
qinzuoyan 已提交
380 381 382 383 384 385 386
    {
        "exit", "exit shell", "", exit_shell,
    },
    {
        nullptr, nullptr, nullptr, nullptr,
    }};

387
void print_help(command_executor *e, size_t name_width, size_t option_width)
Q
qinzuoyan 已提交
388
{
389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
    std::vector<std::string> lines;
    std::string options(e->option_usage);
    int line_start = 0;
    int line_end = -1;
    int i;
    for (i = 0; i < options.size(); i++) {
        if (i - line_start >= option_width && line_end >= line_start) {
            std::string s = options.substr(line_start, line_end - line_start + 1);
            std::string r = dsn::utils::trim_string((char *)s.c_str());
            if (!r.empty())
                lines.push_back(r);
            line_start = line_end + 2;
        }
        if ((options[i] == ']' || options[i] == '>') && i < options.size() - 1 &&
            options[i + 1] == ' ') {
            line_end = i;
        }
    }
    line_end = i - 1;
    if (line_end >= line_start) {
        std::string s = options.substr(line_start, line_end - line_start + 1);
        std::string r = dsn::utils::trim_string((char *)s.c_str());
        if (!r.empty())
            lines.push_back(r);
    }

    std::cout << "\t" << e->name << std::string(name_width + 2 - strlen(e->name), ' ');
    if (lines.empty()) {
        std::cout << std::endl;
    } else {
        for (int k = 0; k < lines.size(); k++) {
            if (k != 0)
                std::cout << "\t" << std::string(name_width + 2, ' ');
            std::cout << lines[k] << std::endl;
        }
    }
Q
qinzuoyan 已提交
425 426 427 428 429 430
}

void print_help()
{
    std::cout << "Usage:" << std::endl;
    for (int i = 0; commands[i].name != nullptr; ++i) {
431
        print_help(&commands[i], s_max_name_length, s_option_width);
Q
qinzuoyan 已提交
432 433 434 435 436 437
    }
}

void register_all_commands()
{
    for (int i = 0; commands[i].name != nullptr; ++i) {
438
        auto pr = s_commands_map.emplace(commands[i].name, &commands[i]);
Q
qinzuoyan 已提交
439
        dassert(pr.second, "the command '%s' is already registered!!!", commands[i].name);
440
        s_max_name_length = std::max(s_max_name_length, strlen(commands[i].name));
Q
qinzuoyan 已提交
441 442 443
    }
}

444
void execute_command(command_executor *e, int argc, sds *str_args)
Q
qinzuoyan 已提交
445
{
446
    if (!e->exec(e, &s_global_context, {argc, str_args})) {
Q
qinzuoyan 已提交
447
        printf("USAGE: ");
448
        print_help(e, s_max_name_length, s_option_width);
Q
qinzuoyan 已提交
449 450 451
    }
}

452 453
/* Linenoise completion callback. */
static void completionCallback(const char *buf, linenoiseCompletions *lc)
Q
qinzuoyan 已提交
454
{
455 456 457 458 459 460 461
    for (int i = 0; commands[i].name != nullptr; ++i) {
        const command_executor &c = commands[i];

        size_t matchlen = strlen(buf);
        if (strncasecmp(buf, c.name, matchlen) == 0) {
            linenoiseAddCompletion(lc, c.name);
        }
Q
qinzuoyan 已提交
462 463 464
    }
}

465 466
/* Linenoise hints callback. */
static char *hintsCallback(const char *buf, int *color, int *bold)
Q
qinzuoyan 已提交
467
{
468 469 470 471 472 473
    int argc;
    sds *argv = sdssplitargs(buf, &argc);
    auto cleanup = dsn::defer([argc, argv]() { sdsfreesplitres(argv, argc); });

    /* Check if the argument list is empty and return ASAP. */
    if (argc == 0) {
Q
QinZuoyan 已提交
474
        return nullptr;
Q
qinzuoyan 已提交
475 476
    }

477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496
    size_t buflen = strlen(buf);
    bool endWithSpace = buflen && isspace(buf[buflen - 1]);

    for (int i = 0; commands[i].name != nullptr; ++i) {
        if (strcasecmp(argv[0], commands[i].name) == 0) {
            *color = 90;
            *bold = 0;
            sds hint = sdsnew(commands[i].option_usage);

            /* Add an initial space if needed. */
            if (!endWithSpace) {
                sds newhint = sdsnewlen(" ", 1);
                newhint = sdscatsds(newhint, hint);
                sdsfree(hint);
                hint = newhint;
            }

            return hint;
        }
    }
Q
QinZuoyan 已提交
497
    return nullptr;
498 499 500 501 502 503 504
}

/* Linenoise free hints callback. */
static void freeHintsCallback(void *ptr) { sdsfree((sds)ptr); }

void initialize(int argc, char **argv)
{
Q
qinzuoyan 已提交
505 506
    std::cout << "Pegasus Shell " << PEGASUS_VERSION << std::endl;
    std::cout << "Type \"help\" for more information." << std::endl;
507
    std::cout << "Type \"Ctrl-D\" or \"Ctrl-C\" to exit the shell." << std::endl;
Q
qinzuoyan 已提交
508 509 510 511 512 513 514 515 516 517 518
    std::cout << std::endl;

    std::string config_file = argc > 1 ? argv[1] : "config.ini";
    if (!pegasus::pegasus_client_factory::initialize(config_file.c_str())) {
        std::cout << "ERROR: init pegasus failed: " << config_file << std::endl;
        dsn_exit(-1);
    } else {
        std::cout << "The config file is: " << config_file << std::endl;
    }

    std::string cluster_name = argc > 2 ? argv[2] : "mycluster";
519 520
    s_global_context.current_cluster_name = cluster_name;
    std::string section = "uri-resolver.dsn://" + s_global_context.current_cluster_name;
Q
qinzuoyan 已提交
521 522 523 524
    std::string key = "arguments";
    std::string server_list = dsn_config_get_value_string(section.c_str(), key.c_str(), "", "");

    dsn::replication::replica_helper::load_meta_servers(
525
        s_global_context.meta_list, section.c_str(), key.c_str());
526 527
    s_global_context.ddl_client.reset(
        new dsn::replication::replication_ddl_client(s_global_context.meta_list));
Q
qinzuoyan 已提交
528

529 530 531 532 533 534 535 536 537
    // get real cluster name from zk
    std::string name;
    ::dsn::error_code err = s_global_context.ddl_client->cluster_name(1000, name);
    if (err == dsn::ERR_OK) {
        cluster_name = name;
    }
    std::cout << "The cluster name is: " << cluster_name << std::endl;
    std::cout << "The cluster meta list is: " << server_list << std::endl;

538 539 540 541 542 543
    linenoiseSetMultiLine(1);
    linenoiseSetCompletionCallback(completionCallback);
    linenoiseSetHintsCallback(hintsCallback);
    linenoiseSetFreeHintsCallback(freeHintsCallback);
    linenoiseHistoryLoad(".shell-history");

Q
qinzuoyan 已提交
544 545 546 547 548 549
    register_all_commands();
}

void run()
{
    while (true) {
550 551 552 553
        int arg_count = 0;
        sds *args = scanfCommand(&arg_count);
        auto cleanup = dsn::defer([args, arg_count] { sdsfreesplitres(args, arg_count); });

Q
QinZuoyan 已提交
554
        if (args == nullptr) {
555 556 557 558
            printf("Invalid argument(s)\n");
            continue;
        }

Q
qinzuoyan 已提交
559
        if (arg_count > 0) {
560 561
            auto iter = s_commands_map.find(args[0]);
            if (iter != s_commands_map.end()) {
562 563 564
                // command executions(e.g. check_and_mutate) may have the different hints, so cancel
                // the commands hints temporarily
                linenoiseSetHintsCallback(nullptr);
Q
qinzuoyan 已提交
565
                execute_command(iter->second, arg_count, args);
566
                linenoiseSetHintsCallback(hintsCallback);
Q
qinzuoyan 已提交
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599
            } else {
                std::cout << "ERROR: invalid subcommand '" << args[0] << "'" << std::endl;
                print_help();
            }
        }
    }
}

int main(int argc, char **argv)
{
    initialize(argc, argv);
    run();
    return 0;
}

#if defined(__linux__)
#include <dsn/git_commit.h>
#include <dsn/version.h>
#include <pegasus/git_commit.h>
#include <pegasus/version.h>
static char const rcsid[] =
    "$Version: Pegasus Shell " PEGASUS_VERSION " (" PEGASUS_GIT_COMMIT ")"
#if defined(DSN_BUILD_TYPE)
    " " STR(DSN_BUILD_TYPE)
#endif
        ", built with rDSN " DSN_CORE_VERSION " (" DSN_GIT_COMMIT ")"
        ", built by gcc " STR(__GNUC__) "." STR(__GNUC_MINOR__) "." STR(__GNUC_PATCHLEVEL__)
#if defined(DSN_BUILD_HOSTNAME)
            ", built on " STR(DSN_BUILD_HOSTNAME)
#endif
                ", built at " __DATE__ " " __TIME__ " $";
const char *pegasus_shell_rcsid() { return rcsid; }
#endif