import sys
import subprocess
from babeltrace import TraceCollection
-from . import mi
-from .. import _version
-from . import progressbar
-from .. import __version__
-from ..common import version_utils
+from . import mi, progressbar
+from .. import _version, __version__
from ..core import analysis
-from ..linuxautomaton import common
+from ..common import (
+ format_utils, parse_utils, time_utils, trace_utils, version_utils
+)
from ..linuxautomaton import automaton
self._post_analysis()
def _print_date(self, begin_ns, end_ns):
- date = 'Timerange: [%s, %s]' % (
- common.ns_to_hour_nsec(begin_ns, gmt=self._args.gmt,
- multi_day=True),
- common.ns_to_hour_nsec(end_ns, gmt=self._args.gmt,
- multi_day=True))
+ time_range_str = format_utils.format_time_range(
+ begin_ns, end_ns, print_date=True, gmt=self._args.gmt
+ )
+ date = 'Timerange: {}'.format(time_range_str)
+
self._print(date)
+ def _format_timestamp(self, timestamp):
+ return format_utils.format_timestamp(
+ timestamp, print_date=self._args.multi_day, gmt=self._args.gmt
+ )
+
def _get_uniform_freq_values(self, durations):
if self._args.uniform_step is not None:
return (self._args.uniform_min, self._args.uniform_max,
refresh_period_ns = None
if args.refresh is not None:
try:
- refresh_period_ns = common.duration_str_to_ns(args.refresh)
+ refresh_period_ns = parse_utils.parse_duration(args.refresh)
except ValueError as e:
self._cmdline_error(str(e))
pass
def _process_date_args(self):
- def date_to_epoch_nsec(date):
- ts = common.date_to_epoch_nsec(self._handles, date, self._args.gmt)
- if ts is None:
- self._cmdline_error('Invalid date format: "{}"'.format(date))
+ def parse_date(date):
+ try:
+ ts = parse_utils.parse_trace_collection_date(
+ self._traces, date, self._args.gmt
+ )
+ except ValueError as e:
+ self._cmdline_error(str(e))
return ts
- self._args.multi_day = common.is_multi_day_trace_collection(
- self._handles)
+ self._args.multi_day = trace_utils.is_multi_day_trace_collection(
+ self._traces
+ )
begin_ts = None
end_ts = None
if self._args.timerange:
- begin_ts, end_ts = common.extract_timerange(self._handles,
- self._args.timerange,
- self._args.gmt)
- if None in [begin_ts, end_ts]:
- self._cmdline_error(
- 'Invalid time format: "{}"'.format(self._args.timerange))
+ try:
+ begin_ts, end_ts = (
+ parse_utils.parse_trace_collection_time_range(
+ self._traces, self._args.timerange, self._args.gmt
+ )
+ )
+ except ValueError as e:
+ self._cmdline_error(str(e))
else:
if self._args.begin:
- begin_ts = date_to_epoch_nsec(self._args.begin)
+ begin_ts = parse_date(self._args.begin)
if self._args.end:
- end_ts = date_to_epoch_nsec(self._args.end)
+ end_ts = parse_date(self._args.end)
# We have to check if timestamp_begin is None, which
# it always is in older versions of babeltrace. In
from ..core import io
from ..common import format_utils
from .command import Command
-from ..linuxautomaton import common
_UsageTables = collections.namedtuple('_UsageTables', [
title='Disk Request Average Latency',
label_header='Disk',
unit='ms',
- get_value=lambda row: row.rtps.value / common.NSEC_PER_MSEC,
+ get_value=lambda row: row.rtps.value / 1000000,
get_label=lambda row: row.disk.name,
data=result_table.rows
)
def _print_log_row(self, row):
fmt = '{:<40} {:<16} {:>16} {:>11} {:<24} {:<8} {:<14}'
- begin_time = common.ns_to_hour_nsec(row.time_range.begin,
- self._args.multi_day,
- self._args.gmt)
- end_time = common.ns_to_hour_nsec(row.time_range.end,
- self._args.multi_day,
- self._args.gmt)
- time_range_str = '[' + begin_time + ',' + end_time + ']'
+ time_range_str = format_utils.format_time_range(
+ row.time_range.begin,
+ row.time_range.end,
+ self._args.multi_day,
+ self._args.gmt
+ )
duration_str = '%0.03f' % row.duration.to_us()
if type(row.size) is mi.Empty:
print()
fmt = '{} {} (usec)'
print(fmt.format(result_table.title, result_table.subtitle))
- header_fmt = '{:<19} {:<20} {:<16} {:<23} {:<5} {:<24} {:<8} {:<14}'
+ header_fmt = '{:<20} {:<20} {:<16} {:<23} {:<5} {:<24} {:<8} {:<14}'
print(header_fmt.format(
'Begin', 'End', 'Name', 'Duration (usec)', 'Size', 'Proc', 'PID',
'Filename'))
from . import termgraph
from .command import Command
from ..core import irq as core_irq
-from ..linuxautomaton import common, sv
+from ..linuxautomaton import sv
class IrqAnalysisCommand(Command):
return hard_stats_table, soft_stats_table, freq_tables
- def _ns_to_hour_nsec(self, ts):
- return common.ns_to_hour_nsec(ts, self._args.multi_day, self._args.gmt)
-
def _print_log(self, result_table):
fmt = '[{:<18}, {:<18}] {:>15} {:>4} {:<9} {:>4} {:<22}'
title_fmt = '{:<20} {:<19} {:>15} {:>4} {:<9} {:>4} {:<22}'
end_ts = timerange.end
if type(row.raised_ts) is mi.Timestamp:
- raised_fmt = ' (raised at %s)'
- raised_ts = \
- raised_fmt % self._ns_to_hour_nsec(row.raised_ts.value)
+ raised_ts = ' (raised at {})'.format(
+ self._format_timestamp(row.raised_ts.value)
+ )
else:
raised_ts = ''
else:
irqtype = 'SoftIRQ'
- print(fmt.format(self._ns_to_hour_nsec(begin_ts),
- self._ns_to_hour_nsec(end_ts),
+ print(fmt.format(self._format_timestamp(begin_ts),
+ self._format_timestamp(end_ts),
'%0.03f' % ((end_ts - begin_ts) / 1000),
'%d' % cpu_id, irqtype, irq_do.nr,
irq_do.name + raised_ts))
import operator
import statistics
import collections
-from . import mi
-from . import termgraph
+from . import mi, termgraph
from ..core import sched
from .command import Command
from ..common import format_utils
-from ..linuxautomaton import common
_SchedStats = collections.namedtuple('_SchedStats', [
return statistics.stdev(sched_latencies)
- def _ns_to_hour_nsec(self, ts):
- return common.ns_to_hour_nsec(ts, self._args.multi_day, self._args.gmt)
-
def _print_sched_events(self, result_table):
fmt = '[{:<18}, {:<18}] {:>15} {:>10} {:>3} {:<25} {:<25}'
title_fmt = '{:<20} {:<19} {:>15} {:>10} {:>3} {:<25} {:<25}'
else:
waker_str = '%s (%d)' % (waker_proc.name, waker_proc.tid)
- print(fmt.format(self._ns_to_hour_nsec(wakeup_ts),
- self._ns_to_hour_nsec(switch_ts),
+ print(fmt.format(self._format_timestamp(wakeup_ts),
+ self._format_timestamp(switch_ts),
'%0.03f' % (latency / 1000), prio,
target_cpu, wakee_str, waker_str))
# SOFTWARE.
import math
+import socket
+import struct
+import time
+from .time_utils import NSEC_PER_SEC
def format_size(size, binary_prefix=True):
- """Convert an integral number of bytes to a human-readable string
+ """Convert an integral number of bytes to a human-readable string.
Args:
- size (int): a non-negative number of bytes
+ size (int): a non-negative number of bytes.
+
binary_prefix (bool, optional): whether to use binary units
- prefixes, over SI prefixes. default: True
+ prefixes, over SI prefixes (default: True).
Returns:
- The formatted string comprised of the size and units
+ The formatted string comprised of the size and units.
Raises:
- ValueError: if size < 0
+ ValueError: if size < 0.
"""
if size < 0:
raise ValueError('Cannot format negative size')
if binary_prefix:
base = 1024
- units = [' B', 'KiB', 'MiB', 'GiB','TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
+ units = [' B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
else:
base = 1000
units = [' B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
return format_str.format(size, unit)
+
def format_prio_list(prio_list):
- """Format a list of prios into a string of unique prios with count
+ """Format a list of prios into a string of unique prios with count.
Args:
- prio_list: a list of PrioEvent objects
+ prio_list (list): a list of PrioEvent objects.
Returns:
The formatted string containing the unique priorities and
prio_str += ']'
return prio_str
+
+
+def format_timestamp(timestamp, print_date=False, gmt=False):
+ """Format a timestamp into a human-readable date string
+
+ Args:
+ timestamp (int): nanoseconds since epoch.
+
+ print_date (bool, optional): flag indicating whether to print
+ the full date or just the time of day (default: False).
+
+ gmt (bool, optional): flag indicating whether the timestamp is
+ in the local timezone or gmt (default: False).
+
+ Returns:
+ The formatted date string, containing either the full date or
+ just the time of day.
+ """
+ date_fmt = '{:04}-{:02}-{:02} '
+ time_fmt = '{:02}:{:02}:{:02}.{:09}'
+
+ if gmt:
+ date = time.gmtime(timestamp / NSEC_PER_SEC)
+ else:
+ date = time.localtime(timestamp / NSEC_PER_SEC)
+
+ formatted_ts = time_fmt.format(
+ date.tm_hour, date.tm_min, date.tm_sec,
+ timestamp % NSEC_PER_SEC
+ )
+
+ if print_date:
+ date_str = date_fmt.format(date.tm_year, date.tm_mon, date.tm_mday)
+ formatted_ts = date_str + formatted_ts
+
+ return formatted_ts
+
+
+def format_time_range(begin_ts, end_ts, print_date=False, gmt=False):
+ """Format a pair of timestamps into a human-readable date string.
+
+ Args:
+ begin_ts (int): nanoseconds since epoch to beginning of
+ time range.
+
+ end_ts (int): nanoseconds since epoch to end of time range.
+
+ print_date (bool, optional): flag indicating whether to print
+ the full date or just the time of day (default: False).
+
+ gmt (bool, optional): flag indicating whether the timestamp is
+ in the local timezone or gmt (default: False).
+
+ Returns:
+ The formatted dates string, containing either the full date or
+ just the time of day, enclosed within square brackets and
+ delimited by a comma.
+ """
+ time_range_fmt = '[{}, {}]'
+
+ begin_str = format_timestamp(begin_ts, print_date, gmt)
+ end_str = format_timestamp(end_ts, print_date, gmt)
+
+ return time_range_fmt.format(begin_str, end_str)
+
+
+def format_ipv4(ip, port=None):
+ """Format an ipv4 address into a human-readable string.
+
+ Args:
+ ip (varies): the ip address as extracted in an LTTng event.
+ Either an integer or a list of integers, depending on the
+ tracer version.
+
+ port (int, optional): the port number associated with the
+ address.
+
+ Returns:
+ The formatted string containing the ipv4 address and, optionally,
+ the port number.
+
+ """
+ # depending on the version of lttng-modules, the v4addr is an
+ # integer (< 2.6) or sequence (>= 2.6)
+ try:
+ ip_str = '{}.{}.{}.{}'.format(ip[0], ip[1], ip[2], ip[3])
+ except TypeError:
+ # The format string '!I' tells pack to interpret ip as a
+ # packed structure of network-endian 32-bit unsigned integers,
+ # which inet_ntoa can then convert into the formatted string
+ ip_str = socket.inet_ntoa(struct.pack('!I', ip))
+
+ if port is not None:
+ ip_str += ':{}'.format(port)
+
+ return ip_str
--- /dev/null
+# The MIT License (MIT)
+#
+# Copyright (C) 2016 - Antoine Busque <abusque@efficios.com>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import datetime
+import math
+import re
+from time import timezone
+from . import trace_utils
+from .time_utils import NSEC_PER_SEC
+
+
+def _split_value_units(raw_str):
+ """Take a string with a numerical value and units, and separate the
+ two.
+
+ Args:
+ raw_str (str): the string to parse, with numerical value and
+ (optionally) units.
+
+ Returns:
+ A tuple (value, units), where value is a string and units is
+ either a string or `None` if no units were found.
+ """
+ try:
+ units_index = next(i for i, c in enumerate(raw_str) if c.isalpha())
+ except StopIteration:
+ # no units found
+ return (raw_str, None)
+
+ return (raw_str[:units_index], raw_str[units_index:])
+
+
+def parse_size(size_str):
+ """Convert a human-readable size string to an integral number of
+ bytes.
+
+ Args:
+ size_str (str): the formatted string comprised of the size and
+ units.
+
+ Returns:
+ A non-negative number of bytes.
+
+ Raises:
+ ValueError: if units are unrecognised or the size is not a
+ real number.
+ """
+ binary_units = ['B', 'KiB', 'MiB', 'GiB', 'TiB',
+ 'PiB', 'EiB', 'ZiB', 'YiB']
+ # units as printed by GNU coreutils (e.g. ls or du), using base
+ # 1024 as well
+ coreutils_units = ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
+ si_units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
+
+ size, units = _split_value_units(size_str)
+
+ try:
+ size = float(size)
+ except ValueError:
+ raise ValueError('invalid size: {}'.format(size))
+
+ # If no units have been found, assume bytes
+ if units is not None:
+ if units in binary_units:
+ base = 1024
+ exponent = binary_units.index(units)
+ elif units in coreutils_units:
+ base = 1024
+ exponent = coreutils_units.index(units)
+ elif units in si_units:
+ base = 1000
+ exponent = si_units.index(units)
+ else:
+ raise ValueError('unrecognised units: {}'.format(units))
+
+ size *= math.pow(base, exponent)
+
+ return int(size)
+
+
+def parse_duration(duration_str):
+ """Convert a human-readable duration string to an integral number of
+ nanoseconds.
+
+ Args:
+ duration_str (str): the formatted string comprised of the
+ duration and units.
+
+ Returns:
+ A non-negative number of nanoseconds.
+
+ Raises:
+ ValueError: if units are unrecognised or the size is not a
+ real number.
+ """
+ base = 1000
+
+ try:
+ units_index = next(i for i, c in enumerate(duration_str)
+ if c.isalpha())
+ except StopIteration:
+ # no units found
+ units_index = None
+
+ if units_index is not None:
+ duration = duration_str[:units_index]
+ units = duration_str[units_index:].lower()
+ else:
+ duration = duration_str
+ units = None
+
+ try:
+ duration = float(duration)
+ except ValueError:
+ raise ValueError('invalid duration: {}'.format(duration))
+
+ if units is not None:
+ if units == 's':
+ exponent = 3
+ elif units == 'ms':
+ exponent = 2
+ elif units in ['us', 'µs']:
+ exponent = 1
+ elif units == 'ns':
+ exponent = 0
+ else:
+ raise ValueError('unrecognised units: {}'.format(units))
+ else:
+ # no units defaults to seconds
+ exponent = 3
+
+ duration *= math.pow(base, exponent)
+
+ return int(duration)
+
+
+def _parse_date_full_with_nsec(date):
+ """Parse full date string with nanosecond resolution.
+
+ This matches either 2014-12-12 17:29:43.802588035 or
+ 2014-12-12T17:29:43.802588035.
+
+ Args:
+ date (str): the date string to be parsed.
+
+ Returns:
+ A tuple of the format (date_time, nsec), where date_time is a
+ datetime.datetime object and nsec is an int of the remaining
+ nanoseconds.
+
+ Raises:
+ ValueError: if the date format does not match.
+ """
+ pattern = re.compile(
+ r'^(?P<year>\d{4})-(?P<mon>[01]\d)-(?P<day>[0-3]\d)[\sTt]'
+ r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})\.(?P<nsec>\d{9})$'
+ )
+
+ if not pattern.match(date):
+ raise ValueError('Wrong date format: {}'.format(date))
+
+ year = pattern.search(date).group('year')
+ month = pattern.search(date).group('mon')
+ day = pattern.search(date).group('day')
+ hour = pattern.search(date).group('hour')
+ minute = pattern.search(date).group('min')
+ sec = pattern.search(date).group('sec')
+ nsec = pattern.search(date).group('nsec')
+
+ date_time = datetime.datetime(
+ int(year), int(month), int(day),
+ int(hour), int(minute), int(sec)
+ )
+
+ return date_time, int(nsec)
+
+
+def _parse_date_full(date):
+ """Parse full date string.
+
+ This matches either 2014-12-12 17:29:43 or 2014-12-12T17:29:43.
+
+ Args:
+ date (str): the date string to be parsed.
+
+ Returns:
+ A tuple of the format (date_time, nsec), where date_time is a
+ datetime.datetime object and nsec is 0.
+
+ Raises:
+ ValueError: if the date format does not match.
+ """
+ pattern = re.compile(
+ r'^(?P<year>\d{4})-(?P<mon>[01]\d)-(?P<day>[0-3]\d)[\sTt]'
+ r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})$'
+ )
+
+ if not pattern.match(date):
+ raise ValueError('Wrong date format: {}'.format(date))
+
+ year = pattern.search(date).group('year')
+ month = pattern.search(date).group('mon')
+ day = pattern.search(date).group('day')
+ hour = pattern.search(date).group('hour')
+ minute = pattern.search(date).group('min')
+ sec = pattern.search(date).group('sec')
+ nsec = 0
+
+ date_time = datetime.datetime(
+ int(year), int(month), int(day),
+ int(hour), int(minute), int(sec)
+ )
+
+ return date_time, nsec
+
+
+def _parse_date_time_with_nsec(date):
+ """Parse time string with nanosecond resolution.
+
+ This matches 17:29:43.802588035.
+
+ Args:
+ date (str): the date string to be parsed.
+
+ Returns:
+ A tuple of the format (date_time, nsec), where date_time is a
+ datetime.time object and nsec is an int of the remaining
+ nanoseconds.
+
+ Raises:
+ ValueError: if the date format does not match.
+ """
+ pattern = re.compile(
+ r'^(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})\.(?P<nsec>\d{9})$'
+ )
+
+ if not pattern.match(date):
+ raise ValueError('Wrong date format: {}'.format(date))
+
+ hour = pattern.search(date).group('hour')
+ minute = pattern.search(date).group('min')
+ sec = pattern.search(date).group('sec')
+ nsec = pattern.search(date).group('nsec')
+
+ time = datetime.time(int(hour), int(minute), int(sec))
+
+ return time, int(nsec)
+
+
+def _parse_date_time(date):
+ """Parse time string.
+
+ This matches 17:29:43.
+
+ Args:
+ date (str): the date string to be parsed.
+
+ Returns:
+ A tuple of the format (date_time, nsec), where date_time is a
+ datetime.time object and nsec is 0.
+
+ Raises:
+ ValueError: if the date format does not match.
+ """
+ pattern = re.compile(
+ r'^(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})$'
+ )
+
+ if not pattern.match(date):
+ raise ValueError('Wrong date format: {}'.format(date))
+
+ hour = pattern.search(date).group('hour')
+ minute = pattern.search(date).group('min')
+ sec = pattern.search(date).group('sec')
+ nsec = 0
+
+ time = datetime.time(int(hour), int(minute), int(sec))
+
+ return time, nsec
+
+
+def _parse_date_timestamp(date):
+ """Parse timestamp string in nanoseconds from epoch.
+
+ This matches 93847238974923874.
+
+ Args:
+ date (str): the date string to be parsed.
+
+ Returns:
+ A tuple of the format (date_time, nsec), where date_time is a
+ datetime.datetime object and nsec is an int of the remaining
+ nanoseconds.
+
+ Raises:
+ ValueError: if the date format does not match.
+ """
+ pattern = re.compile(r'^\d+$')
+
+ if not pattern.match(date):
+ raise ValueError('Wrong date format: {}'.format(date))
+
+ timestamp_ns = int(date)
+
+ date_time = datetime.datetime.fromtimestamp(
+ timestamp_ns / NSEC_PER_SEC
+ )
+ nsec = timestamp_ns % NSEC_PER_SEC
+
+ return date_time, nsec
+
+
+def parse_date(date):
+ """Try to parse a date string from one of many formats.
+
+ Args:
+ date (str): the date string to be parsed.
+
+ Returns:
+ A tuple of the format (date_time, nsec), where date_time is
+ one of either datetime.datetime or datetime.time, depending on
+ whether the date string contains full date information or only
+ the time of day. The latter case can still be useful when used
+ in conjuction with a trace collection's date to provide the
+ missing information. The nsec element of the tuple is an int and
+ corresponds to the nanoseconds for the given date/timestamp.
+ This is due to datetime objects only supporting a resolution
+ down to the microsecond.
+
+ Raises:
+ ValueError: if the date does not correspond to any of the
+ supported formats.
+ """
+ parsers = [
+ _parse_date_full_with_nsec, _parse_date_full,
+ _parse_date_time_with_nsec, _parse_date_time,
+ _parse_date_timestamp
+ ]
+
+ date_time = None
+ nsec = None
+
+ for parser in parsers:
+ try:
+ (date_time, nsec) = parser(date)
+ except ValueError:
+ continue
+
+ # If no exception was raised, the parser found a match, so
+ # stop iterating
+ break
+
+ if date_time is None or nsec is None:
+ # None of the parsers were a match
+ raise ValueError('Unrecognised date format: {}'.format(date))
+
+ return date_time, nsec
+
+
+def parse_trace_collection_date(collection, date, gmt=False):
+ """Parse a date string, using a trace collection to disambiguate
+ incomplete dates.
+
+ Args:
+ collection (TraceCollection): a babeltrace TraceCollection
+ instance.
+
+ date (string): the date string to be parsed.
+
+ gmt (bool, optional): flag indicating whether the timestamp is
+ in the local timezone or gmt (default: False).
+
+ Returns:
+ A timestamp (int) in nanoseconds since epoch, corresponding to
+ the parsed date.
+
+ Raises:
+ ValueError: if the date format is unrecognised, or if the date
+ format does not specify the date and the trace collection spans
+ multiple days.
+ """
+ try:
+ date_time, nsec = parse_date(date)
+ except ValueError:
+ # This might raise ValueError if the date is in an invalid
+ # format, so just re-raise the exception to inform the caller
+ # of the problem.
+ raise
+
+ # date_time will either be an actual datetime.datetime object, or
+ # just a datetime.time object, depending on the format. In the
+ # latter case, try and fill out the missing date information from
+ # the trace collection's date.
+ if isinstance(date_time, datetime.time):
+ try:
+ collection_date = trace_utils.get_trace_collection_date(collection)
+ except ValueError:
+ raise ValueError(
+ 'Invalid date format for multi-day trace: {}'.format(date)
+ )
+
+ date_time = datetime.datetime.combine(collection_date, date_time)
+
+ if gmt:
+ date_time = date_time + datetime.timedelta(seconds=timezone)
+
+ timestamp_ns = date_time.timestamp() * NSEC_PER_SEC + nsec
+
+ return timestamp_ns
+
+
+def parse_trace_collection_time_range(collection, time_range, gmt=False):
+ """Parse a time range string, using a trace collection to
+ disambiguate incomplete dates.
+
+ Args:
+ collection (TraceCollection): a babeltrace TraceCollection
+ instance.
+
+ time_range (string): the time range string to be parsed.
+
+ gmt (bool, optional): flag indicating whether the timestamps are
+ in the local timezone or gmt (default: False).
+
+ Returns:
+ A tuple (begin, end) of the two timestamps (int) in nanoseconds
+ since epoch, corresponding to the parsed dates.
+
+ Raises:
+ ValueError: if the time range or date format is unrecognised,
+ or if the date format does not specify the date and the trace
+ collection spans multiple days.
+ """
+ pattern = re.compile(r'^\[(?P<begin>.*),(?P<end>.*)\]$')
+ if not pattern.match(time_range):
+ raise ValueError('Invalid time range format: {}'.format(time_range))
+
+ begin_str = pattern.search(time_range).group('begin').strip()
+ end_str = pattern.search(time_range).group('end').strip()
+
+ try:
+ begin = parse_trace_collection_date(collection, begin_str, gmt)
+ end = parse_trace_collection_date(collection, end_str, gmt)
+ except ValueError:
+ # Either of the dates was in the wrong format, propagate the
+ # exception to the caller.
+ raise
+
+ return begin, end
--- /dev/null
+# The MIT License (MIT)
+#
+# Copyright (C) 2016 - Antoine Busque <abusque@efficios.com>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+NSEC_PER_SEC = 1000000000
--- /dev/null
+# The MIT License (MIT)
+#
+# Copyright (C) 2016 - Antoine Busque <abusque@efficios.com>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import datetime
+from .time_utils import NSEC_PER_SEC
+
+
+def is_multi_day_trace_collection(collection):
+ """Check whether a trace collection spans more than one day.
+
+ Args:
+ collection (TraceCollection): a babeltrace TraceCollection
+ instance.
+
+ Returns:
+ True if the trace collection spans more than one day,
+ False otherwise.
+ """
+ date_begin = datetime.date.fromtimestamp(
+ collection.timestamp_begin / NSEC_PER_SEC
+ )
+ date_end = datetime.date.fromtimestamp(
+ collection.timestamp_end / NSEC_PER_SEC
+ )
+
+ return date_begin != date_end
+
+
+def get_trace_collection_date(collection):
+ """Get a trace collection's date.
+
+ Args:
+ collection (TraceCollection): a babeltrace TraceCollection
+ instance.
+
+ Returns:
+ A datetime.date object corresponding to the date at which the
+ trace collection was recorded.
+
+ Raises:
+ ValueError: if the trace collection spans more than one day.
+ """
+ if is_multi_day_trace_collection(collection):
+ raise ValueError('Trace collection spans multiple days')
+
+ trace_date = datetime.date.fromtimestamp(
+ collection.timestamp_begin / NSEC_PER_SEC
+ )
+
+ return trace_date
+
+
+def get_syscall_name(event):
+ """Get the name of a syscall from an event.
+
+ Args:
+ event (Event): an instance of a babeltrace Event for a syscall
+ entry.
+
+ Returns:
+ The name of the syscall, stripped of any superfluous prefix.
+
+ Raises:
+ ValueError: if the event is not a syscall event.
+ """
+ name = event.name
+
+ if name.startswith('sys_'):
+ return name[4:]
+ elif name.startswith('syscall_entry_'):
+ return name[14:]
+ else:
+ raise ValueError('Not a syscall event')
return self._get_io_requests(sv.IORequest.OP_READ_WRITE)
def _get_io_requests(self, io_operation=None):
- """Create a generator of syscall io requests by operation
+ """Create a generator of syscall io requests by operation.
Args:
io_operation (IORequest.OP_*, optional): The operation of
@staticmethod
def _get_fd_by_timestamp(fd_list, timestamp):
- """Return the FDStats object whose lifetime contains timestamp
+ """Return the FDStats object whose lifetime contains timestamp.
This method performs a recursive binary search on the given
fd_list argument, and will find the FDStats object for which
Args:
fd_list (list): list of FDStats object, sorted
- chronologically by open_ts
+ chronologically by open_ts.
timestamp (int): timestamp in nanoseconds (ns) since unix
epoch which should be contained in the FD's lifetime.
+++ /dev/null
-# The MIT License (MIT)
-#
-# Copyright (C) 2015 - Julien Desfossez <jdesfossez@efficios.com>
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-
-import re
-import time
-import datetime
-import socket
-import struct
-
-NSEC_PER_SEC = 1000000000
-NSEC_PER_MSEC = 1000000
-NSEC_PER_USEC = 1000
-
-BYTES_PER_TIB = 1099511627776
-BYTES_PER_GIB = 1073741824
-BYTES_PER_MIB = 1048576
-BYTES_PER_KIB = 1024
-
-O_CLOEXEC = 0o2000000
-
-
-def get_syscall_name(event):
- name = event.name
-
- if name.startswith('sys_'):
- # Strip first 4 because sys_ is 4 chars long
- return name[4:]
-
- # Name begins with syscall_entry_ (14 chars long)
- return name[14:]
-
-
-def is_multi_day_trace_collection(handles):
- time_begin = None
-
- for handle in handles.values():
- if time_begin is None:
- time_begin = time.localtime(handle.timestamp_begin / NSEC_PER_SEC)
- year_begin = time_begin.tm_year
- month_begin = time_begin.tm_mon
- day_begin = time_begin.tm_mday
-
- time_end = time.localtime(handle.timestamp_end / NSEC_PER_SEC)
- year_end = time_end.tm_year
- month_end = time_end.tm_mon
- day_end = time_end.tm_mday
-
- if year_begin != year_end:
- return True
- elif month_begin != month_end:
- return True
- elif day_begin != day_end:
- return True
-
- return False
-
-
-def trace_collection_date(handles):
- if is_multi_day_trace_collection(handles):
- return None
-
- for handle in handles.values():
- trace_time = time.localtime(handle.timestamp_begin / NSEC_PER_SEC)
- year = trace_time.tm_year
- month = trace_time.tm_mon
- day = trace_time.tm_mday
- return (year, month, day)
-
-
-def extract_timerange(handles, timerange, gmt):
- pattern = re.compile(r'^\[(?P<begin>.*),(?P<end>.*)\]$')
- if not pattern.match(timerange):
- return None, None
- begin_str = pattern.search(timerange).group('begin').strip()
- end_str = pattern.search(timerange).group('end').strip()
- begin = date_to_epoch_nsec(handles, begin_str, gmt)
- end = date_to_epoch_nsec(handles, end_str, gmt)
- return (begin, end)
-
-
-def date_to_epoch_nsec(handles, date, gmt):
- # match 2014-12-12 17:29:43.802588035 or 2014-12-12T17:29:43.802588035
- pattern1 = re.compile(r'^(?P<year>\d{4})-(?P<mon>[01]\d)-'
- r'(?P<day>[0-3]\d)[\sTt]'
- r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})\.'
- r'(?P<nsec>\d{9})$')
- # match 2014-12-12 17:29:43 or 2014-12-12T17:29:43
- pattern2 = re.compile(r'^(?P<year>\d{4})-(?P<mon>[01]\d)-'
- r'(?P<day>[0-3]\d)[\sTt]'
- r'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})$')
- # match 17:29:43.802588035
- pattern3 = re.compile(r'^(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})\.'
- r'(?P<nsec>\d{9})$')
- # match 17:29:43
- pattern4 = re.compile(r'^(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})$')
-
- # match 93847238974923874
- pattern5 = re.compile(r'^\d+$')
-
- if pattern1.match(date):
- year = pattern1.search(date).group('year')
- month = pattern1.search(date).group('mon')
- day = pattern1.search(date).group('day')
- hour = pattern1.search(date).group('hour')
- minute = pattern1.search(date).group('min')
- sec = pattern1.search(date).group('sec')
- nsec = pattern1.search(date).group('nsec')
- elif pattern2.match(date):
- year = pattern2.search(date).group('year')
- month = pattern2.search(date).group('mon')
- day = pattern2.search(date).group('day')
- hour = pattern2.search(date).group('hour')
- minute = pattern2.search(date).group('min')
- sec = pattern2.search(date).group('sec')
- nsec = 0
- elif pattern3.match(date):
- collection_date = trace_collection_date(handles)
- if collection_date is None:
- print("Use the format 'yyyy-mm-dd hh:mm:ss[.nnnnnnnnn]' "
- "for multi-day traces")
- return None
- (year, month, day) = collection_date
- hour = pattern3.search(date).group('hour')
- minute = pattern3.search(date).group('min')
- sec = pattern3.search(date).group('sec')
- nsec = pattern3.search(date).group('nsec')
- elif pattern4.match(date):
- collection_date = trace_collection_date(handles)
- if collection_date is None:
- print("Use the format 'yyyy-mm-dd hh:mm:ss[.nnnnnnnnn]' "
- "for multi-day traces")
- return None
- (year, month, day) = collection_date
- hour = pattern4.search(date).group('hour')
- minute = pattern4.search(date).group('min')
- sec = pattern4.search(date).group('sec')
- nsec = 0
- elif pattern5.match(date):
- return int(date)
- else:
- return None
-
- date_time = datetime.datetime(int(year), int(month), int(day), int(hour),
- int(minute), int(sec))
- if gmt:
- date_time = date_time + datetime.timedelta(seconds=time.timezone)
- return int(date_time.timestamp()) * NSEC_PER_SEC + int(nsec)
-
-
-def ns_to_asctime(ns):
- return time.asctime(time.localtime(ns/NSEC_PER_SEC))
-
-
-def ns_to_hour(ns):
- date = time.localtime(ns / NSEC_PER_SEC)
- return '%02d:%02d:%02d' % (date.tm_hour, date.tm_min, date.tm_sec)
-
-
-def ns_to_hour_nsec(ns, multi_day=False, gmt=False):
- if gmt:
- date = time.gmtime(ns / NSEC_PER_SEC)
- else:
- date = time.localtime(ns / NSEC_PER_SEC)
- if multi_day:
- return ('%04d-%02d-%02d %02d:%02d:%02d.%09d' %
- (date.tm_year, date.tm_mon, date.tm_mday, date.tm_hour,
- date.tm_min, date.tm_sec, ns % NSEC_PER_SEC))
- else:
- return ('%02d:%02d:%02d.%09d' %
- (date.tm_hour, date.tm_min, date.tm_sec, ns % NSEC_PER_SEC))
-
-
-def ns_to_sec(ns):
- return '%lu.%09u' % (ns / NSEC_PER_SEC, ns % NSEC_PER_SEC)
-
-
-def ns_to_day(ns):
- date = time.localtime(ns/NSEC_PER_SEC)
- return '%04d-%02d-%02d' % (date.tm_year, date.tm_mon, date.tm_mday)
-
-
-def sec_to_hour(ns):
- date = time.localtime(ns)
- return '%02d:%02d:%02d' % (date.tm_hour, date.tm_min, date.tm_sec)
-
-
-def sec_to_nsec(sec):
- return sec * NSEC_PER_SEC
-
-
-def seq_to_ipv4(ip):
- return '{}.{}.{}.{}'.format(ip[0], ip[1], ip[2], ip[3])
-
-
-def int_to_ipv4(ip):
- return socket.inet_ntoa(struct.pack('!I', ip))
-
-
-def size_str_to_bytes(size_str):
- try:
- units_index = next(i for i, c in enumerate(size_str) if c.isalpha())
- except StopIteration:
- # no units found
- units_index = None
-
- if units_index is not None:
- size = size_str[:units_index]
- units = size_str[units_index:]
- else:
- size = size_str
- units = None
-
- try:
- size = float(size)
- except ValueError:
- raise ValueError('invalid size: {}'.format(size))
-
- # no units defaults to bytes
- if units is not None:
- if units in ['t', 'T', 'tB', 'TB']:
- size *= BYTES_PER_TIB
- elif units in ['g', 'G', 'gB', 'GB']:
- size *= BYTES_PER_GIB
- elif units in ['m', 'M', 'mB', 'MB']:
- size *= BYTES_PER_MIB
- elif units in ['k', 'K', 'kB', 'KB']:
- size *= BYTES_PER_KIB
- elif units == 'B':
- # bytes is already the target unit
- pass
- else:
- raise ValueError('unrecognised units: {}'.format(units))
-
- size = int(size)
-
- return size
-
-
-def duration_str_to_ns(duration_str):
- try:
- units_index = next(i for i, c in enumerate(duration_str)
- if c.isalpha())
- except StopIteration:
- # no units found
- units_index = None
-
- if units_index is not None:
- duration = duration_str[:units_index]
- units = duration_str[units_index:].lower()
- else:
- duration = duration_str
- units = None
-
- try:
- duration = float(duration)
- except ValueError:
- raise ValueError('invalid duration: {}'.format(duration))
-
- if units is not None:
- if units == 's':
- duration *= NSEC_PER_SEC
- elif units == 'ms':
- duration *= NSEC_PER_MSEC
- elif units in ['us', 'µs']:
- duration *= NSEC_PER_USEC
- elif units == 'ns':
- # ns is already the target unit
- pass
- else:
- raise ValueError('unrecognised units: {}'.format(units))
- else:
- # no units defaults to seconds
- duration *= NSEC_PER_SEC
-
- duration = int(duration)
-
- return duration
-
-
-def get_v4_addr_str(ip):
- # depending on the version of lttng-modules, the v4addr is a
- # string (< 2.6) or sequence (>= 2.6)
- try:
- return seq_to_ipv4(ip)
- except TypeError:
- return int_to_ipv4(ip)
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
+import os
import socket
from babeltrace import CTFScope
-from . import sp, sv, common
+from . import sp, sv
+from ..common import format_utils, trace_utils
class IoStateProvider(sp.StateProvider):
def _process_syscall_entry(self, event):
# Only handle IO Syscalls
- name = common.get_syscall_name(event)
+ name = trace_utils.get_syscall_name(event)
if name not in sv.SyscallConsts.IO_SYSCALLS:
return
if 'family' in event and event['family'] == socket.AF_INET:
fd = event['fd']
if fd in parent_proc.fds:
- parent_proc.fds[fd].filename = (
- '%s:%d' % (common.get_v4_addr_str(event['v4addr']),
- event['dport']))
+ parent_proc.fds[fd].filename = format_utils.format_ipv4(
+ event['v4addr'], event['dport']
+ )
def _process_writeback_pages_written(self, event):
for cpu in self._state.cpus.values():
event, proc.tid, old_file)
if name == 'dup3':
- cloexec = event['flags'] & common.O_CLOEXEC == common.O_CLOEXEC
+ cloexec = event['flags'] & os.O_CLOEXEC == os.O_CLOEXEC
current_syscall.io_rq.cloexec = cloexec
def _track_close(self, event, name, proc):
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
-from . import sp, sv, common
+import os
+from . import sp, sv
class StatedumpStateProvider(sp.StateProvider):
pid = event['pid']
fd = event['fd']
filename = event['filename']
- cloexec = event['flags'] & common.O_CLOEXEC == common.O_CLOEXEC
+ cloexec = event['flags'] & os.O_CLOEXEC == os.O_CLOEXEC
if pid not in self._state.tids:
self._state.tids[pid] = sv.Process(tid=pid, pid=pid)
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
+import os
import socket
-from . import common
+from ..common import format_utils, trace_utils
class Process():
@classmethod
def new_from_entry(cls, event):
- name = common.get_syscall_name(event)
+ name = trace_utils.get_syscall_name(event)
return cls(name, event.timestamp)
@classmethod
def new_from_disk_open(cls, event, tid):
begin_ts = event.timestamp
- name = common.get_syscall_name(event)
+ name = trace_utils.get_syscall_name(event)
filename = event['filename']
req = cls(begin_ts, tid, name, filename, FDType.disk)
- req.cloexec = event['flags'] & common.O_CLOEXEC == common.O_CLOEXEC
+ req.cloexec = event['flags'] & os.O_CLOEXEC == os.O_CLOEXEC
return req
def new_from_accept(cls, event, tid):
# Handle both accept and accept4
begin_ts = event.timestamp
- name = common.get_syscall_name(event)
+ name = trace_utils.get_syscall_name(event)
req = cls(begin_ts, tid, name, 'socket', FDType.net)
if 'family' in event:
req.family = event['family']
# Set filename to ip:port if INET socket
if req.family == socket.AF_INET:
- req.filename = '%s:%d' % (common.get_v4_addr_str(
- event['v4addr']), event['sport'])
+ req.filename = format_utils.format_ipv4(
+ event['v4addr'], event['sport']
+ )
return req
@classmethod
def new_from_old_fd(cls, event, tid, old_fd):
begin_ts = event.timestamp
- name = common.get_syscall_name(event)
+ name = trace_utils.get_syscall_name(event)
if old_fd is None:
filename = 'unknown'
fd_type = FDType.unknown
else:
size = None
- syscall_name = common.get_syscall_name(event)
+ syscall_name = trace_utils.get_syscall_name(event)
if syscall_name in SyscallConsts.READ_SYSCALLS:
operation = IORequest.OP_READ
else:
# Also handle fdatasync
begin_ts = event.timestamp
size = None
- syscall_name = common.get_syscall_name(event)
+ syscall_name = trace_utils.get_syscall_name(event)
req = cls(begin_ts, size, tid, syscall_name)
req.fd = event['fd']
Timerange: [1970-01-01 00:00:01.000000000, 1970-01-01 00:00:01.024000000]
Top system call latencies open (usec)
-Begin End Name Duration (usec) Size Proc PID Filename
-[00:00:01.023000000,00:00:01.024000000] open 1000.000 N/A app3 101 test/open/file (fd=42)
+Begin End Name Duration (usec) Size Proc PID Filename
+[00:00:01.023000000, 00:00:01.024000000] open 1000.000 N/A app3 101 test/open/file (fd=42)
Top system call latencies read (usec)
-Begin End Name Duration (usec) Size Proc PID Filename
-[00:00:01.008000000,00:00:01.009000000] read 1000.000 100 B app2 100 testfile (fd=3)
-[00:00:01.012000000,00:00:01.013000000] read 1000.000 42 B app3 101 unknown (fd=3)
+Begin End Name Duration (usec) Size Proc PID Filename
+[00:00:01.008000000, 00:00:01.009000000] read 1000.000 100 B app2 100 testfile (fd=3)
+[00:00:01.012000000, 00:00:01.013000000] read 1000.000 42 B app3 101 unknown (fd=3)
Top system call latencies write (usec)
-Begin End Name Duration (usec) Size Proc PID Filename
-[00:00:01.004000000,00:00:01.005000000] write 1000.000 10 B app 99 unknown (fd=4)
\ No newline at end of file
+Begin End Name Duration (usec) Size Proc PID Filename
+[00:00:01.004000000, 00:00:01.005000000] write 1000.000 10 B app 99 unknown (fd=4)
\ No newline at end of file