Files
go-k8s-btop/metrics-row.go
matst80 b4abcfd4f8 code
2025-07-14 16:59:51 +02:00

151 lines
4.1 KiB
Go

package main
import (
"context"
"fmt"
"sort"
"time"
"github.com/dustin/go-humanize"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
metricsv "k8s.io/metrics/pkg/client/clientset/versioned"
)
type PodInfo struct {
Name string
Namespace string
Status string
ReadyContainers int
ContainerCount int
RestartCount int
CPUUsage string
MemoryUsage string
Age time.Duration
Node string
IsStarting bool // Added to track if pod is starting or restarting
}
func refreshData(clientset *kubernetes.Clientset, metricsClient *metricsv.Clientset, namespaces []string) ([]PodInfo, error) {
pods, err := clientset.CoreV1().Pods("").List(context.Background(), metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("error listing pods: %v", err)
}
// Create a map for faster namespace lookups
namespaceMap := make(map[string]bool)
for _, ns := range namespaces {
namespaceMap[ns] = true
}
podMetricsMap := make(map[string]map[string]struct {
CPU int64
Memory int64
})
if metricsClient != nil {
podMetrics, err := metricsClient.MetricsV1beta1().PodMetricses("").List(context.Background(), metav1.ListOptions{})
if err == nil {
for _, metric := range podMetrics.Items {
nsKey := metric.Namespace
// Only process metrics for specified namespaces
if !namespaceMap[nsKey] {
continue
}
if _, ok := podMetricsMap[nsKey]; !ok {
podMetricsMap[nsKey] = make(map[string]struct {
CPU int64
Memory int64
})
}
var totalCPU, totalMemory int64
for _, container := range metric.Containers {
totalCPU += container.Usage.Cpu().MilliValue()
totalMemory += container.Usage.Memory().Value()
}
podMetricsMap[nsKey][metric.Name] = struct {
CPU int64
Memory int64
}{
CPU: totalCPU,
Memory: totalMemory,
}
}
}
}
var result []PodInfo
for _, pod := range pods.Items {
readyCount := 0
restarts := 0
nsKey := pod.Namespace
// Only process pods for specified namespaces
if !namespaceMap[nsKey] {
continue
}
for _, status := range pod.Status.ContainerStatuses {
if status.Ready {
readyCount++
}
restarts += int(status.RestartCount)
}
podInfo := PodInfo{
Name: pod.Name,
Namespace: pod.Namespace,
Status: string(pod.Status.Phase),
ContainerCount: len(pod.Spec.Containers),
ReadyContainers: readyCount,
RestartCount: restarts,
Age: time.Since(pod.CreationTimestamp.Time),
//IP: pod.Status.PodIP,
IsStarting: time.Since(pod.CreationTimestamp.Time) < 1*time.Minute || isPodStartingOrRestarting(pod),
Node: pod.Spec.NodeName,
}
if metrics, ok := podMetricsMap[pod.Namespace][pod.Name]; ok {
podInfo.CPUUsage = fmt.Sprintf("%dm", metrics.CPU)
podInfo.MemoryUsage = humanize.Bytes(uint64(metrics.Memory))
} else {
podInfo.CPUUsage = "N/A"
podInfo.MemoryUsage = "N/A"
}
result = append(result, podInfo)
}
sort.Slice(result, func(i, j int) bool {
if result[i].Namespace == result[j].Namespace {
return result[i].Name < result[j].Name
}
return result[i].Namespace < result[j].Namespace
})
return result, nil
}
// Helper function to check if pod is starting or restarting
func isPodStartingOrRestarting(pod v1.Pod) bool {
// Check if pod is in Pending state
if pod.Status.Phase == corev1.PodPending {
return true
}
// Check if any container is in waiting state with specific reasons
for _, containerStatus := range pod.Status.ContainerStatuses {
if containerStatus.State.Waiting != nil {
reason := containerStatus.State.Waiting.Reason
if reason == "ContainerCreating" ||
reason == "PodInitializing" ||
reason == "CrashLoopBackOff" {
return true
}
}
// Check if container recently restarted (within last minute)
if containerStatus.LastTerminationState.Terminated != nil {
terminatedTime := containerStatus.LastTerminationState.Terminated.FinishedAt.Time
if time.Since(terminatedTime) < 60*time.Second {
return true
}
}
}
return false
}