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
25 from time
import timezone
26 from . import trace_utils
27 from .time_utils
import NSEC_PER_SEC
30 def _split_value_units(raw_str
):
31 """Take a string with a numerical value and units, and separate the
35 raw_str (str): the string to parse, with numerical value and
39 A tuple (value, units), where value is a string and units is
40 either a string or `None` if no units were found.
43 units_index
= next(i
for i
, c
in enumerate(raw_str
) if c
.isalpha())
46 return (raw_str
, None)
48 return (raw_str
[:units_index
], raw_str
[units_index
:])
51 def parse_size(size_str
):
52 """Convert a human-readable size string to an integral number of
56 size_str (str): the formatted string comprised of the size and
63 ValueError: if units are unrecognised or the size is not a
66 binary_units
= ['B', 'KiB', 'MiB', 'GiB', 'TiB',
67 'PiB', 'EiB', 'ZiB', 'YiB']
68 # units as printed by GNU coreutils (e.g. ls or du), using base
70 coreutils_units
= ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
71 si_units
= ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
73 size
, units
= _split_value_units(size_str
)
78 raise ValueError('invalid size: {}'.format(size
))
80 # If no units have been found, assume bytes
82 if units
in binary_units
:
84 exponent
= binary_units
.index(units
)
85 elif units
in coreutils_units
:
87 exponent
= coreutils_units
.index(units
)
88 elif units
in si_units
:
90 exponent
= si_units
.index(units
)
92 raise ValueError('unrecognised units: {}'.format(units
))
94 size
*= base
** exponent
99 def parse_duration(duration_str
):
100 """Convert a human-readable duration string to an integral number of
104 duration_str (str): the formatted string comprised of the
108 A number of nanoseconds.
111 ValueError: if units are unrecognised or the size is not a
117 units_index
= next(i
for i
, c
in enumerate(duration_str
)
119 except StopIteration:
123 if units_index
is not None:
124 duration
= duration_str
[:units_index
]
125 units
= duration_str
[units_index
:].lower()
127 duration
= duration_str
131 duration
= float(duration
)
133 raise ValueError('invalid duration: {}'.format(duration
))
135 if units
is not None:
140 elif units
in ['us', 'µs']:
145 raise ValueError('unrecognised units: {}'.format(units
))
147 # no units defaults to seconds
150 duration
*= base
** exponent
155 def _parse_date_full_with_nsec(date
):
156 """Parse full date string with nanosecond resolution.
158 This matches either 2014-12-12 17:29:43.802588035 or
159 2014-12-12T17:29:43.802588035.
162 date (str): the date string to be parsed.
165 A tuple of the format (date_time, nsec), where date_time is a
166 datetime.datetime object and nsec is an int of the remaining
170 ValueError: if the date format does not match.
172 pattern
= re
.compile(
173 r
'^(?P<year>\d{4})-(?P<mon>[01]\d)-(?P<day>[0-3]\d)[\sTt]'
174 r
'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})\.(?P<nsec>\d{9})$'
177 if not pattern
.match(date
):
178 raise ValueError('Wrong date format: {}'.format(date
))
180 year
= pattern
.search(date
).group('year')
181 month
= pattern
.search(date
).group('mon')
182 day
= pattern
.search(date
).group('day')
183 hour
= pattern
.search(date
).group('hour')
184 minute
= pattern
.search(date
).group('min')
185 sec
= pattern
.search(date
).group('sec')
186 nsec
= pattern
.search(date
).group('nsec')
188 date_time
= datetime
.datetime(
189 int(year
), int(month
), int(day
),
190 int(hour
), int(minute
), int(sec
)
193 return date_time
, int(nsec
)
196 def _parse_date_full(date
):
197 """Parse full date string.
199 This matches either 2014-12-12 17:29:43 or 2014-12-12T17:29:43.
202 date (str): the date string to be parsed.
205 A tuple of the format (date_time, nsec), where date_time is a
206 datetime.datetime object and nsec is 0.
209 ValueError: if the date format does not match.
211 pattern
= re
.compile(
212 r
'^(?P<year>\d{4})-(?P<mon>[01]\d)-(?P<day>[0-3]\d)[\sTt]'
213 r
'(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})$'
216 if not pattern
.match(date
):
217 raise ValueError('Wrong date format: {}'.format(date
))
219 year
= pattern
.search(date
).group('year')
220 month
= pattern
.search(date
).group('mon')
221 day
= pattern
.search(date
).group('day')
222 hour
= pattern
.search(date
).group('hour')
223 minute
= pattern
.search(date
).group('min')
224 sec
= pattern
.search(date
).group('sec')
227 date_time
= datetime
.datetime(
228 int(year
), int(month
), int(day
),
229 int(hour
), int(minute
), int(sec
)
232 return date_time
, nsec
235 def _parse_date_time_with_nsec(date
):
236 """Parse time string with nanosecond resolution.
238 This matches 17:29:43.802588035.
241 date (str): the date string to be parsed.
244 A tuple of the format (date_time, nsec), where date_time is a
245 datetime.time object and nsec is an int of the remaining
249 ValueError: if the date format does not match.
251 pattern
= re
.compile(
252 r
'^(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})\.(?P<nsec>\d{9})$'
255 if not pattern
.match(date
):
256 raise ValueError('Wrong date format: {}'.format(date
))
258 hour
= pattern
.search(date
).group('hour')
259 minute
= pattern
.search(date
).group('min')
260 sec
= pattern
.search(date
).group('sec')
261 nsec
= pattern
.search(date
).group('nsec')
263 time
= datetime
.time(int(hour
), int(minute
), int(sec
))
265 return time
, int(nsec
)
268 def _parse_date_time(date
):
269 """Parse time string.
271 This matches 17:29:43.
274 date (str): the date string to be parsed.
277 A tuple of the format (date_time, nsec), where date_time is a
278 datetime.time object and nsec is 0.
281 ValueError: if the date format does not match.
283 pattern
= re
.compile(
284 r
'^(?P<hour>\d{2}):(?P<min>\d{2}):(?P<sec>\d{2})$'
287 if not pattern
.match(date
):
288 raise ValueError('Wrong date format: {}'.format(date
))
290 hour
= pattern
.search(date
).group('hour')
291 minute
= pattern
.search(date
).group('min')
292 sec
= pattern
.search(date
).group('sec')
295 time
= datetime
.time(int(hour
), int(minute
), int(sec
))
300 def _parse_date_timestamp(date
):
301 """Parse timestamp string in nanoseconds from epoch.
303 This matches 1418423383802588035.
306 date (str): the date string to be parsed.
309 A tuple of the format (date_time, nsec), where date_time is a
310 datetime.datetime object and nsec is an int of the remaining
314 ValueError: if the date format does not match.
316 pattern
= re
.compile(r
'^\d+$')
318 if not pattern
.match(date
):
319 raise ValueError('Wrong date format: {}'.format(date
))
321 timestamp_ns
= int(date
)
323 date_time
= datetime
.datetime
.fromtimestamp(
324 timestamp_ns
/ NSEC_PER_SEC
326 nsec
= timestamp_ns
% NSEC_PER_SEC
328 return date_time
, nsec
331 def parse_date(date
):
332 """Try to parse a date string from one of many formats.
335 date (str): the date string to be parsed.
338 A tuple of the format (date_time, nsec), where date_time is
339 one of either datetime.datetime or datetime.time, depending on
340 whether the date string contains full date information or only
341 the time of day. The latter case can still be useful when used
342 in conjuction with a trace collection's date to provide the
343 missing information. The nsec element of the tuple is an int and
344 corresponds to the nanoseconds for the given date/timestamp.
345 This is due to datetime objects only supporting a resolution
346 down to the microsecond.
349 ValueError: if the date does not correspond to any of the
353 _parse_date_full_with_nsec
, _parse_date_full
,
354 _parse_date_time_with_nsec
, _parse_date_time
,
355 _parse_date_timestamp
361 for parser
in parsers
:
363 (date_time
, nsec
) = parser(date
)
367 # If no exception was raised, the parser found a match, so
371 if date_time
is None or nsec
is None:
372 # None of the parsers were a match
373 raise ValueError('Unrecognised date format: {}'.format(date
))
375 return date_time
, nsec
378 def parse_trace_collection_date(collection
, date
, gmt
=False):
379 """Parse a date string, using a trace collection to disambiguate
383 collection (TraceCollection): a babeltrace TraceCollection
386 date (string): the date string to be parsed.
388 gmt (bool, optional): flag indicating whether the timestamp is
389 in the local timezone or gmt (default: False).
392 A timestamp (int) in nanoseconds since epoch, corresponding to
396 ValueError: if the date format is unrecognised, or if the date
397 format does not specify the date and the trace collection spans
401 date_time
, nsec
= parse_date(date
)
403 # This might raise ValueError if the date is in an invalid
404 # format, so just re-raise the exception to inform the caller
408 # date_time will either be an actual datetime.datetime object, or
409 # just a datetime.time object, depending on the format. In the
410 # latter case, try and fill out the missing date information from
411 # the trace collection's date.
412 if isinstance(date_time
, datetime
.time
):
414 collection_date
= trace_utils
.get_trace_collection_date(collection
)
417 'Invalid date format for multi-day trace: {}'.format(date
)
420 date_time
= datetime
.datetime
.combine(collection_date
, date_time
)
423 date_time
= date_time
+ datetime
.timedelta(seconds
=timezone
)
425 timestamp_ns
= date_time
.timestamp() * NSEC_PER_SEC
+ nsec
430 def parse_trace_collection_time_range(collection
, time_range
, gmt
=False):
431 """Parse a time range string, using a trace collection to
432 disambiguate incomplete dates.
435 collection (TraceCollection): a babeltrace TraceCollection
438 time_range (string): the time range string to be parsed.
440 gmt (bool, optional): flag indicating whether the timestamps are
441 in the local timezone or gmt (default: False).
444 A tuple (begin, end) of the two timestamps (int) in nanoseconds
445 since epoch, corresponding to the parsed dates.
448 ValueError: if the time range or date format is unrecognised,
449 or if the date format does not specify the date and the trace
450 collection spans multiple days.
452 pattern
= re
.compile(r
'^\[(?P<begin>.*),(?P<end>.*)\]$')
453 if not pattern
.match(time_range
):
454 raise ValueError('Invalid time range format: {}'.format(time_range
))
456 begin_str
= pattern
.search(time_range
).group('begin').strip()
457 end_str
= pattern
.search(time_range
).group('end').strip()
460 begin
= parse_trace_collection_date(collection
, begin_str
, gmt
)
461 end
= parse_trace_collection_date(collection
, end_str
, gmt
)
463 # Either of the dates was in the wrong format, propagate the
464 # exception to the caller.