Skip to content

Commit 8f2633d

Browse files
authored
Merge pull request #856 from netenglabs/dateparser-leak
sq-poller: remove RELATIVE_BASE option to avoid leak
2 parents 3f6a0a1 + d2c5868 commit 8f2633d

File tree

4 files changed

+56
-52
lines changed

4 files changed

+56
-52
lines changed

suzieq/poller/worker/services/bgp.py

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
from datetime import datetime
33
from copy import deepcopy
44

5-
from dateparser import parse
65
import numpy as np
76

87
from suzieq.poller.worker.services.service import Service
98
from suzieq.shared.utils import (get_timestamp_from_cisco_time,
109
get_timestamp_from_junos_time,
11-
convert_asndot_to_asn)
10+
convert_asndot_to_asn,
11+
parse_relative_timestamp)
1212

1313

1414
# pylint: disable=too-many-statements
@@ -46,11 +46,8 @@ def _clean_eos_data(self, processed_data, raw_data):
4646
entry['keepaliveTime'] = entry['configKeepalive']
4747
continue
4848

49-
estdTime = parse(
50-
entry.get('estdTime', ''),
51-
settings={'RELATIVE_BASE':
52-
datetime.fromtimestamp(
53-
(raw_data[0]['timestamp'])/1000), })
49+
estdTime = parse_relative_timestamp(
50+
entry.get('estdTime', ''), raw_data[0]['timestamp'], ms=True)
5451

5552
if 'EVPN' in entry.get('afi', []):
5653
afidx = entry['afi'].index('EVPN')
@@ -81,7 +78,7 @@ def _clean_eos_data(self, processed_data, raw_data):
8178
new_entry['rrclient'] = True
8279
else:
8380
new_entry['rrclient'] = False
84-
new_entry['estdTime'] = int(estdTime.timestamp()*1000)
81+
new_entry['estdTime'] = estdTime
8582
if entry['pfxBestRx']:
8683
# Depending on the moodiness of the output, this field
8784
# may not be present. So, ignore it.
@@ -448,13 +445,10 @@ def _clean_iosxr_data(self, processed_data, raw_data):
448445
entry['afi'] = entry['afi'].lower()
449446
if entry.get('safi', ''):
450447
entry['safi'] = entry['safi'].lower()
451-
estdTime = parse(
452-
entry.get('estdTime', ''),
453-
settings={'RELATIVE_BASE':
454-
datetime.fromtimestamp(
455-
(raw_data[0]['timestamp'])/1000), })
448+
estdTime = parse_relative_timestamp(
449+
entry.get('estdTime', ''), raw_data[0]['timestamp'], ms=True)
456450
if estdTime:
457-
entry['estdTime'] = int(estdTime.timestamp()*1000)
451+
entry['estdTime'] = estdTime
458452
entry['routerId'] = vrf_rtrid.get(entry['vrf'], '')
459453
if entry.get('rrclient', '') == '':
460454
entry['rrclient'] = 'False'

suzieq/poller/worker/services/interfaces.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
from datetime import datetime
33
from collections import defaultdict
44
from json import loads
5-
from dateparser import parse
65
import numpy as np
76

87
from suzieq.poller.worker.services.service import Service
98
from suzieq.shared.utils import (get_timestamp_from_junos_time,
10-
expand_ios_ifname, expand_nxos_ifname)
11-
from suzieq.shared.utils import convert_macaddr_format_to_colon
9+
expand_ios_ifname, expand_nxos_ifname,
10+
convert_macaddr_format_to_colon,
11+
parse_relative_timestamp)
1212
from suzieq.shared.utils import MISSING_SPEED, NO_SPEED, MISSING_SPEED_IF_TYPES
1313

1414

@@ -590,14 +590,10 @@ def fix_nxos_speed(entry):
590590
if any(x in lastChange for x in 'dwmy'):
591591
lastChange = f'{lastChange} hours ago'
592592

593-
lastChange = parse(
594-
lastChange,
595-
settings={'RELATIVE_BASE': datetime.fromtimestamp(
596-
(raw_data[0]['timestamp'])/1000),
597-
'TIMEZONE': 'UTC'})
593+
lastChange = parse_relative_timestamp(
594+
lastChange, raw_data[0]['timestamp'], ms=True)
598595
if lastChange:
599-
old_entry['statusChangeTimestamp'] = int(
600-
lastChange.timestamp() * 1000)
596+
old_entry['statusChangeTimestamp'] = lastChange
601597
else:
602598
old_entry['statusChangeTimestamp'] = 0
603599
old_entry['description'] = entry.get('description', '')
@@ -895,14 +891,12 @@ def _clean_iosxr_data(self, processed_data, raw_data):
895891
entry['interfaceMac'] = convert_macaddr_format_to_colon(
896892
entry.get('interfaceMac', '0000.0000.0000'))
897893

898-
lastChange = parse(
894+
lastChange = parse_relative_timestamp(
899895
entry.get('statusChangeTimestamp', ''),
900-
settings={'RELATIVE_BASE':
901-
datetime.fromtimestamp(
902-
(raw_data[0]['timestamp'])/1000), })
896+
raw_data[0]['timestamp'], ms=True)
897+
903898
if lastChange:
904-
entry['statusChangeTimestamp'] = int(lastChange.timestamp()
905-
* 1000)
899+
entry['statusChangeTimestamp'] = lastChange
906900
if 'ipAddressList' not in entry:
907901
entry['ipAddressList'] = []
908902
entry['ip6AddressList'] = []

suzieq/poller/worker/services/routes.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import re
2-
from datetime import datetime
3-
4-
from dateparser import parse
52
import numpy as np
63

74
from suzieq.poller.worker.services.service import Service
85
from suzieq.shared.utils import (expand_nxos_ifname,
96
get_timestamp_from_cisco_time,
10-
get_timestamp_from_junos_time)
7+
get_timestamp_from_junos_time,
8+
parse_relative_timestamp)
119

1210

1311
class RoutesService(Service):
@@ -289,11 +287,8 @@ def _clean_iosxr_data(self, processed_data, raw_data):
289287
lastchange = lastchange.split(':')
290288
lastchange = (f'{lastchange[0]} hour '
291289
f'{lastchange[1]}:{lastchange[2]} mins ago')
292-
lastchange = parse(
293-
lastchange,
294-
settings={'RELATIVE_BASE':
295-
datetime.fromtimestamp(
296-
(raw_data[0]['timestamp'])/1000), })
290+
lastchange = parse_relative_timestamp(
291+
lastchange, raw_data[0]['timestamp'], ms=True)
297292
if lastchange:
298293
entry['statusChangeTimestamp'] = lastchange.timestamp()*1000
299294
else:

suzieq/shared/utils.py

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66
import os
77
import re
88
import sys
9-
from datetime import datetime, timezone
9+
from datetime import datetime
1010
from enum import Enum
1111
from importlib.util import find_spec
1212
from ipaddress import ip_network
1313
from itertools import groupby
1414
from logging.handlers import RotatingFileHandler
1515
from os import getenv
16+
from time import time
1617
from typing import Any, Dict, List, Tuple
1718
from tzlocal import get_localzone
1819

@@ -381,41 +382,61 @@ def calc_avg(oldval, newval):
381382
return float((oldval+newval)/2)
382383

383384

384-
def parse_relative_timestamp(uptime: str, relative_to: int = None) -> int:
385+
def parse_relative_timestamp(
386+
uptime: str, relative_to: int = None, ms=False) -> int:
385387
"""Get a relative time (i.e. with format 10 weeks, 4 days, 3 hours 11 mins)
386388
and convert it into a timestamp.
387389
388390
Args:
389391
uptime (str): _description_
390392
relative_to (int, optional): provide a custom base epoch timestamp, if
391-
not provided, the base timestamp is "now".
393+
not provided, the base timestamp is "now". Time in s or ms
394+
depending on the value of the `ms` argument.
395+
ms (bool, optional): whether the function assumes time in seconds or
396+
milliseconds. Default to False.
392397
393398
Returns:
394-
int: The epoch timestamp of the base time minus the uptime
399+
int: The epoch timestamp of the base time minus the uptime. Returned
400+
time in ms or s depending if ms=True
395401
"""
402+
# We do not use the RELATIVE_BASE option of dateparser due to a bug in the
403+
# library causing a memory leak:
404+
# https://github.com/scrapinghub/dateparser/issues/985
405+
396406
settings = {'TIMEZONE': 'utc',
397407
'RETURN_AS_TIMEZONE_AWARE': True}
398-
if relative_to:
399-
base_ts = datetime.fromtimestamp(relative_to, timezone.utc)
400-
settings['RELATIVE_BASE'] = base_ts
408+
conversion_value = 1000 if ms else 1
409+
410+
parsed_uptime = parse(uptime, settings=settings)
411+
if not parsed_uptime:
412+
return None
413+
414+
ts_offset = time() - (relative_to / conversion_value) if relative_to else 0
401415

402-
return int(parse(uptime, settings=settings).timestamp())
416+
return int((parsed_uptime.timestamp() - ts_offset) * conversion_value)
403417

404418

405-
def get_timestamp_from_cisco_time(in_data, timestamp) -> int:
419+
def get_timestamp_from_cisco_time(in_data: str, timestamp: int) -> int:
406420
"""Get timestamp in ms from the Cisco-specific timestamp string
407421
Examples of Cisco timestamp str are P2DT14H45M16S, P1M17DT4H49M50S etc.
422+
423+
Args:
424+
in_data (str): The Cisco uptime string
425+
timestamp (int): the base unix timestamp IS SECONDS from which
426+
subtracting the uptime.
427+
428+
Returns:
429+
int: a unix timestamp in milliseconds
408430
"""
409431
if in_data and not in_data.startswith('P'):
410432
in_data = in_data.replace('y', 'years')
411433
in_data = in_data.replace('w', 'weeks')
412434
in_data = in_data.replace('d', 'days')
413435

414-
other_time = parse(in_data,
415-
settings={'RELATIVE_BASE':
416-
datetime.utcfromtimestamp(timestamp)})
436+
ts_offset = time() - timestamp
437+
other_time = parse(in_data)
417438
if other_time:
418-
return int(other_time.timestamp()*1000)
439+
return int((other_time.timestamp() - ts_offset) * 1000)
419440
else:
420441
logger.error(f'Unable to parse relative time string, {in_data}')
421442
return 0

0 commit comments

Comments
 (0)