Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

143 lines
4.6 KiB
Go

package telemetry
import (
"regexp"
"time"
)
// allowedMetrics is the hard allow-list of telemetry counter keys. It is the
// privacy backbone: the aggregator physically cannot record a key that is not
// on this list, so a path, a symbol name, or any arbitrary string can never
// become a metric. Adding a counter is a deliberate edit here and nowhere else.
var allowedMetrics = map[string]bool{
"mcp_tool_call": true, // an MCP tool was invoked; dim = tool name
"cli_command": true, // a CLI subcommand ran; dim = command name
"index": true, // an index pass completed; dim = file-count bucket
"index_lang": true, // a language was present in an index pass; dim = language
"daemon_session": true, // a daemon session started; dim = backend kind
"install": true, // an install applied; dim = agent target / scope
"uninstall": true, // an uninstall applied; dim = agent target / scope
"client": true, // an MCP client connected; dim = client app name
}
// IsAllowedMetric reports whether key may be recorded.
func IsAllowedMetric(key string) bool { return allowedMetrics[key] }
// dimPattern bounds a metric dimension to a short, non-identifying token —
// bucket labels (`1k-10k`), tool/command names (`search_symbols`), backend
// kinds (`sqlite`). Anything with a path separator, whitespace, or length
// beyond 32 is rejected, so even a caller that passes a path or a symbol name
// as a dimension cannot leak it: the counter falls back to the bare key.
var dimPattern = regexp.MustCompile(`^[A-Za-z0-9_.<>+-]{1,32}$`)
// safeDim returns dim when it is a bounded, non-identifying token, else "".
func safeDim(dim string) string {
if dimPattern.MatchString(dim) {
return dim
}
return ""
}
// BucketFileCount collapses a file count into a coarse bucket so an exact
// count — which can narrow identification — is never recorded.
func BucketFileCount(n int) string {
switch {
case n < 100:
return "<100"
case n < 1000:
return "100-1k"
case n < 10000:
return "1k-10k"
default:
return "10k+"
}
}
// BucketDuration collapses an elapsed time into a coarse bucket.
func BucketDuration(d time.Duration) string {
ms := d.Milliseconds()
switch {
case ms < 10_000:
return "<10s"
case ms < 60_000:
return "10-60s"
case ms < 300_000:
return "1-5m"
default:
return "5m+"
}
}
// Rollup is one UTC day's aggregated, coarse counts. Counts maps an
// allow-listed metric key (optionally suffixed with a bucketed dimension after
// a colon) to the number of times it occurred that day.
type Rollup struct {
Day string `json:"day"` // YYYY-MM-DD in UTC
Counts map[string]int `json:"counts"`
}
// DayKey renders the UTC calendar day of t.
func DayKey(t time.Time) string { return t.UTC().Format("2006-01-02") }
// NewRollup creates an empty rollup for the UTC day of t.
func NewRollup(t time.Time) *Rollup {
return &Rollup{Day: DayKey(t), Counts: map[string]int{}}
}
// metricName resolves an allow-listed (key, dim) pair to its counter name. ok
// is false when key is not on the allow-list. A dimension that is not a bounded
// token is dropped, so the bare key is returned — the path/name guard. This is
// the single place the allow-list and dimension sanitiser are applied, shared
// by Rollup.Add and the Recorder so they cannot diverge.
func metricName(key, dim string) (name string, ok bool) {
if !allowedMetrics[key] {
return "", false
}
if d := safeDim(dim); d != "" {
return key + ":" + d, true
}
return key, true
}
// Add increments the counter for an allow-listed metric, optionally qualified
// by a dimension (a bucket label or a fixed enum like a tool name). It returns
// whether the event counted: a key not on the allow-list is silently dropped,
// and a dimension that is not a bounded token is discarded (the bare key still
// counts).
func (r *Rollup) Add(key, dim string) bool {
name, ok := metricName(key, dim)
if !ok {
return false
}
if r.Counts == nil {
r.Counts = map[string]int{}
}
r.Counts[name]++
return true
}
// Merge folds other's counts into r. Days are assumed equal (the caller groups
// by day); a mismatched day is a programmer error and is ignored rather than
// silently corrupting r's day label.
func (r *Rollup) Merge(other *Rollup) {
if other == nil || other.Day != r.Day {
return
}
if r.Counts == nil {
r.Counts = map[string]int{}
}
for k, v := range other.Counts {
r.Counts[k] += v
}
}
// Total is the sum of every counter — a convenience for "did anything happen
// today" checks before persisting or sending.
func (r *Rollup) Total() int {
n := 0
for _, v := range r.Counts {
n += v
}
return n
}