Add metrics and progress report capabilities for deferred items seperate from other statuses

This commit is contained in:
Peter Wilhelm 2024-01-07 18:04:47 -06:00
parent 48bfaef350
commit 3c0441b94c
10 changed files with 84 additions and 43 deletions

View file

@ -10,19 +10,21 @@ var metrics *Metrics
// Metric is the data points of a single scan // Metric is the data points of a single scan
type Metric struct { type Metric struct {
Scanned int Scanned int
Updated int Updated int
Failed int Deferred int
Failed int
} }
// Metrics is the handler processing all individual scan metrics // Metrics is the handler processing all individual scan metrics
type Metrics struct { type Metrics struct {
channel chan *Metric channel chan *Metric
scanned prometheus.Gauge scanned prometheus.Gauge
updated prometheus.Gauge updated prometheus.Gauge
failed prometheus.Gauge deferred prometheus.Gauge
total prometheus.Counter failed prometheus.Gauge
skipped prometheus.Counter total prometheus.Counter
skipped prometheus.Counter
} }
// NewMetric returns a Metric with the counts taken from the appropriate types.Report fields // NewMetric returns a Metric with the counts taken from the appropriate types.Report fields
@ -30,8 +32,9 @@ func NewMetric(report types.Report) *Metric {
return &Metric{ return &Metric{
Scanned: len(report.Scanned()), Scanned: len(report.Scanned()),
// Note: This is for backwards compatibility. ideally, stale containers should be counted separately // Note: This is for backwards compatibility. ideally, stale containers should be counted separately
Updated: len(report.Updated()) + len(report.Stale()), Updated: len(report.Updated()) + len(report.Stale()),
Failed: len(report.Failed()), Deferred: len(report.Deferred()),
Failed: len(report.Failed()),
} }
} }
@ -60,6 +63,10 @@ func Default() *Metrics {
Name: "watchtower_containers_updated", Name: "watchtower_containers_updated",
Help: "Number of containers updated by watchtower during the last scan", Help: "Number of containers updated by watchtower during the last scan",
}), }),
deferred: promauto.NewGauge(prometheus.GaugeOpts{
Name: "watchtower_containers_deferred",
Help: "Number of containers deferred by watchtower during the last scan",
}),
failed: promauto.NewGauge(prometheus.GaugeOpts{ failed: promauto.NewGauge(prometheus.GaugeOpts{
Name: "watchtower_containers_failed", Name: "watchtower_containers_failed",
Help: "Number of containers where update failed during the last scan", Help: "Number of containers where update failed during the last scan",
@ -95,6 +102,7 @@ func (metrics *Metrics) HandleUpdate(channel <-chan *Metric) {
metrics.skipped.Inc() metrics.skipped.Inc()
metrics.scanned.Set(0) metrics.scanned.Set(0)
metrics.updated.Set(0) metrics.updated.Set(0)
metrics.deferred.Set(0)
metrics.failed.Set(0) metrics.failed.Set(0)
continue continue
} }
@ -102,6 +110,7 @@ func (metrics *Metrics) HandleUpdate(channel <-chan *Metric) {
metrics.total.Inc() metrics.total.Inc()
metrics.scanned.Set(float64(change.Scanned)) metrics.scanned.Set(float64(change.Scanned))
metrics.updated.Set(float64(change.Updated)) metrics.updated.Set(float64(change.Updated))
metrics.deferred.Set(float64(change.Deferred))
metrics.failed.Set(float64(change.Failed)) metrics.failed.Set(float64(change.Failed))
} }
} }

View file

@ -23,12 +23,13 @@ func (d Data) MarshalJSON() ([]byte, error) {
var report jsonMap var report jsonMap
if d.Report != nil { if d.Report != nil {
report = jsonMap{ report = jsonMap{
`scanned`: marshalReports(d.Report.Scanned()), `scanned`: marshalReports(d.Report.Scanned()),
`updated`: marshalReports(d.Report.Updated()), `updated`: marshalReports(d.Report.Updated()),
`failed`: marshalReports(d.Report.Failed()), `deferred`: marshalReports(d.Report.Deferred()),
`skipped`: marshalReports(d.Report.Skipped()), `failed`: marshalReports(d.Report.Failed()),
`stale`: marshalReports(d.Report.Stale()), `skipped`: marshalReports(d.Report.Skipped()),
`fresh`: marshalReports(d.Report.Fresh()), `stale`: marshalReports(d.Report.Stale()),
`fresh`: marshalReports(d.Report.Fresh()),
} }
} }

View file

@ -21,6 +21,7 @@ var _ = Describe("JSON template", func() {
], ],
"host": "Mock", "host": "Mock",
"report": { "report": {
"deferred": [],
"failed": [ "failed": [
{ {
"currentImageId": "01d210000000", "currentImageId": "01d210000000",
@ -110,7 +111,7 @@ var _ = Describe("JSON template", func() {
}, },
"title": "Watchtower updates on Mock" "title": "Watchtower updates on Mock"
}` }`
data := mockDataFromStates(s.UpdatedState, s.FreshState, s.FailedState, s.SkippedState, s.UpdatedState) data := mockDataFromStates(s.UpdatedState, s.DeferredState, s.FreshState, s.FailedState, s.SkippedState, s.UpdatedState)
Expect(getTemplatedResult(`json.v1`, false, data)).To(MatchJSON(expected)) Expect(getTemplatedResult(`json.v1`, false, data)).To(MatchJSON(expected))
}) })
}) })

View file

@ -72,6 +72,8 @@ func (pb *previewData) addContainer(c containerStatus) {
pb.report.scanned = append(pb.report.scanned, &c) pb.report.scanned = append(pb.report.scanned, &c)
case UpdatedState: case UpdatedState:
pb.report.updated = append(pb.report.updated, &c) pb.report.updated = append(pb.report.updated, &c)
case DeferredState:
pb.report.deferred = append(pb.report.deferred, &c)
case FailedState: case FailedState:
pb.report.failed = append(pb.report.failed, &c) pb.report.failed = append(pb.report.failed, &c)
case SkippedState: case SkippedState:

View file

@ -10,12 +10,13 @@ import (
type State string type State string
const ( const (
ScannedState State = "scanned" ScannedState State = "scanned"
UpdatedState State = "updated" UpdatedState State = "updated"
FailedState State = "failed" DeferredState State = "deferred"
SkippedState State = "skipped" FailedState State = "failed"
StaleState State = "stale" SkippedState State = "skipped"
FreshState State = "fresh" StaleState State = "stale"
FreshState State = "fresh"
) )
// StatesFromString parses a string of state characters and returns a slice of the corresponding report states // StatesFromString parses a string of state characters and returns a slice of the corresponding report states
@ -27,6 +28,8 @@ func StatesFromString(str string) []State {
states = append(states, ScannedState) states = append(states, ScannedState)
case 'u': case 'u':
states = append(states, UpdatedState) states = append(states, UpdatedState)
case 'd':
states = append(states, DeferredState)
case 'e': case 'e':
states = append(states, FailedState) states = append(states, FailedState)
case 'k': case 'k':
@ -43,12 +46,13 @@ func StatesFromString(str string) []State {
} }
type report struct { type report struct {
scanned []types.ContainerReport scanned []types.ContainerReport
updated []types.ContainerReport updated []types.ContainerReport
failed []types.ContainerReport deferred []types.ContainerReport
skipped []types.ContainerReport failed []types.ContainerReport
stale []types.ContainerReport skipped []types.ContainerReport
fresh []types.ContainerReport stale []types.ContainerReport
fresh []types.ContainerReport
} }
func (r *report) Scanned() []types.ContainerReport { func (r *report) Scanned() []types.ContainerReport {
@ -57,6 +61,9 @@ func (r *report) Scanned() []types.ContainerReport {
func (r *report) Updated() []types.ContainerReport { func (r *report) Updated() []types.ContainerReport {
return r.updated return r.updated
} }
func (r *report) Deferred() []types.ContainerReport {
return r.deferred
}
func (r *report) Failed() []types.ContainerReport { func (r *report) Failed() []types.ContainerReport {
return r.failed return r.failed
} }
@ -87,6 +94,7 @@ func (r *report) All() []types.ContainerReport {
} }
appendUnique(r.updated) appendUnique(r.updated)
appendUnique(r.deferred)
appendUnique(r.failed) appendUnique(r.failed)
appendUnique(r.skipped) appendUnique(r.skipped)
appendUnique(r.stale) appendUnique(r.stale)

View file

@ -12,6 +12,7 @@ const (
SkippedState SkippedState
ScannedState ScannedState
UpdatedState UpdatedState
DeferredState
FailedState FailedState
FreshState FreshState
StaleState StaleState
@ -70,6 +71,8 @@ func (u *ContainerStatus) State() string {
return "Scanned" return "Scanned"
case UpdatedState: case UpdatedState:
return "Updated" return "Updated"
case DeferredState:
return "Deferred"
case FailedState: case FailedState:
return "Failed" return "Failed"
case FreshState: case FreshState:

View file

@ -50,6 +50,11 @@ func (m Progress) MarkForUpdate(containerID types.ContainerID) {
m[containerID].state = UpdatedState m[containerID].state = UpdatedState
} }
// MarkForUpdate marks the container identified by containerID for deferral
func (m Progress) MarkDeferred(containerID types.ContainerID) {
m[containerID].state = DeferredState
}
// Report creates a new Report from a Progress instance // Report creates a new Report from a Progress instance
func (m Progress) Report() types.Report { func (m Progress) Report() types.Report {
return NewReport(m) return NewReport(m)

View file

@ -7,12 +7,13 @@ import (
) )
type report struct { type report struct {
scanned []types.ContainerReport scanned []types.ContainerReport
updated []types.ContainerReport updated []types.ContainerReport
failed []types.ContainerReport deferred []types.ContainerReport
skipped []types.ContainerReport failed []types.ContainerReport
stale []types.ContainerReport skipped []types.ContainerReport
fresh []types.ContainerReport stale []types.ContainerReport
fresh []types.ContainerReport
} }
func (r *report) Scanned() []types.ContainerReport { func (r *report) Scanned() []types.ContainerReport {
@ -21,6 +22,9 @@ func (r *report) Scanned() []types.ContainerReport {
func (r *report) Updated() []types.ContainerReport { func (r *report) Updated() []types.ContainerReport {
return r.updated return r.updated
} }
func (r *report) Deferred() []types.ContainerReport {
return r.deferred
}
func (r *report) Failed() []types.ContainerReport { func (r *report) Failed() []types.ContainerReport {
return r.failed return r.failed
} }
@ -50,6 +54,7 @@ func (r *report) All() []types.ContainerReport {
} }
appendUnique(r.updated) appendUnique(r.updated)
appendUnique(r.deferred)
appendUnique(r.failed) appendUnique(r.failed)
appendUnique(r.skipped) appendUnique(r.skipped)
appendUnique(r.stale) appendUnique(r.stale)
@ -64,12 +69,13 @@ func (r *report) All() []types.ContainerReport {
// NewReport creates a types.Report from the supplied Progress // NewReport creates a types.Report from the supplied Progress
func NewReport(progress Progress) types.Report { func NewReport(progress Progress) types.Report {
report := &report{ report := &report{
scanned: []types.ContainerReport{}, scanned: []types.ContainerReport{},
updated: []types.ContainerReport{}, updated: []types.ContainerReport{},
failed: []types.ContainerReport{}, deferred: []types.ContainerReport{},
skipped: []types.ContainerReport{}, failed: []types.ContainerReport{},
stale: []types.ContainerReport{}, skipped: []types.ContainerReport{},
fresh: []types.ContainerReport{}, stale: []types.ContainerReport{},
fresh: []types.ContainerReport{},
} }
for _, update := range progress { for _, update := range progress {
@ -88,9 +94,13 @@ func NewReport(progress Progress) types.Report {
switch update.state { switch update.state {
case UpdatedState: case UpdatedState:
report.updated = append(report.updated, update) report.updated = append(report.updated, update)
case DeferredState:
report.deferred = append(report.deferred, update)
case FailedState: case FailedState:
report.failed = append(report.failed, update) report.failed = append(report.failed, update)
default: default:
// TODO: should this be changed to something lke UnknownState since it shouldn't be possible for a container
// to be stale but its state to not be either UpdatedState, DeferredState, or FailedState?
update.state = StaleState update.state = StaleState
report.stale = append(report.stale, update) report.stale = append(report.stale, update)
} }
@ -98,6 +108,7 @@ func NewReport(progress Progress) types.Report {
sort.Sort(sortableContainers(report.scanned)) sort.Sort(sortableContainers(report.scanned))
sort.Sort(sortableContainers(report.updated)) sort.Sort(sortableContainers(report.updated))
sort.Sort(sortableContainers(report.deferred))
sort.Sort(sortableContainers(report.failed)) sort.Sort(sortableContainers(report.failed))
sort.Sort(sortableContainers(report.skipped)) sort.Sort(sortableContainers(report.skipped))
sort.Sort(sortableContainers(report.stale)) sort.Sort(sortableContainers(report.stale))

View file

@ -4,6 +4,7 @@ package types
type Report interface { type Report interface {
Scanned() []ContainerReport Scanned() []ContainerReport
Updated() []ContainerReport Updated() []ContainerReport
Deferred() []ContainerReport
Failed() []ContainerReport Failed() []ContainerReport
Skipped() []ContainerReport Skipped() []ContainerReport
Stale() []ContainerReport Stale() []ContainerReport

View file

@ -18,7 +18,7 @@ func main() {
var states string var states string
var entries string var entries string
flag.StringVar(&states, "states", "cccuuueeekkktttfff", "sCanned, Updated, failEd, sKipped, sTale, Fresh") flag.StringVar(&states, "states", "cccuuudddeeekkktttfff", "sCanned, Updated, Deferred, failEd, sKipped, sTale, Fresh")
flag.StringVar(&entries, "entries", "ewwiiidddd", "Fatal,Error,Warn,Info,Debug,Trace") flag.StringVar(&entries, "entries", "ewwiiidddd", "Fatal,Error,Warn,Info,Debug,Trace")
flag.Parse() flag.Parse()