1 # The MIT License (MIT)
3 # Copyright (C) 2016 - Antoine Busque <abusque@efficios.com>
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26 from time
import timezone
27 from . import trace_utils
28 from .time_utils
import NSEC_PER_SEC
31 def _split_value_units(raw_str
):
32 """Take a string with a numerical value and units, and separate the
36 raw_str (str): the string to parse, with numerical value and
40 A tuple (value, units), where value is a string and units is
41 either a string or `None` if no units were found.
44 units_index
= next(i
for i
, c
in enumerate(raw_str
) if c
.isalpha())
47 return (raw_str
, None)
49 return (raw_str
[:units_index
], raw_str
[units_index
:])
52 def parse_size(size_str
):
53 """Convert a human-readable size string to an integral number of
57 size_str (str): the formatted string comprised of the size and
64 ValueError: if units are unrecognised or the size is not a
67 binary_units
= ['B', 'KiB', 'MiB', 'GiB', 'TiB',
68 'PiB', 'EiB', 'ZiB', 'YiB']
69 # units as printed by GNU coreutils (e.g. ls or du), using base
71 coreutils_units
= ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
72 si_units
= ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
74 size
, units
= _split_value_units(size_str
)
79 raise ValueError('invalid size: {}'.format(size
))
81 # If no units have been found, assume bytes
83 if units
in binary_units
:
85 exponent
= binary_units
.index(units
)
86 elif units
in coreutils_units
:
88 exponent
= coreutils_units
.index(units
)
89 elif units
in si_units
:
91 exponent
= si_units
.index(units
)
93 raise ValueError('unrecognised units: {}'.format(units
))
95 size
*= math
.pow(base
, exponent
)
100 def parse_duration(duration_str
):
101 """Convert a human-readable duration string to an integral number of
105 duration_str (str): the formatted string comprised of the
109 A number of nanoseconds.
112 ValueError: if units are unrecognised or the size is not a
118 units_index
= next(i
for i
, c
in enumerate(duration_str
)
120 except StopIteration:
124 if units_index
is not None:
125 duration
= duration_str
[:units_index
]
126 units
= duration_str
[units_index
:].lower()
128 duration
= duration_str
132 duration
= float(duration
)
134 raise ValueError('invalid duration: {}'.format(duration
))
136 if units
is not None:
141 elif units
in ['us', 'µs']:
146 raise ValueError('unrecognised units: {}'.format(units
))
148 # no units defaults to seconds
151 duration
*= math
.pow(base
, exponent
)
156 def _parse_date_full_with_nsec(date
):
157 """Parse full date string with nanosecond resolution.
159 This matches either 2014-12-12 17:29:43.802588035 or
160 2014-12-12T17:29:43.802588035.
163 date (str): the date string to be parsed.
166 A tuple of the format (date_time, nsec), where date_time is a
167 datetime.datetime object and nsec is an int of the remaining
171 ValueError: if the date format does not match.
173 pattern
= re
.compile(
174 r
'^(?P<year>\d{4})-(?P<mon>[01]\d)-(?P<day>[0-3]\d)[\sTt]'
175 r
'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})\.(?P<nsec>\d{9})$'
178 if not pattern
.match(date
):
179 raise ValueError('Wrong date format: {}'.format(date
))
181 year
= pattern
.search(date
).group('year')
182 month
= pattern
.search(date
).group('mon')
183 day
= pattern
.search(date
).group('day')
184 hour
= pattern
.search(date
).group('hour')
185 minute
= pattern
.search(date
).group('min')
186 sec
= pattern
.search(date
).group('sec')
187 nsec
= pattern
.search(date
).group('nsec')
189 date_time
= datetime
.datetime(
190 int(year
), int(month
), int(day
),
191 int(hour
), int(minute
), int(sec
)
194 return date_time
, int(nsec
)
197 def _parse_date_full(date
):
198 """Parse full date string.
200 This matches either 2014-12-12 17:29:43 or 2014-12-12T17:29:43.
203 date (str): the date string to be parsed.
206 A tuple of the format (date_time, nsec), where date_time is a
207 datetime.datetime object and nsec is 0.
210 ValueError: if the date format does not match.
212 pattern
= re
.compile(
213 r
'^(?P<year>\d{4})-(?P<mon>[01]\d)-(?P<day>[0-3]\d)[\sTt]'
214 r
'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})$'
217 if not pattern
.match(date
):
218 raise ValueError('Wrong date format: {}'.format(date
))
220 year
= pattern
.search(date
).group('year')
221 month
= pattern
.search(date
).group('mon')
222 day
= pattern
.search(date
).group('day')
223 hour
= pattern
.search(date
).group('hour')
224 minute
= pattern
.search(date
).group('min')
225 sec
= pattern
.search(date
).group('sec')
228 date_time
= datetime
.datetime(
229 int(year
), int(month
), int(day
),
230 int(hour
), int(minute
), int(sec
)
233 return date_time
, nsec
236 def _parse_date_time_with_nsec(date
):
237 """Parse time string with nanosecond resolution.
239 This matches 17:29:43.802588035.
242 date (str): the date string to be parsed.
245 A tuple of the format (date_time, nsec), where date_time is a
246 datetime.time object and nsec is an int of the remaining
250 ValueError: if the date format does not match.
252 pattern
= re
.compile(
253 r
'^(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})\.(?P<nsec>\d{9})$'
256 if not pattern
.match(date
):
257 raise ValueError('Wrong date format: {}'.format(date
))
259 hour
= pattern
.search(date
).group('hour')
260 minute
= pattern
.search(date
).group('min')
261 sec
= pattern
.search(date
).group('sec')
262 nsec
= pattern
.search(date
).group('nsec')
264 time
= datetime
.time(int(hour
), int(minute
), int(sec
))
266 return time
, int(nsec
)
269 def _parse_date_time(date
):
270 """Parse time string.
272 This matches 17:29:43.
275 date (str): the date string to be parsed.
278 A tuple of the format (date_time, nsec), where date_time is a
279 datetime.time object and nsec is 0.
282 ValueError: if the date format does not match.
284 pattern
= re
.compile(
285 r
'^(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})$'
288 if not pattern
.match(date
):
289 raise ValueError('Wrong date format: {}'.format(date
))
291 hour
= pattern
.search(date
).group('hour')
292 minute
= pattern
.search(date
).group('min')
293 sec
= pattern
.search(date
).group('sec')
296 time
= datetime
.time(int(hour
), int(minute
), int(sec
))
301 def _parse_date_timestamp(date
):
302 """Parse timestamp string in nanoseconds from epoch.
304 This matches 1418423383802588035.
307 date (str): the date string to be parsed.
310 A tuple of the format (date_time, nsec), where date_time is a
311 datetime.datetime object and nsec is an int of the remaining
315 ValueError: if the date format does not match.
317 pattern
= re
.compile(r
'^\d+$')
319 if not pattern
.match(date
):
320 raise ValueError('Wrong date format: {}'.format(date
))
322 timestamp_ns
= int(date
)
324 date_time
= datetime
.datetime
.fromtimestamp(
325 timestamp_ns
/ NSEC_PER_SEC
327 nsec
= timestamp_ns
% NSEC_PER_SEC
329 return date_time
, nsec
332 def parse_date(date
):
333 """Try to parse a date string from one of many formats.
336 date (str): the date string to be parsed.
339 A tuple of the format (date_time, nsec), where date_time is
340 one of either datetime.datetime or datetime.time, depending on
341 whether the date string contains full date information or only
342 the time of day. The latter case can still be useful when used
343 in conjuction with a trace collection's date to provide the
344 missing information. The nsec element of the tuple is an int and
345 corresponds to the nanoseconds for the given date/timestamp.
346 This is due to datetime objects only supporting a resolution
347 down to the microsecond.
350 ValueError: if the date does not correspond to any of the
354 _parse_date_full_with_nsec
, _parse_date_full
,
355 _parse_date_time_with_nsec
, _parse_date_time
,
356 _parse_date_timestamp
362 for parser
in parsers
:
364 (date_time
, nsec
) = parser(date
)
368 # If no exception was raised, the parser found a match, so
372 if date_time
is None or nsec
is None:
373 # None of the parsers were a match
374 raise ValueError('Unrecognised date format: {}'.format(date
))
376 return date_time
, nsec
379 def parse_trace_collection_date(collection
, date
, gmt
=False):
380 """Parse a date string, using a trace collection to disambiguate
384 collection (TraceCollection): a babeltrace TraceCollection
387 date (string): the date string to be parsed.
389 gmt (bool, optional): flag indicating whether the timestamp is
390 in the local timezone or gmt (default: False).
393 A timestamp (int) in nanoseconds since epoch, corresponding to
397 ValueError: if the date format is unrecognised, or if the date
398 format does not specify the date and the trace collection spans
402 date_time
, nsec
= parse_date(date
)
404 # This might raise ValueError if the date is in an invalid
405 # format, so just re-raise the exception to inform the caller
409 # date_time will either be an actual datetime.datetime object, or
410 # just a datetime.time object, depending on the format. In the
411 # latter case, try and fill out the missing date information from
412 # the trace collection's date.
413 if isinstance(date_time
, datetime
.time
):
415 collection_date
= trace_utils
.get_trace_collection_date(collection
)
418 'Invalid date format for multi-day trace: {}'.format(date
)
421 date_time
= datetime
.datetime
.combine(collection_date
, date_time
)
424 date_time
= date_time
+ datetime
.timedelta(seconds
=timezone
)
426 timestamp_ns
= date_time
.timestamp() * NSEC_PER_SEC
+ nsec
431 def parse_trace_collection_time_range(collection
, time_range
, gmt
=False):
432 """Parse a time range string, using a trace collection to
433 disambiguate incomplete dates.
436 collection (TraceCollection): a babeltrace TraceCollection
439 time_range (string): the time range string to be parsed.
441 gmt (bool, optional): flag indicating whether the timestamps are
442 in the local timezone or gmt (default: False).
445 A tuple (begin, end) of the two timestamps (int) in nanoseconds
446 since epoch, corresponding to the parsed dates.
449 ValueError: if the time range or date format is unrecognised,
450 or if the date format does not specify the date and the trace
451 collection spans multiple days.
453 pattern
= re
.compile(r
'^\[(?P<begin>.*),(?P<end>.*)\]$')
454 if not pattern
.match(time_range
):
455 raise ValueError('Invalid time range format: {}'.format(time_range
))
457 begin_str
= pattern
.search(time_range
).group('begin').strip()
458 end_str
= pattern
.search(time_range
).group('end').strip()
461 begin
= parse_trace_collection_date(collection
, begin_str
, gmt
)
462 end
= parse_trace_collection_date(collection
, end_str
, gmt
)
464 # Either of the dates was in the wrong format, propagate the
465 # exception to the caller.