Skip to content

Commit f7285dc

Browse files
authored
feat(timeline): drop no-diff updates after filling diff-function gaps (#662) (#673)
1 parent a70ef8c commit f7285dc

4 files changed

Lines changed: 1173 additions & 86 deletions

File tree

internal/k8s/cache.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,11 @@ func isNoisyResource(kind, name, op string) bool {
314314
switch kind {
315315
case "Lease", "Endpoints", "EndpointSlice", "Event":
316316
return true
317+
case "VerticalPodAutoscalerCheckpoint":
318+
// VPA writes per-container resource recommendations here on every
319+
// reconcile (~1m). The whole point of the resource is to be a live
320+
// ticker — per-update is never user-meaningful.
321+
return true
317322
}
318323

319324
if kind == "ConfigMap" {
@@ -339,6 +344,20 @@ func isNoisyResource(kind, name, op string) bool {
339344
return false
340345
}
341346

347+
// getGeneration returns the metadata.generation of an informer object, or 0
348+
// if the object is nil or doesn't expose metav1.Object. Both typed K8s
349+
// resources and *unstructured.Unstructured satisfy metav1.Object.
350+
func getGeneration(obj any) int64 {
351+
if obj == nil {
352+
return 0
353+
}
354+
m, ok := obj.(metav1.Object)
355+
if !ok {
356+
return 0
357+
}
358+
return m.GetGeneration()
359+
}
360+
342361
// recordToTimelineStore records an event to the timeline store
343362
func recordToTimelineStore(kind, namespace, name, uid, op string, oldObj, newObj any) {
344363
store := timeline.GetStore()
@@ -391,6 +410,30 @@ func recordToTimelineStore(kind, namespace, name, uid, op string, oldObj, newObj
391410
NewValue: f.NewValue,
392411
}
393412
}
413+
} else if KindHasDiffer(kind) {
414+
// Audited diff function found nothing observable — usually a
415+
// heartbeat, managedFields-only update, or reconcile counter.
416+
// Before dropping, check metadata.generation: it bumps only on
417+
// spec changes (status updates don't touch it), so a generation
418+
// flip with a nil diff means our diff function missed a real spec
419+
// field. Record those with a fallback summary instead of silently
420+
// losing them — diff coverage gaps shouldn't become silent drops.
421+
if oldGen, newGen := getGeneration(oldObj), getGeneration(newObj); oldGen != newGen && oldGen > 0 && newGen > 0 {
422+
diff = &timeline.DiffInfo{
423+
Fields: []timeline.FieldChange{{
424+
Path: "metadata.generation",
425+
OldValue: oldGen,
426+
NewValue: newGen,
427+
}},
428+
Summary: fmt.Sprintf("spec changed (gen %d→%d, fields not specifically tracked)", oldGen, newGen),
429+
}
430+
} else {
431+
timeline.RecordDrop(kind, namespace, name, timeline.DropReasonNoDiff, op)
432+
if DebugEvents {
433+
log.Printf("[DEBUG] No-diff update, skipping: %s/%s/%s", kind, namespace, name)
434+
}
435+
return
436+
}
394437
}
395438
}
396439

0 commit comments

Comments
 (0)