44Data is sent via HTTP POST to the /write endpoint.
55"""
66import sys
7+ from datetime import datetime
78import requests
89from 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