feat(api): differentiate traces and pings

This commit is contained in:
Enot (ded) Skelly
2026-06-11 14:52:31 -07:00
parent 73fa0be858
commit b58e5a1edc
12 changed files with 90 additions and 20 deletions
+3 -1
View File
@@ -812,7 +812,8 @@ SELECT
MIN(p.first_heard_at)::timestamptz AS first_heard_at,
MAX(p.last_heard_at)::timestamptz AS last_heard_at,
COUNT(DISTINCT p.packet_hash) AS packet_count,
COUNT(DISTINCT po.iata) AS iata_count
COUNT(DISTINCT po.iata) AS iata_count,
MAX(p.parsed_payload->>'type')::text AS trace_type
FROM packets p
LEFT JOIN packet_observations po ON po.packet_hash = p.packet_hash
WHERE p.trace_tag IS NOT NULL
@@ -821,6 +822,7 @@ WHERE p.trace_tag IS NOT NULL
AND ($3::timestamptz IS NULL OR p.first_heard_at >= $3)
AND ($4::timestamptz IS NULL OR p.first_heard_at <= $4)
AND ($5::timestamptz IS NULL OR p.last_heard_at < $5)
AND ($7::text = '' OR p.parsed_payload->>'type' = $7)
GROUP BY p.trace_tag
ORDER BY MAX(p.last_heard_at) DESC
LIMIT $6;
+7 -1
View File
@@ -2620,7 +2620,8 @@ SELECT
MIN(p.first_heard_at)::timestamptz AS first_heard_at,
MAX(p.last_heard_at)::timestamptz AS last_heard_at,
COUNT(DISTINCT p.packet_hash) AS packet_count,
COUNT(DISTINCT po.iata) AS iata_count
COUNT(DISTINCT po.iata) AS iata_count,
MAX(p.parsed_payload->>'type')::text AS trace_type
FROM packets p
LEFT JOIN packet_observations po ON po.packet_hash = p.packet_hash
WHERE p.trace_tag IS NOT NULL
@@ -2629,6 +2630,7 @@ WHERE p.trace_tag IS NOT NULL
AND ($3::timestamptz IS NULL OR p.first_heard_at >= $3)
AND ($4::timestamptz IS NULL OR p.first_heard_at <= $4)
AND ($5::timestamptz IS NULL OR p.last_heard_at < $5)
AND ($7::text = '' OR p.parsed_payload->>'type' = $7)
GROUP BY p.trace_tag
ORDER BY MAX(p.last_heard_at) DESC
LIMIT $6
@@ -2641,6 +2643,7 @@ type ListTraceTagsParams struct {
Column4 pgtype.Timestamptz `json:"column_4"`
Column5 pgtype.Timestamptz `json:"column_5"`
Limit int32 `json:"limit"`
Column7 string `json:"column_7"`
}
type ListTraceTagsRow struct {
@@ -2649,6 +2652,7 @@ type ListTraceTagsRow struct {
LastHeardAt pgtype.Timestamptz `json:"last_heard_at"`
PacketCount int64 `json:"packet_count"`
IataCount int64 `json:"iata_count"`
TraceType string `json:"trace_type"`
}
// ============================================================
@@ -2663,6 +2667,7 @@ func (q *Queries) ListTraceTags(ctx context.Context, arg ListTraceTagsParams) ([
arg.Column4,
arg.Column5,
arg.Limit,
arg.Column7,
)
if err != nil {
return nil, err
@@ -2677,6 +2682,7 @@ func (q *Queries) ListTraceTags(ctx context.Context, arg ListTraceTagsParams) ([
&i.LastHeardAt,
&i.PacketCount,
&i.IataCount,
&i.TraceType,
); err != nil {
return nil, err
}
+3 -1
View File
@@ -21,7 +21,7 @@ type tracePayload struct {
SNRValues []float32 `json:"snrValues"`
}
func (s *Store) ListTraceTags(ctx context.Context, iatas []string, scope string, since, until time.Time, cursor time.Time, limit int32) ([]api.TraceTagSummary, error) {
func (s *Store) ListTraceTags(ctx context.Context, iatas []string, scope, traceType string, since, until time.Time, cursor time.Time, limit int32) ([]api.TraceTagSummary, error) {
iataFilter := strings.Join(iatas, ",")
var sinceTS, untilTS, cursorTS pgtype.Timestamptz
if !since.IsZero() {
@@ -40,6 +40,7 @@ func (s *Store) ListTraceTags(ctx context.Context, iatas []string, scope string,
Column4: untilTS,
Column5: cursorTS,
Limit: limit,
Column7: traceType,
})
if err != nil {
return nil, err
@@ -52,6 +53,7 @@ func (s *Store) ListTraceTags(ctx context.Context, iatas []string, scope string,
LastHeardAt: r.LastHeardAt.Time.UnixMilli(),
PacketCount: r.PacketCount,
IATACount: r.IataCount,
TraceType: r.TraceType,
})
}
return items, nil
+18
View File
@@ -1941,6 +1941,12 @@ const docTemplate = `{
"name": "scope",
"in": "query"
},
{
"type": "string",
"description": "Filter by type: TRACE or PING (default: all)",
"name": "type",
"in": "query"
},
{
"type": "integer",
"description": "Filter by first_heard_at \u003e= since (epoch ms)",
@@ -3123,6 +3129,7 @@ const docTemplate = `{
"type": "string"
},
"snr": {
"description": "signal-to-noise ratio in dB, if available",
"type": "number"
}
}
@@ -3358,6 +3365,7 @@ const docTemplate = `{
"type": "object",
"properties": {
"packets": {
"description": "all packets observed for this trace",
"type": "array",
"items": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.TracePacket"
@@ -3381,27 +3389,33 @@ const docTemplate = `{
"type": "integer"
},
"packetHash": {
"description": "hex-encoded packet hash",
"type": "string"
},
"rawPath": {
"description": "hops as received in the packet",
"type": "array",
"items": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.RawHop"
}
},
"resolvedRoute": {
"description": "hops resolved to known nodes",
"type": "array",
"items": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.ResolvedHop"
}
},
"routeType": {
"description": "numeric route type",
"type": "integer"
},
"routeTypeName": {
"description": "human-readable route type",
"type": "string"
},
"scope": {
"description": "transport scope name, if known",
"type": "string"
}
}
@@ -3428,6 +3442,10 @@ const docTemplate = `{
"traceTag": {
"description": "hex-encoded 4-byte tag",
"type": "string"
},
"traceType": {
"description": "TRACE or PING",
"type": "string"
}
}
},
+18
View File
@@ -1939,6 +1939,12 @@
"name": "scope",
"in": "query"
},
{
"type": "string",
"description": "Filter by type: TRACE or PING (default: all)",
"name": "type",
"in": "query"
},
{
"type": "integer",
"description": "Filter by first_heard_at \u003e= since (epoch ms)",
@@ -3121,6 +3127,7 @@
"type": "string"
},
"snr": {
"description": "signal-to-noise ratio in dB, if available",
"type": "number"
}
}
@@ -3356,6 +3363,7 @@
"type": "object",
"properties": {
"packets": {
"description": "all packets observed for this trace",
"type": "array",
"items": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.TracePacket"
@@ -3379,27 +3387,33 @@
"type": "integer"
},
"packetHash": {
"description": "hex-encoded packet hash",
"type": "string"
},
"rawPath": {
"description": "hops as received in the packet",
"type": "array",
"items": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.RawHop"
}
},
"resolvedRoute": {
"description": "hops resolved to known nodes",
"type": "array",
"items": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.ResolvedHop"
}
},
"routeType": {
"description": "numeric route type",
"type": "integer"
},
"routeTypeName": {
"description": "human-readable route type",
"type": "string"
},
"scope": {
"description": "transport scope name, if known",
"type": "string"
}
}
@@ -3426,6 +3440,10 @@
"traceTag": {
"description": "hex-encoded 4-byte tag",
"type": "string"
},
"traceType": {
"description": "TRACE or PING",
"type": "string"
}
}
},
+15
View File
@@ -755,6 +755,7 @@ definitions:
description: hex-encoded path hash
type: string
snr:
description: signal-to-noise ratio in dB, if available
type: number
type: object
github_com_MeshCore-Beacon_beacon-server_internal_api.Region:
@@ -912,6 +913,7 @@ definitions:
github_com_MeshCore-Beacon_beacon-server_internal_api.TraceDetail:
properties:
packets:
description: all packets observed for this trace
items:
$ref: '#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.TracePacket'
type: array
@@ -928,20 +930,26 @@ definitions:
description: epoch ms
type: integer
packetHash:
description: hex-encoded packet hash
type: string
rawPath:
description: hops as received in the packet
items:
$ref: '#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.RawHop'
type: array
resolvedRoute:
description: hops resolved to known nodes
items:
$ref: '#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.ResolvedHop'
type: array
routeType:
description: numeric route type
type: integer
routeTypeName:
description: human-readable route type
type: string
scope:
description: transport scope name, if known
type: string
type: object
github_com_MeshCore-Beacon_beacon-server_internal_api.TraceTagSummary:
@@ -961,6 +969,9 @@ definitions:
traceTag:
description: hex-encoded 4-byte tag
type: string
traceType:
description: TRACE or PING
type: string
type: object
internal_api_handlers.APIError:
properties:
@@ -2246,6 +2257,10 @@ paths:
in: query
name: scope
type: string
- description: 'Filter by type: TRACE or PING (default: all)'
in: query
name: type
type: string
- description: Filter by first_heard_at >= since (epoch ms)
in: query
name: since
+1 -1
View File
@@ -148,7 +148,7 @@ func (stubReader) GetScopeByName(ctx context.Context, name string) (*api.ScopeDe
return nil, nil
}
func (stubReader) ListTraceTags(ctx context.Context, iatas []string, scope string, since, until time.Time, cursor time.Time, limit int32) ([]api.TraceTagSummary, error) {
func (stubReader) ListTraceTags(ctx context.Context, iatas []string, scope, traceType string, since, until time.Time, cursor time.Time, limit int32) ([]api.TraceTagSummary, error) {
return nil, nil
}
+4 -1
View File
@@ -6,6 +6,7 @@ package handlers
import (
"net/http"
"strconv"
"strings"
"time"
"github.com/MeshCore-Beacon/beacon-server/internal/api"
@@ -32,6 +33,7 @@ func TracesRouter(reader api.Reader) http.Handler {
// @Param region query string false "Filter by region slug"
// @Param regionId query int false "Filter by region ID"
// @Param scope query string false "Filter by transport scope name"
// @Param type query string false "Filter by type: TRACE or PING (default: all)"
// @Param since query int false "Filter by first_heard_at >= since (epoch ms)"
// @Param until query int false "Filter by first_heard_at <= until (epoch ms)"
// @Param cursor query int false "last_heard_at epoch ms of last item for pagination"
@@ -73,7 +75,8 @@ func listTraceTags(reader api.Reader) http.HandlerFunc {
iatas = append(iatas, regionIATAs...)
}
scope := r.URL.Query().Get("scope")
tags, err := reader.ListTraceTags(r.Context(), iatas, scope, since, until, cursor, limit)
traceType := strings.ToUpper(r.URL.Query().Get("type"))
tags, err := reader.ListTraceTags(r.Context(), iatas, scope, traceType, since, until, cursor, limit)
if err != nil {
respondError(w, http.StatusInternalServerError, "internal server error")
return
+1 -1
View File
@@ -172,7 +172,7 @@ type Reader interface {
GetScopeByName(ctx context.Context, name string) (*ScopeDetail, error)
// ListTraceTags returns a paginated list of trace tags with aggregate metadata.
ListTraceTags(ctx context.Context, iatas []string, scope string, since, until time.Time, cursor time.Time, limit int32) ([]TraceTagSummary, error)
ListTraceTags(ctx context.Context, iatas []string, scope, traceType string, since, until time.Time, cursor time.Time, limit int32) ([]TraceTagSummary, error)
// GetTraceByTag returns all packets for a given trace tag with resolved routes.
GetTraceByTag(ctx context.Context, tag string) (*TraceDetail, error)
+13 -11
View File
@@ -10,28 +10,30 @@ type TraceTagSummary struct {
LastHeardAt int64 `json:"lastHeardAt"` // epoch ms
PacketCount int64 `json:"packetCount"` // number of packets with this trace tag
IATACount int64 `json:"iataCount"` // number of distinct IATAs where heard
TraceType string `json:"traceType"` // TRACE or PING
}
// TracePacket is a single packet within a trace series, including its
// resolved route derived from the trace path hashes.
type TracePacket struct {
PacketHash string `json:"packetHash"`
RouteType int16 `json:"routeType"`
RouteTypeName string `json:"routeTypeName"`
Scope *string `json:"scope,omitempty"`
FirstHeardAt int64 `json:"firstHeardAt"` // epoch ms
LastHeardAt int64 `json:"lastHeardAt"` // epoch ms
RawPath []RawHop `json:"rawPath"`
ResolvedRoute []ResolvedHop `json:"resolvedRoute"`
PacketHash string `json:"packetHash"` // hex-encoded packet hash
RouteType int16 `json:"routeType"` // numeric route type
RouteTypeName string `json:"routeTypeName"` // human-readable route type
Scope *string `json:"scope,omitempty"` // transport scope name, if known
FirstHeardAt int64 `json:"firstHeardAt"` // epoch ms
LastHeardAt int64 `json:"lastHeardAt"` // epoch ms
RawPath []RawHop `json:"rawPath"` // hops as received in the packet
ResolvedRoute []ResolvedHop `json:"resolvedRoute"` // hops resolved to known nodes
}
// RawHop is a single hop in a trace path as received in the packet.
type RawHop struct {
Hash string `json:"hash"` // hex-encoded path hash
SNR *float32 `json:"snr,omitempty"`
Hash string `json:"hash"` // hex-encoded path hash
SNR *float32 `json:"snr,omitempty"` // signal-to-noise ratio in dB, if available
}
// TraceDetail is the full trace series for a given trace tag.
type TraceDetail struct {
TraceTag string `json:"traceTag"` // hex-encoded 4-byte tag
Packets []TracePacket `json:"packets"`
Packets []TracePacket `json:"packets"` // all packets observed for this trace
}
+2 -2
View File
@@ -389,6 +389,6 @@ func (cr *CachedReader) SearchCrossIATARoutes(ctx context.Context, fromHash, fro
}
// ListTraceTags implements [api.Reader].
func (cr *CachedReader) ListTraceTags(ctx context.Context, iatas []string, scope string, since, until time.Time, cursor time.Time, limit int32) ([]api.TraceTagSummary, error) {
return cr.inner.ListTraceTags(ctx, iatas, scope, since, until, cursor, limit)
func (cr *CachedReader) ListTraceTags(ctx context.Context, iatas []string, scope, traceType string, since, until time.Time, cursor time.Time, limit int32) ([]api.TraceTagSummary, error) {
return cr.inner.ListTraceTags(ctx, iatas, scope, traceType, since, until, cursor, limit)
}
+5 -1
View File
@@ -447,9 +447,13 @@ func (w *Worker) handlePacket(ctx context.Context, iata, pubkeyHex string, raw [
for _, b := range packet.Path {
snrValues = append(snrValues, float32(int8(b))/4.0)
}
traceType := "TRACE"
if len(hashes) == 1 {
traceType = "PING"
}
pt := parsedTrace{
Raw: hex.EncodeToString(packet.Payload),
Type: "TRACE",
Type: traceType,
TraceTag: hex.EncodeToString(uint32ToBytes(trace.Tag)),
AuthCode: trace.AuthCode,
Flags: trace.Flags,