/*
 * Copyright (c) 2018 Red Hat, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA. 
 *
 * $Id: //eng/vdo-releases/magnesium/src/c++/vdo/kernel/dump.c#2 $
 */

#include "dump.h"

#include <linux/module.h>

#include "memoryAlloc.h"

#include "constants.h"
#include "vdo.h"

#include "dedupeIndex.h"
#include "histogram.h"
#include "logger.h"
#include "readCache.h"

enum dumpOptions {
  // WorkQueues
  SHOW_ALBIREO_QUEUE,
  SHOW_BIO_ACK_QUEUE,
  SHOW_BIO_QUEUE,
  SHOW_CPU_QUEUES,
  SHOW_REQUEST_QUEUE,
  // MemoryPools
  SHOW_VIO_POOL,
  // Others
  SHOW_READCACHES,
  SHOW_VDO_STATUS,
  // This one means an option overrides the "default" choices, instead
  // of altering them.
  SKIP_DEFAULT
};

enum dumpOptionFlags {
  // WorkQueues
  FLAG_SHOW_ALBIREO_QUEUE = (1 << SHOW_ALBIREO_QUEUE),
  FLAG_SHOW_BIO_ACK_QUEUE = (1 << SHOW_BIO_ACK_QUEUE),
  FLAG_SHOW_BIO_QUEUE     = (1 << SHOW_BIO_QUEUE),
  FLAG_SHOW_CPU_QUEUES    = (1 << SHOW_CPU_QUEUES),
  FLAG_SHOW_REQUEST_QUEUE = (1 << SHOW_REQUEST_QUEUE),
  // MemoryPools
  FLAG_SHOW_VIO_POOL      = (1 << SHOW_VIO_POOL),
  // Others
  FLAG_SHOW_READCACHES    = (1 << SHOW_READCACHES),
  FLAG_SHOW_VDO_STATUS    = (1 << SHOW_VDO_STATUS),
  // Special
  FLAG_SKIP_DEFAULT       = (1 << SKIP_DEFAULT)
  };

enum {
  FLAGS_ALL_POOLS    = (FLAG_SHOW_VIO_POOL),
  FLAGS_ALL_QUEUES   = (FLAG_SHOW_REQUEST_QUEUE
                        | FLAG_SHOW_ALBIREO_QUEUE
                        | FLAG_SHOW_BIO_ACK_QUEUE
                        | FLAG_SHOW_BIO_QUEUE
                        | FLAG_SHOW_CPU_QUEUES),
  FLAGS_ALL_THREADS  = (FLAGS_ALL_QUEUES),
  DEFAULT_DUMP_FLAGS = (FLAGS_ALL_THREADS | FLAG_SHOW_VDO_STATUS)
};

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

/**********************************************************************/
static void doDump(KernelLayer  *layer,
                   unsigned int  dumpOptionsRequested,
                   const char   *why)
{
  logInfo("%s dump triggered via %s", THIS_MODULE->name, why);
  // XXX Add in number of outstanding requests being processed by vdo
  uint32_t active, maximum;
  getLimiterValuesAtomically(&layer->requestLimiter, &active, &maximum);
  logInfo("%" PRIu32 " device requests outstanding (max %" PRIu32 "), "
          "%ld bio requests outstanding, poolName '%s'",
          active, maximum,
          (atomic64_read(&layer->biosSubmitted)
           - atomic64_read(&layer->biosCompleted)),
          layer->deviceConfig->poolName);
  if ((dumpOptionsRequested & FLAG_SHOW_REQUEST_QUEUE) != 0) {
    dumpKVDOWorkQueue(&layer->kvdo);
  }
  if ((dumpOptionsRequested & FLAG_SHOW_BIO_QUEUE) != 0) {
    dumpBioWorkQueue(layer->ioSubmitter);
  }
  if (useBioAckQueue(layer)
      && ((dumpOptionsRequested & FLAG_SHOW_BIO_ACK_QUEUE) != 0)) {
    dumpWorkQueue(layer->bioAckQueue);
  }
  if ((dumpOptionsRequested & FLAG_SHOW_CPU_QUEUES) != 0) {
    dumpWorkQueue(layer->cpuQueue);
  }
  dumpDedupeIndex(layer->dedupeIndex,
                  (dumpOptionsRequested & FLAG_SHOW_ALBIREO_QUEUE) != 0);
  dumpBufferPool(layer->dataKVIOPool,
                 (dumpOptionsRequested & FLAG_SHOW_VIO_POOL) != 0);
  if ((dumpOptionsRequested & FLAG_SHOW_READCACHES) != 0) {
    readCacheDump(getIOSubmitterReadCache(layer->ioSubmitter), true, false);
  }
  if ((dumpOptionsRequested & FLAG_SHOW_VDO_STATUS) != 0) {
    // Options should become more fine-grained when we have more to
    // display here.
    dumpKVDOStatus(&layer->kvdo);
  }
  reportMemoryUsage();
  logInfo("end of %s dump", THIS_MODULE->name);
}

/**********************************************************************/
static int parseDumpOptions(unsigned int  argc,
                            char * const *argv,
                            unsigned int *dumpOptionsRequestedPtr)
{
  unsigned int dumpOptionsRequested = 0;

  static const struct {
    const char   *name;
    unsigned int  flags;
  } optionNames[] = {
    // Should "albireo" mean sending queue + receiving thread + outstanding?
    { "dedupe",      FLAG_SKIP_DEFAULT | FLAG_SHOW_ALBIREO_QUEUE },
    { "dedupeq",     FLAG_SKIP_DEFAULT | FLAG_SHOW_ALBIREO_QUEUE },
    { "kvdodedupeq", FLAG_SKIP_DEFAULT | FLAG_SHOW_ALBIREO_QUEUE },
    { "bioack",      FLAG_SKIP_DEFAULT | FLAG_SHOW_BIO_ACK_QUEUE },
    { "kvdobioackq", FLAG_SKIP_DEFAULT | FLAG_SHOW_BIO_ACK_QUEUE },
    { "bioackq",     FLAG_SKIP_DEFAULT | FLAG_SHOW_BIO_ACK_QUEUE },
    { "bio",         FLAG_SKIP_DEFAULT | FLAG_SHOW_BIO_QUEUE },
    { "kvdobioq",    FLAG_SKIP_DEFAULT | FLAG_SHOW_BIO_QUEUE },
    { "bioq",        FLAG_SKIP_DEFAULT | FLAG_SHOW_BIO_QUEUE },
    { "cpu",         FLAG_SKIP_DEFAULT | FLAG_SHOW_CPU_QUEUES },
    { "kvdocpuq",    FLAG_SKIP_DEFAULT | FLAG_SHOW_CPU_QUEUES },
    { "cpuq",        FLAG_SKIP_DEFAULT | FLAG_SHOW_CPU_QUEUES },
    { "request",     FLAG_SKIP_DEFAULT | FLAG_SHOW_REQUEST_QUEUE },
    { "kvdoreqq",    FLAG_SKIP_DEFAULT | FLAG_SHOW_REQUEST_QUEUE },
    { "reqq",        FLAG_SKIP_DEFAULT | FLAG_SHOW_REQUEST_QUEUE },
    { "viopool",     FLAG_SKIP_DEFAULT | FLAG_SHOW_VIO_POOL },
    { "vdo",         FLAG_SKIP_DEFAULT | FLAG_SHOW_VDO_STATUS },
    { "readcache",   FLAG_SKIP_DEFAULT | FLAG_SHOW_READCACHES },
    { "readcaches",  FLAG_SKIP_DEFAULT | FLAG_SHOW_READCACHES },

    { "pools",   FLAG_SKIP_DEFAULT | FLAGS_ALL_POOLS },
    { "queues",  FLAG_SKIP_DEFAULT | FLAGS_ALL_QUEUES },
    { "threads", FLAG_SKIP_DEFAULT | FLAGS_ALL_THREADS },
    { "default", FLAG_SKIP_DEFAULT | DEFAULT_DUMP_FLAGS },
    { "all",      ~0 },
  };

  bool optionsOkay = true;
  for (int i = 1; i < argc; i++) {
    int j;
    for (j = 0; j < COUNT_OF(optionNames); j++) {
      if (isArgString(argv[i], optionNames[j].name)) {
        dumpOptionsRequested |= optionNames[j].flags;
        break;
      }
    }
    if (j == COUNT_OF(optionNames)) {
      logWarning("dump option name '%s' unknown", argv[i]);
      optionsOkay = false;
    }
  }
  if (!optionsOkay) {
    return -EINVAL;
  }
  if ((dumpOptionsRequested & FLAG_SKIP_DEFAULT) == 0) {
    dumpOptionsRequested |= DEFAULT_DUMP_FLAGS;
  }
  *dumpOptionsRequestedPtr = dumpOptionsRequested;
  return 0;
}

/**********************************************************************/
int vdoDump(KernelLayer  *layer,
            unsigned int  argc,
            char * const *argv,
            const char   *why)
{
  unsigned int dumpOptionsRequested = 0;
  int result = parseDumpOptions(argc, argv, &dumpOptionsRequested);
  if (result != 0) {
    return result;
  }
  doDump(layer, dumpOptionsRequested, why);
  return 0;
}

/**********************************************************************/
void vdoDumpAll(KernelLayer *layer, const char *why)
{
  doDump(layer, ~0, why);
}
