/* **********************************************************
 * Copyright (c) 2011-2020 Google, Inc.  All rights reserved.
 * **********************************************************/

/*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * * Neither the name of VMware, Inc. nor the names of its contributors may be
 *   used to endorse or promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */

/* DRSyms benchmarking standalone app. */

/* This is a standalone app for benchmarking drsyms.  Currently we just time
 * symbol enumeration of an arbitrary object file.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "dr_api.h"
#include "drsyms.h"

static char sym_buf[4096];

static int
usage(const char *msg)
{
#ifdef WINDOWS
    /* use a symbol forwarded by DR to ntdll that's not an intrinsic to test
     * duplicate link issues vs libcmt
     */
    if (isdigit(msg[0]))
        sym_buf[0] = '\0'; /* avoid warning about empty statement */
#endif
    if (msg != NULL && msg[0] != '\0') {
        dr_fprintf(STDERR, "%s\n", msg);
    }
    dr_fprintf(STDERR, "usage: bench <modpath>\n");
    return 1;
}

/* The work done in this callback is minimal.  Right now it prints out a
 * sampling of mangled, demangled, and fully demangled names.
 */
static bool
sym_callback(const char *name, size_t modoffs, void *data)
{
    uint64 *count = (uint64 *)data;
    *count += 1;
    if (*count % 50000 == 0) {
        dr_printf("{\"%s\",\n", name);
        memset(sym_buf, 0, sizeof(sym_buf));
        if (drsym_demangle_symbol(sym_buf, sizeof(sym_buf), name, DRSYM_DEMANGLE_FULL) !=
            0) {
            dr_printf(" \"%s\",\n", sym_buf);
        }
        memset(sym_buf, 0, sizeof(sym_buf));
        if (drsym_demangle_symbol(sym_buf, sizeof(sym_buf), name, DRSYM_DEMANGLE) != 0) {
            dr_printf(" \"%s\"},\n", sym_buf);
        }
    }
    return true;
}

static void
enumerate_with_flags(const char *modpath, drsym_flags_t flags)
{
    uint64 start, end, time;
    uint64 sym_count = 0;

    dr_printf("Beginning symbol enumeration\n");
    /* Should use clock_gettime with CLOCK_MONOTONIC instead. */
    start = dr_get_milliseconds();
    drsym_enumerate_symbols(modpath, sym_callback, &sym_count, flags);
    end = dr_get_milliseconds();
    dr_printf("Finished symbol enumeration.\n");

    time = end - start;

    dr_printf("Took %d.%03d seconds.\n", (int)(time / 1000), (int)(time % 1000));
}

int
main(int argc, char **argv)
{
    const char *modpath;
#ifdef WINDOWS
    char full_path[2048];
#endif

    dr_standalone_init();
    drsym_init(0);

    if (argc != 2) {
        return usage(NULL);
    }
    modpath = argv[1];
#ifdef WINDOWS
    /* Work around i#289. */
    if (GetFullPathName(modpath, sizeof(full_path), full_path, NULL) == 0) {
        return usage("GetFullPathName failed.\n");
    }
    modpath = full_path;
#endif
    if (!dr_file_exists(modpath)) {
        return usage("Path does not exist.");
    }

    /* The first enumeration populates dbghelp's symbol cache.  We mostly care
     * about how long the second enumeration takes.
     */
    enumerate_with_flags(modpath, DRSYM_DEFAULT_FLAGS);
    enumerate_with_flags(modpath, DRSYM_DEFAULT_FLAGS);

    drsym_exit();
    dr_standalone_exit();
}