SPB Git forge
15commits 1branches 0releases
29.7 MBsize
maindefault branch
10 days agolast push
TypeScript 36.3% Python 31.8% Go 18% JavaScript 9.8% Shell 1.9% SQL 1.4% CSS 0.5%
9.0 KB · 274 lines go
Raw Blame History
1// Package protocol holds the wire types of the probe ↔ ingestion protocol (docs/PROBE-PROTOCOL.md, v1).2//3// Field names and JSON tags mirror the document exactly; optional fields use pointers with omitempty so that4// "fields not relevant to kind" are omitted (the protocol allows omitted or null).5package protocol67import "time"89// TimeFormat is the UTC RFC 3339 format with milliseconds used everywhere on the wire.10const TimeFormat = "2006-01-02T15:04:05.000Z07:00"1112// FormatTime renders t as UTC RFC 3339 with milliseconds ("2026-09-12T05:10:04.123Z").13func FormatTime(t time.Time) string { return t.UTC().Format(TimeFormat) }1415// ParseTime accepts RFC 3339 with or without fractional seconds.16func ParseTime(s string) (time.Time, error) {17	if t, err := time.Parse(time.RFC3339Nano, s); err == nil {18		return t, nil19	}20	return time.Parse(time.RFC3339, s)21}2223// ---- GET /config -------------------------------------------------------------------------------------------2425// RemoteConfig is the probe assignment returned by GET /ingest/v1/config.26type RemoteConfig struct {27	ServerTime    string     `json:"server_time"`28	ConfigVersion string     `json:"config_version"`29	Probe         ProbeInfo  `json:"probe"`30	Schedule      Schedule   `json:"schedule"`31	Resolvers     []Resolver `json:"resolvers"`32	Targets       []Target   `json:"targets"`33}3435// ProbeInfo describes the probe as known by the server.36type ProbeInfo struct {37	ProbeID  string   `json:"probe_id"`38	Name     string   `json:"name"`39	Region   string   `json:"region"`40	Country  string   `json:"country"`41	City     string   `json:"city"`42	Provider string   `json:"provider"`43	ASN      int      `json:"asn"`44	Lat      *float64 `json:"lat"`45	Lon      *float64 `json:"lon"`46	Enabled  bool     `json:"enabled"`47}4849// Schedule carries the interval parameters. Tiers keys are strings ("1", "2", "3") as in the JSON document.50type Schedule struct {51	Tiers                map[string]int `json:"tiers"`52	DNSEvery             int            `json:"dns_every"`53	PingEvery            int            `json:"ping_every"`54	TracerouteEvery      int            `json:"traceroute_every"`55	BatchFlushSeconds    int            `json:"batch_flush_seconds"`56	MaxBatch             int            `json:"max_batch"`57	ConfigRefreshSeconds int            `json:"config_refresh_seconds"`58	Boost                *Boost         `json:"boost,omitempty"`59}6061// Boost multiplies the intervals of the listed targets by Factor until Until.62type Boost struct {63	Targets []string `json:"targets"`64	Factor  float64  `json:"factor"`65	Until   string   `json:"until"`66}6768// Active reports whether the boost applies to targetID at time now.69func (b *Boost) Active(targetID string, now time.Time) bool {70	if b == nil || b.Factor <= 0 {71		return false72	}73	until, err := ParseTime(b.Until)74	if err != nil || !now.Before(until) {75		return false76	}77	for _, t := range b.Targets {78		if t == targetID {79			return true80		}81	}82	return false83}8485// Resolver is a DNS resolver to query. ID "system" (empty address) means the OS resolver.86type Resolver struct {87	ID      string `json:"id"`88	Address string `json:"address"`89}9091// Target is one monitored endpoint.92type Target struct {93	TargetID   string   `json:"target_id"`94	Name       string   `json:"name"`95	Hostname   string   `json:"hostname"`96	URL        string   `json:"url"`97	IP         *string  `json:"ip"`98	Port       int      `json:"port"`99	Category   string   `json:"category"`100	Provider   string   `json:"provider"`101	ServiceID  string   `json:"service_id"`102	Country    *string  `json:"country"`103	Region     string   `json:"region"`104	Importance int      `json:"importance"`105	Tier       int      `json:"tier"`106	Checks     []string `json:"checks"`107	Traceroute bool     `json:"traceroute"`108}109110// HasCheck reports whether kind is listed in Checks.111func (t Target) HasCheck(kind string) bool {112	for _, c := range t.Checks {113		if c == kind {114			return true115		}116	}117	return false118}119120// FixedIP returns the pinned IP ("" when none).121func (t Target) FixedIP() string {122	if t.IP == nil {123		return ""124	}125	return *t.IP126}127128// ---- POST /batch -------------------------------------------------------------------------------------------129130// Batch is the (pre-gzip) body of POST /ingest/v1/batch.131type Batch struct {132	ProbeID      string        `json:"probe_id"`133	AgentVersion string        `json:"agent_version"`134	SentAt       string        `json:"sent_at"`135	Measurements []Measurement `json:"measurements"`136	Traceroutes  []Traceroute  `json:"traceroutes,omitempty"`137	Health       *Health       `json:"health,omitempty"`138}139140// Measurement is one http / dns / ping / tcp observation.141type Measurement struct {142	TS       string `json:"ts"`143	TargetID string `json:"target_id"`144	Kind     string `json:"kind"` // "http" | "dns" | "ping" | "tcp"145	OK       bool   `json:"ok"`146	Error    string `json:"error"` // "" or one of the protocol error codes147148	// http149	DNSMs      *float64 `json:"dns_ms,omitempty"`150	TCPMs      *float64 `json:"tcp_ms,omitempty"`151	TLSMs      *float64 `json:"tls_ms,omitempty"`152	TTFBMs     *float64 `json:"ttfb_ms,omitempty"`153	TotalMs    *float64 `json:"total_ms,omitempty"`154	HTTPStatus *int     `json:"http_status,omitempty"`155	HTTPProto  string   `json:"http_proto,omitempty"`156	TLSVersion string   `json:"tls_version,omitempty"`157	ResolvedIP string   `json:"resolved_ip,omitempty"`158159	// dns160	Resolver   string   `json:"resolver,omitempty"`161	DNSRcode   string   `json:"dns_rcode,omitempty"`162	DNSAnswers []string `json:"dns_answers,omitempty"`163164	// ping / tcp165	Sent       *int     `json:"sent,omitempty"`166	Received   *int     `json:"received,omitempty"`167	PacketLoss *float64 `json:"packet_loss,omitempty"`168	RTTMinMs   *float64 `json:"rtt_min_ms,omitempty"`169	RTTAvgMs   *float64 `json:"rtt_avg_ms,omitempty"`170	RTTMaxMs   *float64 `json:"rtt_max_ms,omitempty"`171	JitterMs   *float64 `json:"jitter_ms,omitempty"`172}173174// Traceroute is one path measurement.175type Traceroute struct {176	TS        string   `json:"ts"`177	TargetID  string   `json:"target_id"`178	DestIP    string   `json:"dest_ip"`179	Reached   bool     `json:"reached"`180	HopCount  int      `json:"hop_count"`181	TotalMs   *float64 `json:"total_ms"`182	RouteHash string   `json:"route_hash"`183	Hops      []Hop    `json:"hops"`184}185186// Hop is one traceroute hop; IP is "*" and RTTMs nil when unanswered.187type Hop struct {188	N     int      `json:"n"`189	IP    string   `json:"ip"`190	RTTMs *float64 `json:"rtt_ms"`191}192193// Health is the agent self-report (at most once per minute).194type Health struct {195	TS                string    `json:"ts"`196	AgentVersion      string    `json:"agent_version"`197	UptimeS           int64     `json:"uptime_s"`198	Buffered          int       `json:"buffered"`199	SpoolBytes        int64     `json:"spool_bytes"`200	MeasurementsTotal uint64    `json:"measurements_total"`201	ErrorsTotal       uint64    `json:"errors_total"`202	ClockOffsetMs     int64     `json:"clock_offset_ms"`203	RSSMb             float64   `json:"rss_mb"`204	Goroutines        int       `json:"goroutines"`205	Capabilities      []string  `json:"capabilities"`206	OS                string    `json:"os"`207	Arch              string    `json:"arch"`208	Identity          *Identity `json:"identity,omitempty"`209}210211// Identity is the public, city-level network identity of the probe (never more precise than city).212type Identity struct {213	PublicIP string   `json:"public_ip"`214	ASN      int      `json:"asn"`215	Org      string   `json:"org"`216	Country  string   `json:"country"`217	City     string   `json:"city"`218	Lat      *float64 `json:"lat"`219	Lon      *float64 `json:"lon"`220	Source   string   `json:"source"`221}222223// BatchResponse is the body returned by POST /batch.224type BatchResponse struct {225	Accepted      int    `json:"accepted"`226	Rejected      int    `json:"rejected"`227	ConfigVersion string `json:"config_version"`228	ServerTime    string `json:"server_time"`229	Boost         *Boost `json:"boost,omitempty"`230}231232// ---- GET /agent/latest -------------------------------------------------------------------------------------233234// AgentLatest describes the newest agent release.235type AgentLatest struct {236	Version string           `json:"version"`237	Assets  map[string]Asset `json:"assets"`238}239240// Asset is one downloadable binary.241type Asset struct {242	URL    string `json:"url"`243	SHA256 string `json:"sha256"`244}245246// Error codes (docs/PROBE-PROTOCOL.md § Measurement).247const (248	ErrDNSFail         = "dns_fail"249	ErrDNSTimeout      = "dns_timeout"250	ErrDNSServfail     = "dns_servfail"251	ErrDNSNxdomain     = "dns_nxdomain"252	ErrTCPTimeout      = "tcp_timeout"253	ErrTCPRefused      = "tcp_refused"254	ErrTCPReset        = "tcp_reset"255	ErrTLSFail         = "tls_fail"256	ErrTLSCert         = "tls_cert"257	ErrHTTPTimeout     = "http_timeout"258	ErrHTTP5xx         = "http_5xx"259	ErrHTTP4xx         = "http_4xx"260	ErrReset           = "reset"261	ErrUnreachable     = "unreachable"262	ErrICMPUnavailable = "icmp_unavailable"263	ErrOther           = "other"264)265266// F returns a pointer to v (helper for optional float fields).267func F(v float64) *float64 { return &v }268269// I returns a pointer to v (helper for optional int fields).270func I(v int) *int { return &v }271272// Ms converts a duration to milliseconds with 0.1 ms resolution.273func Ms(d time.Duration) float64 { return float64(d.Microseconds()) / 1000 }274