k8s.io/perf-tests/clusterloader2@v0.0.0-20240304094227-64bdb12da87e/pkg/measurement/util/pods.go (about)

     1  /*
     2  Copyright 2018 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 util
    18  
    19  import (
    20  	"fmt"
    21  
    22  	corev1 "k8s.io/api/core/v1"
    23  	"k8s.io/apimachinery/pkg/util/sets"
    24  )
    25  
    26  const (
    27  	nonExist = "NonExist"
    28  )
    29  
    30  type Status int
    31  
    32  const (
    33  	Unknown Status = iota
    34  	Terminating
    35  	RunningAndReady
    36  	RunningButNotReady
    37  	PendingScheduled
    38  	PendingNotScheduled
    39  	Inactive
    40  )
    41  
    42  var _statusName = [...]string{"Unknown", "Terminating", "RunningAndReady",
    43  	"RunningButNotReady", "PendingScheduled", "PendingNotScheduled", "Inactive"}
    44  
    45  func (i Status) String() string {
    46  	if int(i) >= len(_statusName) {
    47  		return "Unknown"
    48  	}
    49  	return _statusName[i]
    50  }
    51  
    52  // PodsStartupStatus represents status of a pods group.
    53  type PodsStartupStatus struct {
    54  	Expected              int
    55  	Terminating           int
    56  	Running               int
    57  	Scheduled             int
    58  	RunningButNotReady    int
    59  	Waiting               int
    60  	Pending               int
    61  	Unknown               int
    62  	Inactive              int
    63  	Created               int
    64  	RunningUpdated        int
    65  	LastIsPodUpdatedError error
    66  }
    67  
    68  // String returns string representation for podsStartupStatus.
    69  func (s *PodsStartupStatus) String() string {
    70  	return fmt.Sprintf("Pods: %d out of %d created, %d running (%d updated), %d pending scheduled, %d not scheduled, %d inactive, %d terminating, %d unknown, %d runningButNotReady ",
    71  		s.Created, s.Expected, s.Running, s.RunningUpdated, s.Pending, s.Waiting, s.Inactive, s.Terminating, s.Unknown, s.RunningButNotReady)
    72  }
    73  
    74  func podStatus(p *corev1.Pod) Status {
    75  	if p.DeletionTimestamp != nil {
    76  		return Terminating
    77  	}
    78  	if p.Status.Phase == corev1.PodRunning {
    79  		ready := false
    80  		for _, c := range p.Status.Conditions {
    81  			if c.Type == corev1.PodReady && c.Status == corev1.ConditionTrue {
    82  				ready = true
    83  				break
    84  			}
    85  		}
    86  		if ready {
    87  			// Only count a pod is running when it is also ready.
    88  			return RunningAndReady
    89  		}
    90  		return RunningButNotReady
    91  	}
    92  	if p.Status.Phase == corev1.PodPending {
    93  		if p.Spec.NodeName == "" {
    94  			return PendingNotScheduled
    95  		}
    96  		return PendingScheduled
    97  	}
    98  	if p.Status.Phase == corev1.PodSucceeded || p.Status.Phase == corev1.PodFailed {
    99  		return Inactive
   100  	}
   101  	return Unknown
   102  }
   103  
   104  // ComputePodsStartupStatus computes PodsStartupStatus for a group of pods.
   105  // TODO(mborsz): Migrate to podStatus instead of recalculating per pod status here.
   106  func ComputePodsStartupStatus(pods []*corev1.Pod, expected int, isPodUpdated func(*corev1.Pod) error) PodsStartupStatus {
   107  	startupStatus := PodsStartupStatus{
   108  		Expected: expected,
   109  	}
   110  	for _, p := range pods {
   111  		if p.DeletionTimestamp != nil {
   112  			startupStatus.Terminating++
   113  			continue
   114  		}
   115  		startupStatus.Created++
   116  		if p.Status.Phase == corev1.PodRunning {
   117  			ready := false
   118  			for _, c := range p.Status.Conditions {
   119  				if c.Type == corev1.PodReady && c.Status == corev1.ConditionTrue {
   120  					ready = true
   121  					break
   122  				}
   123  			}
   124  			if ready {
   125  				// Only count a pod is running when it is also ready.
   126  				startupStatus.Running++
   127  				if isPodUpdated == nil {
   128  					startupStatus.RunningUpdated++
   129  				} else {
   130  					if err := isPodUpdated(p); err != nil {
   131  						startupStatus.LastIsPodUpdatedError = err
   132  					} else {
   133  						startupStatus.RunningUpdated++
   134  					}
   135  				}
   136  			} else {
   137  				startupStatus.RunningButNotReady++
   138  			}
   139  		} else if p.Status.Phase == corev1.PodPending {
   140  			if p.Spec.NodeName == "" {
   141  				startupStatus.Waiting++
   142  			} else {
   143  				startupStatus.Pending++
   144  			}
   145  		} else if p.Status.Phase == corev1.PodSucceeded || p.Status.Phase == corev1.PodFailed {
   146  			startupStatus.Inactive++
   147  		} else if p.Status.Phase == corev1.PodUnknown {
   148  			startupStatus.Unknown++
   149  		}
   150  		if p.Spec.NodeName != "" {
   151  			startupStatus.Scheduled++
   152  		}
   153  	}
   154  	return startupStatus
   155  }
   156  
   157  type podDiffInfo struct {
   158  	oldHostname string
   159  	oldPhase    string
   160  	hostname    string
   161  	phase       string
   162  }
   163  
   164  // PodDiff represets diff between old and new group of pods.
   165  type PodDiff map[string]*podDiffInfo
   166  
   167  // Print formats and prints the give PodDiff.
   168  func (p PodDiff) String(ignorePhases sets.String) string {
   169  	ret := ""
   170  	for name, info := range p {
   171  		if ignorePhases.Has(info.phase) {
   172  			continue
   173  		}
   174  		if info.phase == nonExist {
   175  			ret += fmt.Sprintf("Pod %v was deleted, had phase %v and host %v\n", name, info.oldPhase, info.oldHostname)
   176  			continue
   177  		}
   178  		phaseChange, hostChange := false, false
   179  		msg := fmt.Sprintf("Pod %v ", name)
   180  		if info.oldPhase != info.phase {
   181  			phaseChange = true
   182  			if info.oldPhase == nonExist {
   183  				msg += fmt.Sprintf("in phase %v ", info.phase)
   184  			} else {
   185  				msg += fmt.Sprintf("went from phase: %v -> %v ", info.oldPhase, info.phase)
   186  			}
   187  		}
   188  		if info.oldHostname != info.hostname {
   189  			hostChange = true
   190  			if info.oldHostname == nonExist || info.oldHostname == "" {
   191  				msg += fmt.Sprintf("assigned host %v ", info.hostname)
   192  			} else {
   193  				msg += fmt.Sprintf("went from host: %v -> %v ", info.oldHostname, info.hostname)
   194  			}
   195  		}
   196  		if phaseChange || hostChange {
   197  			ret += msg + "\n"
   198  		}
   199  	}
   200  	return ret
   201  }
   202  
   203  // DeletedPods returns a slice of pods that were present at the beginning
   204  // and then disappeared.
   205  func (p PodDiff) DeletedPods() []string {
   206  	var deletedPods []string
   207  	for podName, podDiffInfo := range p {
   208  		if podDiffInfo.hostname == nonExist {
   209  			deletedPods = append(deletedPods, podName)
   210  		}
   211  	}
   212  	return deletedPods
   213  }
   214  
   215  // AddedPods returns a slice of pods that were added.
   216  func (p PodDiff) AddedPods() []string {
   217  	var addedPods []string
   218  	for podName, podDiffInfo := range p {
   219  		if podDiffInfo.oldHostname == nonExist {
   220  			addedPods = append(addedPods, podName)
   221  		}
   222  	}
   223  	return addedPods
   224  }
   225  
   226  // DiffPods computes a PodDiff given 2 lists of pods.
   227  func DiffPods(oldPods []*corev1.Pod, curPods []*corev1.Pod) PodDiff {
   228  	podDiffInfoMap := PodDiff{}
   229  
   230  	// New pods will show up in the curPods list but not in oldPods. They have oldhostname/phase == nonexist.
   231  	for _, pod := range curPods {
   232  		podDiffInfoMap[pod.Name] = &podDiffInfo{hostname: pod.Spec.NodeName, phase: string(pod.Status.Phase), oldHostname: nonExist, oldPhase: nonExist}
   233  	}
   234  
   235  	// Deleted pods will show up in the oldPods list but not in curPods. They have a hostname/phase == nonexist.
   236  	for _, pod := range oldPods {
   237  		if info, ok := podDiffInfoMap[pod.Name]; ok {
   238  			info.oldHostname, info.oldPhase = pod.Spec.NodeName, string(pod.Status.Phase)
   239  		} else {
   240  			podDiffInfoMap[pod.Name] = &podDiffInfo{hostname: nonExist, phase: nonExist, oldHostname: pod.Spec.NodeName, oldPhase: string(pod.Status.Phase)}
   241  		}
   242  	}
   243  	return podDiffInfoMap
   244  }
   245  
   246  type PodInfo struct {
   247  	Name      string
   248  	Hostname  string
   249  	Phase     string
   250  	Status    Status
   251  	Namespace string
   252  }
   253  
   254  func (p *PodInfo) String() string {
   255  	return fmt.Sprintf("{%v %v %v %v %v}", p.Namespace, p.Name, p.Phase, p.Status.String(), p.Hostname)
   256  }
   257  
   258  // PodsStatus is a collection of current pod phases and node assignments data.
   259  type PodsStatus struct {
   260  	Info []*PodInfo
   261  }
   262  
   263  // ComputePodsStatus computes PodsStatus for a group of pods.
   264  func ComputePodsStatus(pods []*corev1.Pod) *PodsStatus {
   265  	ps := &PodsStatus{
   266  		Info: make([]*PodInfo, len(pods)),
   267  	}
   268  	for i := range pods {
   269  		ps.Info[i] = &PodInfo{
   270  			Name:      pods[i].Name,
   271  			Hostname:  pods[i].Spec.NodeName,
   272  			Phase:     string(pods[i].Status.Phase),
   273  			Status:    podStatus(pods[i]),
   274  			Namespace: pods[i].Namespace,
   275  		}
   276  	}
   277  	return ps
   278  }
   279  
   280  // String returns string representation of a PodsStatus.
   281  func (ps *PodsStatus) String() string {
   282  	return fmt.Sprintf("%v", ps.Info)
   283  }
   284  
   285  func (ps *PodsStatus) NotRunningAndReady() *PodsStatus {
   286  	res := &PodsStatus{
   287  		Info: make([]*PodInfo, 0),
   288  	}
   289  
   290  	for _, info := range ps.Info {
   291  		if info.Status != RunningAndReady {
   292  			res.Info = append(res.Info, info)
   293  		}
   294  	}
   295  
   296  	return res
   297  }