1
0
Fork 1
mirror of https://github.com/thatmattlove/hyperglass.git synced 2026-04-17 21:38:27 +00:00

fix(plugins): improve MikroTik traceroute output deduplication

- Add IPv6 address pattern matching alongside IPv4
- Extract SENT count from traceroute lines to prioritize complete data
- Keep lines with highest SENT count (final results) instead of first occurrence
- Maintain original hop order while using best data for each IP
- Prefer non-timeout results when SENT counts are equal

Fixes issues where IPv4 showed early results (SENT=1) instead of final (SENT=3)
and IPv6 showed all intermediate steps instead of final consolidated results.
This commit is contained in:
Wilhelm Schonfeldt 2025-09-26 13:03:23 +02:00
parent 5ace4fdf97
commit 716687db63

View file

@ -35,8 +35,7 @@ class MikrotikGarbageOutput(OutputPlugin):
lines = raw_output.splitlines()
cleaned_lines = []
found_header = False
unique_hops = {}
hop_order = []
hop_data = {} # IP -> (line, sent_count)
for line in lines:
stripped = line.strip()
@ -60,34 +59,47 @@ class MikrotikGarbageOutput(OutputPlugin):
found_header = True
continue
# Only include data lines after we've found the header
# Only process data lines after we've found the header
if found_header and stripped:
# Try to extract IP address from the line to deduplicate
ip_match = re.match(r'^(\d+\.\d+\.\d+\.\d+)', stripped)
if ip_match:
ip = ip_match.group(1)
if ip not in unique_hops:
unique_hops[ip] = line
hop_order.append(ip)
else:
# Keep the line with better data (non-timeout over timeout)
if "timeout" not in stripped and "timeout" in unique_hops[ip]:
unique_hops[ip] = line
elif "100%" in stripped and "timeout" in stripped:
# This is likely a timeout line without IP - skip standalone timeout lines
continue
else:
# Keep any other data lines that might be relevant
cleaned_lines.append(line)
# Try to extract IP address (IPv4 or IPv6) from the line
ipv4_match = re.match(r'^(\d+\.\d+\.\d+\.\d+)', stripped)
ipv6_match = re.match(r'^([0-9a-fA-F:]+)', stripped) if not ipv4_match else None
# Reconstruct the output
if found_header and (unique_hops or any("timeout" not in line for line in cleaned_lines[1:] if line.strip())):
if ipv4_match or ipv6_match:
ip = ipv4_match.group(1) if ipv4_match else ipv6_match.group(1)
# Extract the SENT count from the line (look for pattern like "0% 3" or "100% 2")
sent_match = re.search(r'\s+(\d+)%\s+(\d+)\s+', stripped)
sent_count = int(sent_match.group(2)) if sent_match else 0
# Keep the line with the highest SENT count (most complete data)
if ip not in hop_data or sent_count > hop_data[ip][1]:
hop_data[ip] = (line, sent_count)
elif sent_count == hop_data[ip][1] and "timeout" not in stripped and "timeout" in hop_data[ip][0]:
# If SENT counts are equal, prefer non-timeout over timeout
hop_data[ip] = (line, sent_count)
elif "100%" in stripped and "timeout" in stripped:
# Skip standalone timeout lines without IP
continue
# Reconstruct the output with only the best results
if found_header and hop_data:
result_lines = [cleaned_lines[0]] # Header
result_lines.extend(unique_hops[ip] for ip in hop_order)
# Add any non-IP lines that weren't already included
for line in cleaned_lines[1:]:
if line not in result_lines and not any(ip in line for ip in hop_order):
result_lines.append(line)
# Sort by the order IPs first appeared, but use the best data for each
seen_ips = []
for line in lines:
stripped = line.strip()
if found_header:
ipv4_match = re.match(r'^(\d+\.\d+\.\d+\.\d+)', stripped)
ipv6_match = re.match(r'^([0-9a-fA-F:]+)', stripped) if not ipv4_match else None
if ipv4_match or ipv6_match:
ip = ipv4_match.group(1) if ipv4_match else ipv6_match.group(1)
if ip not in seen_ips and ip in hop_data:
seen_ips.append(ip)
result_lines.append(hop_data[ip][0])
return "\n".join(result_lines)
return raw_output