Added telegraf configs
This commit is contained in:
318
influxMeasurements.md
Normal file
318
influxMeasurements.md
Normal file
@@ -0,0 +1,318 @@
|
||||
This is a collection of all the metrics collected by **gpsdMeasurement.py** script.
|
||||
|
||||
# Table of contents
|
||||
|
||||
* [gps_fix](#gps_fix)
|
||||
|
||||
* [mode](#mode)
|
||||
* [lat](#lat)
|
||||
* [lon](#lon)
|
||||
* [alt](#alt)
|
||||
* [alt_msl](#alt_msl)
|
||||
* [alt_hae](#alt_hae)
|
||||
* [speed](#speed)
|
||||
* [climb](#climb)
|
||||
* [track](#track)
|
||||
* [magtrack](#magtrack)
|
||||
* [magvar](#magvar)
|
||||
* [ept](#ept)
|
||||
* [epx](#epx)
|
||||
* [epy](#epy)
|
||||
* [epv](#epv)
|
||||
* [eph](#eph)
|
||||
* [sep](#sep)
|
||||
* [ecefpAcc](#ecefpacc)
|
||||
* [ecefvAcc](#ecefvacc)
|
||||
* [leapseconds](#leapseconds)
|
||||
* [fix_valid](#fix_valid)
|
||||
* [fix_3d](#fix_3d)
|
||||
|
||||
* [gps_sky](#gps_sky)
|
||||
|
||||
* [nSat](#nsat)
|
||||
* [uSat](#usat)
|
||||
* [sat_used_ratio](#sat_used_ratio)
|
||||
* [gdop](#gdop)
|
||||
* [hdop](#hdop)
|
||||
* [pdop](#pdop)
|
||||
* [tdop](#tdop)
|
||||
* [vdop](#vdop)
|
||||
* [xdop](#xdop)
|
||||
* [ydop](#ydop)
|
||||
* [gps_visible](#gps_visible)
|
||||
* [gps_used](#gps_used)
|
||||
* [sbas_visible](#sbas_visible)
|
||||
* [sbas_used](#sbas_used)
|
||||
* [galileo_visible](#galileo_visible)
|
||||
* [galileo_used](#galileo_used)
|
||||
* [beidou_visible](#beidou_visible)
|
||||
* [beidou_used](#beidou_used)
|
||||
* [qzss_visible](#qzss_visible)
|
||||
* [qzss_used](#qzss_used)
|
||||
* [glonass_visible](#glonass_visible)
|
||||
* [glonass_used](#glonass_used)
|
||||
* [unknown_visible](#unknown_visible)
|
||||
* [unknown_used](#unknown_used)
|
||||
* [avg_signal_visible](#avg_signal_visible)
|
||||
* [avg_signal_used](#avg_signal_used)
|
||||
* [max_signal](#max_signal)
|
||||
|
||||
* [gps_pps](#gps_pps)
|
||||
|
||||
* [offset_ns](#offset_ns)
|
||||
* [precision_exp](#precision_exp)
|
||||
* [present](#present)
|
||||
|
||||
|
||||
## gps_fix
|
||||
|
||||
This measurement is the current navigation solution from the latest TPV message.
|
||||
|
||||
Basically: where am I, what kind of fix do I have, and how confident is the receiver?
|
||||
|
||||
### mode
|
||||
|
||||
Fix type.
|
||||
|
||||
Usually:
|
||||
|
||||
1 = no fix
|
||||
|
||||
2 = 2D fix
|
||||
|
||||
3 = 3D fix
|
||||
|
||||
### lat
|
||||
|
||||
Latitude in decimal degrees.
|
||||
|
||||
### lon
|
||||
|
||||
Longitude in decimal degrees.
|
||||
|
||||
### alt
|
||||
|
||||
Altitude in meters.This is effectively the same as mean sea level altitude.
|
||||
|
||||
### alt_msl
|
||||
|
||||
Altitude above mean sea level, in meters.
|
||||
|
||||
### alt_hae
|
||||
|
||||
Altitude above the ellipsoid, in meters.
|
||||
|
||||
### speed
|
||||
|
||||
Current ground speed, in meters per second.
|
||||
|
||||
### climb
|
||||
|
||||
Vertical speed, in meters per second. Positive means climbing, negative means descending.
|
||||
|
||||
### track
|
||||
|
||||
Course over ground in degrees.
|
||||
|
||||
### magtrack
|
||||
|
||||
Magnetic course over ground in degrees.
|
||||
|
||||
### magvar
|
||||
|
||||
Magnetic variation in degrees.
|
||||
|
||||
### ept
|
||||
|
||||
Estimated timestamp error, in seconds. This is gpsd estimate of uncertainty for the reported time.
|
||||
|
||||
### epx
|
||||
|
||||
Estimated longitude-related horizontal uncertainty, in meters.
|
||||
|
||||
### epy
|
||||
|
||||
Estimated latitude-related horizontal uncertainty, in meters.
|
||||
|
||||
### epv
|
||||
|
||||
Estimated vertical uncertainty, in meters.
|
||||
|
||||
### eph
|
||||
|
||||
Estimated horizontal position uncertainty, in meters.
|
||||
|
||||
### sep
|
||||
|
||||
Estimated spherical error or 3D position uncertainty, in meters.
|
||||
|
||||
### ecefpAcc
|
||||
|
||||
Estimated ECEF position accuracy, in meters.
|
||||
|
||||
### ecefvAcc
|
||||
|
||||
Estimated ECEF velocity accuracy, in meters per second.
|
||||
|
||||
### leapseconds
|
||||
|
||||
Current leap-second offset between GPS time and UTC.
|
||||
|
||||
### fix_valid
|
||||
|
||||
Boolean derived by the script.
|
||||
|
||||
true if mode >= 2.
|
||||
|
||||
### fix_3d
|
||||
|
||||
Boolean derived by the script.
|
||||
|
||||
true if mode == 3.
|
||||
|
||||
## gps_sky
|
||||
|
||||
This measurement comes from the latest SKY message.
|
||||
|
||||
It says: how good is the sky view, how many satellites are visible and used, and how good is the geometry?
|
||||
|
||||
### nSat
|
||||
|
||||
Number of satellites visible or reported.
|
||||
|
||||
### uSat
|
||||
|
||||
Number of satellites currently used in the navigation solution.
|
||||
|
||||
### sat_used_ratio
|
||||
|
||||
Derived value: uSat / nSat.
|
||||
|
||||
Shows how many visible satellites are actually being used.
|
||||
|
||||
### gdop
|
||||
|
||||
Geometric dilution of precision.
|
||||
|
||||
Overall combined geometry quality. Lower is better.
|
||||
|
||||
### hdop
|
||||
|
||||
Horizontal dilution of precision. Lower is better.
|
||||
|
||||
### pdop
|
||||
|
||||
Position dilution of precision. Lower is better.
|
||||
|
||||
### tdop
|
||||
|
||||
Time dilution of precision. Lower is better.
|
||||
|
||||
### vdop
|
||||
|
||||
Vertical dilution of precision. Lower is better.
|
||||
|
||||
### xdop
|
||||
|
||||
Dilution of precision in the X direction. Lower is better.
|
||||
|
||||
### ydop
|
||||
|
||||
Dilution of precision in the Y direction. Lower is better.
|
||||
|
||||
### gps_visible
|
||||
|
||||
How many GPS satellites were listed.
|
||||
|
||||
### gps_used
|
||||
|
||||
How many GPS satellites were actually used in the fix.
|
||||
|
||||
### sbas_visible
|
||||
|
||||
How many SBAS satellites were listed.
|
||||
|
||||
### sbas_used
|
||||
|
||||
How many SBAS satellites were used in the fix.
|
||||
|
||||
### galileo_visible
|
||||
|
||||
How many Galileo satellites were listed.
|
||||
|
||||
### galileo_used
|
||||
|
||||
How many Galileo satellites were used in the fix.
|
||||
|
||||
### beidou_visible
|
||||
|
||||
How many BeiDou satellites were listed.
|
||||
|
||||
### beidou_used
|
||||
|
||||
How many BeiDou satellites were used in the fix.
|
||||
|
||||
### qzss_visible
|
||||
|
||||
How many QZSS satellites were listed.
|
||||
|
||||
### qzss_used
|
||||
|
||||
How many QZSS satellites were used in the fix.
|
||||
|
||||
### glonass_visible
|
||||
|
||||
How many GLONASS satellites were listed.
|
||||
|
||||
### glonass_used
|
||||
|
||||
How many GLONASS satellites were used in the fix.
|
||||
|
||||
### unknown_visible
|
||||
|
||||
How many satellites had an unknown gnssid.
|
||||
|
||||
### unknown_used
|
||||
|
||||
How many unknown-constellation satellites were used.
|
||||
|
||||
### avg_signal_visible
|
||||
|
||||
Average signal strength across all visible satellites, based on ss.
|
||||
|
||||
### avg_signal_used
|
||||
|
||||
Average signal strength across satellites actually used in the fix.
|
||||
|
||||
### max_signal
|
||||
|
||||
Maximum ss value seen among visible satellites.
|
||||
|
||||
## gps_pps
|
||||
|
||||
This measurement comes from the latest PPS message.
|
||||
|
||||
What is says: is PPS present, and what is the current timing offset between the PPS event and the host clock timestamp?
|
||||
|
||||
### offset_ns
|
||||
|
||||
Derived value in nanoseconds.
|
||||
|
||||
Calculated by the script as:
|
||||
|
||||
(clock_sec * 1e9 + clock_nsec) - (real_sec * 1e9 + real_nsec)
|
||||
|
||||
The difference between the kernel or host timestamp and the PPS event timestamp.
|
||||
|
||||
A negative value means the host timestamp is slightly earlier than the PPS reference.
|
||||
A positive value means it is slightly later.
|
||||
|
||||
### precision_exp
|
||||
|
||||
Precision exponent reported by gpsd for the PPS source.
|
||||
|
||||
### present
|
||||
|
||||
Boolean derived by the script.
|
||||
|
||||
Always true when a PPS message was received and exported.
|
||||
333
telegraf/gpsdMeasurement.py
Normal file
333
telegraf/gpsdMeasurement.py
Normal file
@@ -0,0 +1,333 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import math
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
# offset_ns is computed as:
|
||||
|
||||
# (clock_sec * 1e9 + clock_nsec) - (real_sec * 1e9 + real_nsec)
|
||||
|
||||
|
||||
GPSPIPE_CMD = ["gpspipe", "-w", "--json"]
|
||||
HOST_TAG = "ntprana"
|
||||
|
||||
|
||||
running = True
|
||||
|
||||
|
||||
def handle_signal(signum, frame):
|
||||
global running
|
||||
running = False
|
||||
|
||||
|
||||
signal.signal(signal.SIGINT, handle_signal)
|
||||
signal.signal(signal.SIGTERM, handle_signal)
|
||||
|
||||
|
||||
def esc_tag(value: Any) -> str:
|
||||
s = str(value)
|
||||
return s.replace("\\", "\\\\").replace(" ", r"\ ").replace(",", r"\,").replace("=", r"\=")
|
||||
|
||||
|
||||
def esc_field_str(value: Any) -> str:
|
||||
s = str(value)
|
||||
s = s.replace("\\", "\\\\").replace('"', r"\"")
|
||||
return f'"{s}"'
|
||||
|
||||
|
||||
def is_num(value: Any) -> bool:
|
||||
return isinstance(value, (int, float)) and not isinstance(value, bool) and math.isfinite(value)
|
||||
|
||||
|
||||
def bool_to_field(value: bool) -> str:
|
||||
return "true" if value else "false"
|
||||
|
||||
|
||||
def build_line(measurement: str, tags: Dict[str, Any], fields: Dict[str, Any], ts_ns: Optional[int] = None) -> Optional[str]:
|
||||
clean_fields = []
|
||||
for key, value in fields.items():
|
||||
if value is None:
|
||||
continue
|
||||
if isinstance(value, bool):
|
||||
clean_fields.append(f"{key}={bool_to_field(value)}")
|
||||
elif isinstance(value, int) and not isinstance(value, bool):
|
||||
clean_fields.append(f"{key}={value}i")
|
||||
elif is_num(value):
|
||||
clean_fields.append(f"{key}={value}")
|
||||
else:
|
||||
clean_fields.append(f"{key}={esc_field_str(value)}")
|
||||
|
||||
if not clean_fields:
|
||||
return None
|
||||
|
||||
tag_str = ",".join(f"{esc_tag(k)}={esc_tag(v)}" for k, v in tags.items() if v is not None)
|
||||
line = measurement
|
||||
if tag_str:
|
||||
line += "," + tag_str
|
||||
line += " " + ",".join(clean_fields)
|
||||
if ts_ns is not None:
|
||||
line += f" {ts_ns}"
|
||||
return line
|
||||
|
||||
|
||||
def gps_constellation_name(gnssid: Optional[int]) -> str:
|
||||
mapping = {
|
||||
0: "gps",
|
||||
1: "sbas",
|
||||
2: "galileo",
|
||||
3: "beidou",
|
||||
4: "imes",
|
||||
5: "qzss",
|
||||
6: "glonass",
|
||||
}
|
||||
return mapping.get(gnssid, "unknown")
|
||||
|
||||
|
||||
def summarize_satellites(satellites: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
out: Dict[str, Any] = {
|
||||
"gps_visible": 0,
|
||||
"gps_used": 0,
|
||||
"sbas_visible": 0,
|
||||
"sbas_used": 0,
|
||||
"galileo_visible": 0,
|
||||
"galileo_used": 0,
|
||||
"beidou_visible": 0,
|
||||
"beidou_used": 0,
|
||||
"qzss_visible": 0,
|
||||
"qzss_used": 0,
|
||||
"glonass_visible": 0,
|
||||
"glonass_used": 0,
|
||||
"unknown_visible": 0,
|
||||
"unknown_used": 0,
|
||||
"used_signal_sum": 0.0,
|
||||
"used_signal_count": 0,
|
||||
"visible_signal_sum": 0.0,
|
||||
"visible_signal_count": 0,
|
||||
"max_signal": 0.0,
|
||||
}
|
||||
|
||||
for sat in satellites:
|
||||
constellation = gps_constellation_name(sat.get("gnssid"))
|
||||
ss = sat.get("ss", 0.0)
|
||||
used = bool(sat.get("used", False))
|
||||
|
||||
vis_key = f"{constellation}_visible"
|
||||
used_key = f"{constellation}_used"
|
||||
|
||||
if vis_key not in out:
|
||||
vis_key = "unknown_visible"
|
||||
used_key = "unknown_used"
|
||||
|
||||
out[vis_key] += 1
|
||||
if used:
|
||||
out[used_key] += 1
|
||||
|
||||
if is_num(ss):
|
||||
out["visible_signal_sum"] += float(ss)
|
||||
out["visible_signal_count"] += 1
|
||||
out["max_signal"] = max(out["max_signal"], float(ss))
|
||||
if used:
|
||||
out["used_signal_sum"] += float(ss)
|
||||
out["used_signal_count"] += 1
|
||||
|
||||
out["avg_signal_visible"] = (
|
||||
out["visible_signal_sum"] / out["visible_signal_count"] if out["visible_signal_count"] else None
|
||||
)
|
||||
out["avg_signal_used"] = (
|
||||
out["used_signal_sum"] / out["used_signal_count"] if out["used_signal_count"] else None
|
||||
)
|
||||
|
||||
# cleanup helper sums/counters from final output
|
||||
del out["visible_signal_sum"]
|
||||
del out["visible_signal_count"]
|
||||
del out["used_signal_sum"]
|
||||
del out["used_signal_count"]
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def pps_offset_ns(pps: Dict[str, Any]) -> Optional[int]:
|
||||
real_sec = pps.get("real_sec")
|
||||
real_nsec = pps.get("real_nsec")
|
||||
clock_sec = pps.get("clock_sec")
|
||||
clock_nsec = pps.get("clock_nsec")
|
||||
|
||||
if not all(isinstance(v, int) for v in [real_sec, real_nsec, clock_sec, clock_nsec]):
|
||||
return None
|
||||
|
||||
real_total = real_sec * 1_000_000_000 + real_nsec
|
||||
clock_total = clock_sec * 1_000_000_000 + clock_nsec
|
||||
return clock_total - real_total
|
||||
|
||||
|
||||
def main():
|
||||
global running
|
||||
|
||||
proc = subprocess.Popen(
|
||||
GPSPIPE_CMD,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
bufsize=1,
|
||||
)
|
||||
|
||||
latest_tpv: Optional[Dict[str, Any]] = None
|
||||
latest_sky: Optional[Dict[str, Any]] = None
|
||||
latest_pps: Optional[Dict[str, Any]] = None
|
||||
last_emit_sec: Optional[int] = None
|
||||
# NGL at some point I just put cloudee to do it bc I've wrote simmilar code before many times
|
||||
try:
|
||||
assert proc.stdout is not None
|
||||
while running:
|
||||
line = proc.stdout.readline()
|
||||
if not line:
|
||||
if proc.poll() is not None:
|
||||
break
|
||||
time.sleep(0.05)
|
||||
continue
|
||||
|
||||
line = line.strip()
|
||||
if not line or not line.startswith("{"):
|
||||
continue
|
||||
|
||||
try:
|
||||
msg = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
cls = msg.get("class")
|
||||
|
||||
if cls == "TPV":
|
||||
latest_tpv = msg
|
||||
elif cls == "SKY":
|
||||
if latest_sky is None:
|
||||
latest_sky = msg
|
||||
else:
|
||||
merged = latest_sky.copy()
|
||||
merged.update(msg)
|
||||
|
||||
# Keep the richer satellites list if the new SKY message doesn't include it
|
||||
if "satellites" not in msg and "satellites" in latest_sky:
|
||||
merged["satellites"] = latest_sky["satellites"]
|
||||
|
||||
# Keep nSat/uSat if the new SKY message doesn't include them
|
||||
if "nSat" not in msg and "nSat" in latest_sky:
|
||||
merged["nSat"] = latest_sky["nSat"]
|
||||
|
||||
if "uSat" not in msg and "uSat" in latest_sky:
|
||||
merged["uSat"] = latest_sky["uSat"]
|
||||
|
||||
latest_sky = merged
|
||||
elif cls == "PPS":
|
||||
latest_pps = msg
|
||||
else:
|
||||
continue
|
||||
|
||||
now_sec = int(time.time())
|
||||
if last_emit_sec == now_sec:
|
||||
continue
|
||||
|
||||
lines_out = []
|
||||
ts_ns = time.time_ns()
|
||||
|
||||
common_tags = {
|
||||
"host": HOST_TAG,
|
||||
"device": (latest_tpv or latest_sky or latest_pps or {}).get("device", "unknown"),
|
||||
}
|
||||
|
||||
if latest_tpv:
|
||||
fix_fields = {
|
||||
"mode": latest_tpv.get("mode"),
|
||||
"lat": latest_tpv.get("lat"),
|
||||
"lon": latest_tpv.get("lon"),
|
||||
"alt": latest_tpv.get("alt"),
|
||||
"alt_msl": latest_tpv.get("altMSL"),
|
||||
"alt_hae": latest_tpv.get("altHAE"),
|
||||
"speed": latest_tpv.get("speed"),
|
||||
"climb": latest_tpv.get("climb"),
|
||||
"track": latest_tpv.get("track"),
|
||||
"magtrack": latest_tpv.get("magtrack"),
|
||||
"magvar": latest_tpv.get("magvar"),
|
||||
"ept": latest_tpv.get("ept"),
|
||||
"epx": latest_tpv.get("epx"),
|
||||
"epy": latest_tpv.get("epy"),
|
||||
"epv": latest_tpv.get("epv"),
|
||||
"eph": latest_tpv.get("eph"),
|
||||
"sep": latest_tpv.get("sep"),
|
||||
"ecefpAcc": latest_tpv.get("ecefpAcc"),
|
||||
"ecefvAcc": latest_tpv.get("ecefvAcc"),
|
||||
"leapseconds": latest_tpv.get("leapseconds"),
|
||||
"fix_valid": latest_tpv.get("mode", 0) >= 2,
|
||||
"fix_3d": latest_tpv.get("mode", 0) == 3,
|
||||
}
|
||||
line_fix = build_line("gps_fix", common_tags, fix_fields, ts_ns)
|
||||
if line_fix:
|
||||
lines_out.append(line_fix)
|
||||
|
||||
if latest_sky:
|
||||
satellites = latest_sky.get("satellites", [])
|
||||
sat_summary = summarize_satellites(satellites) if isinstance(satellites, list) else {}
|
||||
|
||||
n_sat = latest_sky.get("nSat")
|
||||
u_sat = latest_sky.get("uSat")
|
||||
sat_used_ratio = None
|
||||
if isinstance(n_sat, int) and n_sat > 0 and isinstance(u_sat, int):
|
||||
sat_used_ratio = u_sat / n_sat
|
||||
|
||||
sky_fields = {
|
||||
"nSat": n_sat,
|
||||
"uSat": u_sat,
|
||||
"sat_used_ratio": sat_used_ratio,
|
||||
"gdop": latest_sky.get("gdop"),
|
||||
"hdop": latest_sky.get("hdop"),
|
||||
"pdop": latest_sky.get("pdop"),
|
||||
"tdop": latest_sky.get("tdop"),
|
||||
"vdop": latest_sky.get("vdop"),
|
||||
"xdop": latest_sky.get("xdop"),
|
||||
"ydop": latest_sky.get("ydop"),
|
||||
**sat_summary,
|
||||
}
|
||||
line_sky = build_line("gps_sky", common_tags, sky_fields, ts_ns)
|
||||
if line_sky:
|
||||
lines_out.append(line_sky)
|
||||
|
||||
if latest_pps:
|
||||
offset_ns = pps_offset_ns(latest_pps)
|
||||
pps_fields = {
|
||||
"offset_ns": offset_ns,
|
||||
"precision_exp": latest_pps.get("precision"),
|
||||
"present": True,
|
||||
}
|
||||
line_pps = build_line(
|
||||
"gps_pps",
|
||||
{
|
||||
"host": HOST_TAG,
|
||||
"device": latest_pps.get("device", "/dev/pps0"),
|
||||
},
|
||||
pps_fields,
|
||||
ts_ns,
|
||||
)
|
||||
if line_pps:
|
||||
lines_out.append(line_pps)
|
||||
|
||||
if lines_out:
|
||||
sys.stdout.write("\n".join(lines_out) + "\n")
|
||||
sys.stdout.flush()
|
||||
last_emit_sec = now_sec
|
||||
|
||||
finally:
|
||||
if proc.poll() is None:
|
||||
proc.terminate()
|
||||
try:
|
||||
proc.wait(timeout=2)
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
23
telegraf/telegraf.d/inputs.conf
Normal file
23
telegraf/telegraf.d/inputs.conf
Normal file
@@ -0,0 +1,23 @@
|
||||
# Get standard chrony metrics, requires chronyc executable.
|
||||
[[inputs.chrony]]
|
||||
|
||||
|
||||
# Read metrics about system load & uptime
|
||||
[[inputs.system]]
|
||||
|
||||
# Disk status
|
||||
[[inputs.disk]]
|
||||
[[inputs.diskio]]
|
||||
|
||||
# Temperatures
|
||||
[[inputs.sensors]]
|
||||
|
||||
# System Data
|
||||
[[inputs.cpu]]
|
||||
[[inputs.mem]]
|
||||
|
||||
# gpsd data
|
||||
[[inputs.execd]]
|
||||
command = ["/etc/telegraf/gpsdMeasurements.py"]
|
||||
signal = "none"
|
||||
data_format = "influx"
|
||||
5
telegraf/telegraf.d/outputs.conf
Normal file
5
telegraf/telegraf.d/outputs.conf
Normal file
@@ -0,0 +1,5 @@
|
||||
[[outputs.influxdb_v2]]
|
||||
urls = ["<LinkToYourInfluxDB>"]
|
||||
token = "<YourToken>"
|
||||
organization = "<YourOrganization>"
|
||||
bucket = "<YourBucket>"
|
||||
Reference in New Issue
Block a user