Skip to content

Commit bb3c14f

Browse files
add constants and helpers; refactor k8s functions
1 parent 5abd87f commit bb3c14f

File tree

9 files changed

+268
-232
lines changed

9 files changed

+268
-232
lines changed

internal/scout/advise/k8s.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package advise
22

33
import (
44
"context"
5-
"fmt"
65

76
"github.com/sourcegraph/sourcegraph/lib/errors"
87
"github.com/sourcegraph/src-cli/internal/scout"
@@ -41,12 +40,29 @@ func K8s(
4140
return errors.Wrap(err, "could not get list of pods")
4241
}
4342

44-
PrintPods(pods)
43+
if cfg.Pod != "" {
44+
pod, err := kube.GetPod(cfg.Pod, pods)
45+
if err != nil {
46+
return errors.Wrap(err, "could not get pod")
47+
}
48+
Advise(ctx, cfg, pod)
49+
}
4550
return nil
4651
}
4752

48-
func PrintPods(pods []v1.Pod) {
49-
for _, p := range pods {
50-
fmt.Println(p.Name)
53+
func Advise(ctx context.Context, cfg *scout.Config, pod v1.Pod) error {
54+
return nil
55+
}
56+
57+
// getPercentage calculates the percentage of x in relation to y.
58+
func getPercentage(x, y float64) float64 {
59+
if x == 0 {
60+
return 0
5161
}
62+
63+
if y == 0 {
64+
return 0
65+
}
66+
67+
return x * 100 / y
5268
}

internal/scout/constants.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package scout
2+
3+
const (
4+
ABillion float64 = 1000000000
5+
)

internal/scout/helpers.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package scout
2+
3+
// contains checks if a string slice contains a given value.
4+
func Contains(slice []string, value string) bool {
5+
for _, v := range slice {
6+
if v == value {
7+
return true
8+
}
9+
}
10+
return false
11+
}
12+
13+
// getPercentage calculates the percentage of x in relation to y.
14+
func GetPercentage(x, y float64) float64 {
15+
if x == 0 {
16+
return 0
17+
}
18+
19+
if y == 0 {
20+
return 0
21+
}
22+
23+
return x * 100 / y
24+
}

internal/scout/kube/kube.go

Lines changed: 165 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,7 @@ import (
2121
metav1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
2222
)
2323

24-
// GetPods returns a list of Pod objects in a given Kubernetes namespace.
25-
//
26-
// It accepts:
27-
// - ctx: The context for the request
28-
// - k8sClient: A Kubernetes clientset for making API requests
29-
// - namespace: The Kubernetes namespace to list Pods from
30-
//
31-
// It returns:
32-
// - A slice of Pod objects from the given namespace
33-
// - An error if there was an issue listing Pods
34-
//
35-
// If no Pods exist in the given namespace, a warning message is printed and the
36-
// program exits with a non-zero status code.
24+
// GetPods fetches all pods in a given namespace.
3725
func GetPods(ctx context.Context, cfg *scout.Config) ([]corev1.Pod, error) {
3826
podInterface := cfg.K8sClient.CoreV1().Pods(cfg.Namespace)
3927
podList, err := podInterface.List(ctx, metav1.ListOptions{})
@@ -58,10 +46,7 @@ func GetPods(ctx context.Context, cfg *scout.Config) ([]corev1.Pod, error) {
5846
return podList.Items, nil
5947
}
6048

61-
// getPod returns a Pod object with the given name.
62-
//
63-
// If a Pod with the given name exists in pods, it is returned.
64-
// Otherwise, an error is returned indicating no Pod with that name exists.
49+
// GetPod returns a pod object with the given name from a list of pods.
6550
func GetPod(podName string, pods []corev1.Pod) (corev1.Pod, error) {
6651
for _, p := range pods {
6752
if p.Name == podName {
@@ -71,11 +56,16 @@ func GetPod(podName string, pods []corev1.Pod) (corev1.Pod, error) {
7156
return corev1.Pod{}, errors.New("no pod with this name exists in this namespace")
7257
}
7358

74-
// getPodMetrics retrieves metrics for a given pod.
59+
// GetPodMetrics fetches metrics for a given pod from the Kubernetes Metrics API.
60+
//
61+
// It accepts:
62+
// - ctx: The context for the request
63+
// - cfg: The scout config containing Kubernetes clientsets
64+
// - pod: The pod specification
7565
//
76-
// It returns:
66+
// It returns:
7767
// - podMetrics: The PodMetrics object containing metrics for the pod
78-
// - err: Any error that occurred while getting the pod metrics
68+
// - Any error that occurred while fetching the metrics
7969
func GetPodMetrics(ctx context.Context, cfg *scout.Config, pod corev1.Pod) (*metav1beta1.PodMetrics, error) {
8070
podMetrics, err := cfg.MetricsClient.
8171
MetricsV1beta1().
@@ -88,15 +78,117 @@ func GetPodMetrics(ctx context.Context, cfg *scout.Config, pod corev1.Pod) (*met
8878
return podMetrics, nil
8979
}
9080

91-
// GetRawUsage retrieves the raw usage value for a given resource type from a Kubernetes ResourceList.
81+
// GetLimits generates resource limits for containers in a pod.
82+
//
83+
// It accepts:
84+
// - ctx: The context for the request
85+
// - cfg: The scout config containing Kubernetes clientsets
86+
// - pod: The pod specification
87+
// - containerMetrics: A pointer to a ContainerMetrics struct to populate
88+
//
89+
// It populates the containerMetrics.Limits field with a map of container names
90+
// to resource limits (CPU, memory, storage) for each container in the pod.
9291
//
92+
// It returns:
93+
// - Any error that occurred while fetching resource limits
94+
func GetLimits(ctx context.Context, cfg *scout.Config, pod *corev1.Pod, containerMetrics *scout.ContainerMetrics) error {
95+
for _, container := range pod.Spec.Containers {
96+
containerName := container.Name
97+
capacity, err := GetPvcCapacity(ctx, cfg, container, pod)
98+
if err != nil {
99+
return errors.Wrap(err, "while getting storage capacity of PV")
100+
}
101+
102+
rsrcs := scout.Resources{
103+
Cpu: container.Resources.Limits.Cpu().ToDec(),
104+
Memory: container.Resources.Limits.Memory().ToDec(),
105+
Storage: capacity,
106+
}
107+
containerMetrics.Limits[containerName] = rsrcs
108+
}
109+
110+
return nil
111+
}
112+
113+
// GetUsage generates resource usage statistics for a Kubernetes container.
114+
//
93115
// It accepts:
94-
// - usages: A Kubernetes ResourceList containing usage values for multiple resource types
95-
// - targetKey: The key for the resource type to retrieve usage for (e.g. "cpu" or "memory")
116+
// - ctx: The context for the request
117+
// - cfg: The scout config containing Kubernetes clientsets
118+
// - metrics: Container resource limits
119+
// - pod: The pod specification
120+
// - container: Container metrics from the Metrics API
96121
//
97122
// It returns:
98-
// - The raw usage value for the target resource type as a float64
99-
// - An error if there was an issue retrieving or parsing the usage value
123+
// - usageStats: A UsageStats struct containing the resource usage info
124+
// - Any error that occurred while generating the usage stats
125+
func GetUsage(
126+
ctx context.Context,
127+
cfg *scout.Config,
128+
metrics scout.ContainerMetrics,
129+
pod corev1.Pod,
130+
container metav1beta1.ContainerMetrics,
131+
) (scout.UsageStats, error) {
132+
var usageStats scout.UsageStats
133+
usageStats.ContainerName = container.Name
134+
135+
cpuUsage, err := GetRawUsage(container.Usage, "cpu")
136+
if err != nil {
137+
return usageStats, errors.Wrap(err, "failed to get raw CPU usage")
138+
}
139+
140+
memUsage, err := GetRawUsage(container.Usage, "memory")
141+
if err != nil {
142+
return usageStats, errors.Wrap(err, "failed to get raw memory usage")
143+
}
144+
145+
storageCapacity, storageUsage, err := GetStorageUsage(ctx, cfg, pod.Name, container.Name)
146+
if err != nil {
147+
return usageStats, errors.Wrap(err, "failed to get storage usage")
148+
}
149+
150+
limits := metrics.Limits[container.Name]
151+
152+
usageStats.CpuCores = limits.Cpu
153+
usageStats.CpuUsage = scout.GetPercentage(
154+
cpuUsage,
155+
limits.Cpu.AsApproximateFloat64()*scout.ABillion,
156+
)
157+
158+
usageStats.Memory = limits.Memory
159+
usageStats.MemoryUsage = scout.GetPercentage(
160+
memUsage,
161+
limits.Memory.AsApproximateFloat64(),
162+
)
163+
164+
if limits.Storage == nil {
165+
storageDec := *inf.NewDec(0, 0)
166+
usageStats.Storage = resource.NewDecimalQuantity(storageDec, resource.Format("DecimalSI"))
167+
} else {
168+
usageStats.Storage = limits.Storage
169+
}
170+
171+
usageStats.StorageUsage = scout.GetPercentage(
172+
storageUsage,
173+
storageCapacity,
174+
)
175+
176+
if metrics.Limits[container.Name].Storage == nil {
177+
usageStats.Storage = nil
178+
}
179+
180+
return usageStats, nil
181+
}
182+
183+
// GetRawUsage returns the raw usage value for a given resource type from a Kubernetes ResourceList.
184+
//
185+
// It accepts:
186+
// - usages: A Kubernetes ResourceList containing usage values
187+
// - targetKey: The resource type to get the usage for (e.g. "cpu" or "memory")
188+
//
189+
// It returns:
190+
// - The raw usage value for the target resource type
191+
// - Any error that occurred while parsing the usage value
100192
func GetRawUsage(usages corev1.ResourceList, targetKey string) (float64, error) {
101193
var usage *inf.Dec
102194

@@ -114,13 +206,19 @@ func GetRawUsage(usages corev1.ResourceList, targetKey string) (float64, error)
114206
return toFloat, nil
115207
}
116208

117-
// getPvcCapacity retrieves the storage capacity of a PersistentVolumeClaim
118-
// mounted as a volume by a container.
209+
// GetPvcCapacity returns the storage capacity of a PersistentVolumeClaim mounted to a container.
210+
//
211+
// It accepts:
212+
// - ctx: The context for the request
213+
// - cfg: The scout config containing Kubernetes clientsets
214+
// - container: The container specification
215+
// - pod: The pod specification
119216
//
120217
// It returns:
121-
// - The capacity Quantity of the PVC if a matching PVC mount is found
122-
// - nil if no PVC mount is found
123-
// - Any error that occurred while getting the PVC
218+
// - The storage capacity of the PVC in bytes
219+
// - Any error that occurred while fetching the PVC
220+
//
221+
// If no PVC is mounted to the container, nil is returned for the capacity and no error.
124222
func GetPvcCapacity(ctx context.Context, cfg *scout.Config, container corev1.Container, pod *corev1.Pod) (*resource.Quantity, error) {
125223
for _, vm := range container.VolumeMounts {
126224
for _, v := range pod.Spec.Volumes {
@@ -147,18 +245,49 @@ func GetPvcCapacity(ctx context.Context, cfg *scout.Config, container corev1.Con
147245
return nil, nil
148246
}
149247

150-
// getStorageUsage executes the df -h command in a container and parses the
151-
// output to get the storage usage percentage for ephemeral storage volumes.
248+
// GetStorageUsage returns the storage capacity and usage for a given pod and container.
249+
//
250+
// It accepts:
251+
// - ctx: The context for the request
252+
// - cfg: The scout config containing Kubernetes clientsets
253+
// - podName: The name of the pod
254+
// - containerName: The name of the container
152255
//
153256
// It returns:
154-
// - The storage usage percentage for storage volumes
155-
// - "-" if no storage volumes are found
156-
// - Any error that occurred while executing the df -h command or parsing the output
257+
// - storageCapacity: The total storage capacity for the container in bytes
258+
// - storageUsage: The used storage for the container in bytes
259+
// - Any error that occurred while fetching the storage usage
157260
func GetStorageUsage(
158261
ctx context.Context,
159262
cfg *scout.Config,
160-
podName, containerName string,
263+
podName string,
264+
containerName string,
161265
) (float64, float64, error) {
266+
var storageCapacity float64
267+
var storageUsage float64
268+
269+
stateless := []string{
270+
"cadvisor",
271+
"pgsql-exporter",
272+
"executor",
273+
"dind",
274+
"github-proxy",
275+
"jaeger",
276+
"node-exporter",
277+
"otel-agent",
278+
"otel-collector",
279+
"precise-code-intel-worker",
280+
"redis-exporter",
281+
"repo-updater",
282+
"frontend",
283+
"syntect-server",
284+
"worker",
285+
}
286+
287+
if scout.Contains(stateless, containerName) {
288+
return storageCapacity, storageUsage, nil
289+
}
290+
162291
req := cfg.K8sClient.CoreV1().RESTClient().Post().
163292
Resource("pods").
164293
Name(podName).

internal/scout/types.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package scout
22

33
import (
44
"github.com/docker/docker/client"
5+
"k8s.io/apimachinery/pkg/api/resource"
56
"k8s.io/client-go/kubernetes"
67
"k8s.io/client-go/rest"
78
metricsv "k8s.io/metrics/pkg/client/clientset/versioned"
@@ -18,3 +19,24 @@ type Config struct {
1819
DockerClient *client.Client
1920
MetricsClient *metricsv.Clientset
2021
}
22+
23+
type ContainerMetrics struct {
24+
PodName string
25+
Limits map[string]Resources
26+
}
27+
28+
type Resources struct {
29+
Cpu *resource.Quantity
30+
Memory *resource.Quantity
31+
Storage *resource.Quantity
32+
}
33+
34+
type UsageStats struct {
35+
ContainerName string
36+
CpuCores *resource.Quantity
37+
Memory *resource.Quantity
38+
Storage *resource.Quantity
39+
CpuUsage float64
40+
MemoryUsage float64
41+
StorageUsage float64
42+
}

internal/scout/usage/docker.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ func makeDockerUsageRow(ctx context.Context, cfg *scout.Config, container types.
107107
return table.Row{
108108
container.Name,
109109
fmt.Sprintf("%.2f", cpuCores/1_000_000_000),
110-
fmt.Sprintf("%.2f%%", getPercentage(cpuUsage, cpuCores)),
110+
fmt.Sprintf("%.2f%%", scout.GetPercentage(cpuUsage, cpuCores)),
111111
fmt.Sprintf("%.2fG", memory/1_000_000_000),
112-
fmt.Sprintf("%.2f%%", getPercentage(memoryUsage, memory)),
112+
fmt.Sprintf("%.2f%%", scout.GetPercentage(memoryUsage, memory)),
113113
}
114114
}

0 commit comments

Comments
 (0)