Skip to content

[BUG] Gauge dockerupdateguard.runtime_containers counts removed containers that still have snapshots #294

Open

Description

@LarsLaskowski

Summary

The inventory gauge dockerupdateguard.runtime_containers counts every distinct container (per Docker instance and container ID) that has any stored snapshot. Snapshots of containers that have since been removed or recreated stay in the database until cleanup deletes them (default 30 days), so the gauge drifts far above the number of containers the UI shows and that actually exist. It should count only the currently known containers — the same set the Runtime Containers page shows, based on the latest runtime scan.

Area

  • docs/areas/telemetry.md (added in [Docs] Describe the current behavior by area #263) — Metrics, inventory gauges, Open question 2
  • docs/areas/docker-instances.md, docs/areas/web-ui.md (definition of currently known containers; informational)
  • Security area: None

Current behavior

src/DockerUpdateGuard/ApplicationTelemetry.cs:142-149 (inside RefreshInventoryMetricsAsync, :135-171):

_runtimeContainers = await dbContext.ContainerSnapshots
                                    .GroupBy(entity => new { entity.DockerInstanceId, entity.ContainerId })
                                    .LongCountAsync(cancellationToken)
                                    .ConfigureAwait(false);

The gauge is registered at :94 and refreshed at startup, after every observed-image scan, runtime-container scan, vulnerability refresh and cleanup pass.

The UI defines "currently known containers" differently — src/DockerUpdateGuard/UI/ApplicationViewService.cs:1083-… (GetLatestContainerSnapshotsAsync): per Docker instance, the snapshots of the most recent RuntimeContainer scan run that has snapshots; for an instance without any runtime scan, the latest snapshot of each container. The live-image set uses the same rule (src/DockerUpdateGuard.Data/Queries/LiveImageInventoryQueryService.cs:41-82). Container discovery lists all containers including stopped ones (Docker/DockerInstanceClient.cs:996, containers/json?all=1), so a stopped container that still exists remains "currently known"; a removed one does not.

Snapshots older than RetainScanRunsDays are removed only by cleanup and only when no active update finding or container action refers to them (Images/ScanCleanupBackgroundService.cs:231-236).

Scenario: one Docker instance runs 10 containers; Compose recreates them on each deploy (new container IDs), twice a week. After 30 days the Runtime Containers page and the dashboard show 10 containers, while the gauge reports about 10 × (1 + 8) ≈ 90. An alert on "container count dropped/grew" is meaningless.

Expected behavior

  • dockerupdateguard.runtime_containers equals the number of currently known runtime containers: per Docker instance, the containers of the latest runtime container scan (the same scan the Runtime Containers page uses); for an instance that has no runtime scan yet, the latest snapshot per container.
  • The gauge value equals the runtime container count shown on the dashboard and the Runtime Containers page for the same database state.
  • Refresh points and semantics of the other gauges stay unchanged.

Acceptance criteria

  • With snapshots of an older runtime scan (containers A, B, C) and a newer runtime scan (containers A, D) for one instance, the gauge reports 2.
  • With two instances, one with runtime scans and one with only non-runtime snapshots, the gauge combines both rules exactly like GetLatestContainerSnapshotsAsync.
  • With no snapshots, the gauge reports 0.
  • Unit tests (new ApplicationTelemetryTests.cs in src/Tests/DockerUpdateGuard.Tests, EF Core InMemory), e.g. ApplicationTelemetryRefreshInventoryMetricsCountsOnlyLatestRuntimeScanContainers, ApplicationTelemetryRefreshInventoryMetricsInstanceWithoutRuntimeScanUsesLatestSnapshotPerContainer, ApplicationTelemetryRefreshInventoryMetricsNoSnapshotsReportsZero. Read the gauge with a MeterListener (or an internal accessor) — assertions with messages.
  • A test proves the gauge and the UI count agree for the same data (shared query, see below).
  • Coverage gate passes.
  • The area document docs/areas/telemetry.md describes the new gauge definition and its Known gaps entry for this issue is removed.

Implementation notes

  • Recommended: move the "currently known containers" rule into one shared query in the data layer, next to LiveImageInventoryQueryService (e.g. a method on ILiveImageInventoryQueryService or a new IRuntimeContainerInventoryQueryService returning the current (DockerInstanceId, ContainerId) keys or their count), and use it from ApplicationTelemetry.RefreshInventoryMetricsAsync, ApplicationViewService.GetLatestContainerSnapshotsAsync and LiveImageInventoryQueryService.GetRunningImageVersionIdsAsync, so the three definitions cannot drift apart again.
  • RefreshInventoryMetricsAsync takes the DbContext as a parameter and is called from several orchestrators and cleanup; keep that signature or pass the query service — both are acceptable, keep the call sites consistent.
  • Performance: the gauge is refreshed after every scan run. Prefer a query that only projects keys (no Includes) instead of loading full snapshots; do not make F-026 (P3) — ResourceStatisticsCollector materializes the instance's entire sample history per cycle #80/F-027 (P3) — ScanCleanupBackgroundService loads all rows to be deleted into memory before RemoveRange #81 worse.
  • Must not change: metric name, unit (none), type (observable gauge), the fact that observing a gauge never queries the database, the refresh points.
  • No configuration keys, no migration.

Related


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions