| index 25ce64b03..c4a598e9d 100644 |
| --- a/internal/host/telemetry_pass.go |
| +++ b/internal/host/telemetry_pass.go |
| @@ -29,8 +29,10 @@ func setAnalyticsSink(fn func(map[string]bool)) { analyticsSink.Store(&fn) } |
| // gate skips, or one that fails, reuses the last good value. A failed read |
| // marks its scope failed, so the next pass reads it again. |
| // |
| -// Disarmed, each pass gets a fresh one over a nil watch: every read is due, |
| -// nothing is remembered, and a failed read leaves its apps off, as before. |
| +// Disarmed, each pass gets a fresh one over a nil watch, and the pass is |
| +// main's (telemetrySwitchesResolve): every app resolves both levels on its |
| +// own, nothing is remembered within or across passes, and a failed read |
| +// leaves only that app off, as before the index. |
| type telemetryReads struct { |
| // mu is held across a whole pass, reads included: the fast converge loop |
| // and the instances loop both run the pass and take turns here. Nothing |
| @@ -75,13 +77,16 @@ func telemetrySwitches(ctx context.Context, s release.Store, reg *registry.Regis |
| return telemetrySwitchesWith(ctx, s, reg, currentTelemetryReads(), time.Now()) |
| } |
| |
| -// telemetrySwitchesWith is telemetrySwitches over r at now. Each account's |
| -// telemetry.json is read at most once per pass, however many of its apps the |
| -// box serves, and each app's at most once; a read is skipped when r's gate |
| -// says its scope has not moved and r holds a last good value. A pair with no |
| -// good value at either level is left OFF and logged: off is the safe side of |
| -// this switch. |
| +// telemetrySwitchesWith is telemetrySwitches over r at now. Over a watch, |
| +// each account's telemetry.json is read at most once per pass, however many |
| +// of its apps the box serves, and each app's at most once; a read is skipped |
| +// when r's gate says its scope has not moved and r holds a last good value. |
| +// A pair with no good value at either level is left OFF and logged: off is |
| +// the safe side of this switch. Without one (disarmed), it is main's pass. |
| func telemetrySwitchesWith(ctx context.Context, s release.Store, reg *registry.Registry, r *telemetryReads, now time.Time) (map[string]bool, error) { |
| + if r == nil || r.w == nil { |
| + return telemetrySwitchesResolve(ctx, s, reg) |
| + } |
| routes, err := reg.List() |
| if err != nil { |
| return nil, err |
| @@ -125,6 +130,38 @@ func telemetrySwitchesWith(ctx context.Context, s release.Store, reg *registry.R |
| return on, nil |
| } |
| |
| +// telemetrySwitchesResolve is the pass with no change index armed, exactly |
| +// as before the index: one Resolve (two Gets) per (account, app); a pair |
| +// that fails to resolve is left OFF and logged, and no other pair is. |
| +func telemetrySwitchesResolve(ctx context.Context, s release.Store, reg *registry.Registry) (map[string]bool, error) { |
| + routes, err := reg.List() |
| + if err != nil { |
| + return nil, err |
| + } |
| + on := map[string]bool{} |
| + seen := map[string]bool{} |
| + for _, rt := range routes { |
| + if rt.Kind == "status" || rt.App == "" { |
| + continue |
| + } |
| + sqid := release.RouteAccount(rt) |
| + key := sqid + "/" + rt.App |
| + if seen[key] { |
| + continue |
| + } |
| + seen[key] = true |
| + res, err := telemetry.Resolve(ctx, s, sqid, rt.App) |
| + if err != nil { |
| + log.Printf("agent: telemetry resolve %s: %v (analytics stays off)", key, err) |
| + continue |
| + } |
| + if res.Count == telemetry.CountEdge { |
| + on[key] = true |
| + } |
| + } |
| + return on, nil |
| +} |
| + |
| // read is key's settings for this pass: settled once per pass in pass. The |
| // last good value stands in when the gate says the key's scope has not moved; |
| // otherwise the object is read. A failed read keeps the last good value, logs |