| .. | .. |
|---|
| 1 | | -#!/usr/bin/python |
|---|
| 1 | +#!/usr/bin/env python3 |
|---|
| 2 | +# SPDX-License-Identifier: GPL-2.0-only |
|---|
| 2 | 3 | # |
|---|
| 3 | 4 | # top-like utility for displaying kvm statistics |
|---|
| 4 | 5 | # |
|---|
| .. | .. |
|---|
| 8 | 9 | # Authors: |
|---|
| 9 | 10 | # Avi Kivity <avi@redhat.com> |
|---|
| 10 | 11 | # |
|---|
| 11 | | -# This work is licensed under the terms of the GNU GPL, version 2. See |
|---|
| 12 | | -# the COPYING file in the top-level directory. |
|---|
| 13 | 12 | """The kvm_stat module outputs statistics about running KVM VMs |
|---|
| 14 | 13 | |
|---|
| 15 | 14 | Three different ways of output formatting are available: |
|---|
| .. | .. |
|---|
| 26 | 25 | import locale |
|---|
| 27 | 26 | import os |
|---|
| 28 | 27 | import time |
|---|
| 29 | | -import optparse |
|---|
| 28 | +import argparse |
|---|
| 30 | 29 | import ctypes |
|---|
| 31 | 30 | import fcntl |
|---|
| 32 | 31 | import resource |
|---|
| 33 | 32 | import struct |
|---|
| 34 | 33 | import re |
|---|
| 35 | 34 | import subprocess |
|---|
| 35 | +import signal |
|---|
| 36 | 36 | from collections import defaultdict, namedtuple |
|---|
| 37 | +from functools import reduce |
|---|
| 38 | +from datetime import datetime |
|---|
| 37 | 39 | |
|---|
| 38 | 40 | VMX_EXIT_REASONS = { |
|---|
| 39 | 41 | 'EXCEPTION_NMI': 0, |
|---|
| .. | .. |
|---|
| 226 | 228 | 'DISABLE': 0x00002401, |
|---|
| 227 | 229 | 'RESET': 0x00002403, |
|---|
| 228 | 230 | } |
|---|
| 231 | + |
|---|
| 232 | +signal_received = False |
|---|
| 229 | 233 | |
|---|
| 230 | 234 | ENCODING = locale.getpreferredencoding(False) |
|---|
| 231 | 235 | TRACE_FILTER = re.compile(r'^[^\(]*$') |
|---|
| .. | .. |
|---|
| 738 | 742 | The fields are all available KVM debugfs files |
|---|
| 739 | 743 | |
|---|
| 740 | 744 | """ |
|---|
| 741 | | - return self.walkdir(PATH_DEBUGFS_KVM)[2] |
|---|
| 745 | + exempt_list = ['halt_poll_fail_ns', 'halt_poll_success_ns'] |
|---|
| 746 | + fields = [field for field in self.walkdir(PATH_DEBUGFS_KVM)[2] |
|---|
| 747 | + if field not in exempt_list] |
|---|
| 748 | + |
|---|
| 749 | + return fields |
|---|
| 742 | 750 | |
|---|
| 743 | 751 | def update_fields(self, fields_filter): |
|---|
| 744 | 752 | """Refresh fields, applying fields_filter""" |
|---|
| .. | .. |
|---|
| 874 | 882 | |
|---|
| 875 | 883 | if options.debugfs: |
|---|
| 876 | 884 | providers.append(DebugfsProvider(options.pid, options.fields, |
|---|
| 877 | | - options.dbgfs_include_past)) |
|---|
| 885 | + options.debugfs_include_past)) |
|---|
| 878 | 886 | if options.tracepoints or not providers: |
|---|
| 879 | 887 | providers.append(TracepointProvider(options.pid, options.fields)) |
|---|
| 880 | 888 | |
|---|
| .. | .. |
|---|
| 975 | 983 | MAX_GUEST_NAME_LEN = 48 |
|---|
| 976 | 984 | MAX_REGEX_LEN = 44 |
|---|
| 977 | 985 | SORT_DEFAULT = 0 |
|---|
| 986 | +MIN_DELAY = 0.1 |
|---|
| 987 | +MAX_DELAY = 25.5 |
|---|
| 978 | 988 | |
|---|
| 979 | 989 | |
|---|
| 980 | 990 | class Tui(object): |
|---|
| 981 | 991 | """Instruments curses to draw a nice text ui.""" |
|---|
| 982 | | - def __init__(self, stats): |
|---|
| 992 | + def __init__(self, stats, opts): |
|---|
| 983 | 993 | self.stats = stats |
|---|
| 984 | 994 | self.screen = None |
|---|
| 985 | 995 | self._delay_initial = 0.25 |
|---|
| 986 | | - self._delay_regular = DELAY_DEFAULT |
|---|
| 996 | + self._delay_regular = opts.set_delay |
|---|
| 987 | 997 | self._sorting = SORT_DEFAULT |
|---|
| 988 | 998 | self._display_guests = 0 |
|---|
| 989 | 999 | |
|---|
| .. | .. |
|---|
| 1184 | 1194 | |
|---|
| 1185 | 1195 | if not self._is_running_guest(self.stats.pid_filter): |
|---|
| 1186 | 1196 | if self._gname: |
|---|
| 1187 | | - try: # ...to identify the guest by name in case it's back |
|---|
| 1197 | + try: # ...to identify the guest by name in case it's back |
|---|
| 1188 | 1198 | pids = self.get_pid_from_gname(self._gname) |
|---|
| 1189 | 1199 | if len(pids) == 1: |
|---|
| 1190 | 1200 | self._refresh_header(pids[0]) |
|---|
| .. | .. |
|---|
| 1283 | 1293 | ' p filter by guest name/PID', |
|---|
| 1284 | 1294 | ' q quit', |
|---|
| 1285 | 1295 | ' r reset stats', |
|---|
| 1286 | | - ' s set update interval', |
|---|
| 1296 | + ' s set delay between refreshs (value range: ' |
|---|
| 1297 | + '%s-%s secs)' % (MIN_DELAY, MAX_DELAY), |
|---|
| 1287 | 1298 | ' x toggle reporting of stats for individual child trace' |
|---|
| 1288 | 1299 | ' events', |
|---|
| 1289 | 1300 | 'Any other key refreshes statistics immediately') |
|---|
| .. | .. |
|---|
| 1337 | 1348 | msg = '' |
|---|
| 1338 | 1349 | while True: |
|---|
| 1339 | 1350 | self.screen.erase() |
|---|
| 1340 | | - self.screen.addstr(0, 0, 'Set update interval (defaults to %.1fs).' % |
|---|
| 1341 | | - DELAY_DEFAULT, curses.A_BOLD) |
|---|
| 1351 | + self.screen.addstr(0, 0, 'Set update interval (defaults to %.1fs).' |
|---|
| 1352 | + % DELAY_DEFAULT, curses.A_BOLD) |
|---|
| 1342 | 1353 | self.screen.addstr(4, 0, msg) |
|---|
| 1343 | 1354 | self.screen.addstr(2, 0, 'Change delay from %.1fs to ' % |
|---|
| 1344 | 1355 | self._delay_regular) |
|---|
| .. | .. |
|---|
| 1349 | 1360 | try: |
|---|
| 1350 | 1361 | if len(val) > 0: |
|---|
| 1351 | 1362 | delay = float(val) |
|---|
| 1352 | | - if delay < 0.1: |
|---|
| 1353 | | - msg = '"' + str(val) + '": Value must be >=0.1' |
|---|
| 1354 | | - continue |
|---|
| 1355 | | - if delay > 25.5: |
|---|
| 1356 | | - msg = '"' + str(val) + '": Value must be <=25.5' |
|---|
| 1363 | + err = is_delay_valid(delay) |
|---|
| 1364 | + if err is not None: |
|---|
| 1365 | + msg = err |
|---|
| 1357 | 1366 | continue |
|---|
| 1358 | 1367 | else: |
|---|
| 1359 | 1368 | delay = DELAY_DEFAULT |
|---|
| .. | .. |
|---|
| 1489 | 1498 | pass |
|---|
| 1490 | 1499 | |
|---|
| 1491 | 1500 | |
|---|
| 1492 | | -def log(stats): |
|---|
| 1501 | +class StdFormat(object): |
|---|
| 1502 | + def __init__(self, keys): |
|---|
| 1503 | + self._banner = '' |
|---|
| 1504 | + for key in keys: |
|---|
| 1505 | + self._banner += key.split(' ')[0] + ' ' |
|---|
| 1506 | + |
|---|
| 1507 | + def get_banner(self): |
|---|
| 1508 | + return self._banner |
|---|
| 1509 | + |
|---|
| 1510 | + def get_statline(self, keys, s): |
|---|
| 1511 | + res = '' |
|---|
| 1512 | + for key in keys: |
|---|
| 1513 | + res += ' %9d' % s[key].delta |
|---|
| 1514 | + return res |
|---|
| 1515 | + |
|---|
| 1516 | + |
|---|
| 1517 | +class CSVFormat(object): |
|---|
| 1518 | + def __init__(self, keys): |
|---|
| 1519 | + self._banner = 'timestamp' |
|---|
| 1520 | + self._banner += reduce(lambda res, key: "{},{!s}".format(res, |
|---|
| 1521 | + key.split(' ')[0]), keys, '') |
|---|
| 1522 | + |
|---|
| 1523 | + def get_banner(self): |
|---|
| 1524 | + return self._banner |
|---|
| 1525 | + |
|---|
| 1526 | + def get_statline(self, keys, s): |
|---|
| 1527 | + return reduce(lambda res, key: "{},{!s}".format(res, s[key].delta), |
|---|
| 1528 | + keys, '') |
|---|
| 1529 | + |
|---|
| 1530 | + |
|---|
| 1531 | +def log(stats, opts, frmt, keys): |
|---|
| 1493 | 1532 | """Prints statistics as reiterating key block, multiple value blocks.""" |
|---|
| 1494 | | - keys = sorted(stats.get().keys()) |
|---|
| 1495 | | - |
|---|
| 1496 | | - def banner(): |
|---|
| 1497 | | - for key in keys: |
|---|
| 1498 | | - print(key.split(' ')[0], end=' ') |
|---|
| 1499 | | - print() |
|---|
| 1500 | | - |
|---|
| 1501 | | - def statline(): |
|---|
| 1502 | | - s = stats.get() |
|---|
| 1503 | | - for key in keys: |
|---|
| 1504 | | - print(' %9d' % s[key].delta, end=' ') |
|---|
| 1505 | | - print() |
|---|
| 1533 | + global signal_received |
|---|
| 1506 | 1534 | line = 0 |
|---|
| 1507 | 1535 | banner_repeat = 20 |
|---|
| 1536 | + f = None |
|---|
| 1537 | + |
|---|
| 1538 | + def do_banner(opts): |
|---|
| 1539 | + nonlocal f |
|---|
| 1540 | + if opts.log_to_file: |
|---|
| 1541 | + if not f: |
|---|
| 1542 | + try: |
|---|
| 1543 | + f = open(opts.log_to_file, 'a') |
|---|
| 1544 | + except (IOError, OSError): |
|---|
| 1545 | + sys.exit("Error: Could not open file: %s" % |
|---|
| 1546 | + opts.log_to_file) |
|---|
| 1547 | + if isinstance(frmt, CSVFormat) and f.tell() != 0: |
|---|
| 1548 | + return |
|---|
| 1549 | + print(frmt.get_banner(), file=f or sys.stdout) |
|---|
| 1550 | + |
|---|
| 1551 | + def do_statline(opts, values): |
|---|
| 1552 | + statline = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + \ |
|---|
| 1553 | + frmt.get_statline(keys, values) |
|---|
| 1554 | + print(statline, file=f or sys.stdout) |
|---|
| 1555 | + |
|---|
| 1556 | + do_banner(opts) |
|---|
| 1557 | + banner_printed = True |
|---|
| 1508 | 1558 | while True: |
|---|
| 1509 | 1559 | try: |
|---|
| 1510 | | - time.sleep(1) |
|---|
| 1511 | | - if line % banner_repeat == 0: |
|---|
| 1512 | | - banner() |
|---|
| 1513 | | - statline() |
|---|
| 1514 | | - line += 1 |
|---|
| 1560 | + time.sleep(opts.set_delay) |
|---|
| 1561 | + if signal_received: |
|---|
| 1562 | + banner_printed = True |
|---|
| 1563 | + line = 0 |
|---|
| 1564 | + f.close() |
|---|
| 1565 | + do_banner(opts) |
|---|
| 1566 | + signal_received = False |
|---|
| 1567 | + if (line % banner_repeat == 0 and not banner_printed and |
|---|
| 1568 | + not (opts.log_to_file and isinstance(frmt, CSVFormat))): |
|---|
| 1569 | + do_banner(opts) |
|---|
| 1570 | + banner_printed = True |
|---|
| 1571 | + values = stats.get() |
|---|
| 1572 | + if (not opts.skip_zero_records or |
|---|
| 1573 | + any(values[k].delta != 0 for k in keys)): |
|---|
| 1574 | + do_statline(opts, values) |
|---|
| 1575 | + line += 1 |
|---|
| 1576 | + banner_printed = False |
|---|
| 1515 | 1577 | except KeyboardInterrupt: |
|---|
| 1516 | 1578 | break |
|---|
| 1579 | + |
|---|
| 1580 | + if opts.log_to_file: |
|---|
| 1581 | + f.close() |
|---|
| 1582 | + |
|---|
| 1583 | + |
|---|
| 1584 | +def handle_signal(sig, frame): |
|---|
| 1585 | + global signal_received |
|---|
| 1586 | + |
|---|
| 1587 | + signal_received = True |
|---|
| 1588 | + |
|---|
| 1589 | + return |
|---|
| 1590 | + |
|---|
| 1591 | + |
|---|
| 1592 | +def is_delay_valid(delay): |
|---|
| 1593 | + """Verify delay is in valid value range.""" |
|---|
| 1594 | + msg = None |
|---|
| 1595 | + if delay < MIN_DELAY: |
|---|
| 1596 | + msg = '"' + str(delay) + '": Delay must be >=%s' % MIN_DELAY |
|---|
| 1597 | + if delay > MAX_DELAY: |
|---|
| 1598 | + msg = '"' + str(delay) + '": Delay must be <=%s' % MAX_DELAY |
|---|
| 1599 | + return msg |
|---|
| 1517 | 1600 | |
|---|
| 1518 | 1601 | |
|---|
| 1519 | 1602 | def get_options(): |
|---|
| .. | .. |
|---|
| 1546 | 1629 | p filter by PID |
|---|
| 1547 | 1630 | q quit |
|---|
| 1548 | 1631 | r reset stats |
|---|
| 1549 | | - s set update interval |
|---|
| 1632 | + s set update interval (value range: 0.1-25.5 secs) |
|---|
| 1550 | 1633 | x toggle reporting of stats for individual child trace events |
|---|
| 1551 | 1634 | Press any other key to refresh statistics immediately. |
|---|
| 1552 | 1635 | """ % (PATH_DEBUGFS_KVM, PATH_DEBUGFS_TRACING) |
|---|
| 1553 | 1636 | |
|---|
| 1554 | | - class PlainHelpFormatter(optparse.IndentedHelpFormatter): |
|---|
| 1555 | | - def format_description(self, description): |
|---|
| 1556 | | - if description: |
|---|
| 1557 | | - return description + "\n" |
|---|
| 1558 | | - else: |
|---|
| 1559 | | - return "" |
|---|
| 1637 | + class Guest_to_pid(argparse.Action): |
|---|
| 1638 | + def __call__(self, parser, namespace, values, option_string=None): |
|---|
| 1639 | + try: |
|---|
| 1640 | + pids = Tui.get_pid_from_gname(values) |
|---|
| 1641 | + except: |
|---|
| 1642 | + sys.exit('Error while searching for guest "{}". Use "-p" to ' |
|---|
| 1643 | + 'specify a pid instead?'.format(values)) |
|---|
| 1644 | + if len(pids) == 0: |
|---|
| 1645 | + sys.exit('Error: No guest by the name "{}" found' |
|---|
| 1646 | + .format(values)) |
|---|
| 1647 | + if len(pids) > 1: |
|---|
| 1648 | + sys.exit('Error: Multiple processes found (pids: {}). Use "-p"' |
|---|
| 1649 | + ' to specify the desired pid' |
|---|
| 1650 | + .format(" ".join(map(str, pids)))) |
|---|
| 1651 | + namespace.pid = pids[0] |
|---|
| 1560 | 1652 | |
|---|
| 1561 | | - def cb_guest_to_pid(option, opt, val, parser): |
|---|
| 1562 | | - try: |
|---|
| 1563 | | - pids = Tui.get_pid_from_gname(val) |
|---|
| 1564 | | - except: |
|---|
| 1565 | | - sys.exit('Error while searching for guest "{}". Use "-p" to ' |
|---|
| 1566 | | - 'specify a pid instead?'.format(val)) |
|---|
| 1567 | | - if len(pids) == 0: |
|---|
| 1568 | | - sys.exit('Error: No guest by the name "{}" found'.format(val)) |
|---|
| 1569 | | - if len(pids) > 1: |
|---|
| 1570 | | - sys.exit('Error: Multiple processes found (pids: {}). Use "-p" ' |
|---|
| 1571 | | - 'to specify the desired pid'.format(" ".join(pids))) |
|---|
| 1572 | | - parser.values.pid = pids[0] |
|---|
| 1573 | | - |
|---|
| 1574 | | - optparser = optparse.OptionParser(description=description_text, |
|---|
| 1575 | | - formatter=PlainHelpFormatter()) |
|---|
| 1576 | | - optparser.add_option('-1', '--once', '--batch', |
|---|
| 1577 | | - action='store_true', |
|---|
| 1578 | | - default=False, |
|---|
| 1579 | | - dest='once', |
|---|
| 1580 | | - help='run in batch mode for one second', |
|---|
| 1581 | | - ) |
|---|
| 1582 | | - optparser.add_option('-i', '--debugfs-include-past', |
|---|
| 1583 | | - action='store_true', |
|---|
| 1584 | | - default=False, |
|---|
| 1585 | | - dest='dbgfs_include_past', |
|---|
| 1586 | | - help='include all available data on past events for ' |
|---|
| 1587 | | - 'debugfs', |
|---|
| 1588 | | - ) |
|---|
| 1589 | | - optparser.add_option('-l', '--log', |
|---|
| 1590 | | - action='store_true', |
|---|
| 1591 | | - default=False, |
|---|
| 1592 | | - dest='log', |
|---|
| 1593 | | - help='run in logging mode (like vmstat)', |
|---|
| 1594 | | - ) |
|---|
| 1595 | | - optparser.add_option('-t', '--tracepoints', |
|---|
| 1596 | | - action='store_true', |
|---|
| 1597 | | - default=False, |
|---|
| 1598 | | - dest='tracepoints', |
|---|
| 1599 | | - help='retrieve statistics from tracepoints', |
|---|
| 1600 | | - ) |
|---|
| 1601 | | - optparser.add_option('-d', '--debugfs', |
|---|
| 1602 | | - action='store_true', |
|---|
| 1603 | | - default=False, |
|---|
| 1604 | | - dest='debugfs', |
|---|
| 1605 | | - help='retrieve statistics from debugfs', |
|---|
| 1606 | | - ) |
|---|
| 1607 | | - optparser.add_option('-f', '--fields', |
|---|
| 1608 | | - action='store', |
|---|
| 1609 | | - default='', |
|---|
| 1610 | | - dest='fields', |
|---|
| 1611 | | - help='''fields to display (regex) |
|---|
| 1612 | | - "-f help" for a list of available events''', |
|---|
| 1613 | | - ) |
|---|
| 1614 | | - optparser.add_option('-p', '--pid', |
|---|
| 1615 | | - action='store', |
|---|
| 1616 | | - default=0, |
|---|
| 1617 | | - type='int', |
|---|
| 1618 | | - dest='pid', |
|---|
| 1619 | | - help='restrict statistics to pid', |
|---|
| 1620 | | - ) |
|---|
| 1621 | | - optparser.add_option('-g', '--guest', |
|---|
| 1622 | | - action='callback', |
|---|
| 1623 | | - type='string', |
|---|
| 1624 | | - dest='pid', |
|---|
| 1625 | | - metavar='GUEST', |
|---|
| 1626 | | - help='restrict statistics to guest by name', |
|---|
| 1627 | | - callback=cb_guest_to_pid, |
|---|
| 1628 | | - ) |
|---|
| 1629 | | - options, unkn = optparser.parse_args(sys.argv) |
|---|
| 1630 | | - if len(unkn) != 1: |
|---|
| 1631 | | - sys.exit('Error: Extra argument(s): ' + ' '.join(unkn[1:])) |
|---|
| 1653 | + argparser = argparse.ArgumentParser(description=description_text, |
|---|
| 1654 | + formatter_class=argparse |
|---|
| 1655 | + .RawTextHelpFormatter) |
|---|
| 1656 | + argparser.add_argument('-1', '--once', '--batch', |
|---|
| 1657 | + action='store_true', |
|---|
| 1658 | + default=False, |
|---|
| 1659 | + help='run in batch mode for one second', |
|---|
| 1660 | + ) |
|---|
| 1661 | + argparser.add_argument('-c', '--csv', |
|---|
| 1662 | + action='store_true', |
|---|
| 1663 | + default=False, |
|---|
| 1664 | + help='log in csv format - requires option -l/-L', |
|---|
| 1665 | + ) |
|---|
| 1666 | + argparser.add_argument('-d', '--debugfs', |
|---|
| 1667 | + action='store_true', |
|---|
| 1668 | + default=False, |
|---|
| 1669 | + help='retrieve statistics from debugfs', |
|---|
| 1670 | + ) |
|---|
| 1671 | + argparser.add_argument('-f', '--fields', |
|---|
| 1672 | + default='', |
|---|
| 1673 | + help='''fields to display (regex) |
|---|
| 1674 | +"-f help" for a list of available events''', |
|---|
| 1675 | + ) |
|---|
| 1676 | + argparser.add_argument('-g', '--guest', |
|---|
| 1677 | + type=str, |
|---|
| 1678 | + help='restrict statistics to guest by name', |
|---|
| 1679 | + action=Guest_to_pid, |
|---|
| 1680 | + ) |
|---|
| 1681 | + argparser.add_argument('-i', '--debugfs-include-past', |
|---|
| 1682 | + action='store_true', |
|---|
| 1683 | + default=False, |
|---|
| 1684 | + help='include all available data on past events for' |
|---|
| 1685 | + ' debugfs', |
|---|
| 1686 | + ) |
|---|
| 1687 | + argparser.add_argument('-l', '--log', |
|---|
| 1688 | + action='store_true', |
|---|
| 1689 | + default=False, |
|---|
| 1690 | + help='run in logging mode (like vmstat)', |
|---|
| 1691 | + ) |
|---|
| 1692 | + argparser.add_argument('-L', '--log-to-file', |
|---|
| 1693 | + type=str, |
|---|
| 1694 | + metavar='FILE', |
|---|
| 1695 | + help="like '--log', but logging to a file" |
|---|
| 1696 | + ) |
|---|
| 1697 | + argparser.add_argument('-p', '--pid', |
|---|
| 1698 | + type=int, |
|---|
| 1699 | + default=0, |
|---|
| 1700 | + help='restrict statistics to pid', |
|---|
| 1701 | + ) |
|---|
| 1702 | + argparser.add_argument('-s', '--set-delay', |
|---|
| 1703 | + type=float, |
|---|
| 1704 | + default=DELAY_DEFAULT, |
|---|
| 1705 | + metavar='DELAY', |
|---|
| 1706 | + help='set delay between refreshs (value range: ' |
|---|
| 1707 | + '%s-%s secs)' % (MIN_DELAY, MAX_DELAY), |
|---|
| 1708 | + ) |
|---|
| 1709 | + argparser.add_argument('-t', '--tracepoints', |
|---|
| 1710 | + action='store_true', |
|---|
| 1711 | + default=False, |
|---|
| 1712 | + help='retrieve statistics from tracepoints', |
|---|
| 1713 | + ) |
|---|
| 1714 | + argparser.add_argument('-z', '--skip-zero-records', |
|---|
| 1715 | + action='store_true', |
|---|
| 1716 | + default=False, |
|---|
| 1717 | + help='omit records with all zeros in logging mode', |
|---|
| 1718 | + ) |
|---|
| 1719 | + options = argparser.parse_args() |
|---|
| 1720 | + if options.csv and not (options.log or options.log_to_file): |
|---|
| 1721 | + sys.exit('Error: Option -c/--csv requires -l/--log') |
|---|
| 1722 | + if options.skip_zero_records and not (options.log or options.log_to_file): |
|---|
| 1723 | + sys.exit('Error: Option -z/--skip-zero-records requires -l/-L') |
|---|
| 1632 | 1724 | try: |
|---|
| 1633 | 1725 | # verify that we were passed a valid regex up front |
|---|
| 1634 | 1726 | re.compile(options.fields) |
|---|
| .. | .. |
|---|
| 1694 | 1786 | sys.stderr.write('Did you use a (unsupported) tid instead of a pid?\n') |
|---|
| 1695 | 1787 | sys.exit('Specified pid does not exist.') |
|---|
| 1696 | 1788 | |
|---|
| 1789 | + err = is_delay_valid(options.set_delay) |
|---|
| 1790 | + if err is not None: |
|---|
| 1791 | + sys.exit('Error: ' + err) |
|---|
| 1792 | + |
|---|
| 1697 | 1793 | stats = Stats(options) |
|---|
| 1698 | 1794 | |
|---|
| 1699 | 1795 | if options.fields == 'help': |
|---|
| .. | .. |
|---|
| 1704 | 1800 | sys.stdout.write(' ' + '\n '.join(sorted(set(event_list))) + '\n') |
|---|
| 1705 | 1801 | sys.exit(0) |
|---|
| 1706 | 1802 | |
|---|
| 1707 | | - if options.log: |
|---|
| 1708 | | - log(stats) |
|---|
| 1803 | + if options.log or options.log_to_file: |
|---|
| 1804 | + if options.log_to_file: |
|---|
| 1805 | + signal.signal(signal.SIGHUP, handle_signal) |
|---|
| 1806 | + keys = sorted(stats.get().keys()) |
|---|
| 1807 | + if options.csv: |
|---|
| 1808 | + frmt = CSVFormat(keys) |
|---|
| 1809 | + else: |
|---|
| 1810 | + frmt = StdFormat(keys) |
|---|
| 1811 | + log(stats, options, frmt, keys) |
|---|
| 1709 | 1812 | elif not options.once: |
|---|
| 1710 | | - with Tui(stats) as tui: |
|---|
| 1813 | + with Tui(stats, options) as tui: |
|---|
| 1711 | 1814 | tui.show_stats() |
|---|
| 1712 | 1815 | else: |
|---|
| 1713 | 1816 | batch(stats) |
|---|
| 1714 | 1817 | |
|---|
| 1818 | + |
|---|
| 1715 | 1819 | if __name__ == "__main__": |
|---|
| 1716 | 1820 | main() |
|---|