github.com/kubevela/workflow@v0.6.0/pkg/monitor/watcher/workflow.go (about)

     1  /*
     2  Copyright 2022 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package watcher
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"k8s.io/client-go/tools/cache"
    27  	"k8s.io/klog/v2"
    28  	ctrlcache "sigs.k8s.io/controller-runtime/pkg/cache"
    29  
    30  	"github.com/kubevela/workflow/api/v1alpha1"
    31  	"github.com/kubevela/workflow/pkg/monitor/metrics"
    32  )
    33  
    34  type workflowRunMetricsWatcher struct {
    35  	mu               sync.Mutex
    36  	phaseCounter     map[string]int
    37  	stepPhaseCounter map[string]int
    38  	phaseDirty       map[string]struct{}
    39  	stepPhaseDirty   map[string]struct{}
    40  	informer         ctrlcache.Informer
    41  	stopCh           chan struct{}
    42  }
    43  
    44  func (watcher *workflowRunMetricsWatcher) getRun(obj interface{}) *v1alpha1.WorkflowRun {
    45  	wr := &v1alpha1.WorkflowRun{}
    46  	bs, _ := json.Marshal(obj)
    47  	_ = json.Unmarshal(bs, wr)
    48  	return wr
    49  }
    50  
    51  func (watcher *workflowRunMetricsWatcher) getPhase(phase string) string {
    52  	if phase == "" {
    53  		return "-"
    54  	}
    55  	return phase
    56  }
    57  
    58  func (watcher *workflowRunMetricsWatcher) inc(wr *v1alpha1.WorkflowRun, delta int) {
    59  	watcher.mu.Lock()
    60  	defer watcher.mu.Unlock()
    61  	phase := watcher.getPhase(string(wr.Status.Phase))
    62  	watcher.phaseCounter[phase] += delta
    63  	watcher.phaseDirty[phase] = struct{}{}
    64  	for _, step := range wr.Status.Steps {
    65  		stepPhase := watcher.getPhase(string(step.Phase))
    66  		key := fmt.Sprintf("%s/%s#%s", step.Type, stepPhase, step.Reason)
    67  		watcher.stepPhaseCounter[key] += delta
    68  		watcher.stepPhaseDirty[key] = struct{}{}
    69  	}
    70  }
    71  
    72  func (watcher *workflowRunMetricsWatcher) report() {
    73  	watcher.mu.Lock()
    74  	defer watcher.mu.Unlock()
    75  	for phase := range watcher.phaseDirty {
    76  		metrics.WorkflowRunPhaseCounter.WithLabelValues(phase).Set(float64(watcher.phaseCounter[phase]))
    77  	}
    78  	for stepPhase := range watcher.stepPhaseDirty {
    79  		metrics.WorkflowRunStepPhaseGauge.WithLabelValues(strings.Split(stepPhase, "/")[:2]...).Set(float64(watcher.stepPhaseCounter[stepPhase]))
    80  	}
    81  	watcher.phaseDirty = map[string]struct{}{}
    82  	watcher.stepPhaseDirty = map[string]struct{}{}
    83  }
    84  
    85  func (watcher *workflowRunMetricsWatcher) run() {
    86  	go func() {
    87  		for {
    88  			select {
    89  			case <-watcher.stopCh:
    90  				return
    91  			default:
    92  				time.Sleep(time.Second)
    93  				watcher.report()
    94  			}
    95  		}
    96  	}()
    97  }
    98  
    99  // StartWorkflowRunMetricsWatcher start metrics watcher for reporting workflow run stats
   100  func StartWorkflowRunMetricsWatcher(informer ctrlcache.Informer) {
   101  	watcher := &workflowRunMetricsWatcher{
   102  		phaseCounter:     map[string]int{},
   103  		stepPhaseCounter: map[string]int{},
   104  		phaseDirty:       map[string]struct{}{},
   105  		stepPhaseDirty:   map[string]struct{}{},
   106  		informer:         informer,
   107  		stopCh:           make(chan struct{}, 1),
   108  	}
   109  	_, err := watcher.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
   110  		AddFunc: func(obj interface{}) {
   111  			wr := watcher.getRun(obj)
   112  			watcher.inc(wr, 1)
   113  		},
   114  		UpdateFunc: func(oldObj, obj interface{}) {
   115  			oldWr := watcher.getRun(oldObj)
   116  			wr := watcher.getRun(obj)
   117  			watcher.inc(oldWr, -1)
   118  			watcher.inc(wr, 1)
   119  		},
   120  		DeleteFunc: func(obj interface{}) {
   121  			wr := watcher.getRun(obj)
   122  			watcher.inc(wr, -1)
   123  		},
   124  	})
   125  	if err != nil {
   126  		klog.ErrorS(err, "failed to add event handler to workflow metrics watcher")
   127  	}
   128  	watcher.run()
   129  }