/*
 * print_info.c
 *
 * Copyright (C) 2011 NEC Corporation
 *
 * 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.
 */
#include "print_info.h"
#include <time.h>
#include <string.h>

#define PROGRESS_MAXLEN		"50"

#define NSEC_PER_SEC		1000000000L

int message_level;
int flag_strerr_message;
int flag_ignore_r_char; /* 0: '\r' is effective. 1: not effective. */

void
show_version(void)
{
	MSG("makedumpfile: version " VERSION " (released on " RELEASE_DATE ")\n");
#ifdef USELZO
	MSG("lzo\tenabled\n");
#else
	MSG("lzo\tdisabled\n");
#endif
#ifdef USESNAPPY
	MSG("snappy\tenabled\n");
#else
	MSG("snappy\tdisabled\n");
#endif
#ifdef USEZSTD
	MSG("zstd\tenabled\n");
#else
	MSG("zstd\tdisabled\n");
#endif
	MSG("\n");
}

void
print_usage(void)
{
	MSG("\n");
	MSG("LZO support:\n");
#ifdef USELZO
	MSG("  enabled\n");
#else
	MSG("  disabled ('-l' option will be ignored.)\n");
#endif
	MSG("snappy support:\n");
#ifdef USESNAPPY
	MSG("  enabled\n");
#else
	MSG("  disabled ('-p' option will be ignored.)\n");
#endif
	MSG("zstd support:\n");
#ifdef USEZSTD
	MSG("  enabled\n");
#else
	MSG("  disabled ('-z' option will be ignored.)\n");
#endif
	MSG("\n");
	MSG("Usage:\n");
	MSG("  Creating DUMPFILE:\n");
	MSG("  # makedumpfile    [-c|-l|-p|-z|-E] [-d DL] [-e] [-x VMLINUX|-i VMCOREINFO] VMCORE\n");
	MSG("    DUMPFILE\n");
	MSG("\n");
	MSG("  Creating DUMPFILE with filtered kernel data specified through filter config\n");
	MSG("  file or eppic macro:\n");
	MSG("  # makedumpfile    [-c|-l|-p|-z|-E] [-d DL] -x VMLINUX [--config FILTERCONFIGFILE]\n");
	MSG("    [--eppic EPPICMACRO] VMCORE DUMPFILE\n");
	MSG("\n");
	MSG("  Outputting the dump data in the flattened format to the standard output:\n");
	MSG("  # makedumpfile -F [-c|-l|-p|-z|-E] [-d DL] [-x VMLINUX|-i VMCOREINFO] VMCORE\n");
	MSG("\n");
	MSG("  Rearranging the dump data in the flattened format to a readable DUMPFILE:\n");
	MSG("  # makedumpfile -R DUMPFILE\n");
	MSG("\n");
	MSG("  Split the dump data to multiple DUMPFILEs in parallel:\n");
	MSG("  # makedumpfile --split [OPTION] [-x VMLINUX|-i VMCOREINFO] VMCORE DUMPFILE1\n");
	MSG("    DUMPFILE2 [DUMPFILE3 ..]\n");
	MSG("\n");
	MSG("  Using multiple threads to create DUMPFILE in parallel:\n");
	MSG("  # makedumpfile [OPTION] [-x VMLINUX|-i VMCOREINFO] --num-threads THREADNUM\n");
	MSG("    VMCORE DUMPFILE1\n");
	MSG("\n");
	MSG("  Reassemble multiple DUMPFILEs:\n");
	MSG("  # makedumpfile --reassemble DUMPFILE1 DUMPFILE2 [DUMPFILE3 ..] DUMPFILE\n");
	MSG("\n");
	MSG("  Generating VMCOREINFO:\n");
	MSG("  # makedumpfile -g VMCOREINFO -x VMLINUX\n");
	MSG("\n");
	MSG("  Extracting the dmesg log from a VMCORE:\n");
	MSG("  # makedumpfile --dump-dmesg [-x VMLINUX|-i VMCOREINFO] VMCORE LOGFILE\n");
	MSG("\n");
	MSG("\n");
	MSG("  Creating DUMPFILE of Xen:\n");
	MSG("  # makedumpfile [-c|-l|-p|-z|-E] [--xen-syms XEN-SYMS|--xen-vmcoreinfo VMCOREINFO]\n");
	MSG("    VMCORE DUMPFILE\n");
	MSG("\n");
	MSG("  Filtering domain-0 of Xen:\n");
	MSG("  # makedumpfile [-c|-l|-p|-z|-E] -d DL -x vmlinux VMCORE DUMPFILE\n");
	MSG("\n");
	MSG("  Generating VMCOREINFO of Xen:\n");
	MSG("  # makedumpfile -g VMCOREINFO --xen-syms XEN-SYMS\n");
	MSG("\n");
	MSG("\n");
	MSG("  Creating DUMPFILE from multiple VMCOREs generated on sadump diskset configuration:\n");
	MSG("  # makedumpfile [-c|-l|-p|-z] [-d DL] -x VMLINUX --diskset=VMCORE1 --diskset=VMCORE2\n");
	MSG("    [--diskset=VMCORE3 ..] DUMPFILE\n");
	MSG("\n");
	MSG("\n");
	MSG("Available options:\n");
	MSG("  [-c|-l|-p|-z]:\n");
	MSG("      Compress dump data by each page using zlib for -c option, lzo for -l option\n");
	MSG("      snappy for -p option or zstd for -z option. A user cannot specify either of\n");
	MSG("      these options with -E option, because the ELF format does not support\n");
	MSG("      compressed data.\n");
	MSG("      THIS IS ONLY FOR THE CRASH UTILITY.\n");
	MSG("\n");
	MSG("  [-e]:\n");
	MSG("      Exclude the page structures (vmemmap) which represent excluded pages.\n");
	MSG("      This greatly shortens the dump of a very large memory system.\n");
	MSG("      The --work-dir option must also be specified, as it will be used\n");
	MSG("      to hold bitmaps and a file of page numbers that are to be excluded.\n");
	MSG("      The -e option will cause a noncyclic dump procedure.\n");
	MSG("      This option is only for x86_64.\n");
	MSG("\n");
	MSG("  [-d DL]:\n");
	MSG("      Specify the type of unnecessary page for analysis.\n");
	MSG("      Pages of the specified type are not copied to DUMPFILE. The page type\n");
	MSG("      marked in the following table is excluded. A user can specify multiple\n");
	MSG("      page types by setting the sum of each page type for Dump_Level (DL).\n");
	MSG("      The maximum of Dump_Level is 31.\n");
	MSG("      Note that Dump_Level for Xen dump filtering is 0 or 1 except on x86_64\n");
	MSG("\n");
	MSG("            |         non-\n");
	MSG("      Dump  |  zero   private  private  user    free\n");
	MSG("      Level |  page   cache    cache    data    page\n");
	MSG("     -------+---------------------------------------\n");
	MSG("         0  |\n");
	MSG("         1  |   X\n");
	MSG("         2  |           X\n");
	MSG("         4  |           X        X\n");
	MSG("         8  |                            X\n");
	MSG("        16  |                                    X\n");
	MSG("        31  |   X       X        X       X       X\n");
	MSG("\n");
	MSG("  [-L SIZE]:\n");
	MSG("      Limit the size of the output file to SIZE bytes.\n");
	MSG("\n");
	MSG("  [-E]:\n");
	MSG("      Create DUMPFILE in the ELF format.\n");
	MSG("      This option cannot be specified with the -c, -l or -p options,\n");
	MSG("      because the ELF format does not support compressed data.\n");
	MSG("\n");
	MSG("  [-x VMLINUX]:\n");
	MSG("      Specify the first kernel's VMLINUX to analyze the first kernel's\n");
	MSG("      memory usage.\n");
	MSG("      The page size of the first kernel and the second kernel should match.\n");
	MSG("\n");
	MSG("  [-i VMCOREINFO]:\n");
	MSG("      Specify VMCOREINFO instead of VMLINUX for analyzing the first kernel's\n");
	MSG("      memory usage.\n");
	MSG("      VMCOREINFO should be made beforehand by makedumpfile with -g option,\n");
	MSG("      and it contains the first kernel's information. This option is necessary\n");
	MSG("      if VMCORE does not contain VMCOREINFO, [-x VMLINUX] is not specified,\n");
	MSG("      and dump_level is 2 or more.\n");
	MSG("\n");
	MSG("  [-g VMCOREINFO]:\n");
	MSG("      Generate VMCOREINFO from the first kernel's VMLINUX.\n");
	MSG("      VMCOREINFO must be generated on the system that is running the first\n");
	MSG("      kernel. With -i option, a user can specify VMCOREINFO generated on the\n");
	MSG("      other system that is running the same first kernel. [-x VMLINUX] must\n");
	MSG("      be specified.\n");
	MSG("\n");
	MSG("  [--config FILTERCONFIGFILE]:\n");
	MSG("      Used in conjunction with -x VMLINUX option, to specify the filter config\n");
	MSG("      file that contains filter commands to filter out desired kernel data\n");
	MSG("      from vmcore while creating DUMPFILE.\n");
	MSG("\n");
	MSG("  [--eppic EPPICMACRO]:\n");
	MSG("      Used in conjunction with -x VMLINUX option, to specify the eppic macro\n");
	MSG("      file that contains filter rules or directory that contains eppic macro\n");
	MSG("      files to filter out desired kernel data from vmcore while creating DUMPFILE.\n");
	MSG("      When directory is specified, all the eppic macros in the directory are\n");
	MSG("      processed\n");
	MSG("\n");
	MSG("  [-F]:\n");
	MSG("      Output the dump data in the flattened format to the standard output\n");
	MSG("      for transporting the dump data by SSH.\n");
	MSG("      Analysis tools cannot read the flattened format directly. For analysis,\n");
	MSG("      the dump data in the flattened format should be rearranged to a readable\n");
	MSG("      DUMPFILE by -R option.\n");
	MSG("\n");
	MSG("  [-R]:\n");
	MSG("      Rearrange the dump data in the flattened format from the standard input\n");
	MSG("      to a readable DUMPFILE.\n");
	MSG("\n");
	MSG("  [--split]:\n");
	MSG("      Split the dump data to multiple DUMPFILEs in parallel. If specifying\n");
	MSG("      DUMPFILEs on different storage devices, a device can share I/O load with\n");
	MSG("      other devices and it reduces time for saving the dump data. The file size\n");
	MSG("      of each DUMPFILE is smaller than the system memory size which is divided\n");
	MSG("      by the number of DUMPFILEs.\n");
	MSG("      This feature supports only the kdump-compressed format.\n");
	MSG("\n");
	MSG("  [--num-threads THREADNUM]:\n");
	MSG("      Using multiple threads to read and compress data of each page in parallel.\n");
	MSG("      And it will reduces time for saving DUMPFILE.\n");
	MSG("      Note that if the usable cpu number is less than the thread number, it may\n");
	MSG("      lead to great performance degradation.\n");
	MSG("      This feature only supports creating DUMPFILE in kdump-compressed format from\n");
	MSG("      VMCORE in kdump-compressed format or elf format.\n");
	MSG("\n");
	MSG("  [--reassemble]:\n");
	MSG("      Reassemble multiple DUMPFILEs, which are created by --split option,\n");
	MSG("      into one DUMPFILE. dumpfile1 and dumpfile2 are reassembled into dumpfile.\n");
	MSG("\n");
	MSG("  [-b <order>]\n");
	MSG("      Specify the cache 2^order pages in ram when generating DUMPFILE before\n");
	MSG("      writing to output. The default value is 4.\n");
	MSG("\n");
	MSG("  [--cyclic-buffer BUFFER_SIZE]:\n");
	MSG("      Specify the buffer size in kilo bytes for bitmap data.\n");
	MSG("      Filtering processing will be divided into multi cycles to fix the memory\n");
	MSG("      consumption, the number of cycles is represented as:\n");
	MSG("\n");
	MSG("          num_of_cycles = system_memory / \n");
	MSG("                          (BUFFER_SIZE * 1024 * bit_per_bytes * page_size)\n");
	MSG("\n");
	MSG("      The lesser number of cycles, the faster working speed is expected.\n");
	MSG("      By default, BUFFER_SIZE will be calculated automatically depending on\n");
	MSG("      system memory size, so ordinary users don't need to specify this option.\n");
	MSG("\n");
	MSG("  [--splitblock-size SPLITBLOCK_SIZE]:\n");
	MSG("      Specify the splitblock size in kilo bytes for analysis with --split.\n");
	MSG("      If --splitblock N is specified, difference of each splitted dumpfile\n");
	MSG("      size is at most N kilo bytes.\n");
	MSG("\n");
	MSG("  [--work-dir]:\n");
	MSG("      Specify the working directory for the temporary bitmap file.\n");
	MSG("      If this option isn't specified, the bitmap will be saved on memory.\n");
	MSG("      Filtering processing has to do 2 pass scanning to fix the memory consumption,\n");
	MSG("      but it can be avoided by using working directory on file system.\n");
	MSG("      So if you specify this option, the filtering speed may be bit faster.\n");
	MSG("\n");
	MSG("  [--non-mmap]:\n");
	MSG("      Never use mmap(2) to read VMCORE even if it supports mmap(2).\n");
	MSG("      Generally, reading VMCORE with mmap(2) is faster than without it,\n");
	MSG("      so ordinary users don't need to specify this option.\n");
	MSG("      This option is mainly for debugging.\n");
	MSG("\n");
	MSG("  [--xen-syms XEN-SYMS]:\n");
	MSG("      Specify the XEN-SYMS to analyze Xen's memory usage.\n");
	MSG("\n");
	MSG("  [--xen-vmcoreinfo VMCOREINFO]:\n");
	MSG("      Specify the VMCOREINFO of Xen to analyze Xen's memory usage.\n");
	MSG("\n");
	MSG("  [--xen_phys_start XEN_PHYS_START_ADDRESS]:\n");
	MSG("      This option is only for x86_64.\n");
	MSG("      Specify the XEN_PHYS_START_ADDRESS, if the xen code/data is relocatable\n");
	MSG("      and VMCORE does not contain XEN_PHYS_START_ADDRESS in the CRASHINFO.\n");
	MSG("\n");
	MSG("  [-X]:\n");
	MSG("      Exclude all the user domain pages from Xen kdump's VMCORE, and extract\n");
	MSG("      the part of Xen and domain-0.\n");
	MSG("\n");
	MSG("  [--diskset=VMCORE]:\n");
	MSG("      Specify multiple VMCOREs created on sadump diskset configuration the same\n");
	MSG("      number of times as the number of VMCOREs in increasing order from left to\n");
	MSG("      right.\n");
	MSG("\n");
	MSG("  [--message-level ML]:\n");
	MSG("      Specify the message types.\n");
	MSG("      Users can restrict output printed by specifying Message_Level (ML) with\n");
	MSG("      this option. The message type marked with an X in the following table is\n");
	MSG("      printed. For example, according to the table, specifying 7 as ML means\n");
	MSG("      progress indicator, common message, and error message are printed, and\n");
	MSG("      this is a default value.\n");
	MSG("      Note that the maximum value of message_level is 31.\n");
	MSG("\n");
	MSG("      Message | progress    common    error     debug     report\n");
	MSG("      Level   | indicator   message   message   message   message\n");
	MSG("     ---------+------------------------------------------------------\n");
	MSG("            0 |\n");
	MSG("            1 |     X\n");
	MSG("            2 |                X\n");
	MSG("            4 |                          X\n");
	MSG("          * 7 |     X          X         X\n");
	MSG("            8 |                                    X\n");
	MSG("           16 |                                              X\n");
	MSG("           31 |     X          X         X         X         X\n");
	MSG("\n");
	MSG("  [--vtop VIRTUAL_ADDRESS]:\n");
	MSG("      This option is useful, when user debugs the translation problem\n");
	MSG("      of virtual address. If specifying the VIRTUAL_ADDRESS, its physical\n");
	MSG("      address is printed.\n");
	MSG("\n");
	MSG("  [--dump-dmesg]:\n");
	MSG("      This option overrides the normal behavior of makedumpfile. Instead of\n");
	MSG("      compressing and filtering a VMCORE to make it smaller, it simply\n");
	MSG("      extracts the dmesg log from a VMCORE and writes it to the specified\n");
	MSG("      LOGFILE. If a VMCORE does not contain VMCOREINFO for dmesg, it is\n");
	MSG("      necessary to specify [-x VMLINUX] or [-i VMCOREINFO].\n");
	MSG("\n");
	MSG("  [--mem-usage]:\n");
	MSG("      This option is currently supported on x86_64, arm64, ppc64 and s390x.\n");
	MSG("      This option is used to show the page numbers of current system in different\n");
	MSG("      use. It should be executed in 1st kernel. By the help of this, user can know\n");
	MSG("      how many pages is dumpable when different dump_level is specified. It analyzes\n");
	MSG("      the 'System Ram' and 'kernel text' program segment of /proc/kcore excluding\n");
	MSG("      the crashkernel range, then calculates the page number of different kind per\n");
	MSG("      vmcoreinfo. So currently /proc/kcore need be specified explicitly.\n");
	MSG("\n");
	MSG("  [--dry-run]:\n");
	MSG("      Do not write the output dump file while still performing operations specified\n");
	MSG("      by other options.  This option cannot be used with --dump-dmesg, --reassemble\n");
	MSG("      and -g options.\n");
	MSG("\n");
	MSG("  [--show-stats]:\n");
	MSG("      Set message-level to print report messages\n");
	MSG("\n");
	MSG("  [-D]:\n");
	MSG("      Print debugging message.\n");
	MSG("\n");
	MSG("  [-f]:\n");
	MSG("      Overwrite DUMPFILE even if it already exists.\n");
	MSG("      Force mem-usage to work with older kernel as well.\n");
	MSG("\n");
	MSG("  [-h, --help]:\n");
	MSG("      Show help message and LZO/snappy support status (enabled/disabled).\n");
	MSG("\n");
	MSG("  [-v]:\n");
	MSG("      Show the version of makedumpfile.\n");
	MSG("\n");
	MSG("  [--check-params]:\n");
	MSG("      Only check whether the command-line parameters are valid or not, and exit.\n");
	MSG("      Preferable to be given as the first parameter.\n");
	MSG("\n");
	MSG("  VMLINUX:\n");
	MSG("      This is a pathname to the first kernel's vmlinux.\n");
	MSG("      This file must have the debug information of the first kernel to analyze\n");
	MSG("      the first kernel's memory usage.\n");
	MSG("\n");
	MSG("  VMCORE:\n");
	MSG("      This is a pathname to the first kernel's memory core image.\n");
	MSG("      This argument is generally /proc/vmcore.\n");
	MSG("\n");
	MSG("  DUMPFILE:\n");
	MSG("      This is a pathname to a file created by this command.\n");
	MSG("\n");
	MSG("  XEN-SYMS:\n");
	MSG("      This is a pathname to the xen-syms.\n");
	MSG("      This file must have the debug information of Xen to analyze\n");
	MSG("      Xen's memory usage.\n");
	MSG("\n");
}

static void calc_delta(struct timespec *ts_start, struct timespec *delta)
{
	struct timespec ts_end;

	clock_gettime(CLOCK_MONOTONIC, &ts_end);
	delta->tv_sec = ts_end.tv_sec - ts_start->tv_sec;
	delta->tv_nsec = ts_end.tv_nsec - ts_start->tv_nsec;
	if (delta->tv_nsec < 0) {
		delta->tv_sec--;
		delta->tv_nsec += NSEC_PER_SEC;
	}
}

static int eta_to_human_short (unsigned long secs, char *msg, size_t len)
{
	unsigned long minutes, hours, days;

	minutes = secs / 60;
	hours = minutes / 60;
	days = hours / 24;

	if (secs < 100)
		snprintf(msg, len, "eta: %lus", secs);
	else if (minutes < 100)
		snprintf(msg, len, "eta: %lum%lus", minutes, secs % 60);
	else if (hours < 48)
		snprintf(msg, len, "eta: %luh%lum", hours, minutes % 60);
	else if (days < 100)
		snprintf(msg, len, "eta: %lud%luh", days, hours % 24);
	else
		snprintf(msg, len, "eta: >100day");
	return 0;
}


void
print_progress(const char *msg, unsigned long current, unsigned long end, struct timespec *start)
{
	unsigned progress;	/* in promilles (tenths of a percent) */
	time_t tm;
	static time_t last_time = 0;
	static unsigned int lapse = 0;
	static const char *spinner = "/|\\-";
	struct timespec delta;
	unsigned long eta;
	char eta_msg[32] = " ";

	if (current < end) {
		tm = time(NULL);
		if (tm - last_time < 1)
			return;
		last_time = tm;
		progress = current * 1000 / end;
	} else
		progress = 1000;

	if (start != NULL && progress != 0) {
		calc_delta(start, &delta);
		eta = 1000 * delta.tv_sec + delta.tv_nsec / (NSEC_PER_SEC / 1000);
		eta = eta / progress - delta.tv_sec;
		eta_to_human_short(eta, eta_msg, sizeof(eta_msg));
	}
	if (flag_ignore_r_char) {
		PROGRESS_MSG("%-" PROGRESS_MAXLEN "s: [%3u.%u %%] %c  %16s\n",
			     msg, progress / 10, progress % 10,
			     spinner[lapse % 4], eta_msg);
	} else {
		PROGRESS_MSG("\r");
		PROGRESS_MSG("%-" PROGRESS_MAXLEN "s: [%3u.%u %%] %c  %16s",
			     msg, progress / 10, progress % 10,
			     spinner[lapse % 4], eta_msg);
	}
	lapse++;
}

void
print_execution_time(char *step_name, struct timespec *ts_start)
{
	struct timespec delta;

	calc_delta(ts_start, &delta);
	REPORT_MSG("STEP [%s] : %ld.%06ld seconds\n",
		   step_name, delta.tv_sec, delta.tv_nsec / 1000);
}

