root/drivers/md/dm-vdo/dump.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright 2023 Red Hat
 */

#include "dump.h"

#include <linux/module.h>

#include "memory-alloc.h"
#include "string-utils.h"

#include "constants.h"
#include "data-vio.h"
#include "dedupe.h"
#include "funnel-workqueue.h"
#include "io-submitter.h"
#include "logger.h"
#include "types.h"
#include "vdo.h"

enum dump_options {
        /* Work queues */
        SHOW_QUEUES,
        /* Memory pools */
        SHOW_VIO_POOL,
        /* Others */
        SHOW_VDO_STATUS,
        /* This one means an option overrides the "default" choices, instead of altering them. */
        SKIP_DEFAULT
};

enum dump_option_flags {
        /* Work queues */
        FLAG_SHOW_QUEUES = (1 << SHOW_QUEUES),
        /* Memory pools */
        FLAG_SHOW_VIO_POOL = (1 << SHOW_VIO_POOL),
        /* Others */
        FLAG_SHOW_VDO_STATUS = (1 << SHOW_VDO_STATUS),
        /* Special */
        FLAG_SKIP_DEFAULT = (1 << SKIP_DEFAULT)
};

#define FLAGS_ALL_POOLS (FLAG_SHOW_VIO_POOL)
#define DEFAULT_DUMP_FLAGS (FLAG_SHOW_QUEUES | FLAG_SHOW_VDO_STATUS)
/* Another static buffer... log10(256) = 2.408+, round up: */
#define DIGITS_PER_U64 (1 + sizeof(u64) * 2409 / 1000)

static inline bool is_arg_string(const char *arg, const char *this_option)
{
        /* convention seems to be case-independent options */
        return strncasecmp(arg, this_option, strlen(this_option)) == 0;
}

static void do_dump(struct vdo *vdo, unsigned int dump_options_requested,
                    const char *why)
{
        u32 active, maximum;
        s64 outstanding;

        vdo_log_info("%s dump triggered via %s", VDO_LOGGING_MODULE_NAME, why);
        active = get_data_vio_pool_active_requests(vdo->data_vio_pool);
        maximum = get_data_vio_pool_maximum_requests(vdo->data_vio_pool);
        outstanding = (atomic64_read(&vdo->stats.bios_submitted) -
                       atomic64_read(&vdo->stats.bios_completed));
        vdo_log_info("%u device requests outstanding (max %u), %lld bio requests outstanding, device '%s'",
                     active, maximum, outstanding,
                     vdo_get_device_name(vdo->device_config->owning_target));
        if (((dump_options_requested & FLAG_SHOW_QUEUES) != 0) && (vdo->threads != NULL)) {
                thread_id_t id;

                for (id = 0; id < vdo->thread_config.thread_count; id++)
                        vdo_dump_work_queue(vdo->threads[id].queue);
        }

        vdo_dump_hash_zones(vdo->hash_zones);
        dump_data_vio_pool(vdo->data_vio_pool,
                           (dump_options_requested & FLAG_SHOW_VIO_POOL) != 0);
        if ((dump_options_requested & FLAG_SHOW_VDO_STATUS) != 0)
                vdo_dump_status(vdo);

        vdo_report_memory_usage();
        vdo_log_info("end of %s dump", VDO_LOGGING_MODULE_NAME);
}

static int parse_dump_options(unsigned int argc, char *const *argv,
                              unsigned int *dump_options_requested_ptr)
{
        unsigned int dump_options_requested = 0;

        static const struct {
                const char *name;
                unsigned int flags;
        } option_names[] = {
                { "viopool", FLAG_SKIP_DEFAULT | FLAG_SHOW_VIO_POOL },
                { "vdo", FLAG_SKIP_DEFAULT | FLAG_SHOW_VDO_STATUS },
                { "pools", FLAG_SKIP_DEFAULT | FLAGS_ALL_POOLS },
                { "queues", FLAG_SKIP_DEFAULT | FLAG_SHOW_QUEUES },
                { "threads", FLAG_SKIP_DEFAULT | FLAG_SHOW_QUEUES },
                { "default", FLAG_SKIP_DEFAULT | DEFAULT_DUMP_FLAGS },
                { "all", ~0 },
        };

        bool options_okay = true;
        unsigned int i;

        for (i = 1; i < argc; i++) {
                unsigned int j;

                for (j = 0; j < ARRAY_SIZE(option_names); j++) {
                        if (is_arg_string(argv[i], option_names[j].name)) {
                                dump_options_requested |= option_names[j].flags;
                                break;
                        }
                }
                if (j == ARRAY_SIZE(option_names)) {
                        vdo_log_warning("dump option name '%s' unknown", argv[i]);
                        options_okay = false;
                }
        }
        if (!options_okay)
                return -EINVAL;
        if ((dump_options_requested & FLAG_SKIP_DEFAULT) == 0)
                dump_options_requested |= DEFAULT_DUMP_FLAGS;
        *dump_options_requested_ptr = dump_options_requested;
        return 0;
}

/* Dump as specified by zero or more string arguments. */
int vdo_dump(struct vdo *vdo, unsigned int argc, char *const *argv, const char *why)
{
        unsigned int dump_options_requested = 0;
        int result = parse_dump_options(argc, argv, &dump_options_requested);

        if (result != 0)
                return result;

        do_dump(vdo, dump_options_requested, why);
        return 0;
}

/* Dump everything we know how to dump */
void vdo_dump_all(struct vdo *vdo, const char *why)
{
        do_dump(vdo, ~0, why);
}

/*
 * Dump out the data_vio waiters on a waitq.
 * wait_on should be the label to print for queue (e.g. logical or physical)
 */
static void dump_vio_waiters(struct vdo_wait_queue *waitq, char *wait_on)
{
        struct vdo_waiter *waiter, *first = vdo_waitq_get_first_waiter(waitq);
        struct data_vio *data_vio;

        if (first == NULL)
                return;

        data_vio = vdo_waiter_as_data_vio(first);

        vdo_log_info("      %s is locked. Waited on by: vio %px pbn %llu lbn %llu d-pbn %llu lastOp %s",
                     wait_on, data_vio, data_vio->allocation.pbn, data_vio->logical.lbn,
                     data_vio->duplicate.pbn, get_data_vio_operation_name(data_vio));

        for (waiter = first->next_waiter; waiter != first; waiter = waiter->next_waiter) {
                data_vio = vdo_waiter_as_data_vio(waiter);
                vdo_log_info("     ... and : vio %px pbn %llu lbn %llu d-pbn %llu lastOp %s",
                             data_vio, data_vio->allocation.pbn, data_vio->logical.lbn,
                             data_vio->duplicate.pbn,
                             get_data_vio_operation_name(data_vio));
        }
}

/*
 * Encode various attributes of a data_vio as a string of one-character flags. This encoding is for
 * logging brevity:
 *
 * R => vio completion result not VDO_SUCCESS
 * W => vio is on a waitq
 * D => vio is a duplicate
 * p => vio is a partial block operation
 * z => vio is a zero block
 * d => vio is a discard
 *
 * The common case of no flags set will result in an empty, null-terminated buffer. If any flags
 * are encoded, the first character in the string will be a space character.
 */
static void encode_vio_dump_flags(struct data_vio *data_vio, char buffer[8])
{
        char *p_flag = buffer;
        *p_flag++ = ' ';
        if (data_vio->vio.completion.result != VDO_SUCCESS)
                *p_flag++ = 'R';
        if (data_vio->waiter.next_waiter != NULL)
                *p_flag++ = 'W';
        if (data_vio->is_duplicate)
                *p_flag++ = 'D';
        if (data_vio->is_partial)
                *p_flag++ = 'p';
        if (data_vio->is_zero)
                *p_flag++ = 'z';
        if (data_vio->remaining_discard > 0)
                *p_flag++ = 'd';
        if (p_flag == &buffer[1]) {
                /* No flags, so remove the blank space. */
                p_flag = buffer;
        }
        *p_flag = '\0';
}

/* Implements buffer_dump_function. */
void dump_data_vio(void *data)
{
        struct data_vio *data_vio = data;

        /*
         * This just needs to be big enough to hold a queue (thread) name and a function name (plus
         * a separator character and NUL). The latter is limited only by taste.
         *
         * In making this static, we're assuming only one "dump" will run at a time. If more than
         * one does run, the log output will be garbled anyway.
         */
        static char vio_completion_dump_buffer[100 + MAX_VDO_WORK_QUEUE_NAME_LEN];
        static char vio_block_number_dump_buffer[sizeof("P L D") + 3 * DIGITS_PER_U64];
        static char vio_flush_generation_buffer[sizeof(" FG") + DIGITS_PER_U64];
        static char flags_dump_buffer[8];

        /*
         * We're likely to be logging a couple thousand of these lines, and in some circumstances
         * syslogd may have trouble keeping up, so keep it BRIEF rather than user-friendly.
         */
        vdo_dump_completion_to_buffer(&data_vio->vio.completion,
                                      vio_completion_dump_buffer,
                                      sizeof(vio_completion_dump_buffer));
        if (data_vio->is_duplicate) {
                snprintf(vio_block_number_dump_buffer,
                         sizeof(vio_block_number_dump_buffer), "P%llu L%llu D%llu",
                         data_vio->allocation.pbn, data_vio->logical.lbn,
                         data_vio->duplicate.pbn);
        } else if (data_vio_has_allocation(data_vio)) {
                snprintf(vio_block_number_dump_buffer,
                         sizeof(vio_block_number_dump_buffer), "P%llu L%llu",
                         data_vio->allocation.pbn, data_vio->logical.lbn);
        } else {
                snprintf(vio_block_number_dump_buffer,
                         sizeof(vio_block_number_dump_buffer), "L%llu",
                         data_vio->logical.lbn);
        }

        if (data_vio->flush_generation != 0) {
                snprintf(vio_flush_generation_buffer,
                         sizeof(vio_flush_generation_buffer), " FG%llu",
                         data_vio->flush_generation);
        } else {
                vio_flush_generation_buffer[0] = 0;
        }

        encode_vio_dump_flags(data_vio, flags_dump_buffer);

        vdo_log_info("  vio %px %s%s %s %s%s", data_vio,
                     vio_block_number_dump_buffer,
                     vio_flush_generation_buffer,
                     get_data_vio_operation_name(data_vio),
                     vio_completion_dump_buffer,
                     flags_dump_buffer);
        /*
         * might want info on: wantUDSAnswer / operation / status
         * might want info on: bio / bios_merged
         */

        dump_vio_waiters(&data_vio->logical.waiters, "lbn");

        /* might want to dump more info from vio here */
}