From 8d9c21b4f74b56d70b607595c141b670ed3d0872 Mon Sep 17 00:00:00 2001 From: Karl Isenberg Date: Thu, 3 Nov 2022 12:04:03 -0700 Subject: [PATCH] Update applier stats and status for destroy (#259) - Move stats to a sub-package and clean up the interface a bit - Fix a bug in applier status that was double-counting zeroth enums Change-Id: I10d93b1bd057f409e4d9726bfc7ce156ea633bf9 --- pkg/applier/kpt_applier.go | 29 +-- pkg/applier/kpt_applier_test.go | 43 ++--- pkg/applier/stats.go | 185 ------------------ pkg/applier/stats/stats.go | 261 ++++++++++++++++++++++++++ pkg/applier/{ => stats}/stats_test.go | 56 +++--- pkg/applier/status.go | 14 +- pkg/applier/status_test.go | 52 +++-- 7 files changed, 374 insertions(+), 266 deletions(-) delete mode 100644 pkg/applier/stats.go create mode 100644 pkg/applier/stats/stats.go rename pkg/applier/{ => stats}/stats_test.go (74%) diff --git a/pkg/applier/kpt_applier.go b/pkg/applier/kpt_applier.go index 0e7fbc801f..51e4f93517 100644 --- a/pkg/applier/kpt_applier.go +++ b/pkg/applier/kpt_applier.go @@ -29,6 +29,7 @@ import ( "k8s.io/klog/v2" "kpt.dev/configsync/pkg/api/configmanagement" "kpt.dev/configsync/pkg/api/configsync" + "kpt.dev/configsync/pkg/applier/stats" "kpt.dev/configsync/pkg/core" "kpt.dev/configsync/pkg/declared" "kpt.dev/configsync/pkg/kinds" @@ -180,10 +181,10 @@ func wrapInventoryObj(obj *unstructured.Unstructured) (*live.InventoryResourceGr return inv, nil } -func processApplyEvent(ctx context.Context, e event.ApplyEvent, stats *applyEventStats, objectStatusMap ObjectStatusMap, unknownTypeResources map[core.ID]struct{}) status.Error { +func processApplyEvent(ctx context.Context, e event.ApplyEvent, s *stats.ApplyEventStats, objectStatusMap ObjectStatusMap, unknownTypeResources map[core.ID]struct{}) status.Error { id := idFrom(e.Identifier) klog.V(4).Infof("apply %v for object: %v", e.Status, id) - stats.EventByOp[e.Status]++ + s.Add(e.Status) objectStatus, ok := objectStatusMap[id] if !ok || objectStatus == nil { @@ -222,13 +223,13 @@ func processApplyEvent(ctx context.Context, e event.ApplyEvent, stats *applyEven } } -func processWaitEvent(e event.WaitEvent, waitStats *waitEventStats, objectStatusMap ObjectStatusMap) error { +func processWaitEvent(e event.WaitEvent, s *stats.WaitEventStats, objectStatusMap ObjectStatusMap) error { id := idFrom(e.Identifier) if e.Status != event.ReconcilePending { // Don't log pending. It's noisy and only fires in certain conditions. klog.V(4).Infof("Reconcile %v: %v", e.Status, id) } - waitStats.EventByOp[e.Status]++ + s.Add(e.Status) objectStatus, ok := objectStatusMap[id] if !ok || objectStatus == nil { @@ -277,10 +278,10 @@ func handleApplySkippedEvent(obj *unstructured.Unstructured, id core.ID, err err return SkipErrorForResource(err, id, actuation.ActuationStrategyApply) } -func processPruneEvent(ctx context.Context, e event.PruneEvent, stats *pruneEventStats, objectStatusMap ObjectStatusMap, cs *clientSet) status.Error { +func processPruneEvent(ctx context.Context, e event.PruneEvent, s *stats.PruneEventStats, objectStatusMap ObjectStatusMap, cs *clientSet) status.Error { id := idFrom(e.Identifier) klog.V(4).Infof("prune %v for object: %v", e.Status, id) - stats.EventByOp[e.Status]++ + s.Add(e.Status) objectStatus, ok := objectStatusMap[id] if !ok || objectStatus == nil { @@ -401,7 +402,7 @@ func (a *Applier) sync(ctx context.Context, objs []client.Object) (map[schema.Gr } a.checkInventoryObjectSize(ctx, cs.client) - stats := newApplyStats() + s := stats.NewSyncStats() objStatusMap := make(ObjectStatusMap) // disabledObjs are objects for which the management are disabled // through annotation. @@ -413,7 +414,7 @@ func (a *Applier) sync(ctx context.Context, objs []client.Object) (map[schema.Gr a.errs = status.Append(a.errs, err) return nil, a.errs } - stats.DisableObjs = DisabledObjStats{ + s.DisableObjs = &stats.DisabledObjStats{ Total: uint64(len(disabledObjs)), Succeeded: disabledCount, } @@ -465,7 +466,7 @@ func (a *Applier) sync(ctx context.Context, objs []client.Object) (map[schema.Gr } else { a.errs = status.Append(a.errs, Error(e.ErrorEvent.Err)) } - stats.ErrorTypeEvents++ + s.ErrorTypeEvents++ case event.WaitType: // Log WaitEvent at the verbose level of 4 due to the number of WaitEvent. // For every object which is skipped to apply/prune, there will be one ReconcileSkipped WaitEvent. @@ -474,7 +475,7 @@ func (a *Applier) sync(ctx context.Context, objs []client.Object) (map[schema.Gr // a reconciled object may become pending before a wait task times out. // Record the objs that have been reconciled. klog.V(4).Info(e.WaitEvent) - a.errs = status.Append(a.errs, processWaitEvent(e.WaitEvent, &stats.WaitEvent, objStatusMap)) + a.errs = status.Append(a.errs, processWaitEvent(e.WaitEvent, s.WaitEvent, objStatusMap)) case event.ApplyType: logEvent := event.ApplyEvent{ GroupName: e.ApplyEvent.GroupName, @@ -484,7 +485,7 @@ func (a *Applier) sync(ctx context.Context, objs []client.Object) (map[schema.Gr Error: e.ApplyEvent.Error, } klog.V(4).Info(logEvent) - a.errs = status.Append(a.errs, processApplyEvent(ctx, e.ApplyEvent, &stats.ApplyEvent, objStatusMap, unknownTypeResources)) + a.errs = status.Append(a.errs, processApplyEvent(ctx, e.ApplyEvent, s.ApplyEvent, objStatusMap, unknownTypeResources)) case event.PruneType: logEvent := event.PruneEvent{ GroupName: e.PruneEvent.GroupName, @@ -494,7 +495,7 @@ func (a *Applier) sync(ctx context.Context, objs []client.Object) (map[schema.Gr Error: e.PruneEvent.Error, } klog.V(4).Info(logEvent) - a.errs = status.Append(a.errs, processPruneEvent(ctx, e.PruneEvent, &stats.PruneEvent, objStatusMap, cs)) + a.errs = status.Append(a.errs, processPruneEvent(ctx, e.PruneEvent, s.PruneEvent, objStatusMap, cs)) default: klog.V(4).Infof("skipped %v event", e.Type) } @@ -512,10 +513,10 @@ func (a *Applier) sync(ctx context.Context, objs []client.Object) (map[schema.Gr klog.V(4).Infof("all resources are up to date.") } - if stats.empty() { + if s.Empty() { klog.V(4).Infof("The applier made no new progress") } else { - klog.Infof("The applier made new progress: %s.", stats.string()) + klog.Infof("The applier made new progress: %s", s.String()) objStatusMap.Log(klog.V(0)) } return gvks, a.errs diff --git a/pkg/applier/kpt_applier_test.go b/pkg/applier/kpt_applier_test.go index 1a65b96576..ab8cb0944d 100644 --- a/pkg/applier/kpt_applier_test.go +++ b/pkg/applier/kpt_applier_test.go @@ -30,6 +30,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/cli-runtime/pkg/genericclioptions" + "kpt.dev/configsync/pkg/applier/stats" "kpt.dev/configsync/pkg/core" "kpt.dev/configsync/pkg/kinds" "kpt.dev/configsync/pkg/status" @@ -418,21 +419,21 @@ func TestProcessApplyEvent(t *testing.T) { testID := object.UnstructuredToObjMetadata(newTestObj()) ctx := context.Background() - status := newApplyStats() + s := stats.NewSyncStats() objStatusMap := make(ObjectStatusMap) unknownTypeResources := make(map[core.ID]struct{}) - err := processApplyEvent(ctx, formApplyEvent(event.ApplyFailed, &deploymentID, fmt.Errorf("test error")).ApplyEvent, &status.ApplyEvent, objStatusMap, unknownTypeResources) + err := processApplyEvent(ctx, formApplyEvent(event.ApplyFailed, &deploymentID, fmt.Errorf("test error")).ApplyEvent, s.ApplyEvent, objStatusMap, unknownTypeResources) expectedError := ErrorForResource(fmt.Errorf("test error"), idFrom(deploymentID)) testutil.AssertEqual(t, expectedError, err, "expected processPruneEvent to error on apply %s", event.ApplyFailed) - err = processApplyEvent(ctx, formApplyEvent(event.ApplySuccessful, &testID, nil).ApplyEvent, &status.ApplyEvent, objStatusMap, unknownTypeResources) + err = processApplyEvent(ctx, formApplyEvent(event.ApplySuccessful, &testID, nil).ApplyEvent, s.ApplyEvent, objStatusMap, unknownTypeResources) assert.Nil(t, err, "expected processApplyEvent NOT to error on apply %s", event.ApplySuccessful) - expectedApplyStatus := newApplyStats() - expectedApplyStatus.ApplyEvent.EventByOp[event.ApplyFailed] = 1 - expectedApplyStatus.ApplyEvent.EventByOp[event.ApplySuccessful] = 1 - testutil.AssertEqual(t, expectedApplyStatus, status, "expected event stats to match") + expectedApplyStatus := stats.NewSyncStats() + expectedApplyStatus.ApplyEvent.Add(event.ApplyFailed) + expectedApplyStatus.ApplyEvent.Add(event.ApplySuccessful) + testutil.AssertEqual(t, expectedApplyStatus, s, "expected event stats to match") expectedObjStatusMap := ObjectStatusMap{ idFrom(deploymentID): { @@ -456,21 +457,21 @@ func TestProcessPruneEvent(t *testing.T) { testID := object.UnstructuredToObjMetadata(newTestObj()) ctx := context.Background() - status := newApplyStats() + s := stats.NewSyncStats() objStatusMap := make(ObjectStatusMap) cs := &clientSet{} - err := processPruneEvent(ctx, formPruneEvent(event.PruneFailed, &deploymentID, fmt.Errorf("test error")).PruneEvent, &status.PruneEvent, objStatusMap, cs) + err := processPruneEvent(ctx, formPruneEvent(event.PruneFailed, &deploymentID, fmt.Errorf("test error")).PruneEvent, s.PruneEvent, objStatusMap, cs) expectedError := ErrorForResource(fmt.Errorf("test error"), idFrom(deploymentID)) testutil.AssertEqual(t, expectedError, err, "expected processPruneEvent to error on prune %s", event.PruneFailed) - err = processPruneEvent(ctx, formPruneEvent(event.PruneSuccessful, &testID, nil).PruneEvent, &status.PruneEvent, objStatusMap, cs) + err = processPruneEvent(ctx, formPruneEvent(event.PruneSuccessful, &testID, nil).PruneEvent, s.PruneEvent, objStatusMap, cs) assert.Nil(t, err, "expected processPruneEvent NOT to error on prune %s", event.PruneSuccessful) - expectedApplyStatus := newApplyStats() - expectedApplyStatus.PruneEvent.EventByOp[event.PruneFailed] = 1 - expectedApplyStatus.PruneEvent.EventByOp[event.PruneSuccessful] = 1 - testutil.AssertEqual(t, expectedApplyStatus, status, "expected event stats to match") + expectedApplyStatus := stats.NewSyncStats() + expectedApplyStatus.PruneEvent.Add(event.PruneFailed) + expectedApplyStatus.PruneEvent.Add(event.PruneSuccessful) + testutil.AssertEqual(t, expectedApplyStatus, s, "expected event stats to match") expectedObjStatusMap := ObjectStatusMap{ idFrom(deploymentID): { @@ -494,19 +495,19 @@ func TestProcessWaitEvent(t *testing.T) { deploymentID := object.UnstructuredToObjMetadata(newDeploymentObj()) testID := object.UnstructuredToObjMetadata(newTestObj()) - status := newApplyStats() + s := stats.NewSyncStats() objStatusMap := make(ObjectStatusMap) - err := processWaitEvent(formWaitEvent(event.ReconcileFailed, &deploymentID).WaitEvent, &status.WaitEvent, objStatusMap) + err := processWaitEvent(formWaitEvent(event.ReconcileFailed, &deploymentID).WaitEvent, s.WaitEvent, objStatusMap) assert.Nil(t, err, "expected processWaitEvent NOT to error on reconcile %s", event.ReconcileFailed) - err = processWaitEvent(formWaitEvent(event.ReconcileSuccessful, &testID).WaitEvent, &status.WaitEvent, objStatusMap) + err = processWaitEvent(formWaitEvent(event.ReconcileSuccessful, &testID).WaitEvent, s.WaitEvent, objStatusMap) assert.Nil(t, err, "expected processWaitEvent NOT to error on reconcile %s", event.ReconcileSuccessful) - expectedApplyStatus := newApplyStats() - expectedApplyStatus.WaitEvent.EventByOp[event.ReconcileFailed] = 1 - expectedApplyStatus.WaitEvent.EventByOp[event.ReconcileSuccessful] = 1 - testutil.AssertEqual(t, expectedApplyStatus, status, "expected event stats to match") + expectedApplyStatus := stats.NewSyncStats() + expectedApplyStatus.WaitEvent.Add(event.ReconcileFailed) + expectedApplyStatus.WaitEvent.Add(event.ReconcileSuccessful) + testutil.AssertEqual(t, expectedApplyStatus, s, "expected event stats to match") expectedObjStatusMap := ObjectStatusMap{ idFrom(deploymentID): { diff --git a/pkg/applier/stats.go b/pkg/applier/stats.go deleted file mode 100644 index 52c0b3e2d5..0000000000 --- a/pkg/applier/stats.go +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package applier - -import ( - "fmt" - "sort" - "strings" - - "sigs.k8s.io/cli-utils/pkg/apply/event" -) - -// pruneEventStats tracks the stats for all the PruneType events -type pruneEventStats struct { - // EventByOp tracks the number of PruneType events including no error by PruneEventOperation - EventByOp map[event.PruneEventStatus]uint64 -} - -func (s pruneEventStats) string() string { - var strs []string - var keys []int - for k := range s.EventByOp { - keys = append(keys, int(k)) - } - sort.Ints(keys) - total := uint64(0) - for _, k := range keys { - op := event.PruneEventStatus(k) - if s.EventByOp[op] > 0 { - total += s.EventByOp[op] - strs = append(strs, fmt.Sprintf("%s: %d", op, s.EventByOp[op])) - } - } - if total == 0 { - return "" - } - return fmt.Sprintf("PruneEvents: %d (%s)", total, strings.Join(strs, ", ")) -} - -func (s pruneEventStats) empty() bool { - return len(s.EventByOp) == 0 -} - -// applyEventStats tracks the stats for all the ApplyType events -type applyEventStats struct { - // EventByOp tracks the number of ApplyType events including no error by ApplyEventOperation - // Possible values: Created, Configured, Unchanged. - EventByOp map[event.ApplyEventStatus]uint64 -} - -func (s applyEventStats) string() string { - var strs []string - var keys []int - for k := range s.EventByOp { - keys = append(keys, int(k)) - } - sort.Ints(keys) - total := uint64(0) - for _, k := range keys { - op := event.ApplyEventStatus(k) - if s.EventByOp[op] > 0 { - total += s.EventByOp[op] - strs = append(strs, fmt.Sprintf("%s: %d", op, s.EventByOp[op])) - } - } - if total == 0 { - return "" - } - return fmt.Sprintf("ApplyEvents: %d (%s)", total, strings.Join(strs, ", ")) -} - -func (s applyEventStats) empty() bool { - return len(s.EventByOp) == 0 -} - -// waitEventStats tracks the stats for all the WaitType events -type waitEventStats struct { - // EventByOp tracks the number of WaitType events including no error by WaitTypeOperation - // Possible values: Pending, Successful, Skipped, Timeout, Failed - EventByOp map[event.WaitEventStatus]uint64 -} - -func (s waitEventStats) string() string { - var strs []string - var keys []int - for k := range s.EventByOp { - keys = append(keys, int(k)) - } - sort.Ints(keys) - total := uint64(0) - for _, k := range keys { - op := event.WaitEventStatus(k) - if s.EventByOp[op] > 0 { - total += s.EventByOp[op] - strs = append(strs, fmt.Sprintf("%s: %d", op, s.EventByOp[op])) - } - } - if total == 0 { - return "" - } - return fmt.Sprintf("WaitEvents: %d (%s)", total, strings.Join(strs, ", ")) -} - -func (s waitEventStats) empty() bool { - return len(s.EventByOp) == 0 -} - -// DisabledObjStats tracks the stats for dsiabled objects -type DisabledObjStats struct { - // Total tracks the number of objects to be disabled - Total uint64 - // Succeeded tracks how many ojbects were disabled successfully - Succeeded uint64 -} - -func (s DisabledObjStats) string() string { - if s.empty() { - return "" - } - return fmt.Sprintf("disabled %d out of %d objects", s.Succeeded, s.Total) -} - -func (s DisabledObjStats) empty() bool { - return s.Total == 0 -} - -// ApplyStats tracks the stats for all the events -type ApplyStats struct { - ApplyEvent applyEventStats - PruneEvent pruneEventStats - WaitEvent waitEventStats - DisableObjs DisabledObjStats - // ErrorTypeEvents tracks the number of ErrorType events - ErrorTypeEvents uint64 -} - -func (s ApplyStats) string() string { - var strs []string - if !s.ApplyEvent.empty() { - strs = append(strs, s.ApplyEvent.string()) - } - if !s.PruneEvent.empty() { - strs = append(strs, s.PruneEvent.string()) - } - if !s.WaitEvent.empty() { - strs = append(strs, s.WaitEvent.string()) - } - if !s.DisableObjs.empty() { - strs = append(strs, s.DisableObjs.string()) - } - if s.ErrorTypeEvents > 0 { - strs = append(strs, fmt.Sprintf("ErrorEvents: %d", s.ErrorTypeEvents)) - } - return strings.Join(strs, ", ") -} - -func (s ApplyStats) empty() bool { - return s.ErrorTypeEvents == 0 && s.PruneEvent.empty() && s.ApplyEvent.empty() && s.WaitEvent.empty() && s.DisableObjs.empty() -} - -func newApplyStats() ApplyStats { - return ApplyStats{ - ApplyEvent: applyEventStats{ - EventByOp: map[event.ApplyEventStatus]uint64{}, - }, - PruneEvent: pruneEventStats{ - EventByOp: map[event.PruneEventStatus]uint64{}, - }, - WaitEvent: waitEventStats{ - EventByOp: map[event.WaitEventStatus]uint64{}, - }, - } -} diff --git a/pkg/applier/stats/stats.go b/pkg/applier/stats/stats.go new file mode 100644 index 0000000000..499bfba718 --- /dev/null +++ b/pkg/applier/stats/stats.go @@ -0,0 +1,261 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package stats + +import ( + "fmt" + "sort" + "strings" + + "sigs.k8s.io/cli-utils/pkg/apply/event" +) + +// PruneEventStats tracks the stats for all the PruneType events +type PruneEventStats struct { + // EventByOp tracks the number of PruneType events including no error by PruneEventOperation + EventByOp map[event.PruneEventStatus]uint64 +} + +// Add records a new event +func (s *PruneEventStats) Add(status event.PruneEventStatus) { + if s.EventByOp == nil { + s.EventByOp = map[event.PruneEventStatus]uint64{} + } + s.EventByOp[status]++ +} + +// String returns the stats as a human readable string. +func (s PruneEventStats) String() string { + var strs []string + var keys []int + for k := range s.EventByOp { + keys = append(keys, int(k)) + } + sort.Ints(keys) + total := uint64(0) + for _, k := range keys { + op := event.PruneEventStatus(k) + if s.EventByOp[op] > 0 { + total += s.EventByOp[op] + strs = append(strs, fmt.Sprintf("%s: %d", op, s.EventByOp[op])) + } + } + if total == 0 { + return "" + } + return fmt.Sprintf("PruneEvents: %d (%s)", total, strings.Join(strs, ", ")) +} + +// Empty returns true if no events were recorded. +func (s *PruneEventStats) Empty() bool { + return s == nil || len(s.EventByOp) == 0 +} + +// DeleteEventStats tracks the stats for all the DeleteType events +type DeleteEventStats struct { + // EventByOp tracks the number of DeleteType events including no error by DeleteEventOperation + EventByOp map[event.DeleteEventStatus]uint64 +} + +// Add records a new event +func (s *DeleteEventStats) Add(status event.DeleteEventStatus) { + if s.EventByOp == nil { + s.EventByOp = map[event.DeleteEventStatus]uint64{} + } + s.EventByOp[status]++ +} + +// String returns the stats as a human readable string. +func (s DeleteEventStats) String() string { + var strs []string + var keys []int + for k := range s.EventByOp { + keys = append(keys, int(k)) + } + sort.Ints(keys) + total := uint64(0) + for _, k := range keys { + op := event.DeleteEventStatus(k) + if s.EventByOp[op] > 0 { + total += s.EventByOp[op] + strs = append(strs, fmt.Sprintf("%s: %d", op, s.EventByOp[op])) + } + } + if total == 0 { + return "" + } + return fmt.Sprintf("DeleteEvents: %d (%s)", total, strings.Join(strs, ", ")) +} + +// Empty returns true if no events were recorded. +func (s *DeleteEventStats) Empty() bool { + return s == nil || len(s.EventByOp) == 0 +} + +// ApplyEventStats tracks the stats for all the ApplyType events +type ApplyEventStats struct { + // EventByOp tracks the number of ApplyType events including no error by ApplyEventOperation + // Possible values: Created, Configured, Unchanged. + EventByOp map[event.ApplyEventStatus]uint64 +} + +// Add records a new event +func (s *ApplyEventStats) Add(status event.ApplyEventStatus) { + if s.EventByOp == nil { + s.EventByOp = map[event.ApplyEventStatus]uint64{} + } + s.EventByOp[status]++ +} + +// String returns the stats as a human readable string. +func (s ApplyEventStats) String() string { + var strs []string + var keys []int + for k := range s.EventByOp { + keys = append(keys, int(k)) + } + sort.Ints(keys) + total := uint64(0) + for _, k := range keys { + op := event.ApplyEventStatus(k) + if s.EventByOp[op] > 0 { + total += s.EventByOp[op] + strs = append(strs, fmt.Sprintf("%s: %d", op, s.EventByOp[op])) + } + } + if total == 0 { + return "" + } + return fmt.Sprintf("ApplyEvents: %d (%s)", total, strings.Join(strs, ", ")) +} + +// Empty returns true if no events were recorded. +func (s *ApplyEventStats) Empty() bool { + return s == nil || len(s.EventByOp) == 0 +} + +// WaitEventStats tracks the stats for all the WaitType events +type WaitEventStats struct { + // EventByOp tracks the number of WaitType events including no error by WaitTypeOperation + // Possible values: Pending, Successful, Skipped, Timeout, Failed + EventByOp map[event.WaitEventStatus]uint64 +} + +// Add records a new event +func (s *WaitEventStats) Add(status event.WaitEventStatus) { + if s.EventByOp == nil { + s.EventByOp = map[event.WaitEventStatus]uint64{} + } + s.EventByOp[status]++ +} + +// String returns the stats as a human readable string. +func (s WaitEventStats) String() string { + var strs []string + var keys []int + for k := range s.EventByOp { + keys = append(keys, int(k)) + } + sort.Ints(keys) + total := uint64(0) + for _, k := range keys { + op := event.WaitEventStatus(k) + if s.EventByOp[op] > 0 { + total += s.EventByOp[op] + strs = append(strs, fmt.Sprintf("%s: %d", op, s.EventByOp[op])) + } + } + if total == 0 { + return "" + } + return fmt.Sprintf("WaitEvents: %d (%s)", total, strings.Join(strs, ", ")) +} + +// Empty returns true if no events were recorded. +func (s *WaitEventStats) Empty() bool { + return s == nil || len(s.EventByOp) == 0 +} + +// DisabledObjStats tracks the stats for dsiabled objects +type DisabledObjStats struct { + // Total tracks the number of objects to be disabled + Total uint64 + // Succeeded tracks how many ojbects were disabled successfully + Succeeded uint64 +} + +// String returns the stats as a human readable String. +func (s DisabledObjStats) String() string { + if s.Empty() { + return "" + } + return fmt.Sprintf("disabled %d out of %d objects", s.Succeeded, s.Total) +} + +// Empty returns true if no events were recorded. +func (s *DisabledObjStats) Empty() bool { + return s == nil || s.Total == 0 +} + +// SyncStats tracks the stats for all the events +type SyncStats struct { + ApplyEvent *ApplyEventStats + PruneEvent *PruneEventStats + DeleteEvent *DeleteEventStats + WaitEvent *WaitEventStats + DisableObjs *DisabledObjStats + // ErrorTypeEvents tracks the number of ErrorType events + ErrorTypeEvents uint64 +} + +// String returns the stats as a human readable string. +func (s SyncStats) String() string { + var strs []string + if !s.ApplyEvent.Empty() { + strs = append(strs, s.ApplyEvent.String()) + } + if !s.PruneEvent.Empty() { + strs = append(strs, s.PruneEvent.String()) + } + if !s.DeleteEvent.Empty() { + strs = append(strs, s.DeleteEvent.String()) + } + if !s.WaitEvent.Empty() { + strs = append(strs, s.WaitEvent.String()) + } + if !s.DisableObjs.Empty() { + strs = append(strs, s.DisableObjs.String()) + } + if s.ErrorTypeEvents > 0 { + strs = append(strs, fmt.Sprintf("ErrorEvents: %d", s.ErrorTypeEvents)) + } + return strings.Join(strs, ", ") +} + +// Empty returns true if no events were recorded. +func (s *SyncStats) Empty() bool { + return s == nil || s.ErrorTypeEvents == 0 && s.PruneEvent.Empty() && s.DeleteEvent.Empty() && s.ApplyEvent.Empty() && s.WaitEvent.Empty() && s.DisableObjs.Empty() +} + +// NewSyncStats constructs a SyncStats with empty event maps. +func NewSyncStats() *SyncStats { + return &SyncStats{ + ApplyEvent: &ApplyEventStats{}, + PruneEvent: &PruneEventStats{}, + DeleteEvent: &DeleteEventStats{}, + WaitEvent: &WaitEventStats{}, + DisableObjs: &DisabledObjStats{}, + } +} diff --git a/pkg/applier/stats_test.go b/pkg/applier/stats/stats_test.go similarity index 74% rename from pkg/applier/stats_test.go rename to pkg/applier/stats/stats_test.go index 567dd465fa..49907142f3 100644 --- a/pkg/applier/stats_test.go +++ b/pkg/applier/stats/stats_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package applier +package stats import ( "testing" @@ -45,12 +45,12 @@ func TestDisabledObjStats(t *testing.T) { } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - if tc.stats.empty() != tc.wantEmpty { - t.Errorf("stats.empty() = %t, wanted %t", tc.stats.empty(), tc.wantEmpty) + if tc.stats.Empty() != tc.wantEmpty { + t.Errorf("stats.empty() = %t, wanted %t", tc.stats.Empty(), tc.wantEmpty) } - if tc.stats.string() != tc.wantString { - t.Errorf("stats.string() = %q, wanted %q", tc.stats.string(), tc.wantString) + if tc.stats.String() != tc.wantString { + t.Errorf("stats.string() = %q, wanted %q", tc.stats.String(), tc.wantString) } }) @@ -60,13 +60,13 @@ func TestDisabledObjStats(t *testing.T) { func TestPruneEventStats(t *testing.T) { testcases := []struct { name string - stats pruneEventStats + stats PruneEventStats wantEmpty bool wantString string }{ { name: "empty pruneEventStats", - stats: pruneEventStats{ + stats: PruneEventStats{ EventByOp: map[event.PruneEventStatus]uint64{}, }, wantEmpty: true, @@ -74,7 +74,7 @@ func TestPruneEventStats(t *testing.T) { }, { name: "non-empty pruneEventStats", - stats: pruneEventStats{ + stats: PruneEventStats{ EventByOp: map[event.PruneEventStatus]uint64{ event.PruneSkipped: 4, event.PruneSuccessful: 0, @@ -87,12 +87,12 @@ func TestPruneEventStats(t *testing.T) { } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - if tc.stats.empty() != tc.wantEmpty { - t.Errorf("stats.empty() = %t, wanted %t", tc.stats.empty(), tc.wantEmpty) + if tc.stats.Empty() != tc.wantEmpty { + t.Errorf("stats.empty() = %t, wanted %t", tc.stats.Empty(), tc.wantEmpty) } - if tc.stats.string() != tc.wantString { - t.Errorf("stats.string() = %q, wanted %q", tc.stats.string(), tc.wantString) + if tc.stats.String() != tc.wantString { + t.Errorf("stats.string() = %q, wanted %q", tc.stats.String(), tc.wantString) } }) @@ -102,13 +102,13 @@ func TestPruneEventStats(t *testing.T) { func TestApplyEventStats(t *testing.T) { testcases := []struct { name string - stats applyEventStats + stats ApplyEventStats wantEmpty bool wantString string }{ { name: "empty applyEventStats", - stats: applyEventStats{ + stats: ApplyEventStats{ EventByOp: map[event.ApplyEventStatus]uint64{}, }, wantEmpty: true, @@ -116,7 +116,7 @@ func TestApplyEventStats(t *testing.T) { }, { name: "non-empty applyEventStats", - stats: applyEventStats{ + stats: ApplyEventStats{ EventByOp: map[event.ApplyEventStatus]uint64{ event.ApplySuccessful: 4, event.ApplySkipped: 2, @@ -129,12 +129,12 @@ func TestApplyEventStats(t *testing.T) { } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - if tc.stats.empty() != tc.wantEmpty { - t.Errorf("stats.empty() = %t, wanted %t", tc.stats.empty(), tc.wantEmpty) + if tc.stats.Empty() != tc.wantEmpty { + t.Errorf("stats.empty() = %t, wanted %t", tc.stats.Empty(), tc.wantEmpty) } - if tc.stats.string() != tc.wantString { - t.Errorf("stats.string() = %q, wanted %q", tc.stats.string(), tc.wantString) + if tc.stats.String() != tc.wantString { + t.Errorf("stats.string() = %q, wanted %q", tc.stats.String(), tc.wantString) } }) @@ -144,26 +144,26 @@ func TestApplyEventStats(t *testing.T) { func TestApplyStats(t *testing.T) { testcases := []struct { name string - stats ApplyStats + stats *SyncStats wantEmpty bool wantString string }{ { name: "empty applyStats", - stats: newApplyStats(), + stats: NewSyncStats(), wantEmpty: true, wantString: "", }, { name: "non-empty applyStats", - stats: ApplyStats{ - ApplyEvent: applyEventStats{ + stats: &SyncStats{ + ApplyEvent: &ApplyEventStats{ EventByOp: map[event.ApplyEventStatus]uint64{ event.ApplySuccessful: 1, event.ApplySkipped: 2, }, }, - PruneEvent: pruneEventStats{ + PruneEvent: &PruneEventStats{ EventByOp: map[event.PruneEventStatus]uint64{ event.PruneFailed: 3, }, @@ -176,12 +176,12 @@ func TestApplyStats(t *testing.T) { } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - if tc.stats.empty() != tc.wantEmpty { - t.Errorf("stats.empty() = %t, wanted %t", tc.stats.empty(), tc.wantEmpty) + if tc.stats.Empty() != tc.wantEmpty { + t.Errorf("stats.empty() = %t, wanted %t", tc.stats.Empty(), tc.wantEmpty) } - if tc.stats.string() != tc.wantString { - t.Errorf("stats.string() = %q, wanted %q", tc.stats.string(), tc.wantString) + if tc.stats.String() != tc.wantString { + t.Errorf("stats.string() = %q, wanted %q", tc.stats.String(), tc.wantString) } }) diff --git a/pkg/applier/status.go b/pkg/applier/status.go index 265a8f0038..b7c1417991 100644 --- a/pkg/applier/status.go +++ b/pkg/applier/status.go @@ -96,9 +96,9 @@ func (m ObjectStatusMap) Log(logger infofLogger) { writeStatus(&b, status, ids) } if count == 0 { - logger.Infof("Prune Actuations (Total: %d)", count) + logger.Infof("Delete Actuations (Total: %d)", count) } else { - logger.Infof("Prune Actuations (Total: %d):\n%s", count, b.String()) + logger.Infof("Delete Actuations (Total: %d):\n%s", count, b.String()) } count = 0 @@ -112,9 +112,9 @@ func (m ObjectStatusMap) Log(logger infofLogger) { writeStatus(&b, status, ids) } if count == 0 { - logger.Infof("Prune Reconciles (Total: %d)", count) + logger.Infof("Delete Reconciles (Total: %d)", count) } else { - logger.Infof("Prune Reconciles (Total: %d):\n%s", count, b.String()) + logger.Infof("Delete Reconciles (Total: %d):\n%s", count, b.String()) } } @@ -138,13 +138,13 @@ func (m ObjectStatusMap) Filter( if status == nil { continue } - if strategy > 0 && status.Strategy != strategy { + if strategy >= 0 && status.Strategy != strategy { continue } - if actuation > 0 && status.Actuation != actuation { + if actuation >= 0 && status.Actuation != actuation { continue } - if reconcile > 0 && status.Reconcile != reconcile { + if reconcile >= 0 && status.Reconcile != reconcile { continue } ids = append(ids, id) diff --git a/pkg/applier/status_test.go b/pkg/applier/status_test.go index 3691f1fde5..5e9ee154cb 100644 --- a/pkg/applier/status_test.go +++ b/pkg/applier/status_test.go @@ -165,7 +165,7 @@ func TestObjectStatusMapLog(t *testing.T) { expected []string }{ { - name: "log status", + name: "log applier status", input: ObjectStatusMap{ idFrom(deploymentID): { Strategy: actuation.ActuationStrategyApply, @@ -179,32 +179,62 @@ func TestObjectStatusMapLog(t *testing.T) { }, }, expected: []string{ - "Apply Actuations (Total: 2):\n" + + "Apply Actuations (Total: 1):\n" + "Skipped (0),\n" + - "Succeeded (2): [apps/namespaces/test-namespace/Deployment/random-name, configsync.test/namespaces/test-namespace/Test/random-name],\n" + + "Succeeded (1): [apps/namespaces/test-namespace/Deployment/random-name],\n" + "Failed (0)", - "Apply Reconciles (Total: 2):\n" + + "Apply Reconciles (Total: 1):\n" + "Skipped (0),\n" + - "Succeeded (1): [configsync.test/namespaces/test-namespace/Test/random-name],\n" + + "Succeeded (0),\n" + "Failed (1): [apps/namespaces/test-namespace/Deployment/random-name],\n" + "Timeout (0)", - "Prune Actuations (Total: 1):\n" + + "Delete Actuations (Total: 1):\n" + "Skipped (0),\n" + "Succeeded (1): [configsync.test/namespaces/test-namespace/Test/random-name],\n" + "Failed (0)", - "Prune Reconciles (Total: 1):\n" + + "Delete Reconciles (Total: 1):\n" + "Skipped (0),\n" + "Succeeded (1): [configsync.test/namespaces/test-namespace/Test/random-name],\n" + "Failed (0),\n" + "Timeout (0)", }, }, + { + name: "log destroyer status", + input: ObjectStatusMap{ + idFrom(deploymentID): { + Strategy: actuation.ActuationStrategyDelete, + Actuation: actuation.ActuationSucceeded, + Reconcile: actuation.ReconcileFailed, + }, + idFrom(testID): { + Strategy: actuation.ActuationStrategyDelete, + Actuation: actuation.ActuationSucceeded, + Reconcile: actuation.ReconcileSucceeded, + }, + }, + expected: []string{ + "Apply Actuations (Total: 0)", + "Apply Reconciles (Total: 0)", + "Delete Actuations (Total: 2):\n" + + "Skipped (0),\n" + + "Succeeded (2): [apps/namespaces/test-namespace/Deployment/random-name, configsync.test/namespaces/test-namespace/Test/random-name],\n" + + "Failed (0)", + "Delete Reconciles (Total: 2):\n" + + "Skipped (0),\n" + + "Succeeded (1): [configsync.test/namespaces/test-namespace/Test/random-name],\n" + + "Failed (1): [apps/namespaces/test-namespace/Deployment/random-name],\n" + + "Timeout (0)", + }, + }, } for _, tc := range testcases { - logger := &fakeLogger{} - logger.Enable() - tc.input.Log(logger) - testutil.AssertEqual(t, tc.expected, logger.Logs, "[%s] unexpected log messages", tc.name) + t.Run(tc.name, func(t *testing.T) { + logger := &fakeLogger{} + logger.Enable() + tc.input.Log(logger) + testutil.AssertEqual(t, tc.expected, logger.Logs, "unexpected log messages") + }) } }