public inbox for kvm@vger.kernel.org
 help / color / mirror / Atom feed
From: Stefan Raspl <raspl@linux.ibm.com>
To: kvm@vger.kernel.org
Cc: rkrcmar@redhat.com, pbonzini@redhat.com
Subject: [PATCH 5/7] tools/kvm_stat: add rotating log support
Date: Fri,  6 Mar 2020 12:42:48 +0100	[thread overview]
Message-ID: <20200306114250.57585-6-raspl@linux.ibm.com> (raw)
In-Reply-To: <20200306114250.57585-1-raspl@linux.ibm.com>

From: Stefan Raspl <raspl@de.ibm.com>

Add new command line switches -r to log output to a rotating set of
files. Number of files fixed to a total of 6 for now. Set maximum total

size via -S <size>, i.e. no file will exceed <size> / 6.
Note that each file has a header, so you can easily load each file
individually in an editor. On the downside, the first line of successive
files needs to be stripped in case somebody wants to concatenate them.

Signed-off-by: Stefan Raspl <raspl@linux.ibm.com>
---
 tools/kvm/kvm_stat/kvm_stat     | 119 ++++++++++++++++++++++++++++++--
 tools/kvm/kvm_stat/kvm_stat.txt |  13 +++-
 2 files changed, 126 insertions(+), 6 deletions(-)

diff --git a/tools/kvm/kvm_stat/kvm_stat b/tools/kvm/kvm_stat/kvm_stat
index 7fe767bd2625..2275ab1b070b 100755
--- a/tools/kvm/kvm_stat/kvm_stat
+++ b/tools/kvm/kvm_stat/kvm_stat
@@ -35,6 +35,10 @@ import subprocess
 from collections import defaultdict, namedtuple
 from functools import reduce
 from datetime import datetime
+import glob
+import string
+import logging
+from logging.handlers import RotatingFileHandler
 
 VMX_EXIT_REASONS = {
     'EXCEPTION_NMI':        0,
@@ -974,6 +978,8 @@ MAX_REGEX_LEN = 44
 SORT_DEFAULT = 0
 MIN_DELAY = 0.1
 MAX_DELAY = 25.5
+SIZE_DEFAULT = '10M'
+LOGCOUNT_DEFAULT = 6
 
 
 class Tui(object):
@@ -1535,6 +1541,64 @@ def log(stats, opts, frmt, keys):
             break
 
 
+def rotating_log(stats, opts, frmt, keys):
+    """Prints statistics to file in csv format."""
+    def init(opts, frmt):
+        # Regular RotatingFileHandler doesn't add a header to each file,
+        # so we create our own version
+        class MyRotatingFileHandler(RotatingFileHandler):
+            def __init__(self, logfile):
+                super(MyRotatingFileHandler,
+                      self).__init__(logfile, mode='w', maxBytes=opts.size_num,
+                                     backupCount=LOGCOUNT_DEFAULT-1)
+                self._header = ""
+                self._log = None
+
+            def doRollover(self):
+                super(MyRotatingFileHandler, self).doRollover()
+                if self._log is not None and self._header != "":
+                    self._log.debug(self._header)
+
+            def setHeader(self, header, log):
+                self._header = header
+                self._log = log
+                if not self.stream or self.stream.tell() == 0:
+                    self._log.debug(self._header)
+
+        # Regular Formatter would prepend a timestamp to the header,
+        # so we create our own version again
+        class MyFormatter(logging.Formatter):
+            def __init__(self, fmt, datefmt):
+                logging.Formatter.__init__(self, fmt=fmt, datefmt=datefmt)
+
+            def format(self, record):
+                if record.levelno == logging.DEBUG:
+                    return record.getMessage()
+                return logging.Formatter.format(self, record)
+        try:
+            hdl = MyRotatingFileHandler(opts.rotating_log)
+        except:
+            sys.exit("Error setting up csv log with file '%s'"
+                     % opts.rotating_log)
+        formatter = MyFormatter('%(asctime)s%(message)s', '%Y-%m-%d %H:%M:%S')
+        hdl.setFormatter(formatter)
+
+        logger = logging.getLogger('MyLogger')
+        logger.setLevel(logging.DEBUG)
+        logger.addHandler(hdl)
+        hdl.setHeader(frmt.get_banner(), logger)
+
+        return logger
+
+    log = init(opts, frmt)
+    while True:
+        try:
+            time.sleep(opts.set_delay)
+            log.info(frmt.get_statline(keys, stats.get()))
+        except KeyboardInterrupt:
+            break
+
+
 def is_delay_valid(delay):
     """Verify delay is in valid value range."""
     msg = None
@@ -1580,6 +1644,26 @@ Interactive Commands:
 Press any other key to refresh statistics immediately.
 """ % (PATH_DEBUGFS_KVM, PATH_DEBUGFS_TRACING)
 
+    def convert_from_si(opts):
+        try:
+            factor = 1000000
+            num = int(opts.size.rstrip(string.ascii_letters))
+            unit = opts.size.lstrip(string.digits)
+        except ValueError:
+            sys.exit("Error: Invalid argument to -S/--size: '%s'" % opts.size)
+        if num <= 0:
+            sys.exit("Error: Argument to -S/--size must be >0")
+        if unit != '':
+            if unit in ['m', 'M']:
+                factor = 1000000
+            elif unit in ['g', 'G']:
+                factor = 1000000000
+            elif unit in ['t', 'T']:
+                factor = 1000000000000
+            else:
+                sys.exit("Error: Unsupported unit suffix '%s'" % unit)
+        opts.size_num = int(num * factor / LOGCOUNT_DEFAULT)
+
     class Guest_to_pid(argparse.Action):
         def __call__(self, parser, namespace, values, option_string=None):
             try:
@@ -1606,7 +1690,8 @@ Press any other key to refresh statistics immediately.
     argparser.add_argument('-c', '--csv',
                            action='store_true',
                            default=False,
-                           help='log in csv format - requires option -l/--log',
+                           help='log in csv format - requires option -l/--log '
+                                'or -r/--rotating-log'
                            )
     argparser.add_argument('-d', '--debugfs',
                            action='store_true',
@@ -1639,6 +1724,12 @@ Press any other key to refresh statistics immediately.
                            default=0,
                            help='restrict statistics to pid',
                            )
+    argparser.add_argument('-r', '--rotating-log',
+                           type=str,
+                           default='',
+                           metavar='FILE',
+                           help='write a rotating log to FILE'
+                           )
     argparser.add_argument('-s', '--set-delay',
                            type=float,
                            default=DELAY_DEFAULT,
@@ -1646,14 +1737,28 @@ Press any other key to refresh statistics immediately.
                            help='set delay between refreshs (value range: '
                                 '%s-%s secs)' % (MIN_DELAY, MAX_DELAY),
                            )
+    argparser.add_argument('-S', '--size',
+                           type=str,
+                           default='',
+                           help='''maximum total file size
+supported suffixes: MGT (Megabytes (default), Gigabytes, Terabytes)
+default: %s''' % SIZE_DEFAULT,
+                           )
     argparser.add_argument('-t', '--tracepoints',
                            action='store_true',
                            default=False,
                            help='retrieve statistics from tracepoints',
                            )
     options = argparser.parse_args()
-    if options.csv and not options.log:
-        sys.exit('Error: Option -c/--csv requires -l/--log')
+    if options.csv and not options.log and not options.rotating_log:
+        sys.exit('Error: Option -c/--csv requires one of -l/--log or '
+                 '-r/--rotating-log')
+    if options.rotating_log:
+        if options.log:
+            sys.exit('Error: Cannot mix -l/--log and -r/--rotating-log')
+        if not options.size:
+            options.size = SIZE_DEFAULT
+        convert_from_si(options)
     try:
         # verify that we were passed a valid regex up front
         re.compile(options.fields)
@@ -1733,13 +1838,17 @@ def main():
         sys.stdout.write('  ' + '\n  '.join(sorted(set(event_list))) + '\n')
         sys.exit(0)
 
-    if options.log:
+    if options.log or options.rotating_log:
         keys = sorted(stats.get().keys())
         if options.csv:
             frmt = CSVFormat(keys)
         else:
             frmt = StdFormat(keys)
-        log(stats, options, frmt, keys)
+
+        if options.log:
+            log(stats, options, frmt, keys)
+        else:
+            rotating_log(stats, options, frmt, keys)
     elif not options.once:
         with Tui(stats, options) as tui:
             tui.show_stats()
diff --git a/tools/kvm/kvm_stat/kvm_stat.txt b/tools/kvm/kvm_stat/kvm_stat.txt
index a97ded2aedad..35df0b1261a2 100644
--- a/tools/kvm/kvm_stat/kvm_stat.txt
+++ b/tools/kvm/kvm_stat/kvm_stat.txt
@@ -66,7 +66,7 @@ OPTIONS
 
 -c::
 --csv=<file>::
-        log in csv format - requires option -l/--log
+        log in csv format - requires option -l/--log or -r/--rotating-log
 
 -d::
 --debugfs::
@@ -96,10 +96,21 @@ OPTIONS
 --pid=<pid>::
 	limit statistics to one virtual machine (pid)
 
+-r<file>::
+--rotating-log=<file>::
+	log output to rotating logfiles prefixed <file> - also
+            see option -S/--size
+
 -s::
 --set-delay::
         set delay between refreshs (value range: 0.1-25.5 secs)
 
+-S<size>::
+--size=<size>::
+	maximum total file size for option -r/--rotating-log.
+            Supported suffixes: MGT (Megabytes (default), Gigabytes, Terabytes).
+            Default: 10M
+
 -t::
 --tracepoints::
         retrieve statistics from tracepoints
-- 
2.17.1


  parent reply	other threads:[~2020-03-06 11:43 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-03-06 11:42 [PATCH 0/7] tools/kvm_stat: add logfile support Stefan Raspl
2020-03-06 11:42 ` [PATCH 1/7] tools/kvm_stat: rework command line sequence and message texts Stefan Raspl
2020-03-06 11:42 ` [PATCH 2/7] tools/kvm_stat: switch to argparse Stefan Raspl
2020-03-06 11:42 ` [PATCH 3/7] tools/kvm_stat: add command line switch '-s' to set update interval Stefan Raspl
2020-03-06 11:42 ` [PATCH 4/7] tools/kvm_stat: add command line switch '-c' to log in csv format Stefan Raspl
2020-03-06 11:42 ` Stefan Raspl [this message]
2020-03-06 11:42 ` [PATCH 6/7] tools/kvm_stat: add command line switch '-T' Stefan Raspl
2020-03-06 11:42 ` [PATCH 7/7] tools/kvm_stat: add sample systemd unit file Stefan Raspl
2020-03-29 11:22   ` Stefan Raspl
2020-03-19 11:21 ` [PATCH 0/7] tools/kvm_stat: add logfile support Stefan Raspl
2020-03-19 11:54 ` Paolo Bonzini
2020-03-23  9:58   ` Stefan Raspl
2020-03-23 10:12     ` Paolo Bonzini
2020-03-24  8:26       ` Stefan Raspl
2020-03-24 10:32         ` Paolo Bonzini
2020-03-29 11:22           ` Stefan Raspl
2020-03-30 10:43             ` Paolo Bonzini
2020-03-30 12:24               ` Stefan Raspl
2020-03-30 12:35                 ` Paolo Bonzini

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200306114250.57585-6-raspl@linux.ibm.com \
    --to=raspl@linux.ibm.com \
    --cc=kvm@vger.kernel.org \
    --cc=pbonzini@redhat.com \
    --cc=rkrcmar@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox