k8s.io/kubernetes@v1.29.3/test/e2e/framework/timer/timer.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes 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 timer
    18  
    19  import (
    20  	"time"
    21  
    22  	"bytes"
    23  	"fmt"
    24  
    25  	"sync"
    26  
    27  	"k8s.io/kubernetes/test/e2e/framework"
    28  	"k8s.io/kubernetes/test/e2e/perftype"
    29  )
    30  
    31  var now = time.Now
    32  
    33  // Phase represents a phase of a test. Phases can overlap.
    34  type Phase struct {
    35  	sequenceNumber int
    36  	name           string
    37  	startTime      time.Time
    38  	endTime        time.Time
    39  }
    40  
    41  func (phase *Phase) ended() bool {
    42  	return !phase.endTime.IsZero()
    43  }
    44  
    45  // End marks the phase as ended, unless it had already been ended before.
    46  func (phase *Phase) End() {
    47  	if !phase.ended() {
    48  		phase.endTime = now()
    49  	}
    50  }
    51  
    52  func (phase *Phase) label() string {
    53  	return fmt.Sprintf("%03d-%s", phase.sequenceNumber, phase.name)
    54  }
    55  
    56  func (phase *Phase) duration() time.Duration {
    57  	endTime := phase.endTime
    58  	if !phase.ended() {
    59  		endTime = now()
    60  	}
    61  	return endTime.Sub(phase.startTime)
    62  }
    63  
    64  func (phase *Phase) humanReadable() string {
    65  	if phase.ended() {
    66  		return fmt.Sprintf("Phase %s: %v\n", phase.label(), phase.duration())
    67  	}
    68  	return fmt.Sprintf("Phase %s: %v so far\n", phase.label(), phase.duration())
    69  }
    70  
    71  // A TestPhaseTimer groups phases and provides a way to export their measurements as JSON or human-readable text.
    72  // It is safe to use concurrently.
    73  type TestPhaseTimer struct {
    74  	lock   sync.Mutex
    75  	phases []*Phase
    76  }
    77  
    78  // NewTestPhaseTimer creates a new TestPhaseTimer.
    79  func NewTestPhaseTimer() *TestPhaseTimer {
    80  	return &TestPhaseTimer{}
    81  }
    82  
    83  // StartPhase starts a new phase.
    84  // sequenceNumber is an integer prepended to phaseName in the output, such that lexicographic sorting
    85  // of phases in perfdash reconstructs the order of execution. Unfortunately it needs to be
    86  // provided manually, since a simple incrementing counter would have the effect that inserting
    87  // a new phase would renumber subsequent phases, breaking the continuity of historical records.
    88  func (timer *TestPhaseTimer) StartPhase(sequenceNumber int, phaseName string) *Phase {
    89  	timer.lock.Lock()
    90  	defer timer.lock.Unlock()
    91  	newPhase := &Phase{sequenceNumber: sequenceNumber, name: phaseName, startTime: now()}
    92  	timer.phases = append(timer.phases, newPhase)
    93  	return newPhase
    94  }
    95  
    96  // SummaryKind returns the summary of test summary.
    97  func (timer *TestPhaseTimer) SummaryKind() string {
    98  	return "TestPhaseTimer"
    99  }
   100  
   101  // PrintHumanReadable returns durations of all phases.
   102  func (timer *TestPhaseTimer) PrintHumanReadable() string {
   103  	buf := bytes.Buffer{}
   104  	timer.lock.Lock()
   105  	defer timer.lock.Unlock()
   106  	for _, phase := range timer.phases {
   107  		buf.WriteString(phase.humanReadable())
   108  	}
   109  	return buf.String()
   110  }
   111  
   112  // PrintJSON returns durations of all phases with JSON format.
   113  func (timer *TestPhaseTimer) PrintJSON() string {
   114  	data := perftype.PerfData{
   115  		Version: "v1",
   116  		DataItems: []perftype.DataItem{{
   117  			Unit:   "s",
   118  			Labels: map[string]string{"test": "phases"},
   119  			Data:   make(map[string]float64)}}}
   120  	timer.lock.Lock()
   121  	defer timer.lock.Unlock()
   122  	for _, phase := range timer.phases {
   123  		data.DataItems[0].Data[phase.label()] = phase.duration().Seconds()
   124  		if !phase.ended() {
   125  			data.DataItems[0].Labels["ended"] = "false"
   126  		}
   127  	}
   128  	return framework.PrettyPrintJSON(data)
   129  }