Skip to content

Commit 16bad2c

Browse files
author
Josh S
committed
Fix VictoriaMetrics timestamp format - convert ISO to nanoseconds
Add _convert_timestamp_to_nanoseconds method to properly convert ISO format timestamps to nanoseconds since epoch as required by VictoriaMetrics. Fixes timestamp parsing error when writing speedtest data.
1 parent b2b8e79 commit 16bad2c

1 file changed

Lines changed: 65 additions & 4 deletions

File tree

speedflux/backends/victoriametrics_backend.py

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
Data is sent via HTTP POST to the /write endpoint.
55
"""
66
import sys
7+
from datetime import datetime
78
import requests
89
from requests.exceptions import ConnectionError, Timeout
910

@@ -86,9 +87,68 @@ def _escape_line_protocol_value(self, value):
8687
if value is None:
8788
return ''
8889
str_value = str(value)
89-
# Escape special characters: comma, space, equals
90+
# Escape special characters: backslash first, then comma, space, equals
91+
# Order matters: escape backslash first to avoid double-escaping
9092
return str_value.replace('\\', '\\\\').replace(',', '\\,').replace(' ', '\\ ').replace('=', '\\=')
9193

94+
def _convert_timestamp_to_nanoseconds(self, timestamp):
95+
"""Convert timestamp to nanoseconds since epoch.
96+
97+
VictoriaMetrics requires timestamps in nanoseconds since Unix epoch.
98+
The timestamp can be:
99+
- ISO format string (e.g., "2026-02-03T20:57:12Z")
100+
- datetime object
101+
- Already a numeric value (assumed to be nanoseconds)
102+
103+
Args:
104+
timestamp: Timestamp in various formats
105+
106+
Returns:
107+
Integer nanoseconds since epoch
108+
"""
109+
if isinstance(timestamp, (int, float)):
110+
# Already numeric - assume it's in the correct format
111+
return int(timestamp)
112+
113+
if isinstance(timestamp, str):
114+
# Parse ISO format string
115+
try:
116+
# Try parsing with timezone info
117+
if timestamp.endswith('Z'):
118+
dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
119+
else:
120+
dt = datetime.fromisoformat(timestamp)
121+
except ValueError:
122+
# Fallback: try parsing common formats
123+
for fmt in ['%Y-%m-%dT%H:%M:%S', '%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%d %H:%M:%S']:
124+
try:
125+
dt = datetime.strptime(timestamp, fmt)
126+
break
127+
except ValueError:
128+
continue
129+
else:
130+
raise ValueError(f"Unable to parse timestamp: {timestamp}")
131+
elif isinstance(timestamp, datetime):
132+
dt = timestamp
133+
else:
134+
raise ValueError(f"Unsupported timestamp type: {type(timestamp)}")
135+
136+
# Convert to UTC if timezone-aware, otherwise assume UTC
137+
from datetime import timezone
138+
if dt.tzinfo is None:
139+
# Assume UTC if no timezone info
140+
dt = dt.replace(tzinfo=timezone.utc)
141+
else:
142+
# Convert to UTC if timezone-aware
143+
dt = dt.astimezone(timezone.utc)
144+
145+
# Convert to UTC timestamp (epoch is always UTC)
146+
epoch = datetime(1970, 1, 1, tzinfo=timezone.utc)
147+
seconds_since_epoch = (dt - epoch).total_seconds()
148+
149+
# Convert to nanoseconds
150+
return int(seconds_since_epoch * 1_000_000_000)
151+
92152
def format_data(self, data):
93153
"""Format data as InfluxDB line protocol.
94154
@@ -101,8 +161,8 @@ def format_data(self, data):
101161
else:
102162
tag_str = ''
103163

104-
# Convert timestamp to nanoseconds
105-
timestamp = data['timestamp']
164+
# Convert timestamp to nanoseconds since epoch (required by VictoriaMetrics)
165+
timestamp = self._convert_timestamp_to_nanoseconds(data['timestamp'])
106166

107167
lines = []
108168

@@ -195,7 +255,8 @@ def write_ping(self, data):
195255
tag_str = ''
196256

197257
fields = f"success={measurement['fields']['success']}i,rtt={measurement['fields']['rtt']}"
198-
timestamp = measurement['time'].isoformat() if hasattr(measurement['time'], 'isoformat') else measurement['time']
258+
# Convert timestamp to nanoseconds since epoch (required by VictoriaMetrics)
259+
timestamp = self._convert_timestamp_to_nanoseconds(measurement['time'])
199260

200261
line = self._build_line('pings', tag_str, fields, timestamp)
201262
self.write(line, data_type='Ping')

0 commit comments

Comments
 (0)