From 281210ba66e7e138ee9cae0f363d0464f1600cfc Mon Sep 17 00:00:00 2001 From: Alexandr Kozlenkov Date: Sun, 28 Oct 2018 23:04:51 +0300 Subject: [PATCH 1/6] A draft of connector to kubernetse --- Gopkg.lock | 240 +++++++++++++++++++++---- Gopkg.toml | 4 + connector/collector/kubernetes.go | 125 +++++++++++++ connector/collector/kubernetes_logs.go | 78 ++++++++ connector/kubernetes.go | 195 ++++++++++++++++++++ connector/manager/kubernetes.go | 64 +++++++ main.go | 2 +- 7 files changed, 668 insertions(+), 40 deletions(-) create mode 100644 connector/collector/kubernetes.go create mode 100644 connector/collector/kubernetes_logs.go create mode 100644 connector/kubernetes.go create mode 100644 connector/manager/kubernetes.go diff --git a/Gopkg.lock b/Gopkg.lock index 8d7a4ce..c2af981 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,90 +2,142 @@ [[projects]] + branch = "master" name = "github.com/Azure/go-ansiterm" packages = [".","winterm"] - revision = "fa152c58bc15761d0200cb75fe958b89a9d4888e" + revision = "d6e3b3328b783f23731bc4d058875b0371ff8109" [[projects]] name = "github.com/BurntSushi/toml" packages = ["."] - revision = "b26d9c308763d68093482582cea63d69be07a0f0" - version = "v0.3.0" + revision = "3012a1dbe2e4bd1391d42b32f0577cb7bbc7f005" + version = "v0.3.1" [[projects]] name = "github.com/Microsoft/go-winio" packages = ["."] - revision = "fff283ad5116362ca252298cfc9b95828956d85d" - version = "v0.3.8" - -[[projects]] - branch = "master" - name = "github.com/Nvveen/Gotty" - packages = ["."] - revision = "cd527374f1e5bff4938207604a14f2e38a9cf512" + revision = "97e4973ce50b2ff5f09635a57e2b88a037aae829" + version = "v0.4.11" [[projects]] name = "github.com/Sirupsen/logrus" packages = ["."] - revision = "26709e2714106fb8ad40b773b711ebce25b78914" + revision = "ad15b42461921f1fb3529b058c6786c6a45d5162" + version = "v1.1.1" [[projects]] + branch = "master" name = "github.com/c9s/goprocinfo" packages = ["linux"] - revision = "b34328d6e0cd139894ea7347d2624ccf31fa3c58" + revision = "0010a05ce49fde7f50669bc7ecda7d41dd6ab824" [[projects]] name = "github.com/coreos/go-systemd" packages = ["dbus","util"] - revision = "b4a58d95188dd092ae20072bac14cece0e67c388" + revision = "39ca1b05acc7ad1220e09f133283b8859a8b71ab" + version = "v17" + +[[projects]] + name = "github.com/coreos/pkg" + packages = ["dlopen"] + revision = "97fdf19511ea361ae1c100dd393cc47f8dcfa1e1" version = "v4" [[projects]] name = "github.com/docker/docker" packages = ["api/types","api/types/blkiodev","api/types/container","api/types/filters","api/types/mount","api/types/network","api/types/registry","api/types/strslice","api/types/swarm","api/types/versions","opts","pkg/archive","pkg/fileutils","pkg/homedir","pkg/idtools","pkg/ioutils","pkg/jsonlog","pkg/jsonmessage","pkg/longpath","pkg/mount","pkg/pools","pkg/promise","pkg/stdcopy","pkg/symlink","pkg/system","pkg/term","pkg/term/windows"] - revision = "90d35abf7b3535c1c319c872900fbd76374e521c" - version = "v17.05.0-ce-rc3" + revision = "092cba3727bb9b4a2f0e922cd6c0f93ea270e363" + version = "v1.13.1" [[projects]] name = "github.com/docker/go-connections" packages = ["nat"] - revision = "a2afab9802043837035592f1c24827fb70766de9" + revision = "7395e3f8aa162843a74ed6d48e79627d9792ac55" + version = "v0.4.0" [[projects]] - branch = "master" name = "github.com/docker/go-units" packages = ["."] - revision = "0dadbb0345b35ec7ef35e228dabb8de89a65bf52" + revision = "47565b4f722fb6ceae66b95f853feed578a4a51c" + version = "v0.3.3" [[projects]] name = "github.com/fsouza/go-dockerclient" packages = ["."] revision = "318513eb1ab27495afbc67f671ba1080513d8aa0" +[[projects]] + name = "github.com/ghodss/yaml" + packages = ["."] + revision = "0ca9ea5df5451ffdf184b4428c902747c2c11cd7" + version = "v1.0.0" + [[projects]] branch = "master" name = "github.com/gizak/termui" packages = ["."] - revision = "cdc199d7ea432fd8187db35f0247285d6f5b0267" + revision = "4eb80249d3f5d00f91dbfb780dcfbf2215cad59b" source = "https://github.com/bcicen/termui" [[projects]] name = "github.com/godbus/dbus" packages = ["."] - revision = "c7fdd8b5cd55e87b4e1f4e372cdb1db61dd6c66f" - version = "v3" + revision = "a389bdde4dd695d414e47b755e95e72b7826432c" + version = "v4.1.0" + +[[projects]] + name = "github.com/gogo/protobuf" + packages = ["proto","sortkeys"] + revision = "636bf0302bc95575d69441b25a2603156ffdddf1" + version = "v1.1.1" [[projects]] branch = "master" + name = "github.com/golang/glog" + packages = ["."] + revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" + +[[projects]] name = "github.com/golang/protobuf" - packages = ["proto"] - revision = "0a4f71a498b7c4812f64969510bcb4eca251e33a" + packages = ["proto","ptypes","ptypes/any","ptypes/duration","ptypes/timestamp"] + revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5" + version = "v1.2.0" [[projects]] branch = "master" + name = "github.com/google/btree" + packages = ["."] + revision = "4030bb1f1f0c35b30ca7009e9ebd06849dd45306" + +[[projects]] + branch = "master" + name = "github.com/google/gofuzz" + packages = ["."] + revision = "24818f796faf91cd76ec7bddd72458fbced7a6c1" + +[[projects]] + name = "github.com/googleapis/gnostic" + packages = ["OpenAPIv2","compiler","extensions"] + revision = "7c663266750e7d82587642f65e60bc4083f1f84e" + version = "v0.2.0" + +[[projects]] + branch = "master" + name = "github.com/gregjones/httpcache" + packages = [".","diskcache"] + revision = "9cad4c3443a7200dd6400aef47183728de563a38" + +[[projects]] name = "github.com/hashicorp/go-cleanhttp" packages = ["."] - revision = "3573b8b52aa7b37b9358d966a898feb387f62437" + revision = "e8ab9daed8d1ddd2d3c4efba338fe2eeae2e4f18" + version = "v0.5.0" + +[[projects]] + name = "github.com/imdario/mergo" + packages = ["."] + revision = "9f23e2d6bd2a77f959b2bf6acdbefd708a83a4a4" + version = "v0.3.6" [[projects]] branch = "master" @@ -94,25 +146,46 @@ revision = "16d037c7cc3c9b552fe4af9828b7338d752dbaf9" [[projects]] - name = "github.com/maruel/panicparse" - packages = ["stack"] - revision = "25bcac0d793cf4109483505a0d66e066a3a90a80" + name = "github.com/json-iterator/go" + packages = ["."] + revision = "1624edc4454b8682399def8740d46db5e4362ba4" + version = "v1.1.5" + +[[projects]] + name = "github.com/konsorten/go-windows-terminal-sequences" + packages = ["."] + revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242" + version = "v1.0.1" [[projects]] name = "github.com/mattn/go-runewidth" packages = ["."] - revision = "14207d285c6c197daabb5c9793d63e7af9ab2d50" + revision = "ce7b0b5c7b45a81508558cd1dba6bb1e4ddb51bb" + version = "v0.0.3" [[projects]] - branch = "master" name = "github.com/mitchellh/go-wordwrap" packages = ["."] - revision = "ad45545899c7b13c020ea92b2072220eefad42b8" + revision = "9e67c67572bc5dd02aef930e2b0ae3c02a4b5a5c" + version = "v1.0.0" [[projects]] + name = "github.com/modern-go/concurrent" + packages = ["."] + revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94" + version = "1.0.3" + +[[projects]] + name = "github.com/modern-go/reflect2" + packages = ["."] + revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd" + version = "1.0.1" + +[[projects]] + branch = "master" name = "github.com/nsf/termbox-go" packages = ["."] - revision = "e2050e41c8847748ec5288741c0b19a8cb26d084" + revision = "60ab7e3d12ed91bc1b2486559c4b3a6b62297577" [[projects]] branch = "master" @@ -132,34 +205,123 @@ revision = "baf6536d6259209c3edfa2b22237af82942d3dfa" version = "v0.1.1" +[[projects]] + branch = "master" + name = "github.com/petar/GoLLRB" + packages = ["llrb"] + revision = "53be0d36a84c2a886ca057d34b6aa4468df9ccb4" + +[[projects]] + name = "github.com/peterbourgon/diskv" + packages = ["."] + revision = "5f041e8faa004a95c88a202771f4cc3e991971e6" + version = "v2.0.1" + [[projects]] name = "github.com/seccomp/libseccomp-golang" packages = ["."] - revision = "1b506fc7c24eec5a3693cdcbed40d9c226cfc6a1" + revision = "e3496e3a417d1dc9ecdceca5af2513271fed37a0" + version = "v0.9.0" [[projects]] + name = "github.com/spf13/pflag" + packages = ["."] + revision = "298182f68c66c05229eb03ac171abe6e309ee79a" + version = "v1.0.3" + +[[projects]] + branch = "master" name = "github.com/syndtr/gocapability" packages = ["capability"] - revision = "2c00daeb6c3b45114c80ac44119e7b8801fdd852" + revision = "d98352740cb2c55f81556b63d4a1ec64c5a319c2" [[projects]] name = "github.com/vishvananda/netlink" packages = [".","nl"] - revision = "1e2e08e8a2dcdacaae3f14ac44c5cfa31361f270" + revision = "a2ad57a690f3caf3015351d2d6e1c0b95c349752" + version = "v1.0.0" + +[[projects]] + branch = "master" + name = "github.com/vishvananda/netns" + packages = ["."] + revision = "13995c7128ccc8e51e9a6bd2b551020a27180abd" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = ["ssh/terminal"] + revision = "e84da0312774c21d64ee2317962ef669b27ffb41" [[projects]] + branch = "master" name = "golang.org/x/net" - packages = ["context","context/ctxhttp"] - revision = "a6577fac2d73be281a500b310739095313165611" + packages = ["context","context/ctxhttp","http/httpguts","http2","http2/hpack","idna"] + revision = "9b4f9f5ad5197c79fd623a3638e70d8b26cef344" [[projects]] + branch = "master" + name = "golang.org/x/oauth2" + packages = [".","internal"] + revision = "9dcd33a902f40452422c2367fefcb95b54f9f8f8" + +[[projects]] + branch = "master" name = "golang.org/x/sys" packages = ["unix","windows"] - revision = "99f16d856c9836c42d24e7ab64ea72916925fa97" + revision = "95b1ffbd15a57cc5abb3f04402b9e8ec0016a52c" + +[[projects]] + name = "golang.org/x/text" + packages = ["collate","collate/build","internal/colltab","internal/gen","internal/tag","internal/triegen","internal/ucd","language","secure/bidirule","transform","unicode/bidi","unicode/cldr","unicode/norm","unicode/rangetable"] + revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" + version = "v0.3.0" + +[[projects]] + branch = "master" + name = "golang.org/x/time" + packages = ["rate"] + revision = "fbb02b2291d28baffd63558aa44b4b56f178d650" + +[[projects]] + name = "google.golang.org/appengine" + packages = ["internal","internal/base","internal/datastore","internal/log","internal/remote_api","internal/urlfetch","urlfetch"] + revision = "ae0ab99deb4dc413a2b4bd6c8bdd0eb67f1e4d06" + version = "v1.2.0" + +[[projects]] + name = "gopkg.in/inf.v0" + packages = ["."] + revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf" + version = "v0.9.1" + +[[projects]] + name = "gopkg.in/yaml.v2" + packages = ["."] + revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" + version = "v2.2.1" + +[[projects]] + branch = "master" + name = "k8s.io/api" + packages = ["admissionregistration/v1alpha1","admissionregistration/v1beta1","apps/v1","apps/v1beta1","apps/v1beta2","authentication/v1","authentication/v1beta1","authorization/v1","authorization/v1beta1","autoscaling/v1","autoscaling/v2beta1","autoscaling/v2beta2","batch/v1","batch/v1beta1","batch/v2alpha1","certificates/v1beta1","coordination/v1beta1","core/v1","events/v1beta1","extensions/v1beta1","networking/v1","policy/v1beta1","rbac/v1","rbac/v1alpha1","rbac/v1beta1","scheduling/v1alpha1","scheduling/v1beta1","settings/v1alpha1","storage/v1","storage/v1alpha1","storage/v1beta1"] + revision = "9fcf73cc980bd64f38a4f721a7371b0ebb72e1ff" + +[[projects]] + branch = "master" + name = "k8s.io/apimachinery" + packages = ["pkg/api/errors","pkg/api/meta","pkg/api/resource","pkg/apis/meta/v1","pkg/apis/meta/v1/unstructured","pkg/apis/meta/v1beta1","pkg/conversion","pkg/conversion/queryparams","pkg/fields","pkg/labels","pkg/runtime","pkg/runtime/schema","pkg/runtime/serializer","pkg/runtime/serializer/json","pkg/runtime/serializer/protobuf","pkg/runtime/serializer/recognizer","pkg/runtime/serializer/streaming","pkg/runtime/serializer/versioning","pkg/selection","pkg/types","pkg/util/clock","pkg/util/errors","pkg/util/framer","pkg/util/intstr","pkg/util/json","pkg/util/naming","pkg/util/net","pkg/util/runtime","pkg/util/sets","pkg/util/validation","pkg/util/validation/field","pkg/util/yaml","pkg/version","pkg/watch","third_party/forked/golang/reflect"] + revision = "b7f9f1fa80aef2c4d97c27d38bba371e6caccb47" + +[[projects]] + name = "k8s.io/client-go" + packages = ["discovery","kubernetes","kubernetes/scheme","kubernetes/typed/admissionregistration/v1alpha1","kubernetes/typed/admissionregistration/v1beta1","kubernetes/typed/apps/v1","kubernetes/typed/apps/v1beta1","kubernetes/typed/apps/v1beta2","kubernetes/typed/authentication/v1","kubernetes/typed/authentication/v1beta1","kubernetes/typed/authorization/v1","kubernetes/typed/authorization/v1beta1","kubernetes/typed/autoscaling/v1","kubernetes/typed/autoscaling/v2beta1","kubernetes/typed/autoscaling/v2beta2","kubernetes/typed/batch/v1","kubernetes/typed/batch/v1beta1","kubernetes/typed/batch/v2alpha1","kubernetes/typed/certificates/v1beta1","kubernetes/typed/coordination/v1beta1","kubernetes/typed/core/v1","kubernetes/typed/events/v1beta1","kubernetes/typed/extensions/v1beta1","kubernetes/typed/networking/v1","kubernetes/typed/policy/v1beta1","kubernetes/typed/rbac/v1","kubernetes/typed/rbac/v1alpha1","kubernetes/typed/rbac/v1beta1","kubernetes/typed/scheduling/v1alpha1","kubernetes/typed/scheduling/v1beta1","kubernetes/typed/settings/v1alpha1","kubernetes/typed/storage/v1","kubernetes/typed/storage/v1alpha1","kubernetes/typed/storage/v1beta1","pkg/apis/clientauthentication","pkg/apis/clientauthentication/v1alpha1","pkg/apis/clientauthentication/v1beta1","pkg/version","plugin/pkg/client/auth/exec","rest","rest/watch","tools/auth","tools/clientcmd","tools/clientcmd/api","tools/clientcmd/api/latest","tools/clientcmd/api/v1","tools/metrics","tools/reference","transport","util/cert","util/connrotation","util/flowcontrol","util/homedir","util/integer"] + revision = "1638f8970cefaa404ff3a62950f88b08292b2696" + version = "v9.0.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "a21d4c707f08f26de894adbdd00d5d6e82a54f5e0f52566229dd2da9b94e26ec" + inputs-digest = "afb9ce9b490a09ab8f277e95de2f89ae01eaa91ba0286bf72cc91f76d5996512" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index a33fc75..c3fe5d8 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -49,3 +49,7 @@ [[constraint]] name = "github.com/Microsoft/go-winio" version = "0.3.8" + +[[constraint]] + name = "github.com/kubernetes/client-go" + version = "9.0.0" diff --git a/connector/collector/kubernetes.go b/connector/collector/kubernetes.go new file mode 100644 index 0000000..2c78179 --- /dev/null +++ b/connector/collector/kubernetes.go @@ -0,0 +1,125 @@ +package collector + +import ( + "github.com/bcicen/ctop/config" + "github.com/bcicen/ctop/models" + "k8s.io/client-go/kubernetes" +) + +// Kubernetes collector +type Kubernetes struct { + models.Metrics + name string + client *kubernetes.Clientset + running bool + stream chan models.Metrics + done chan bool + lastCpu float64 + lastSysCpu float64 + scaleCpu bool +} + +func NewKubernetes(client *kubernetes.Clientset, name string) *Kubernetes { + return &Kubernetes{ + Metrics: models.Metrics{}, + name: name, + client: client, + scaleCpu: config.GetSwitchVal("scaleCpu"), + } +} + +func (c *Kubernetes) Start() { + //c.done = make(chan bool) + //c.stream = make(chan models.Metrics) + //stats := make(chan *api.Stats) + + //go func() { + // opts := api.StatsOptions{ + // ID: c.id, + // Stats: stats, + // Stream: true, + // Done: c.done, + // } + // c.client.Stats(opts) + // c.running = false + //}() + + //go func() { + // defer close(c.stream) + // for s := range stats { + // c.ReadCPU(s) + // c.ReadMem(s) + // c.ReadNet(s) + // c.ReadIO(s) + // c.stream <- c.Metrics + // } + // log.Infof("collector stopped for container: %s", c.id) + //}() + + //c.running = true + //log.Infof("collector started for container: %s", c.id) +} + +func (c *Kubernetes) Running() bool { + return c.running +} + +func (c *Kubernetes) Stream() chan models.Metrics { + return c.stream +} + +func (c *Kubernetes) Logs() LogCollector { + return NewKubernetesLogs(c.name, c.client) +} + +// Stop collector +func (c *Kubernetes) Stop() { + c.done <- true +} + +// +//func (c *Kubernetes) ReadCPU(stats *api.Stats) { +// ncpus := float64(len(stats.CPUStats.CPUUsage.PercpuUsage)) +// total := float64(stats.CPUStats.CPUUsage.TotalUsage) +// system := float64(stats.CPUStats.SystemCPUUsage) +// +// cpudiff := total - c.lastCpu +// syscpudiff := system - c.lastSysCpu +// +// if c.scaleCpu { +// c.CPUUtil = round((cpudiff / syscpudiff * 100)) +// } else { +// c.CPUUtil = round((cpudiff / syscpudiff * 100) * ncpus) +// } +// c.lastCpu = total +// c.lastSysCpu = system +// c.Pids = int(stats.PidsStats.Current) +//} + +//func (c *Kubernetes) ReadMem(stats *api.Stats) { +// c.MemUsage = int64(stats.MemoryStats.Usage - stats.MemoryStats.Stats.Cache) +// c.MemLimit = int64(stats.MemoryStats.Limit) +// c.MemPercent = percent(float64(c.MemUsage), float64(c.MemLimit)) +//} + +//func (c *Kubernetes) ReadNet(stats *api.Stats) { +// var rx, tx int64 +// for _, network := range stats.Networks { +// rx += int64(network.RxBytes) +// tx += int64(network.TxBytes) +// } +// c.NetRx, c.NetTx = rx, tx +//} +// +//func (c *Kubernetes) ReadIO(stats *api.Stats) { +// var read, write int64 +// for _, blk := range stats.BlkioStats.IOServiceBytesRecursive { +// if blk.Op == "Read" { +// read = int64(blk.Value) +// } +// if blk.Op == "Write" { +// write = int64(blk.Value) +// } +// } +// c.IOBytesRead, c.IOBytesWrite = read, write +//} diff --git a/connector/collector/kubernetes_logs.go b/connector/collector/kubernetes_logs.go new file mode 100644 index 0000000..e118ce9 --- /dev/null +++ b/connector/collector/kubernetes_logs.go @@ -0,0 +1,78 @@ +package collector + +import ( + "time" + + "github.com/bcicen/ctop/models" + "k8s.io/client-go/kubernetes" +) + +type KubernetesLogs struct { + id string + client *kubernetes.Clientset + done chan bool +} + +func NewKubernetesLogs(id string, client *kubernetes.Clientset) *KubernetesLogs { + return &KubernetesLogs{ + id: id, + client: client, + done: make(chan bool), + } +} + +func (l *KubernetesLogs) Stream() chan models.Log { + //r, w := io.Pipe() + logCh := make(chan models.Log) + //ctx, cancel := context.WithCancel(context.Background()) + + //opts := api.LogsOptions{ + // Context: ctx, + // Container: l.id, + // OutputStream: w, + // ErrorStream: w, + // Stdout: true, + // Stderr: true, + // Tail: "10", + // Follow: true, + // Timestamps: true, + //} + + //// read io pipe into channel + //go func() { + // scanner := bufio.NewScanner(r) + // for scanner.Scan() { + // parts := strings.Split(scanner.Text(), " ") + // ts := l.parseTime(parts[0]) + // logCh <- models.Log{Timestamp: ts, Message: strings.Join(parts[1:], " ")} + // } + //}() + + //// connect to container log stream + //go func() { + // err := l.client.Logs(opts) + // if err != nil { + // log.Errorf("error reading container logs: %s", err) + // } + // log.Infof("log reader stopped for container: %s", l.id) + //}() + + //go func() { + // <-l.done + // cancel() + //}() + + log.Infof("log reader started for container: %s", l.id) + return logCh +} + +func (l *KubernetesLogs) Stop() { l.done <- true } + +func (l *KubernetesLogs) parseTime(s string) time.Time { + ts, err := time.Parse("2006-01-02T15:04:05.000000000Z", s) + if err != nil { + log.Errorf("failed to parse container log: %s", err) + ts = time.Now() + } + return ts +} diff --git a/connector/kubernetes.go b/connector/kubernetes.go new file mode 100644 index 0000000..02f4576 --- /dev/null +++ b/connector/kubernetes.go @@ -0,0 +1,195 @@ +package connector + +import ( + "os" + "path/filepath" + "sync" + + "github.com/bcicen/ctop/connector/collector" + "github.com/bcicen/ctop/connector/manager" + "github.com/bcicen/ctop/container" + api "github.com/fsouza/go-dockerclient" + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +func init() { enabled["kubernetes"] = NewKubernetes } + +var namespace = "akozlenkov" + +type Kubernetes struct { + clientset *kubernetes.Clientset + containers map[string]*container.Container + needsRefresh chan string // container IDs requiring refresh + lock sync.RWMutex +} + +func NewKubernetes() Connector { + var kubeconfig string + //if home := homeDir(); home != "" { + // kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file") + //} else { + // kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file") + //} + //flag.Parse() + kubeconfig = filepath.Join(homeDir(), ".kube", "config") + + // use the current context in kubeconfig + config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + log.Error(err.Error()) + return nil + } + + // create the clientset + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + log.Error(err.Error()) + return nil + } + + // init docker client + k := &Kubernetes{ + clientset: clientset, + containers: make(map[string]*container.Container), + needsRefresh: make(chan string, 60), + lock: sync.RWMutex{}, + } + go k.Loop() + k.refreshAll() + return k +} + +func (k *Kubernetes) Loop() { + for id := range k.needsRefresh { + c := k.MustGet(id) + k.refresh(c) + } + //log.Debug(">>>>>>1") + //for { + // log.Debug(">>>>>>2") + // pods, err := k.clientset.CoreV1().Pods("").List(metav1.ListOptions{}) + // if err != nil { + // panic(err.Error()) + // } + // log.Debugf("There are %d pods in the cluster\n", len(pods.Items)) + + // // Examples for error handling: + // // - Use helper functions like e.g. errors.IsNotFound() + // // - And/or cast to StatusError and use its properties like e.g. ErrStatus.Message + // namespace := "akozlenkov" + // pod := "example-xxxxx" + // _, err = k.clientset.CoreV1().Pods(namespace).Get(pod, metav1.GetOptions{}) + // if errors.IsNotFound(err) { + // log.Debugf("Pod %s in namespace %s not found\n", pod, namespace) + // } else if statusError, isStatus := err.(*errors.StatusError); isStatus { + // log.Debugf("Error getting pod %s in namespace %s: %v\n", + // pod, namespace, statusError.ErrStatus.Message) + // } else if err != nil { + // panic(err.Error()) + // } else { + // log.Debugf("Found pod %s in namespace %s\n", pod, namespace) + // } + + // time.Sleep(10 * time.Second) + //} +} + +// Get a single container, creating one anew if not existing +func (k *Kubernetes) MustGet(name string) *container.Container { + c, ok := k.Get(name) + // append container struct for new containers + if !ok { + // create collector + collector := collector.NewKubernetes(k.clientset, name) + // create manager + manager := manager.NewKubernetes(k.clientset, name) + // create container + c = container.New(name, collector, manager) + k.lock.Lock() + k.containers[name] = c + k.lock.Unlock() + } + return c +} + +func (k *Kubernetes) refresh(c *container.Container) { + insp := k.inspect(c.Id) + // remove container if no longer exists + if insp == nil { + k.delByID(c.Id) + return + } + c.SetMeta("name", insp.Name) + c.SetMeta("image", "stub") + c.SetMeta("IPs", "stub") + c.SetMeta("ports", "stub") + c.SetMeta("created", "stub") + c.SetMeta("health", "stub") + c.SetMeta("[ENV-VAR]", "stub") + c.SetState("stub") +} + +func (k *Kubernetes) inspect(id string) *v1.Pod { + p, err := k.clientset.CoreV1().Pods(namespace).Get(id, metav1.GetOptions{}) + if err != nil { + if _, ok := err.(*api.NoSuchContainer); !ok { + log.Errorf(err.Error()) + } + } + return p +} + +// Remove containers by ID +func (k *Kubernetes) delByID(name string) { + k.lock.Lock() + delete(k.containers, name) + k.lock.Unlock() + log.Infof("removed dead container: %s", name) +} + +func (k *Kubernetes) Get(name string) (c *container.Container, ok bool) { + k.lock.Lock() + c, ok = k.containers[name] + k.lock.Unlock() + return +} + +// Mark all container IDs for refresh +func (k *Kubernetes) refreshAll() { + allPods, err := k.clientset.CoreV1().Pods(namespace).List(metav1.ListOptions{}) + if err != nil { + log.Error(err.Error()) + return + } + + for _, pod := range allPods.Items { + c := k.MustGet(pod.Name) + c.SetMeta("name", pod.Name) + if pod.Initializers != nil && pod.Initializers.Result != nil { + c.SetState(pod.Initializers.Result.Status) + } + k.needsRefresh <- c.Id + } +} + +func (k *Kubernetes) All() (containers container.Containers) { + k.lock.Lock() + for _, c := range k.containers { + containers = append(containers, c) + } + + containers.Sort() + containers.Filter() + k.lock.Unlock() + return containers +} + +func homeDir() string { + if h := os.Getenv("HOME"); h != "" { + return h + } + return os.Getenv("USERPROFILE") // windows +} diff --git a/connector/manager/kubernetes.go b/connector/manager/kubernetes.go new file mode 100644 index 0000000..6a766fa --- /dev/null +++ b/connector/manager/kubernetes.go @@ -0,0 +1,64 @@ +package manager + +import ( + "k8s.io/client-go/kubernetes" +) + +type Kubernetes struct { + id string + client *kubernetes.Clientset +} + +func NewKubernetes(client *kubernetes.Clientset, id string) *Kubernetes { + return &Kubernetes{ + id: id, + client: client, + } +} + +func (dc *Kubernetes) Start() error { + //c, err := dc.client.InspectContainer(dc.id) + //if err != nil { + // return fmt.Errorf("cannot inspect container: %v", err) + //} + + //if err := dc.client.StartContainer(c.ID, c.HostConfig); err != nil { + // return fmt.Errorf("cannot start container: %v", err) + //} + return nil +} + +func (dc *Kubernetes) Stop() error { + //if err := dc.client.StopContainer(dc.id, 3); err != nil { + // return fmt.Errorf("cannot stop container: %v", err) + //} + return nil +} + +func (dc *Kubernetes) Remove() error { + //if err := dc.client.RemoveContainer(api.RemoveContainerOptions{ID: dc.id}); err != nil { + // return fmt.Errorf("cannot remove container: %v", err) + //} + return nil +} + +func (dc *Kubernetes) Pause() error { + //if err := dc.client.PauseContainer(dc.id); err != nil { + // return fmt.Errorf("cannot pause container: %v", err) + //} + return nil +} + +func (dc *Kubernetes) Unpause() error { + //if err := dc.client.UnpauseContainer(dc.id); err != nil { + // return fmt.Errorf("cannot unpause container: %v", err) + //} + return nil +} + +func (dc *Kubernetes) Restart() error { + //if err := dc.client.RestartContainer(dc.id, 3); err != nil { + // return fmt.Errorf("cannot restart container: %v", err) + //} + return nil +} diff --git a/main.go b/main.go index 3a93a4c..ea88f49 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,7 @@ func main() { // parse command line arguments var ( - versionFlag = flag.Bool("v", false, "output version information and exit") + versionFlag = flag.Bool("V", false, "output version information and exit") helpFlag = flag.Bool("h", false, "display this help dialog") filterFlag = flag.String("f", "", "filter containers") activeOnlyFlag = flag.Bool("a", false, "show active containers only") From 8a9009d4fc4706070ab4af0b9896c19b2cc56c00 Mon Sep 17 00:00:00 2001 From: Alexandr Kozlenkov Date: Mon, 29 Oct 2018 02:05:25 +0300 Subject: [PATCH 2/6] Added draft for collector metrics of k8s --- Gopkg.lock | 80 +++++++++++++++++++-------- Gopkg.toml | 6 ++ connector/collector/kubernetes.go | 54 ++++++++++-------- connector/kubernetes.go | 92 +++++++++++++++++++------------ cwidgets/compact/status.go | 6 +- main.go | 2 +- 6 files changed, 157 insertions(+), 83 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index c2af981..637a8e2 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -25,6 +25,12 @@ revision = "ad15b42461921f1fb3529b058c6786c6a45d5162" version = "v1.1.1" +[[projects]] + branch = "master" + name = "github.com/beorn7/perks" + packages = ["quantile"] + revision = "3a771d992973f24aa725d07868b467d1ddfceafb" + [[projects]] branch = "master" name = "github.com/c9s/goprocinfo" @@ -157,12 +163,24 @@ revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242" version = "v1.0.1" +[[projects]] + name = "github.com/kubernetes-incubator/metrics-server" + packages = ["pkg/metrics","pkg/provider","pkg/provider/sink","pkg/sink","pkg/sources"] + revision = "6cd52610db69d662b3b7890b73df82b3b3601ab3" + version = "v0.3.1" + [[projects]] name = "github.com/mattn/go-runewidth" packages = ["."] revision = "ce7b0b5c7b45a81508558cd1dba6bb1e4ddb51bb" version = "v0.0.3" +[[projects]] + name = "github.com/matttproud/golang_protobuf_extensions" + packages = ["pbutil"] + revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" + version = "v1.0.1" + [[projects]] name = "github.com/mitchellh/go-wordwrap" packages = ["."] @@ -217,6 +235,30 @@ revision = "5f041e8faa004a95c88a202771f4cc3e991971e6" version = "v2.0.1" +[[projects]] + name = "github.com/prometheus/client_golang" + packages = ["prometheus"] + revision = "c5b7fccd204277076155f10851dad72b76a49317" + version = "v0.8.0" + +[[projects]] + branch = "master" + name = "github.com/prometheus/client_model" + packages = ["go"] + revision = "5c3871d89910bfb32f5fcab2aa4b9ec68e65a99f" + +[[projects]] + branch = "master" + name = "github.com/prometheus/common" + packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"] + revision = "7e9e6cabbd393fc208072eedef99188d0ce788b6" + +[[projects]] + branch = "master" + name = "github.com/prometheus/procfs" + packages = [".","internal/util","nfs","xfs"] + revision = "185b4288413d2a0dd0806f78c90dde719829e5ae" + [[projects]] name = "github.com/seccomp/libseccomp-golang" packages = ["."] @@ -259,12 +301,6 @@ packages = ["context","context/ctxhttp","http/httpguts","http2","http2/hpack","idna"] revision = "9b4f9f5ad5197c79fd623a3638e70d8b26cef344" -[[projects]] - branch = "master" - name = "golang.org/x/oauth2" - packages = [".","internal"] - revision = "9dcd33a902f40452422c2367fefcb95b54f9f8f8" - [[projects]] branch = "master" name = "golang.org/x/sys" @@ -283,12 +319,6 @@ packages = ["rate"] revision = "fbb02b2291d28baffd63558aa44b4b56f178d650" -[[projects]] - name = "google.golang.org/appengine" - packages = ["internal","internal/base","internal/datastore","internal/log","internal/remote_api","internal/urlfetch","urlfetch"] - revision = "ae0ab99deb4dc413a2b4bd6c8bdd0eb67f1e4d06" - version = "v1.2.0" - [[projects]] name = "gopkg.in/inf.v0" packages = ["."] @@ -302,26 +332,32 @@ version = "v2.2.1" [[projects]] - branch = "master" + branch = "release-1.11" name = "k8s.io/api" - packages = ["admissionregistration/v1alpha1","admissionregistration/v1beta1","apps/v1","apps/v1beta1","apps/v1beta2","authentication/v1","authentication/v1beta1","authorization/v1","authorization/v1beta1","autoscaling/v1","autoscaling/v2beta1","autoscaling/v2beta2","batch/v1","batch/v1beta1","batch/v2alpha1","certificates/v1beta1","coordination/v1beta1","core/v1","events/v1beta1","extensions/v1beta1","networking/v1","policy/v1beta1","rbac/v1","rbac/v1alpha1","rbac/v1beta1","scheduling/v1alpha1","scheduling/v1beta1","settings/v1alpha1","storage/v1","storage/v1alpha1","storage/v1beta1"] - revision = "9fcf73cc980bd64f38a4f721a7371b0ebb72e1ff" + packages = ["admissionregistration/v1alpha1","admissionregistration/v1beta1","apps/v1","apps/v1beta1","apps/v1beta2","authentication/v1","authentication/v1beta1","authorization/v1","authorization/v1beta1","autoscaling/v1","autoscaling/v2beta1","batch/v1","batch/v1beta1","batch/v2alpha1","certificates/v1beta1","core/v1","events/v1beta1","extensions/v1beta1","networking/v1","policy/v1beta1","rbac/v1","rbac/v1alpha1","rbac/v1beta1","scheduling/v1alpha1","scheduling/v1beta1","settings/v1alpha1","storage/v1","storage/v1alpha1","storage/v1beta1"] + revision = "4e7be11eab3ffcfc1876898b8272df53785a9504" [[projects]] - branch = "master" + branch = "release-1.11" name = "k8s.io/apimachinery" - packages = ["pkg/api/errors","pkg/api/meta","pkg/api/resource","pkg/apis/meta/v1","pkg/apis/meta/v1/unstructured","pkg/apis/meta/v1beta1","pkg/conversion","pkg/conversion/queryparams","pkg/fields","pkg/labels","pkg/runtime","pkg/runtime/schema","pkg/runtime/serializer","pkg/runtime/serializer/json","pkg/runtime/serializer/protobuf","pkg/runtime/serializer/recognizer","pkg/runtime/serializer/streaming","pkg/runtime/serializer/versioning","pkg/selection","pkg/types","pkg/util/clock","pkg/util/errors","pkg/util/framer","pkg/util/intstr","pkg/util/json","pkg/util/naming","pkg/util/net","pkg/util/runtime","pkg/util/sets","pkg/util/validation","pkg/util/validation/field","pkg/util/yaml","pkg/version","pkg/watch","third_party/forked/golang/reflect"] - revision = "b7f9f1fa80aef2c4d97c27d38bba371e6caccb47" + packages = ["pkg/api/errors","pkg/api/meta","pkg/api/resource","pkg/apis/meta/v1","pkg/apis/meta/v1/unstructured","pkg/apis/meta/v1beta1","pkg/conversion","pkg/conversion/queryparams","pkg/fields","pkg/labels","pkg/runtime","pkg/runtime/schema","pkg/runtime/serializer","pkg/runtime/serializer/json","pkg/runtime/serializer/protobuf","pkg/runtime/serializer/recognizer","pkg/runtime/serializer/streaming","pkg/runtime/serializer/versioning","pkg/selection","pkg/types","pkg/util/clock","pkg/util/errors","pkg/util/framer","pkg/util/intstr","pkg/util/json","pkg/util/net","pkg/util/runtime","pkg/util/sets","pkg/util/validation","pkg/util/validation/field","pkg/util/wait","pkg/util/yaml","pkg/version","pkg/watch","third_party/forked/golang/reflect"] + revision = "8ee1a638bafa4ae9691077e690cb45dd54f45111" [[projects]] name = "k8s.io/client-go" - packages = ["discovery","kubernetes","kubernetes/scheme","kubernetes/typed/admissionregistration/v1alpha1","kubernetes/typed/admissionregistration/v1beta1","kubernetes/typed/apps/v1","kubernetes/typed/apps/v1beta1","kubernetes/typed/apps/v1beta2","kubernetes/typed/authentication/v1","kubernetes/typed/authentication/v1beta1","kubernetes/typed/authorization/v1","kubernetes/typed/authorization/v1beta1","kubernetes/typed/autoscaling/v1","kubernetes/typed/autoscaling/v2beta1","kubernetes/typed/autoscaling/v2beta2","kubernetes/typed/batch/v1","kubernetes/typed/batch/v1beta1","kubernetes/typed/batch/v2alpha1","kubernetes/typed/certificates/v1beta1","kubernetes/typed/coordination/v1beta1","kubernetes/typed/core/v1","kubernetes/typed/events/v1beta1","kubernetes/typed/extensions/v1beta1","kubernetes/typed/networking/v1","kubernetes/typed/policy/v1beta1","kubernetes/typed/rbac/v1","kubernetes/typed/rbac/v1alpha1","kubernetes/typed/rbac/v1beta1","kubernetes/typed/scheduling/v1alpha1","kubernetes/typed/scheduling/v1beta1","kubernetes/typed/settings/v1alpha1","kubernetes/typed/storage/v1","kubernetes/typed/storage/v1alpha1","kubernetes/typed/storage/v1beta1","pkg/apis/clientauthentication","pkg/apis/clientauthentication/v1alpha1","pkg/apis/clientauthentication/v1beta1","pkg/version","plugin/pkg/client/auth/exec","rest","rest/watch","tools/auth","tools/clientcmd","tools/clientcmd/api","tools/clientcmd/api/latest","tools/clientcmd/api/v1","tools/metrics","tools/reference","transport","util/cert","util/connrotation","util/flowcontrol","util/homedir","util/integer"] - revision = "1638f8970cefaa404ff3a62950f88b08292b2696" - version = "v9.0.0" + packages = ["discovery","kubernetes","kubernetes/scheme","kubernetes/typed/admissionregistration/v1alpha1","kubernetes/typed/admissionregistration/v1beta1","kubernetes/typed/apps/v1","kubernetes/typed/apps/v1beta1","kubernetes/typed/apps/v1beta2","kubernetes/typed/authentication/v1","kubernetes/typed/authentication/v1beta1","kubernetes/typed/authorization/v1","kubernetes/typed/authorization/v1beta1","kubernetes/typed/autoscaling/v1","kubernetes/typed/autoscaling/v2beta1","kubernetes/typed/batch/v1","kubernetes/typed/batch/v1beta1","kubernetes/typed/batch/v2alpha1","kubernetes/typed/certificates/v1beta1","kubernetes/typed/core/v1","kubernetes/typed/events/v1beta1","kubernetes/typed/extensions/v1beta1","kubernetes/typed/networking/v1","kubernetes/typed/policy/v1beta1","kubernetes/typed/rbac/v1","kubernetes/typed/rbac/v1alpha1","kubernetes/typed/rbac/v1beta1","kubernetes/typed/scheduling/v1alpha1","kubernetes/typed/scheduling/v1beta1","kubernetes/typed/settings/v1alpha1","kubernetes/typed/storage/v1","kubernetes/typed/storage/v1alpha1","kubernetes/typed/storage/v1beta1","pkg/apis/clientauthentication","pkg/apis/clientauthentication/v1alpha1","pkg/apis/clientauthentication/v1beta1","pkg/version","plugin/pkg/client/auth/exec","rest","rest/watch","tools/auth","tools/clientcmd","tools/clientcmd/api","tools/clientcmd/api/latest","tools/clientcmd/api/v1","tools/metrics","tools/reference","transport","util/cert","util/connrotation","util/flowcontrol","util/homedir","util/integer"] + revision = "7d04d0e2a0a1a4d4a1cd6baa432a2301492e4e65" + version = "v8.0.0" + +[[projects]] + branch = "release-1.11" + name = "k8s.io/metrics" + packages = ["pkg/apis/metrics"] + revision = "972ef826b8401c180b89cefc7457daa2d116daa9" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "afb9ce9b490a09ab8f277e95de2f89ae01eaa91ba0286bf72cc91f76d5996512" + inputs-digest = "c317229b631a11b3773e710f5a1faa24fa940ba9571388c743637431ec03a419" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index c3fe5d8..06f438e 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -53,3 +53,9 @@ [[constraint]] name = "github.com/kubernetes/client-go" version = "9.0.0" + +[[constraint]] + name = "github.com/kubernetes/metrics" + +[[constraint]] + name = "github.com/kubernetes-incubator/metrics-server" diff --git a/connector/collector/kubernetes.go b/connector/collector/kubernetes.go index 2c78179..2222df5 100644 --- a/connector/collector/kubernetes.go +++ b/connector/collector/kubernetes.go @@ -1,16 +1,21 @@ package collector import ( + "k8s.io/metrics/pkg/client/clientset_generated/clientset" + "github.com/bcicen/ctop/config" "github.com/bcicen/ctop/models" "k8s.io/client-go/kubernetes" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Kubernetes collector type Kubernetes struct { models.Metrics name string - client *kubernetes.Clientset + client clientset.Interface + clientset *kubernetes.Clientset running bool stream chan models.Metrics done chan bool @@ -21,28 +26,33 @@ type Kubernetes struct { func NewKubernetes(client *kubernetes.Clientset, name string) *Kubernetes { return &Kubernetes{ - Metrics: models.Metrics{}, - name: name, - client: client, - scaleCpu: config.GetSwitchVal("scaleCpu"), + Metrics: models.Metrics{}, + name: name, + client: clientset.New(client.RESTClient()), + clientset: client, + scaleCpu: config.GetSwitchVal("scaleCpu"), } } -func (c *Kubernetes) Start() { - //c.done = make(chan bool) - //c.stream = make(chan models.Metrics) - //stats := make(chan *api.Stats) +func (k *Kubernetes) Start() { + k.done = make(chan bool) + k.stream = make(chan models.Metrics) - //go func() { - // opts := api.StatsOptions{ - // ID: c.id, - // Stats: stats, - // Stream: true, - // Done: c.done, - // } - // c.client.Stats(opts) - // c.running = false - //}() + go func() { + k.running = false + for { + + cm, err := k.client.Metrics().PodMetricses("akozlenkov").List(metav1.ListOptions{}) + if err != nil { + log.Errorf(">>>>>> %s here %s", k.name, err.Error()) + continue + } + log.Debugf(">>>> %+v", cm) + //for _, m := range cm.Containers { + // log.Debugf(">>>> %+v", m) + //} + } + }() //go func() { // defer close(c.stream) @@ -56,8 +66,8 @@ func (c *Kubernetes) Start() { // log.Infof("collector stopped for container: %s", c.id) //}() - //c.running = true - //log.Infof("collector started for container: %s", c.id) + k.running = true + log.Infof("collector started for container: %s", k.name) } func (c *Kubernetes) Running() bool { @@ -69,7 +79,7 @@ func (c *Kubernetes) Stream() chan models.Metrics { } func (c *Kubernetes) Logs() LogCollector { - return NewKubernetesLogs(c.name, c.client) + return NewKubernetesLogs(c.name, c.clientset) } // Stop collector diff --git a/connector/kubernetes.go b/connector/kubernetes.go index 02f4576..4e7db32 100644 --- a/connector/kubernetes.go +++ b/connector/kubernetes.go @@ -1,9 +1,12 @@ package connector import ( + "fmt" "os" "path/filepath" + "strings" "sync" + "time" "github.com/bcicen/ctop/connector/collector" "github.com/bcicen/ctop/connector/manager" @@ -59,42 +62,46 @@ func NewKubernetes() Connector { } go k.Loop() k.refreshAll() + go k.watchEvents() return k } +func (k *Kubernetes) watchEvents() { + for { + log.Info("kubernetes event listener starting") + allEvents, err := k.clientset.CoreV1().Events(namespace).List(metav1.ListOptions{}) + if err != nil { + log.Error(err.Error()) + return + } + + for _, e := range allEvents.Items { + if e.Kind != "pod" { + continue + } + + actionName := strings.Split(e.Action, ":")[0] + + switch actionName { + case "start", "die", "pause", "unpause", "health_status": + log.Debugf("handling docker event: action=%s id=%s", e.Action, e.UID) + k.needsRefresh <- e.Name + case "destroy": + log.Debugf("handling docker event: action=%s id=%s", e.Action, e.UID) + k.delByID(e.Name) + default: + log.Debugf("handling docker event: %v", e) + k.needsRefresh <- e.Name + } + } + time.Sleep(1 * time.Second) + } +} func (k *Kubernetes) Loop() { for id := range k.needsRefresh { c := k.MustGet(id) k.refresh(c) } - //log.Debug(">>>>>>1") - //for { - // log.Debug(">>>>>>2") - // pods, err := k.clientset.CoreV1().Pods("").List(metav1.ListOptions{}) - // if err != nil { - // panic(err.Error()) - // } - // log.Debugf("There are %d pods in the cluster\n", len(pods.Items)) - - // // Examples for error handling: - // // - Use helper functions like e.g. errors.IsNotFound() - // // - And/or cast to StatusError and use its properties like e.g. ErrStatus.Message - // namespace := "akozlenkov" - // pod := "example-xxxxx" - // _, err = k.clientset.CoreV1().Pods(namespace).Get(pod, metav1.GetOptions{}) - // if errors.IsNotFound(err) { - // log.Debugf("Pod %s in namespace %s not found\n", pod, namespace) - // } else if statusError, isStatus := err.(*errors.StatusError); isStatus { - // log.Debugf("Error getting pod %s in namespace %s: %v\n", - // pod, namespace, statusError.ErrStatus.Message) - // } else if err != nil { - // panic(err.Error()) - // } else { - // log.Debugf("Found pod %s in namespace %s\n", pod, namespace) - // } - - // time.Sleep(10 * time.Second) - //} } // Get a single container, creating one anew if not existing @@ -123,13 +130,25 @@ func (k *Kubernetes) refresh(c *container.Container) { return } c.SetMeta("name", insp.Name) - c.SetMeta("image", "stub") - c.SetMeta("IPs", "stub") - c.SetMeta("ports", "stub") - c.SetMeta("created", "stub") - c.SetMeta("health", "stub") - c.SetMeta("[ENV-VAR]", "stub") - c.SetState("stub") + if len(insp.Spec.Containers) >= 1 { + c.SetMeta("image", insp.Spec.Containers[0].Image) + c.SetMeta("ports", k8sPort(insp.Spec.Containers[0].Ports)) + for _, env := range insp.Spec.Containers[0].Env { + c.SetMeta("[ENV-VAR]", env.Name+"="+env.Value) + } + } + c.SetMeta("IPs", insp.Status.PodIP) + c.SetMeta("created", insp.CreationTimestamp.Format("Mon Jan 2 15:04:05 2006")) + c.SetMeta("health", string(insp.Status.Phase)) + c.SetState("running") +} + +func k8sPort(ports []v1.ContainerPort) string { + str := []string{} + for _, p := range ports { + str = append(str, fmt.Sprintf("%s:%d -> %d", p.HostIP, p.HostPort, p.ContainerPort)) + } + return strings.Join(str, "\n") } func (k *Kubernetes) inspect(id string) *v1.Pod { @@ -167,9 +186,12 @@ func (k *Kubernetes) refreshAll() { for _, pod := range allPods.Items { c := k.MustGet(pod.Name) + c.SetMeta("uid", string(pod.UID)) c.SetMeta("name", pod.Name) if pod.Initializers != nil && pod.Initializers.Result != nil { c.SetState(pod.Initializers.Result.Status) + } else { + c.SetState(string(pod.Status.Phase)) } k.needsRefresh <- c.Id } diff --git a/cwidgets/compact/status.go b/cwidgets/compact/status.go index eec4c96..07fb272 100644 --- a/cwidgets/compact/status.go +++ b/cwidgets/compact/status.go @@ -67,11 +67,11 @@ func (s *Status) SetHealth(val string) { color := ui.ColorDefault switch val { - case "healthy": + case "healthy", "Succeeded": color = ui.ThemeAttr("status.ok") - case "unhealthy": + case "unhealthy", "Failed", "Unknown": color = ui.ThemeAttr("status.danger") - case "starting": + case "starting", "Pending", "Running": color = ui.ThemeAttr("status.warn") } diff --git a/main.go b/main.go index ea88f49..87df383 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,7 @@ func main() { // parse command line arguments var ( - versionFlag = flag.Bool("V", false, "output version information and exit") + versionFlag = flag.Bool("version", false, "output version information and exit") helpFlag = flag.Bool("h", false, "display this help dialog") filterFlag = flag.String("f", "", "filter containers") activeOnlyFlag = flag.Bool("a", false, "show active containers only") From d0204274dedcf1f77b0be1c068e98538a5731b38 Mon Sep 17 00:00:00 2001 From: Alexandr Kozlenkov Date: Mon, 29 Oct 2018 12:56:33 +0300 Subject: [PATCH 3/6] Added loading CPU and Mem for containers in pod --- Gopkg.lock | 46 +----------------- config/param.go | 5 ++ connector/collector/kubernetes.go | 79 ++++++++++++++----------------- connector/kubernetes.go | 11 +++-- main.go | 2 + 5 files changed, 51 insertions(+), 92 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 637a8e2..a7d0dc0 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -25,12 +25,6 @@ revision = "ad15b42461921f1fb3529b058c6786c6a45d5162" version = "v1.1.1" -[[projects]] - branch = "master" - name = "github.com/beorn7/perks" - packages = ["quantile"] - revision = "3a771d992973f24aa725d07868b467d1ddfceafb" - [[projects]] branch = "master" name = "github.com/c9s/goprocinfo" @@ -163,24 +157,12 @@ revision = "5c8c8bd35d3832f5d134ae1e1e375b69a4d25242" version = "v1.0.1" -[[projects]] - name = "github.com/kubernetes-incubator/metrics-server" - packages = ["pkg/metrics","pkg/provider","pkg/provider/sink","pkg/sink","pkg/sources"] - revision = "6cd52610db69d662b3b7890b73df82b3b3601ab3" - version = "v0.3.1" - [[projects]] name = "github.com/mattn/go-runewidth" packages = ["."] revision = "ce7b0b5c7b45a81508558cd1dba6bb1e4ddb51bb" version = "v0.0.3" -[[projects]] - name = "github.com/matttproud/golang_protobuf_extensions" - packages = ["pbutil"] - revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" - version = "v1.0.1" - [[projects]] name = "github.com/mitchellh/go-wordwrap" packages = ["."] @@ -235,30 +217,6 @@ revision = "5f041e8faa004a95c88a202771f4cc3e991971e6" version = "v2.0.1" -[[projects]] - name = "github.com/prometheus/client_golang" - packages = ["prometheus"] - revision = "c5b7fccd204277076155f10851dad72b76a49317" - version = "v0.8.0" - -[[projects]] - branch = "master" - name = "github.com/prometheus/client_model" - packages = ["go"] - revision = "5c3871d89910bfb32f5fcab2aa4b9ec68e65a99f" - -[[projects]] - branch = "master" - name = "github.com/prometheus/common" - packages = ["expfmt","internal/bitbucket.org/ww/goautoneg","model"] - revision = "7e9e6cabbd393fc208072eedef99188d0ce788b6" - -[[projects]] - branch = "master" - name = "github.com/prometheus/procfs" - packages = [".","internal/util","nfs","xfs"] - revision = "185b4288413d2a0dd0806f78c90dde719829e5ae" - [[projects]] name = "github.com/seccomp/libseccomp-golang" packages = ["."] @@ -352,12 +310,12 @@ [[projects]] branch = "release-1.11" name = "k8s.io/metrics" - packages = ["pkg/apis/metrics"] + packages = ["pkg/apis/metrics","pkg/apis/metrics/v1alpha1","pkg/apis/metrics/v1beta1","pkg/client/clientset_generated/clientset","pkg/client/clientset_generated/clientset/scheme","pkg/client/clientset_generated/clientset/typed/metrics/v1alpha1","pkg/client/clientset_generated/clientset/typed/metrics/v1beta1"] revision = "972ef826b8401c180b89cefc7457daa2d116daa9" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "c317229b631a11b3773e710f5a1faa24fa940ba9571388c743637431ec03a419" + inputs-digest = "475768252defbddd5c08cc0ceb5726612cc7b6865154a7d8065fb55162f0325d" solver-name = "gps-cdcl" solver-version = 1 diff --git a/config/param.go b/config/param.go index 30dd036..d954bdc 100644 --- a/config/param.go +++ b/config/param.go @@ -12,6 +12,11 @@ var params = []*Param{ Val: "state", Label: "Container Sort Field", }, + &Param{ + Key: "namespace", + Val: "state", + Label: "Kubernetes namespace for monitoring", + }, } type Param struct { diff --git a/connector/collector/kubernetes.go b/connector/collector/kubernetes.go index 2222df5..af40472 100644 --- a/connector/collector/kubernetes.go +++ b/connector/collector/kubernetes.go @@ -1,13 +1,16 @@ package collector import ( + "time" + + "k8s.io/metrics/pkg/apis/metrics/v1alpha1" "k8s.io/metrics/pkg/client/clientset_generated/clientset" "github.com/bcicen/ctop/config" "github.com/bcicen/ctop/models" - "k8s.io/client-go/kubernetes" + "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" ) // Kubernetes collector @@ -42,30 +45,20 @@ func (k *Kubernetes) Start() { k.running = false for { - cm, err := k.client.Metrics().PodMetricses("akozlenkov").List(metav1.ListOptions{}) + result := &v1alpha1.PodMetrics{} + err := k.clientset.RESTClient().Get().AbsPath("/api/v1/namespaces/kube-system/services/http:heapster:/proxy/apis/metrics/v1alpha1/namespaces/" + config.GetVal("namespace") + "/pods/" + k.name).Do().Into(result) + if err != nil { - log.Errorf(">>>>>> %s here %s", k.name, err.Error()) + log.Errorf("has error %s here %s", k.name, err.Error()) + time.Sleep(1 * time.Second) continue } - log.Debugf(">>>> %+v", cm) - //for _, m := range cm.Containers { - // log.Debugf(">>>> %+v", m) - //} + k.ReadCPU(result) + k.ReadMem(result) + k.stream <- k.Metrics } }() - //go func() { - // defer close(c.stream) - // for s := range stats { - // c.ReadCPU(s) - // c.ReadMem(s) - // c.ReadNet(s) - // c.ReadIO(s) - // c.stream <- c.Metrics - // } - // log.Infof("collector stopped for container: %s", c.id) - //}() - k.running = true log.Infof("collector started for container: %s", k.name) } @@ -87,30 +80,30 @@ func (c *Kubernetes) Stop() { c.done <- true } -// -//func (c *Kubernetes) ReadCPU(stats *api.Stats) { -// ncpus := float64(len(stats.CPUStats.CPUUsage.PercpuUsage)) -// total := float64(stats.CPUStats.CPUUsage.TotalUsage) -// system := float64(stats.CPUStats.SystemCPUUsage) -// -// cpudiff := total - c.lastCpu -// syscpudiff := system - c.lastSysCpu -// -// if c.scaleCpu { -// c.CPUUtil = round((cpudiff / syscpudiff * 100)) -// } else { -// c.CPUUtil = round((cpudiff / syscpudiff * 100) * ncpus) -// } -// c.lastCpu = total -// c.lastSysCpu = system -// c.Pids = int(stats.PidsStats.Current) -//} +func (k *Kubernetes) ReadCPU(metrics *v1alpha1.PodMetrics) { + all := int64(0) + for _, c := range metrics.Containers { + v := c.Usage[v1.ResourceCPU] + all += v.Value() + } + if all != 0 { + k.CPUUtil = round(float64(all)) + } +} -//func (c *Kubernetes) ReadMem(stats *api.Stats) { -// c.MemUsage = int64(stats.MemoryStats.Usage - stats.MemoryStats.Stats.Cache) -// c.MemLimit = int64(stats.MemoryStats.Limit) -// c.MemPercent = percent(float64(c.MemUsage), float64(c.MemLimit)) -//} +func (k *Kubernetes) ReadMem(metrics *v1alpha1.PodMetrics) { + all := int64(0) + for _, c := range metrics.Containers { + v := c.Usage[v1.ResourceMemory] + a, ok := v.AsInt64() + if ok { + all += a + } + } + k.MemUsage = all + k.MemLimit = int64(0) + //k.MemPercent = percent(float64(k.MemUsage), float64(k.MemLimit)) +} //func (c *Kubernetes) ReadNet(stats *api.Stats) { // var rx, tx int64 diff --git a/connector/kubernetes.go b/connector/kubernetes.go index 4e7db32..6ca41c0 100644 --- a/connector/kubernetes.go +++ b/connector/kubernetes.go @@ -8,6 +8,7 @@ import ( "sync" "time" + bcfg "github.com/bcicen/ctop/config" "github.com/bcicen/ctop/connector/collector" "github.com/bcicen/ctop/connector/manager" "github.com/bcicen/ctop/container" @@ -20,9 +21,8 @@ import ( func init() { enabled["kubernetes"] = NewKubernetes } -var namespace = "akozlenkov" - type Kubernetes struct { + namespace string clientset *kubernetes.Clientset containers map[string]*container.Container needsRefresh chan string // container IDs requiring refresh @@ -59,6 +59,7 @@ func NewKubernetes() Connector { containers: make(map[string]*container.Container), needsRefresh: make(chan string, 60), lock: sync.RWMutex{}, + namespace: bcfg.GetVal("namespace"), } go k.Loop() k.refreshAll() @@ -69,7 +70,7 @@ func NewKubernetes() Connector { func (k *Kubernetes) watchEvents() { for { log.Info("kubernetes event listener starting") - allEvents, err := k.clientset.CoreV1().Events(namespace).List(metav1.ListOptions{}) + allEvents, err := k.clientset.CoreV1().Events(k.namespace).List(metav1.ListOptions{}) if err != nil { log.Error(err.Error()) return @@ -152,7 +153,7 @@ func k8sPort(ports []v1.ContainerPort) string { } func (k *Kubernetes) inspect(id string) *v1.Pod { - p, err := k.clientset.CoreV1().Pods(namespace).Get(id, metav1.GetOptions{}) + p, err := k.clientset.CoreV1().Pods(k.namespace).Get(id, metav1.GetOptions{}) if err != nil { if _, ok := err.(*api.NoSuchContainer); !ok { log.Errorf(err.Error()) @@ -178,7 +179,7 @@ func (k *Kubernetes) Get(name string) (c *container.Container, ok bool) { // Mark all container IDs for refresh func (k *Kubernetes) refreshAll() { - allPods, err := k.clientset.CoreV1().Pods(namespace).List(metav1.ListOptions{}) + allPods, err := k.clientset.CoreV1().Pods(k.namespace).List(metav1.ListOptions{}) if err != nil { log.Error(err.Error()) return diff --git a/main.go b/main.go index 87df383..d0e0fbf 100644 --- a/main.go +++ b/main.go @@ -45,6 +45,7 @@ func main() { invertFlag = flag.Bool("i", false, "invert default colors") scaleCpu = flag.Bool("scale-cpu", false, "show cpu as % of system total") connectorFlag = flag.String("connector", "docker", "container connector to use") + namespace = flag.String("n", "default", "container connector to use") ) flag.Parse() @@ -91,6 +92,7 @@ func main() { if *invertFlag { InvertColorMap() } + config.Update("namespace", *namespace) ui.ColorMap = ColorMap // override default colormap if err := ui.Init(); err != nil { panic(err) From d5afc1c02c7a8258a16ca7f4397fbaab1907ae39 Mon Sep 17 00:00:00 2001 From: Alexandr Kozlenkov Date: Sat, 8 Dec 2018 21:43:20 +0300 Subject: [PATCH 4/6] Added support network rx/tx metrics through heapster --- connector/collector/kubernetes.go | 58 +++++++++++++--- go.mod | 1 + go.sum | 109 ++++++++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 go.sum diff --git a/connector/collector/kubernetes.go b/connector/collector/kubernetes.go index 7404ab4..dffebab 100644 --- a/connector/collector/kubernetes.go +++ b/connector/collector/kubernetes.go @@ -1,6 +1,7 @@ package collector import ( + "encoding/json" "time" "k8s.io/metrics/pkg/apis/metrics/v1alpha1" @@ -27,6 +28,16 @@ type Kubernetes struct { scaleCpu bool } +type Metric struct { + Timestamp time.Time `json:"timestamp"` + Value int64 `json:"value"` +} + +type Response struct { + Metrics []Metric `json:"metrics"` + LatestTimestamp time.Time `json:"latest_timestamp"` +} + func NewKubernetes(client *kubernetes.Clientset, name string) *Kubernetes { return &Kubernetes{ Metrics: models.Metrics{}, @@ -55,6 +66,33 @@ func (k *Kubernetes) Start() { } k.ReadCPU(result) k.ReadMem(result) + txMetrics := &Response{} + b, err := k.clientset.RESTClient().Get().AbsPath("/api/v1/namespaces/kube-system/services/heapster/proxy/api/v1/model/namespaces/" + config.GetVal("namespace") + "/pods/" + k.name + "/metrics/network/tx").Do().Raw() + if err != nil { + log.Errorf("has error %s here %s", k.name, err.Error()) + time.Sleep(1 * time.Second) + continue + } + err = json.Unmarshal(b, txMetrics) + if err != nil { + log.Errorf("has error %s here %s", k.name, err.Error()) + time.Sleep(1 * time.Second) + continue + } + rxMetrics := &Response{} + b, err = k.clientset.RESTClient().Get().AbsPath("/api/v1/namespaces/kube-system/services/heapster/proxy/api/v1/model/namespaces/" + config.GetVal("namespace") + "/pods/" + k.name + "/metrics/network/rx").Do().Raw() + if err != nil { + log.Errorf("has error %s here %s", k.name, err.Error()) + time.Sleep(1 * time.Second) + continue + } + err = json.Unmarshal(b, rxMetrics) + if err != nil { + log.Errorf("has error %s here %s", k.name, err.Error()) + time.Sleep(1 * time.Second) + continue + } + k.ReadNet(rxMetrics, txMetrics) k.stream <- k.Metrics } }() @@ -105,15 +143,17 @@ func (k *Kubernetes) ReadMem(metrics *v1alpha1.PodMetrics) { //k.MemPercent = percent(float64(k.MemUsage), float64(k.MemLimit)) } -//func (c *Kubernetes) ReadNet(stats *api.Stats) { -// var rx, tx int64 -// for _, network := range stats.Networks { -// rx += int64(network.RxBytes) -// tx += int64(network.TxBytes) -// } -// c.NetRx, c.NetTx = rx, tx -//} -// +func (k *Kubernetes) ReadNet(rxR, txR *Response) { + var rx, tx int64 + for _, network := range rxR.Metrics { + rx += int64(network.Value) + } + for _, network := range txR.Metrics { + tx += int64(network.Value) + } + k.NetRx, k.NetTx = rx, tx +} + //func (c *Kubernetes) ReadIO(stats *api.Stats) { // var read, write int64 // for _, blk := range stats.BlkioStats.IOServiceBytesRecursive { diff --git a/go.mod b/go.mod index 1d36d0f..bbc1adb 100644 --- a/go.mod +++ b/go.mod @@ -36,6 +36,7 @@ require ( github.com/opencontainers/runc v0.1.1 github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sah4ez/ctop v0.6.1 // indirect github.com/seccomp/libseccomp-golang v0.0.0-20150813023252-1b506fc7c24e // indirect github.com/spf13/pflag v1.0.3 // indirect github.com/stretchr/testify v1.2.2 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..55d5105 --- /dev/null +++ b/go.sum @@ -0,0 +1,109 @@ +github.com/Azure/go-ansiterm v0.0.0-20160622173216-fa152c58bc15/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY= +github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.3.8/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= +github.com/Sirupsen/logrus v0.0.0-20150423025312-26709e271410 h1:RxP6uUzJlS1Qa3+HYls3u+nQMKlnqC3RhL7wWC4Y4+E= +github.com/Sirupsen/logrus v0.0.0-20150423025312-26709e271410/go.mod h1:rmk17hk6i8ZSAJkSDa7nOxamrG+SP4P0mm+DAvExv4U= +github.com/bcicen/termui v0.0.0-20180326052246-4eb80249d3f5 h1:2pI3ZsoefWIi++8EqmANoC7Px/v2lRwnleVUcCuFgLg= +github.com/bcicen/termui v0.0.0-20180326052246-4eb80249d3f5/go.mod h1:yIA9ITWZD1p4/DvCQ44xvhyVb9XEUlVnY1rzGSHwbiM= +github.com/c9s/goprocinfo v0.0.0-20170609001544-b34328d6e0cd h1:xqaBnULC8wEnQpRDXAsDgXkU/STqoluz1REOoegSfNU= +github.com/c9s/goprocinfo v0.0.0-20170609001544-b34328d6e0cd/go.mod h1:uEyr4WpAH4hio6LFriaPkL938XnrvLpNPmQHBdrmbIE= +github.com/coreos/go-systemd v0.0.0-20151104194251-b4a58d95188d h1:MJ4ge3i0lehw+gE3JcGUUp8TmWjsLAlQlhmdASs/9wk= +github.com/coreos/go-systemd v0.0.0-20151104194251-b4a58d95188d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/docker/docker v0.0.0-20170502054910-90d35abf7b35 h1:zQuy/ry/KtSvCczhEPo+ud47S8alruA/Z9NDlz7EzVo= +github.com/docker/docker v0.0.0-20170502054910-90d35abf7b35/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.0.0-20170301234100-a2afab980204 h1:JYhQLEn7v7EtrpA9ByRz51gwlk8qt12EGP9XlbX/qw4= +github.com/docker/go-connections v0.0.0-20170301234100-a2afab980204/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.2 h1:Kjm80apys7gTtfVmCvVY8gwu10uofaFSrmAKOVrtueE= +github.com/docker/go-units v0.3.2/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/fsouza/go-dockerclient v0.0.0-20170307141636-318513eb1ab2 h1:JuVaWSoRMBrstn2l8cxKXjK8l/qurk5qyJ9dq7EIQmU= +github.com/fsouza/go-dockerclient v0.0.0-20170307141636-318513eb1ab2/go.mod h1:KpcjM623fQYE9MZiTGzKhjfxXAV9wbyX2C1cyRHfhl0= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55 h1:oIgNYSrSUbNH5DJh6DMhU1PiOKOYIHNxrV3djLsLpEI= +github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= +github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= +github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f h1:ShTPMJQes6tubcjzGMODIVG5hlrCeImaBnZzKF2N8SM= +github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hashicorp/go-cleanhttp v0.0.0-20170211013415-3573b8b52aa7 h1:67fHcS+inUoiIqWCKIqeDuq2AlPHNHPiTqp97LdQ+bc= +github.com/hashicorp/go-cleanhttp v0.0.0-20170211013415-3573b8b52aa7/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= +github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/jgautheron/codename-generator v0.0.0-20150829203204-16d037c7cc3c h1:/hc+TxW4Q1v6aqNPHE5jiaNF2xEK0CzWTgo25RQhQ+U= +github.com/jgautheron/codename-generator v0.0.0-20150829203204-16d037c7cc3c/go.mod h1:FJRkXmPrkHw0WDjB/LXMUhjWJ112Y6JUYnIVBOy8oH8= +github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= +github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c h1:eFzthqtg3W6Pihj3DMTXLAF4f+ge5r5Ie5g6HLIZAF0= +github.com/mattn/go-runewidth v0.0.0-20170201023540-14207d285c6c/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= +github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +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= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/nsf/termbox-go v0.0.0-20180303152453-e2050e41c884 h1:fcs71SMqqDhUD+PbpIv9xf3EH9F9s6HfiLwr6jKm1VA= +github.com/nsf/termbox-go v0.0.0-20180303152453-e2050e41c884/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473 h1:J1QZwDXgZ4dJD2s19iqR9+U00OWM2kDzbf1O/fmvCWg= +github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sah4ez/ctop v0.6.1 h1:NXP9v10ZgBq8bHzX2UyOSjzPrmrTIUk4YofKWcxO84c= +github.com/sah4ez/ctop v0.6.1/go.mod h1:Q74wOU81+00gag3YQ7OoJXZauj/epEE2c73q5CaNfzU= +github.com/seccomp/libseccomp-golang v0.0.0-20150813023252-1b506fc7c24e/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/syndtr/gocapability v0.0.0-20150716010906-2c00daeb6c3b h1:UzwAjzrPQVJoxLfb26YI2WRrhD3g09ZHt9vAQckWiPY= +github.com/syndtr/gocapability v0.0.0-20150716010906-2c00daeb6c3b/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/vishvananda/netlink v0.0.0-20150820014904-1e2e08e8a2dc h1:0HAHLwEY4k1VqaO1SzBi4XxT0KA06Cv+QW2LXknBk9g= +github.com/vishvananda/netlink v0.0.0-20150820014904-1e2e08e8a2dc/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI= +golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225 h1:kNX+jCowfMYzvlSvJu5pQWEmyWFrBXJ3PBy10xKMXK8= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20181128211412-28207608b838 h1:Tfp63pG3E68J3jSmfXHbBoEgU5jJm6bGQCcDvQr1Yb0= +golang.org/x/oauth2 v0.0.0-20181128211412-28207608b838/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU= +golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c h1:fqgJT0MGcGpPgpWU7VRdRjuArfcOvC4AoJmILihzhDg= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +k8s.io/api v0.0.0-20181130031204-d04500c8c3dd h1:5aHsneN62ehs/tdtS9tWZlhVk68V7yms/Qw7nsGmvCA= +k8s.io/api v0.0.0-20181130031204-d04500c8c3dd/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= +k8s.io/apimachinery v0.0.0-20181201231028-18a5ff3097b4 h1:JbAkABSztlJL76bNqKsGGBXf9++ZHClITsuRKrZ8wfw= +k8s.io/apimachinery v0.0.0-20181201231028-18a5ff3097b4/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/client-go v9.0.0+incompatible h1:NXWpDuPFeVB5lYP1fTqJUtwigjtmRXJNtndnN53ldGI= +k8s.io/client-go v9.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/klog v0.1.0 h1:I5HMfc/DtuVaGR1KPwUrTc476K8NCqNBldC7H4dYEzk= +k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/metrics v0.0.0-20181121073115-d8618695b08f h1:HyUoIBzks9xTaSnMJ6kv/SSmwaQQccokuiriu2cV0aA= +k8s.io/metrics v0.0.0-20181121073115-d8618695b08f/go.mod h1:a25VAbm3QT3xiVl1jtoF1ueAKQM149UdZ+L93ePfV3M= +sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= From eac320b026ae1b54d70c2355f3f24c1b98ef6302 Mon Sep 17 00:00:00 2001 From: Alexandr Kozlenkov Date: Sun, 9 Dec 2018 13:06:44 +0300 Subject: [PATCH 5/6] Fixed usage URL and the logic calculating values through heapster --- connector/collector/kubernetes.go | 140 ++++++++++++++++-------------- 1 file changed, 76 insertions(+), 64 deletions(-) diff --git a/connector/collector/kubernetes.go b/connector/collector/kubernetes.go index dffebab..d508a82 100644 --- a/connector/collector/kubernetes.go +++ b/connector/collector/kubernetes.go @@ -4,12 +4,10 @@ import ( "encoding/json" "time" - "k8s.io/metrics/pkg/apis/metrics/v1alpha1" clientset "k8s.io/metrics/pkg/client/clientset/versioned" "github.com/bcicen/ctop/config" "github.com/bcicen/ctop/models" - "k8s.io/api/core/v1" "k8s.io/client-go/kubernetes" ) @@ -26,6 +24,7 @@ type Kubernetes struct { lastCpu float64 lastSysCpu float64 scaleCpu bool + interval time.Duration } type Metric struct { @@ -45,9 +44,14 @@ func NewKubernetes(client *kubernetes.Clientset, name string) *Kubernetes { client: clientset.New(client.RESTClient()), clientset: client, scaleCpu: config.GetSwitchVal("scaleCpu"), + interval: time.Duration(30) * time.Second, } } +func buildURL(namespace, podName string) string { + return "/api/v1/namespaces/kube-system/services/heapster/proxy/api/v1/model/namespaces/" + namespace + "/pods/" + podName +} + func (k *Kubernetes) Start() { k.done = make(chan bool) k.stream = make(chan models.Metrics) @@ -55,45 +59,13 @@ func (k *Kubernetes) Start() { go func() { k.running = false for { - - result := &v1alpha1.PodMetrics{} - err := k.clientset.RESTClient().Get().AbsPath("/api/v1/namespaces/kube-system/services/http:heapster:/proxy/apis/metrics/v1alpha1/namespaces/" + config.GetVal("namespace") + "/pods/" + k.name).Do().Into(result) - - if err != nil { - log.Errorf("has error %s here %s", k.name, err.Error()) - time.Sleep(1 * time.Second) - continue - } - k.ReadCPU(result) - k.ReadMem(result) - txMetrics := &Response{} - b, err := k.clientset.RESTClient().Get().AbsPath("/api/v1/namespaces/kube-system/services/heapster/proxy/api/v1/model/namespaces/" + config.GetVal("namespace") + "/pods/" + k.name + "/metrics/network/tx").Do().Raw() - if err != nil { - log.Errorf("has error %s here %s", k.name, err.Error()) - time.Sleep(1 * time.Second) - continue - } - err = json.Unmarshal(b, txMetrics) - if err != nil { - log.Errorf("has error %s here %s", k.name, err.Error()) - time.Sleep(1 * time.Second) - continue - } - rxMetrics := &Response{} - b, err = k.clientset.RESTClient().Get().AbsPath("/api/v1/namespaces/kube-system/services/heapster/proxy/api/v1/model/namespaces/" + config.GetVal("namespace") + "/pods/" + k.name + "/metrics/network/rx").Do().Raw() - if err != nil { - log.Errorf("has error %s here %s", k.name, err.Error()) - time.Sleep(1 * time.Second) - continue - } - err = json.Unmarshal(b, rxMetrics) - if err != nil { - log.Errorf("has error %s here %s", k.name, err.Error()) - time.Sleep(1 * time.Second) - continue - } - k.ReadNet(rxMetrics, txMetrics) + log.Debugf("collect k8s metrics %s\n", k.name) + k.ReadCPU() + k.ReadMem() + k.ReadNetRx() + k.ReadNetTx() k.stream <- k.Metrics + time.Sleep(k.interval) } }() @@ -118,40 +90,80 @@ func (c *Kubernetes) Stop() { c.done <- true } -func (k *Kubernetes) ReadCPU(metrics *v1alpha1.PodMetrics) { - all := int64(0) - for _, c := range metrics.Containers { - v := c.Usage[v1.ResourceCPU] - all += v.Value() +func (k *Kubernetes) ReadCPU() { + cpu, err := k.read("/cpu/usage_rate") + + if err != nil { + log.Errorf("collecte network cpu metric has error %s here %s", k.name, err.Error()) + time.Sleep(1 * time.Second) + return } - if all != 0 { - k.CPUUtil = round(float64(all)) + + // TODO: heapster returning usage CPU in micro values without point so 0.004 is 4 + // because k8s calculate percent usage of all available CPU in cluster + if cpu != 0 { + k.CPUUtil = round(float64(cpu)) } } -func (k *Kubernetes) ReadMem(metrics *v1alpha1.PodMetrics) { - all := int64(0) - for _, c := range metrics.Containers { - v := c.Usage[v1.ResourceMemory] - a, ok := v.AsInt64() - if ok { - all += a - } +func (k *Kubernetes) ReadMem() { + usage, err := k.read("/memory/usage") + if err != nil { + log.Errorf("collecte network memory metric has error %s here %s", k.name, err.Error()) + time.Sleep(1 * time.Second) + return } - k.MemUsage = all - k.MemLimit = int64(0) + cache, err := k.read("/memory/cache") + if err != nil { + log.Errorf("collecte network memory metric has error %s here %s", k.name, err.Error()) + time.Sleep(1 * time.Second) + return + } + k.MemUsage = usage - cache + + limit, err := k.read("/memory/limit") + if err != nil { + log.Errorf("collecte network memory metric has error %s here %s", k.name, err.Error()) + time.Sleep(1 * time.Second) + return + } + k.MemLimit = limit //k.MemPercent = percent(float64(k.MemUsage), float64(k.MemLimit)) } -func (k *Kubernetes) ReadNet(rxR, txR *Response) { - var rx, tx int64 - for _, network := range rxR.Metrics { - rx += int64(network.Value) +func (k *Kubernetes) ReadNetRx() { + rx, err := k.read("/network/rx_rate") + if err != nil { + log.Errorf("collecte network rx_rate metric has error %s here %s", k.name, err.Error()) + time.Sleep(1 * time.Second) + return + } + k.NetRx = rx +} + +func (k *Kubernetes) ReadNetTx() { + tx, err := k.read("/network/tx_rate") + if err != nil { + log.Errorf("collecte network tx_rate metric has error %s here %s", k.name, err.Error()) + time.Sleep(1 * time.Second) + return + } + k.NetTx = tx +} + +func (k *Kubernetes) read(name string) (int64, error) { + m := &Response{} + url := buildURL(config.GetVal("namespace"), k.name) + "/metrics" + name + log.Debugf("get metrics: %s", url) + b, err := k.clientset.RESTClient().Get().AbsPath(url).Do().Raw() + if err != nil { + return 0, err } - for _, network := range txR.Metrics { - tx += int64(network.Value) + err = json.Unmarshal(b, m) + if err != nil { + return 0, err } - k.NetRx, k.NetTx = rx, tx + return m.Metrics[len(m.Metrics)-1].Value, nil } //func (c *Kubernetes) ReadIO(stats *api.Stats) { From 1486beae6164d382d8c6c23f29ddf3e78019a97e Mon Sep 17 00:00:00 2001 From: Alexandr Kozlenkov Date: Sun, 9 Dec 2018 13:51:37 +0300 Subject: [PATCH 6/6] Added uptime column --- connector/collector/kubernetes.go | 11 +++++++++++ cwidgets/compact/header.go | 2 +- cwidgets/compact/main.go | 8 ++++++++ cwidgets/compact/setters.go | 14 ++++++++++++++ cwidgets/compact/util.go | 3 ++- models/main.go | 2 ++ 6 files changed, 38 insertions(+), 2 deletions(-) diff --git a/connector/collector/kubernetes.go b/connector/collector/kubernetes.go index d508a82..ead039b 100644 --- a/connector/collector/kubernetes.go +++ b/connector/collector/kubernetes.go @@ -64,6 +64,7 @@ func (k *Kubernetes) Start() { k.ReadMem() k.ReadNetRx() k.ReadNetTx() + k.ReadUptime() k.stream <- k.Metrics time.Sleep(k.interval) } @@ -151,6 +152,16 @@ func (k *Kubernetes) ReadNetTx() { k.NetTx = tx } +func (k *Kubernetes) ReadUptime() { + uptime, err := k.read("/uptime") + if err != nil { + log.Errorf("collecte network uptime metric has error %s here %s", k.name, err.Error()) + time.Sleep(1 * time.Second) + return + } + k.Uptime = uptime +} + func (k *Kubernetes) read(name string) (int64, error) { m := &Response{} url := buildURL(config.GetVal("namespace"), k.name) + "/metrics" + name diff --git a/cwidgets/compact/header.go b/cwidgets/compact/header.go index 6dbd298..74f8e96 100644 --- a/cwidgets/compact/header.go +++ b/cwidgets/compact/header.go @@ -12,7 +12,7 @@ type CompactHeader struct { } func NewCompactHeader() *CompactHeader { - fields := []string{"", "NAME", "CID", "CPU", "MEM", "NET RX/TX", "IO R/W", "PIDS"} + fields := []string{"", "NAME", "CID", "CPU", "MEM", "NET RX/TX", "IO R/W", "PIDS", "UPTIME"} ch := &CompactHeader{} ch.Height = 2 for _, f := range fields { diff --git a/cwidgets/compact/main.go b/cwidgets/compact/main.go index af948fc..31ba38e 100644 --- a/cwidgets/compact/main.go +++ b/cwidgets/compact/main.go @@ -18,6 +18,7 @@ type Compact struct { Net *TextCol IO *TextCol Pids *TextCol + Uptime *TextCol Bg *RowBg X, Y int Width int @@ -38,6 +39,7 @@ func NewCompact(id string) *Compact { Net: NewTextCol("-"), IO: NewTextCol("-"), Pids: NewTextCol("-"), + Uptime: NewTextCol("-"), Bg: NewRowBg(), X: 1, Height: 1, @@ -70,6 +72,7 @@ func (row *Compact) SetMetrics(m models.Metrics) { row.SetMem(m.MemUsage, m.MemLimit, m.MemPercent) row.SetIO(m.IOBytesRead, m.IOBytesWrite) row.SetPids(m.Pids) + row.SetUptime(m.Uptime) } // Set gauges, counters to default unread values @@ -79,6 +82,7 @@ func (row *Compact) Reset() { row.Net.Reset() row.IO.Reset() row.Pids.Reset() + row.Uptime.Reset() } func (row *Compact) GetHeight() int { @@ -137,6 +141,7 @@ func (row *Compact) Buffer() ui.Buffer { buf.Merge(row.Net.Buffer()) buf.Merge(row.IO.Buffer()) buf.Merge(row.Pids.Buffer()) + buf.Merge(row.Uptime.Buffer()) return buf } @@ -150,6 +155,7 @@ func (row *Compact) all() []ui.GridBufferer { row.Net, row.IO, row.Pids, + row.Uptime, } } @@ -163,6 +169,7 @@ func (row *Compact) Highlight() { row.Net.Highlight() row.IO.Highlight() row.Pids.Highlight() + row.Uptime.Highlight() } } @@ -176,6 +183,7 @@ func (row *Compact) UnHighlight() { row.Net.UnHighlight() row.IO.UnHighlight() row.Pids.UnHighlight() + row.Uptime.UnHighlight() } } diff --git a/cwidgets/compact/setters.go b/cwidgets/compact/setters.go index 779991e..db5d2bf 100644 --- a/cwidgets/compact/setters.go +++ b/cwidgets/compact/setters.go @@ -3,6 +3,7 @@ package compact import ( "fmt" "strconv" + "time" "github.com/bcicen/ctop/cwidgets" ui "github.com/gizak/termui" @@ -23,6 +24,19 @@ func (row *Compact) SetPids(val int) { row.Pids.Set(label) } +func (row *Compact) SetUptime(val int64) { + d := time.Duration(val) * time.Millisecond + label := "- h" + if d.Hours() < 1.0 { + label = fmt.Sprintf("%.0fm", d.Minutes()) + } else if d.Hours() < 24.0 { + label = fmt.Sprintf("%.0fh", d.Hours()) + } else { + label = fmt.Sprintf("%dd", int(d.Hours())%24) + } + row.Uptime.Set(label) +} + func (row *Compact) SetCPU(val int) { row.Cpu.BarColor = colorScale(val) row.Cpu.Label = fmt.Sprintf("%s%%", strconv.Itoa(val)) diff --git a/cwidgets/compact/util.go b/cwidgets/compact/util.go index e634ed9..a7dd1e9 100644 --- a/cwidgets/compact/util.go +++ b/cwidgets/compact/util.go @@ -19,7 +19,8 @@ var colWidths = []int{ 0, // memory 0, // net 0, // io - 4, // pids + 0, // pids + 6, // uptime } // Calculate per-column width, given total width diff --git a/models/main.go b/models/main.go index feb23ce..90b0e22 100644 --- a/models/main.go +++ b/models/main.go @@ -17,6 +17,7 @@ type Metrics struct { IOBytesRead int64 IOBytesWrite int64 Pids int + Uptime int64 } func NewMetrics() Metrics { @@ -29,5 +30,6 @@ func NewMetrics() Metrics { IOBytesRead: -1, IOBytesWrite: -1, Pids: -1, + Uptime: -1, } }