diff --git a/cmd/main.go b/cmd/main.go index c3c9f30..cfabdf5 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,8 +1,10 @@ package main import ( - "log" "os" + "time" + + "github.com/rs/zerolog" "github.com/unagex/kondense/pkg/controller" "github.com/unagex/kondense/pkg/utils" @@ -10,36 +12,34 @@ import ( ) func main() { - // create logger. - l := log.Default() - l.SetFlags(log.Lshortfile | log.LstdFlags) + l := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}).Level(zerolog.TraceLevel).With().Timestamp().Logger() // get pod name and namespace. name := os.Getenv("HOSTNAME") namespaceByte, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") if err != nil { - l.Fatal(err) + l.Fatal().Err(err) } namespace := string(namespaceByte) client, err := utils.GetClient() if err != nil { - l.Fatal(err) + l.Fatal().Err(err) } rawClient, err := utils.GetRawClient() if err != nil { - l.Fatal(err) + l.Fatal().Err(err) } bt, err := utils.GetBearerToken() if err != nil { - l.Fatalf("failed to get k8s bearer token: %s", err) + l.Fatal().Err(err).Msg("failed to get k8s bearer token") } reconciler := controller.Reconciler{ Client: client, RawClient: rawClient, - L: l, + L: &l, BearerToken: bt, diff --git a/go.mod b/go.mod index 4fed4c1..81c7231 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,11 @@ require ( k8s.io/client-go v0.29.3 ) +require ( + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect +) + require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.12.0 // indirect @@ -28,6 +33,7 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/onsi/ginkgo/v2 v2.17.1 // indirect github.com/onsi/gomega v1.32.0 // indirect + github.com/rs/zerolog v1.32.0 golang.org/x/net v0.22.0 // indirect golang.org/x/oauth2 v0.18.0 // indirect golang.org/x/sys v0.18.0 // indirect diff --git a/go.sum b/go.sum index 6d8fbd3..bce8f17 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,4 @@ +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -13,6 +14,7 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -44,6 +46,11 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -55,10 +62,14 @@ github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8 github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0= +github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -96,6 +107,9 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/pkg/controller/container.go b/pkg/controller/container.go index dc5fd8e..f9ecb80 100644 --- a/pkg/controller/container.go +++ b/pkg/controller/container.go @@ -26,53 +26,41 @@ func (r *Reconciler) ReconcileContainer(pod *corev1.Pod, container corev1.Contai err := r.UpdateStats(pod, container) if err != nil { - r.L.Print(err) + r.L.Error().Err(err) return } err = r.KondenseContainer(container) if err != nil { - r.L.Print(err) + r.L.Error().Err(err) } } func (r *Reconciler) UpdateStats(pod *corev1.Pod, container corev1.Container) error { var err error var output []byte - for i := 0; i < 3; i++ { - cmd := exec.Command("kubectl", "exec", "-i", r.Name, "-c", container.Name, "--", "cat", "/sys/fs/cgroup/memory.pressure", "/sys/fs/cgroup/cpu.stat") - // we don't need kubectl for kondense container. - if strings.ToLower(container.Name) == "kondense" { - cmd = exec.Command("cat", "/sys/fs/cgroup/memory.pressure", "/sys/fs/cgroup/cpu.stat") - } - output, err = cmd.Output() - if err == nil { - r.CStats[container.Name].LastUpdate = time.Now() - break - } - time.Sleep(50 * time.Millisecond) - } + + output, err = r.executeStatsCmd(container) if err != nil { return err } + r.CStats[container.Name].LastUpdate = time.Now() txt := strings.Split(string(output), " ") - if len(txt) != 15 { + if len(txt) != 15 { // TODO use regexp return fmt.Errorf("error got unexpected stats for container %s: %s", container.Name, txt) } - err = r.UpdateMemStats(container.Name, txt) - if err != nil { + if err := r.UpdateMemStats(container.Name, txt); err != nil { return err } - err = r.UpdateCPUStats(container.Name, txt) - if err != nil { + if err := r.UpdateCPUStats(container.Name, txt); err != nil { return err } s := r.CStats[container.Name] - r.L.Printf("container=%s memory_limit=%d memory_time_to_dec=%d memory_total=%d, memory_integral=%d, cpu_limit=%dm, cpu_average=%dm", + r.L.Info().Msgf("container=%s memory_limit=%d memory_time_to_dec=%d memory_total=%d, memory_integral=%d, cpu_limit=%dm, cpu_average=%dm", container.Name, s.Mem.Limit, s.Mem.GraceTicks, s.Mem.PrevTotal, s.Mem.Integral, s.Cpu.Limit, s.Cpu.Avg, @@ -81,6 +69,27 @@ func (r *Reconciler) UpdateStats(pod *corev1.Pod, container corev1.Container) er return nil } +func (r *Reconciler) executeStatsCmd(container corev1.Container) ([]byte, error) { + var cmd *exec.Cmd + var err error + var output []byte + retryNb := 3 + + if strings.ToLower(container.Name) == "kondense" { + cmd = exec.Command("cat", "/sys/fs/cgroup/memory.pressure", "/sys/fs/cgroup/cpu.stat") + } else { + cmd = exec.Command("kubectl", "exec", "-i", r.Name, "-c", container.Name, "--", "cat", "/sys/fs/cgroup/memory.pressure", "/sys/fs/cgroup/cpu.stat") + } + for i := 0; i < retryNb; i++ { + output, err = cmd.Output() + if err == nil { + return output, nil + } + time.Sleep(50 * time.Millisecond * time.Duration(i+1)) + } + return nil, err +} + func (r *Reconciler) UpdateMemStats(containerName string, txt []string) error { s := r.CStats[containerName] @@ -199,9 +208,7 @@ func (r *Reconciler) Adjust(containerName string, memFactor float64, cpuFactor f newCPU := uint64(float64(s.Cpu.Limit) * (1 + cpuFactor)) newCPU = min(max(newCPU, s.Cpu.Min), s.Cpu.Max) - MemUpdate := newMemory != uint64(s.Mem.Limit) - CPUUpdate := newCPU != uint64(s.Cpu.Limit) - if !MemUpdate && !CPUUpdate { + if newMemory == uint64(s.Mem.Limit) && newCPU == uint64(s.Cpu.Limit) { return nil } @@ -230,22 +237,20 @@ func (r *Reconciler) Adjust(containerName string, memFactor float64, cpuFactor f // renew k8s token bt, err := utils.GetBearerToken() if err != nil { - r.L.Fatalf("failed to renew k8s bearer token: %s", err) + r.L.Fatal().Msgf("failed to renew k8s bearer token: %s", err) } r.Mu.Lock() - r.L.Print("renewed k8s bearer token.") + r.L.Info().Msg("renewed k8s bearer token.") r.BearerToken = bt r.Mu.Unlock() return r.Adjust(containerName, memFactor, cpuFactor) } if resp.StatusCode != http.StatusOK { - return fmt.Errorf("failed to patch container, want status code: %d, got %d", - http.StatusOK, resp.StatusCode) + return fmt.Errorf("error %d: failed to patch container", resp.StatusCode) } - r.L.Printf("patched container %s with mem factor: %.2f and new memory: %d bytes and with cpu factor : %.2f and new cpu: %dm.", - containerName, memFactor, newMemory, cpuFactor, newCPU) + r.L.Info().Str("container", containerName).Float64("memFactor", memFactor).Uint64("newMemory", newMemory).Float64("cpuFactor", cpuFactor).Uint64("newCPU", newCPU).Msg("patched container") s.Mem.Integral = 0 diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 9082e52..befa5d9 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -2,11 +2,11 @@ package controller import ( "context" - "log" "net/http" "sync" "time" + "github.com/rs/zerolog" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -15,7 +15,7 @@ import ( type Reconciler struct { Client *kubernetes.Clientset RawClient *http.Client - L *log.Logger + L *zerolog.Logger Mu sync.Mutex BearerToken string @@ -34,11 +34,11 @@ func (r *Reconciler) Reconcile() { pod, err := r.Client.CoreV1().Pods(r.Namespace).Get(context.TODO(), r.Name, v1.GetOptions{}) if err != nil { - r.L.Println(err) + r.L.Error().Err(err) continue } if pod.Status.QOSClass != corev1.PodQOSGuaranteed { - r.L.Printf("error kondense is only allowed for pods with a QoS class of Guaranteed, got: %s.", pod.Status.QOSClass) + r.L.Error().Msgf("error kondense is only allowed for pods with a QoS class of Guaranteed, got: %s.", pod.Status.QOSClass) continue } diff --git a/pkg/controller/init.go b/pkg/controller/init.go index 1d081a8..d052c3c 100644 --- a/pkg/controller/init.go +++ b/pkg/controller/init.go @@ -59,13 +59,13 @@ func (r *Reconciler) getMemoryMin(containerName string) uint64 { if v, ok := os.LookupEnv(env); ok { minQ, err := resource.ParseQuantity(v) if err != nil { - r.L.Printf("error cannot parse environment variable: %s. Set %s to default value: %d microseconds.", + r.L.Error().Msgf("error cannot parse environment variable: %s. Set %s to default value: %d microseconds.", env, env, DefaultMemMin) return DefaultMemMin } min := minQ.Value() if min <= 0 { - r.L.Printf("error environment variable: %s should be bigger than 0. Set %s to default value: %d microseconds", + r.L.Error().Msgf("error environment variable: %s should be bigger than 0. Set %s to default value: %d microseconds", env, env, DefaultMemMin) return DefaultMemMin } @@ -80,19 +80,18 @@ func (r *Reconciler) getMemoryMax(containerName string) uint64 { if v, ok := os.LookupEnv(env); ok { maxQ, err := resource.ParseQuantity(v) if err != nil { - r.L.Printf("error cannot parse environment variable: %s. Set %s to default value: %d microseconds.", + r.L.Error().Msgf("error cannot parse environment variable: %s. Set %s to default value: %d microseconds.", env, env, DefaultMemMax) return DefaultMemMax } max := maxQ.Value() if max <= 0 { - r.L.Printf("error environment variable: %s should be bigger than 0. Set %s to default value: %d microseconds", + r.L.Error().Msgf("error environment variable: %s should be bigger than 0. Set %s to default value: %d microseconds", env, env, DefaultMemMax) return DefaultMemMax } return uint64(max) } - return DefaultMemMax } @@ -101,7 +100,7 @@ func (r *Reconciler) getMemoryInterval(containerName string) uint64 { if v, ok := os.LookupEnv(env); ok { interval, err := strconv.ParseUint(v, 10, 64) if err != nil { - r.L.Printf("error cannot parse environment variable: %s. Set %s to default value: %ds.", + r.L.Error().Msgf("error cannot parse environment variable: %s. Set %s to default value: %ds.", env, env, DefaultMemInterval) return DefaultMemInterval } @@ -116,12 +115,12 @@ func (r *Reconciler) getMemoryTargetPressure(containerName string) uint64 { if v, ok := os.LookupEnv(env); ok { targetPressure, err := strconv.ParseUint(v, 10, 64) if err != nil { - r.L.Printf("error cannot parse environment variable: %s pressure. Set %s to default value: %d.", + r.L.Error().Msgf("error cannot parse environment variable: %s pressure. Set %s to default value: %d.", env, env, DefaultMemTargetPressure) return DefaultMemTargetPressure } if targetPressure == 0 { - r.L.Printf("error environment variable: %s should be more than 0. Set %s to default value: %d.", + r.L.Error().Msgf("error environment variable: %s should be more than 0. Set %s to default value: %d.", env, env, DefaultMemTargetPressure) return DefaultMemTargetPressure } @@ -136,12 +135,12 @@ func (r *Reconciler) getMemoryMaxInc(containerName string) float64 { if v, ok := os.LookupEnv(env); ok { maxInc, err := strconv.ParseFloat(v, 64) if err != nil { - r.L.Printf("error cannot parse environment variable: %s. Set %s to default value: %.2f.", + r.L.Error().Msgf("error cannot parse environment variable: %s. Set %s to default value: %.2f.", env, env, DefaultMemMaxInc) return DefaultMemMaxInc } if maxInc <= 0 { - r.L.Printf("error environment variable: %s should be bigger than 0. Set %s to default value: %.2f.", + r.L.Error().Msgf("error environment variable: %s should be bigger than 0. Set %s to default value: %.2f.", env, env, DefaultMemMaxInc) return DefaultMemMaxInc } @@ -156,12 +155,12 @@ func (r *Reconciler) getMemoryMaxDec(containerName string) float64 { if v, ok := os.LookupEnv(env); ok { maxDec, err := strconv.ParseFloat(v, 64) if err != nil { - r.L.Printf("error cannot parse environment variable: %s. Set %s to default value: %.2f.", + r.L.Error().Msgf("error cannot parse environment variable: %s. Set %s to default value: %.2f.", env, env, DefaultMemMaxDec) return DefaultMemMaxDec } if maxDec <= 0 || maxDec >= 1 { - r.L.Printf("error environment variable: %s should be between 0 and 1 exclusive. Set %s to default value: %.2f.", + r.L.Error().Msgf("error environment variable: %s should be between 0 and 1 exclusive. Set %s to default value: %.2f.", env, env, DefaultMemMaxDec) return DefaultMemMaxDec } @@ -176,12 +175,12 @@ func (r *Reconciler) getMemoryCoeffInc(containerName string) float64 { if v, ok := os.LookupEnv(env); ok { coeffInc, err := strconv.ParseFloat(v, 64) if err != nil { - r.L.Printf("error cannot parse environment variable: %s. Set %s to default value: %.2f.", + r.L.Error().Msgf("error cannot parse environment variable: %s. Set %s to default value: %.2f.", env, env, DefaultMemCoeffInc) return DefaultMemCoeffInc } if coeffInc <= 0 { - r.L.Printf("error environment variable: %s should be bigger than 0. Set %s to default value: %.2f.", + r.L.Error().Msgf("error environment variable: %s should be bigger than 0. Set %s to default value: %.2f.", env, env, DefaultMemCoeffInc) return DefaultMemCoeffInc } @@ -196,12 +195,12 @@ func (r *Reconciler) getMemoryCoeffDec(containerName string) float64 { if v, ok := os.LookupEnv(env); ok { coeffDec, err := strconv.ParseFloat(v, 64) if err != nil { - r.L.Printf("error cannot parse environment variable: %s. Set %s to default value: %.2f.", + r.L.Error().Msgf("error cannot parse environment variable: %s. Set %s to default value: %.2f.", env, env, DefaultMemCoeffDec) return DefaultMemCoeffDec } if coeffDec <= 0 { - r.L.Printf("error environment variable: %s should be bigger than 0. Set %s to default value: %.2f.", + r.L.Error().Msgf("error environment variable: %s should be bigger than 0. Set %s to default value: %.2f.", env, env, DefaultMemCoeffDec) return DefaultMemCoeffDec } @@ -216,13 +215,13 @@ func (r *Reconciler) getCPUMin(containerName string) uint64 { if v, ok := os.LookupEnv(env); ok { minQ, err := resource.ParseQuantity(v) if err != nil { - r.L.Printf("error cannot parse environment variable: %s. Set %s to default value: %d milliCPU(s).", + r.L.Error().Msgf("error cannot parse environment variable: %s. Set %s to default value: %d milliCPU(s).", env, env, DefaultCPUMin) return DefaultCPUMin } min := minQ.MilliValue() if min <= 0 { - r.L.Printf("error environment variable: %s should be bigger than 0. Set %s to default value: %d milliCPU(s)", + r.L.Error().Msgf("error environment variable: %s should be bigger than 0. Set %s to default value: %d milliCPU(s)", env, env, DefaultCPUMin) return DefaultCPUMin } @@ -237,13 +236,13 @@ func (r *Reconciler) getCPUMax(containerName string) uint64 { if v, ok := os.LookupEnv(env); ok { maxQ, err := resource.ParseQuantity(v) if err != nil { - r.L.Printf("error cannot parse environment variable: %s. Set %s to default value: %d milliCPU(s).", + r.L.Error().Msgf("error cannot parse environment variable: %s. Set %s to default value: %d milliCPU(s).", env, env, DefaultCPUMax) return DefaultCPUMax } max := maxQ.MilliValue() if max <= 0 { - r.L.Printf("error environment variable: %s should be bigger than 0. Set %s to default value: %d milliCPU(s)", + r.L.Error().Msgf("error environment variable: %s should be bigger than 0. Set %s to default value: %d milliCPU(s)", env, env, DefaultCPUMax) return DefaultCPUMax } @@ -258,7 +257,7 @@ func (r *Reconciler) getCPUInterval(containerName string) uint64 { if v, ok := os.LookupEnv(env); ok { interval, err := strconv.ParseUint(v, 10, 64) if err != nil { - r.L.Printf("error cannot parse environment variable: %s. Set %s to default value: %ds.", + r.L.Error().Msgf("error cannot parse environment variable: %s. Set %s to default value: %ds.", env, env, DefaultCPUInterval) return DefaultCPUInterval } @@ -273,7 +272,7 @@ func (r *Reconciler) getCPUCoeff(containerName string) uint64 { if v, ok := os.LookupEnv(env); ok { coeff, err := strconv.ParseUint(v, 10, 64) if err != nil { - r.L.Printf("error cannot parse environment variable: %s. Set %s to default value: %ds.", + r.L.Error().Msgf("error cannot parse environment variable: %s. Set %s to default value: %ds.", env, env, DefaultCPUCoeff) return DefaultCPUCoeff } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 2064955..12d4600 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -3,9 +3,12 @@ package utils import ( "crypto/tls" "crypto/x509" + "fmt" "net/http" "os" + "regexp" "strings" + "strconv" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -59,3 +62,54 @@ func GetBearerToken() (string, error) { return "Bearer " + string(token), nil } + +type MemPsi struct { + Some MemMetrics + Full MemMetrics +} + +// MemMetrics holds the actual metric data. +type MemMetrics struct { + Avg10 float64 + Avg60 float64 + Avg300 float64 + Total uint64 +} + +func parseMemPsiOutput(output string) (*MemPsi, error) { + // Regex to capture "some" and "full" separately + re := regexp.MustCompile(`(some|full) avg10=(\d+\.\d+) avg60=(\d+\.\d+) avg300=(\d+\.\d+) total=(\d+)`) + matches := re.FindAllStringSubmatch(output, -1) + + if matches == nil || len(matches) < 2 { + return nil, fmt.Errorf("expected data for both 'some' and 'full' not found") + } + + psi := &MemPsi{} + for _, match := range matches { + metrics := MemMetrics{ + Avg10: parseFloat(match[2]), + Avg60: parseFloat(match[3]), + Avg300: parseFloat(match[4]), + Total: parseUint(match[5]), + } + + if match[1] == "some" { + psi.Some = metrics + } else if match[1] == "full" { + psi.Full = metrics + } + } + + return psi, nil +} + +func parseFloat(value string) float64 { + result, _ := strconv.ParseFloat(value, 64) + return result +} + +func parseUint(value string) uint64 { + result, _ := strconv.ParseUint(value, 10, 64) + return result +}