From 48966ce7d90358db04ec81b63aaf62ac848a28fc Mon Sep 17 00:00:00 2001 From: richardxz Date: Tue, 9 Oct 2018 04:32:34 -0400 Subject: [PATCH 1/7] support image search --- .../v1alpha/registries/registries_handler.go | 41 +++ pkg/models/image_registries.go | 296 +++++++++++++++++- 2 files changed, 332 insertions(+), 5 deletions(-) diff --git a/pkg/apis/v1alpha/registries/registries_handler.go b/pkg/apis/v1alpha/registries/registries_handler.go index c67026a67..5126290f8 100644 --- a/pkg/apis/v1alpha/registries/registries_handler.go +++ b/pkg/apis/v1alpha/registries/registries_handler.go @@ -55,6 +55,23 @@ func Register(ws *restful.WebService, subPath string) { Consumes(restful.MIME_JSON). Produces(restful.MIME_JSON) + ws.Route(ws.GET(subPath + "/{name}/namespaces/{namespace}/searchwords/{searchWord}"). + Param(ws.PathParameter("namespace", "registry secret's namespace")). + Param(ws.PathParameter("name", "registry secret's name")). + Param(ws.PathParameter("searchWord", "keyword use to search image")). + To(handlerImageSearch). + Filter(route.RouteLogging)). + Consumes(restful.MIME_JSON). + Produces(restful.MIME_JSON) + ws.Route(ws.GET(subPath + "/{name}/namespaces/{namespace}/tags"). + Param(ws.QueryParameter("image", "imageName")). + Param(ws.PathParameter("namespace", "registry secret's namespace")). + Param(ws.PathParameter("name", "registry secret's name")). + To(handlerGetImageTags). + Filter(route.RouteLogging)). + Consumes(restful.MIME_JSON). + Produces(restful.MIME_JSON) + } func handlerRegistryValidation(request *restful.Request, response *restful.Response) { @@ -77,6 +94,30 @@ func handlerRegistryValidation(request *restful.Request, response *restful.Respo } +func handlerImageSearch(request *restful.Request, response *restful.Response) { + + registry := request.PathParameter("name") + searchWord := request.PathParameter("searchWord") + namespace := request.PathParameter("namespace") + + res := models.ImageSearch(namespace, registry, searchWord) + + response.WriteEntity(res) + +} + +func handlerGetImageTags(request *restful.Request, response *restful.Response) { + + registry := request.PathParameter("name") + image := request.QueryParameter("image") + namespace := request.PathParameter("namespace") + + res := models.GetImageTags(namespace, registry, image) + + response.WriteEntity(res) + +} + func handleCreateRegistries(request *restful.Request, response *restful.Response) { registries := models.Registries{} diff --git a/pkg/models/image_registries.go b/pkg/models/image_registries.go index 6271b55d3..f031c22dc 100644 --- a/pkg/models/image_registries.go +++ b/pkg/models/image_registries.go @@ -29,15 +29,23 @@ import ( "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "crypto/tls" + "io/ioutil" + "net/http" + "time" + kubeclient "kubesphere.io/kubesphere/pkg/client" "kubesphere.io/kubesphere/pkg/constants" ) -const TYPE = "kubernetes.io/dockerconfigjson" - -const SECRET = "Secret" - -const APIVERSION = "v1" +const ( + TYPE = "kubernetes.io/dockerconfigjson" + SECRET = "Secret" + APIVERSION = "v1" + TYPEHARBOR = "harbor" + TYPEDOCKERHUB = "dockerhub" + TYPEDOCKERREGISTRY = "docker-registry" +) type AuthInfo struct { Username string `json:"username"` @@ -45,6 +53,50 @@ type AuthInfo struct { ServerHost string `json:"serverhost"` } +type DockerConfigEntry struct { + Username string `json:"username"` + Password string `json:"password"` + Auth string `json:"auth"` +} + +type RegistryInfo struct { + user, password, registryType, url string +} + +type dockerConfig map[string]map[string]DockerConfigEntry + +type harborRepo struct { + RepoName string `json:"repository_name"` +} + +type harborRepos struct { + Repos []harborRepo `json:"repository"` +} + +type registryRepos struct { + Repositories []string +} + +type registryTags struct { + Name string `json:"name"` + Tags []string `json:"tags"` +} + +type dockerhubRepo struct { + RepoName string `json:"repo_name"` +} +type dockerhubRepos struct { + Repositories []dockerhubRepo `json:"results"` +} + +type dockerhubTag struct { + TagName string `json:"name"` +} + +type dockerhubTags struct { + Tags []dockerhubTag `json:"results"` +} + func NewAuthInfo(para Registries) *AuthInfo { return &AuthInfo{ @@ -440,3 +492,237 @@ func GetReisgtries(name string) (Registries, error) { return reg, nil } + +// by image secret to get registry'info, like username, password, registry url ... +func getRegistryInfo(namespace, registryName string) *RegistryInfo { + + var registry RegistryInfo + k8sClient := kubeclient.NewK8sClient() + secret, err := k8sClient.CoreV1().Secrets(namespace).Get(registryName, meta_v1.GetOptions{}) + if err != nil { + glog.Error(err) + return nil + } + + registry.registryType = secret.Annotations["type"] + + data := secret.Data[v1.DockerConfigJsonKey] + + authsMap := make(dockerConfig) + err = json.Unmarshal(data, &authsMap) + if err != nil { + glog.Error(err) + return nil + } + + for url, config := range authsMap["auths"] { + registry.url = url + registry.user = config.Username + registry.password = config.Password + break + } + + return ®istry +} + +func ImageSearch(namespace, registryName, searchWord string) []string { + registry := getRegistryInfo(namespace, registryName) + if registry == nil { + return nil + } + + switch registry.registryType { + case TYPEDOCKERHUB: + return searchDockerHub(registry.url, searchWord) + case TYPEDOCKERREGISTRY: + return searchDockerRegistry(registry.url, searchWord) + case TYPEHARBOR: + return searchHarbor(registry.url, registry.user, registry.password, searchWord) + } + + return nil +} + +func GetImageTags(namespace, registryName, imageName string) []string { + registry := getRegistryInfo(namespace, registryName) + if registry == nil { + return nil + } + + switch registry.registryType { + case TYPEDOCKERHUB: + return getTagInDockerHub(registry.url, imageName) + case TYPEDOCKERREGISTRY: + return getTagInDockerRegistry(registry.url, imageName) + case TYPEHARBOR: + return getTagInHarbor(registry.url, registry.user, registry.password, imageName) + } + + return nil +} + +func httpGet(url, username, password string, insecure bool) ([]byte, error) { + var httpClient *http.Client + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + if insecure { + httpClient = &http.Client{} + } else { + req.SetBasicAuth(username, password) + tr := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} + httpClient = &http.Client{Timeout: 20 * time.Second, Transport: tr} + } + + resp, err := httpClient.Do(req) + + if err != nil { + err := fmt.Errorf("Request to %s failed reason: %s ", url, err) + return nil, err + } + + body, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if resp.StatusCode >= http.StatusBadRequest || err != nil { + return nil, err + } + + return body, nil +} + +func searchHarbor(url, username, password, searchWord string) []string { + url = strings.TrimSuffix(url, "/") + fmt.Sprintf("/api/search?q=%s", searchWord) + + body, err := httpGet(url, username, password, false) + if err != nil || len(body) == 0 { + glog.Error(err) + return nil + } + + var repos harborRepos + repoList := make([]string, 0, 100) + err = json.Unmarshal(body, &repos) + if err != nil { + glog.Error(err) + return nil + } + + for _, repo := range repos.Repos { + repoList = append(repoList, repo.RepoName) + } + + return repoList +} + +func searchDockerRegistry(url, searchword string) []string { + url = strings.TrimSuffix(url, "/") + "/v2/_catalog" + body, err := httpGet(url, "", "", true) + if err != nil || len(body) == 0 { + glog.Error(err) + return nil + } + + var repos registryRepos + err = json.Unmarshal(body, &repos) + if err != nil { + glog.Error(err) + return nil + } + + repoList := make([]string, 0, 100) + for _, repo := range repos.Repositories { + if strings.HasPrefix(repo, searchword) { + repoList = append(repoList, repo) + } + } + + return repoList +} + +func searchDockerHub(url, searchWord string) []string { + url = fmt.Sprintf("https://hub.docker.com/v2/search/repositories/?page=1&query=%s&page_size=50", searchWord) + body, err := httpGet(url, "", "", true) + if err != nil || len(body) == 0 { + glog.Error(err) + return nil + } + + var repos dockerhubRepos + err = json.Unmarshal(body, &repos) + if err != nil { + glog.Error(err) + return nil + } + + repoList := make([]string, 0, 50) + for _, repo := range repos.Repositories { + repoList = append(repoList, repo.RepoName) + } + + return repoList +} + +func getTagInHarbor(url, username, password, imageName string) []string { + url = strings.TrimSuffix(url, "/") + fmt.Sprintf("/api/repositories/%s/tags", imageName) + body, err := httpGet(url, username, password, false) + if err != nil || len(body) == 0 { + glog.Error(err) + return nil + } + + var tagList []string + err = json.Unmarshal(body, &tagList) + if err != nil { + glog.Error(err) + return nil + } + + return tagList +} + +func getTagInDockerRegistry(url, imageName string) []string { + url = strings.TrimSuffix(url, "/") + fmt.Sprintf("/v2/%s/tags/list", imageName) + body, err := httpGet(url, "", "", true) + if err != nil || len(body) == 0 { + glog.Error(err) + return nil + } + + var tags registryTags + err = json.Unmarshal(body, &tags) + if err != nil { + glog.Error(err) + return nil + } + + return tags.Tags +} + +func getTagInDockerHub(url, imageName string) []string { + if !strings.Contains(imageName, "/") { + imageName = fmt.Sprintf("library/%s", imageName) + } + url = fmt.Sprintf("https://hub.docker.com/v2/repositories/%s/tags/?page=1&page_size=200", imageName) + + body, err := httpGet(url, "", "", true) + if err != nil || len(body) == 0 { + glog.Error(err) + return nil + } + + var tags dockerhubTags + err = json.Unmarshal(body, &tags) + if err != nil { + glog.Error(err) + return nil + } + + tagList := make([]string, 0, 200) + for _, tag := range tags.Tags { + tagList = append(tagList, tag.TagName) + } + + return tagList +} From 0b6480328d777386662e0d0e83662d6fcfc64e86 Mon Sep 17 00:00:00 2001 From: richardxz Date: Thu, 11 Oct 2018 02:34:44 -0400 Subject: [PATCH 2/7] avoid incorrect result when list resource with search conditions --- pkg/models/resources.go | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/pkg/models/resources.go b/pkg/models/resources.go index ed9e1377c..8ace3ba28 100644 --- a/pkg/models/resources.go +++ b/pkg/models/resources.go @@ -220,6 +220,8 @@ func ListResource(resourceName, conditonSrt, pagingStr, order string) (*Resource } func generateConditionStr(conditions *searchConditions) string { + shouldUseAnd := false + shouldUseBrackets := false conditionStr := "" if conditions == nil { @@ -242,11 +244,21 @@ func generateConditionStr(conditions *searchConditions) string { } } + if len(conditionStr) > 0 { + shouldUseAnd = true + } + for k, v := range conditions.matchOr { if len(conditionStr) == 0 { conditionStr = fmt.Sprintf("%s = \"%s\" ", k, v) } else { - conditionStr = fmt.Sprintf("%s OR %s = \"%s\" ", conditionStr, k, v) + if shouldUseAnd { + conditionStr = fmt.Sprintf("%s And (%s = \"%s\" ", conditionStr, k, v) + shouldUseBrackets = true + shouldUseAnd = false + } else { + conditionStr = fmt.Sprintf("%s OR %s = \"%s\" ", conditionStr, k, v) + } } } @@ -254,10 +266,20 @@ func generateConditionStr(conditions *searchConditions) string { if len(conditionStr) == 0 { conditionStr = fmt.Sprintf("%s like '%%%s%%' ", k, v) } else { - conditionStr = fmt.Sprintf("%s OR %s like '%%%s%%' ", conditionStr, k, v) + if shouldUseAnd { + conditionStr = fmt.Sprintf("%s And (%s like '%%%s%%' ", conditionStr, k, v) + shouldUseAnd = false + shouldUseBrackets = true + } else { + conditionStr = fmt.Sprintf("%s OR %s like '%%%s%%' ", conditionStr, k, v) + } } } + if shouldUseBrackets { + conditionStr = fmt.Sprintf("%s )", conditionStr) + } + return conditionStr } From b982f133aab181bfa387195335006aaa66d2c8f7 Mon Sep 17 00:00:00 2001 From: richardxz Date: Mon, 15 Oct 2018 22:23:13 -0400 Subject: [PATCH 3/7] update job's "rerun" function --- pkg/models/controllers/jobs.go | 114 ++++++++++++++++++++++---------- pkg/models/controllers/types.go | 5 +- 2 files changed, 81 insertions(+), 38 deletions(-) diff --git a/pkg/models/controllers/jobs.go b/pkg/models/controllers/jobs.go index 9e93a1e2a..cb3f95a0d 100644 --- a/pkg/models/controllers/jobs.go +++ b/pkg/models/controllers/jobs.go @@ -29,11 +29,16 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" + "reflect" + "strings" + "kubesphere.io/kubesphere/pkg/client" ) var k8sClient *kubernetes.Clientset +const retryTimes = 3 + func (ctl *JobCtl) generateObject(item v1.Job) *Job { var status, displayName string @@ -134,11 +139,13 @@ func (ctl *JobCtl) initListerAndInformer() { object := obj.(*v1.Job) mysqlObject := ctl.generateObject(*object) + ctl.makeRevision(object) db.Create(mysqlObject) }, UpdateFunc: func(old, new interface{}) { object := new.(*v1.Job) mysqlObject := ctl.generateObject(*object) + ctl.makeRevision(object) db.Save(mysqlObject) }, DeleteFunc: func(obj interface{}) { @@ -186,41 +193,41 @@ func getRevisions(job v1.Job) (JobRevisions, error) { err := json.Unmarshal([]byte(revisionsStr), &revisions) if err != nil { - glog.Errorf("failed to rerun job %s, reason: %s", err, err) - return nil, fmt.Errorf("failed to rerun job %s", job.Name) + return nil, fmt.Errorf("failed to get job %s's revisions, reason: %s", job.Name, err) } } return revisions, nil } -func getStatus(item *v1.Job) JobStatus { - var status JobStatus +func getCurrentRevision(item *v1.Job) JobRevision { + var revision JobRevision for _, condition := range item.Status.Conditions { if condition.Type == "Failed" && condition.Status == "True" { - status.Status = Failed - status.Reasons = append(status.Reasons, condition.Reason) - status.Messages = append(status.Messages, condition.Message) + revision.Status = Failed + revision.Reasons = append(revision.Reasons, condition.Reason) + revision.Messages = append(revision.Messages, condition.Message) } if condition.Type == "Complete" && condition.Status == "True" { - status.Status = Completed + revision.Status = Completed } } - if len(status.Status) == 0 { - status.Status = Unfinished + if len(revision.Status) == 0 { + revision.Status = Running } - status.DesirePodNum = *item.Spec.Completions - status.Succeed = item.Status.Succeeded - status.Failed = item.Status.Failed - status.StartTime = item.Status.StartTime.Time + revision.DesirePodNum = *item.Spec.Completions + revision.Succeed = item.Status.Succeeded + revision.Failed = item.Status.Failed + revision.StartTime = item.CreationTimestamp.Time + revision.Uid = string(item.UID) if item.Status.CompletionTime != nil { - status.CompletionTime = item.Status.CompletionTime.Time + revision.CompletionTime = item.Status.CompletionTime.Time } - return status + return revision } func deleteJob(namespace, job string) error { @@ -229,46 +236,81 @@ func deleteJob(namespace, job string) error { return err } +func (ctl *JobCtl) makeRevision(job *v1.Job) { + revisionIndex := -1 + revisions, err := getRevisions(*job) + + if err != nil { + glog.Error(err) + return + } + + uid := job.UID + for index, revision := range revisions { + if revision.Uid == string(uid) { + currentRevision := getCurrentRevision(job) + if reflect.DeepEqual(currentRevision, revision) { + return + } else { + revisionIndex = index + break + } + } + } + + if revisionIndex == -1 { + revisionIndex = len(revisions) + 1 + } + + revisions[revisionIndex] = getCurrentRevision(job) + + revisionsByte, err := json.Marshal(revisions) + if err != nil { + glog.Error(err) + } + + if job.Annotations == nil { + job.Annotations = make(map[string]string) + } + + job.Annotations["revisions"] = string(revisionsByte) + ctl.K8sClient.BatchV1().Jobs(job.Namespace).Update(job) + +} + func JobReRun(namespace, jobName string) (string, error) { k8sClient = client.NewK8sClient() job, err := k8sClient.BatchV1().Jobs(namespace).Get(jobName, metav1.GetOptions{}) if err != nil { return "", err } + newJob := *job newJob.ResourceVersion = "" newJob.Status = v1.JobStatus{} newJob.ObjectMeta.UID = "" + newJob.Annotations["revisions"] = strings.Replace(job.Annotations["revisions"], Running, Unfinished, -1) + delete(newJob.Spec.Selector.MatchLabels, "controller-uid") delete(newJob.Spec.Template.ObjectMeta.Labels, "controller-uid") - revisions, err := getRevisions(*job) - + err = deleteJob(namespace, jobName) if err != nil { - return "", err - } - - index := len(revisions) + 1 - value := getStatus(job) - revisions[index] = value - - revisionsByte, err := json.Marshal(revisions) - if err != nil { - glog.Errorf("failed to rerun job %s, reason: %s", err, err) + glog.Errorf("failed to rerun job %s, reason: %s", jobName, err) return "", fmt.Errorf("failed to rerun job %s", jobName) } - newJob.Annotations["revisions"] = string(revisionsByte) - - err = deleteJob(job.Namespace, job.Name) - if err != nil { - glog.Errorf("failed to rerun job %s, reason: %s", err, err) - return "", fmt.Errorf("failed to rerun job %s", jobName) + for i := 0; i < retryTimes; i++ { + _, err = k8sClient.BatchV1().Jobs(namespace).Create(&newJob) + if err != nil { + time.Sleep(time.Second) + continue + } + break } - _, err = k8sClient.BatchV1().Jobs(namespace).Create(&newJob) if err != nil { - glog.Errorf("failed to rerun job %s, reason: %s", err, err) + glog.Errorf("failed to rerun job %s, reason: %s", jobName, err) return "", fmt.Errorf("failed to rerun job %s", jobName) } diff --git a/pkg/models/controllers/types.go b/pkg/models/controllers/types.go index 35ef692a2..d73d4bc07 100644 --- a/pkg/models/controllers/types.go +++ b/pkg/models/controllers/types.go @@ -284,15 +284,16 @@ type StorageClass struct { Provisioner string `json:"provisioner"` } -type JobRevisions map[int]JobStatus +type JobRevisions map[int]JobRevision -type JobStatus struct { +type JobRevision struct { Status string `json:"status"` Reasons []string `json:"reasons"` Messages []string `json:"messages"` Succeed int32 `json:"succeed"` DesirePodNum int32 `json:"desire"` Failed int32 `json:"failed"` + Uid string `json:"uid"` StartTime time.Time `json:"start-time"` CompletionTime time.Time `json:"completion-time"` } From c65ecddbef67b51a89242d472ad34ec63e0b7cfa Mon Sep 17 00:00:00 2001 From: Carman Zhang Date: Fri, 12 Oct 2018 18:35:44 +0800 Subject: [PATCH 4/7] add cluster level multiple metrics in dashboard --- .../v1alpha/monitoring/monitor_handler.go | 23 +- pkg/client/prometheusclient.go | 33 +- pkg/models/metrics/metrics_collector.go | 184 -------- pkg/models/metrics/metrics_rule_tmpl.go | 147 ------ pkg/models/metrics/metricscollector.go | 422 ++++++++++++++++++ pkg/models/metrics/metricsconst.go | 236 ++++++++++ .../{metrics_rule.go => metricsrule.go} | 9 +- .../{metrics_struct.go => metricsstruct.go} | 0 8 files changed, 714 insertions(+), 340 deletions(-) delete mode 100644 pkg/models/metrics/metrics_collector.go delete mode 100644 pkg/models/metrics/metrics_rule_tmpl.go create mode 100644 pkg/models/metrics/metricscollector.go create mode 100644 pkg/models/metrics/metricsconst.go rename pkg/models/metrics/{metrics_rule.go => metricsrule.go} (94%) rename pkg/models/metrics/{metrics_struct.go => metricsstruct.go} (100%) diff --git a/pkg/apis/v1alpha/monitoring/monitor_handler.go b/pkg/apis/v1alpha/monitoring/monitor_handler.go index 6c08836e4..dc39c12b7 100644 --- a/pkg/apis/v1alpha/monitoring/monitor_handler.go +++ b/pkg/apis/v1alpha/monitoring/monitor_handler.go @@ -46,6 +46,19 @@ func (u MonitorResource) monitorContainer(request *restful.Request, response *re } func (u MonitorResource) monitorWorkload(request *restful.Request, response *restful.Response) { + wlKind := request.PathParameter("workload_kind") + if strings.Trim(wlKind, " ") == "" { + // count all workloads figure + //metricName := "workload_count" + res := metrics.MonitorWorkloadCount(request) + response.WriteAsJson(res) + } else { + res := metrics.MonitorAllMetrics(request) + response.WriteAsJson(res) + } +} + +func (u MonitorResource) monitorWorkspacePodLevelMetrics(request *restful.Request, response *restful.Response) { res := metrics.MonitorAllMetrics(request) response.WriteAsJson(res) } @@ -196,10 +209,18 @@ func Register(ws *restful.WebService, subPath string) { Doc("monitor specific workload level metrics"). Param(ws.PathParameter("ns_name", "namespace").DataType("string").Required(true).DefaultValue("kube-system")). Param(ws.QueryParameter("metrics_filter", "metrics name cpu memory...").DataType("string").Required(false)). - Param(ws.PathParameter("workload_kind", "workload kind").DataType("string").Required(true).DefaultValue("daemonset")). + Param(ws.PathParameter("workload_kind", "workload kind").DataType("string").Required(false).DefaultValue("daemonset")). Param(ws.QueryParameter("workload_name", "workload name").DataType("string").Required(true).DefaultValue("")). Metadata(restfulspec.KeyOpenAPITags, tags)). Consumes(restful.MIME_JSON, restful.MIME_XML). Produces(restful.MIME_JSON) + ws.Route(ws.GET(subPath+"/namespaces/{ns_name}/workloads").To(u.monitorWorkload). + Filter(route.RouteLogging). + Doc("monitor all workload level metrics"). + Param(ws.PathParameter("ns_name", "namespace").DataType("string").Required(true).DefaultValue("kube-system")). + Param(ws.QueryParameter("metrics_filter", "metrics name cpu memory...").DataType("string").Required(false)). + Metadata(restfulspec.KeyOpenAPITags, tags)). + Consumes(restful.MIME_JSON, restful.MIME_XML). + Produces(restful.MIME_JSON) } diff --git a/pkg/client/prometheusclient.go b/pkg/client/prometheusclient.go index 9bfc1b056..95992936f 100644 --- a/pkg/client/prometheusclient.go +++ b/pkg/client/prometheusclient.go @@ -17,6 +17,9 @@ import ( "net/http" "net/url" + "strconv" + "time" + "github.com/emicklei/go-restful" "github.com/golang/glog" "github.com/pkg/errors" @@ -28,6 +31,8 @@ const ( DefaultPrometheusPort = "9090" PrometheusApiPath = "/api/v1/" PrometheusEndpointUrl = DefaultScheme + "://" + DefaultPrometheusService + ":" + DefaultPrometheusPort + PrometheusApiPath + DefaultQueryStep = "10m" + DefaultQueryTimeout = "30s" ) var client = &http.Client{} @@ -79,14 +84,18 @@ func ParseRequestHeader(request *restful.Request) (url.Values, bool, error) { end := request.QueryParameter("end") step := request.QueryParameter("step") timeout := request.QueryParameter("timeout") + if timeout == "" { - timeout = "30s" + timeout = DefaultQueryTimeout + } + if step == "" { + step = DefaultQueryStep } // Whether query or query_range request u := url.Values{} - if start != "" && end != "" && step != "" { - u.Set("start", start) - u.Set("end", end) + if start != "" && end != "" { + u.Set("start", convertTimeGranularity(start)) + u.Set("end", convertTimeGranularity(end)) u.Set("step", step) u.Set("timeout", timeout) return u, true, nil @@ -101,6 +110,18 @@ func ParseRequestHeader(request *restful.Request) (url.Values, bool, error) { return u, false, nil } - glog.Error("Parse request failed", u) - return u, false, errors.Errorf("Parse request failed") + glog.Errorln("Parse request %s failed", u) + return u, false, errors.Errorf("Parse request time range %s failed", u) +} + +func convertTimeGranularity(ts string) string { + timeFloat, err := strconv.ParseFloat(ts, 64) + if err != nil { + glog.Errorf("convert second timestamp %s to minute timestamp failed", ts) + return strconv.FormatInt(int64(time.Now().Unix()), 10) + } + timeInt := int64(timeFloat) + // convert second timestamp to minute timestamp + secondTime := time.Unix(timeInt, 0).Truncate(time.Minute).Unix() + return strconv.FormatInt(secondTime, 10) } diff --git a/pkg/models/metrics/metrics_collector.go b/pkg/models/metrics/metrics_collector.go deleted file mode 100644 index 8f5762cdc..000000000 --- a/pkg/models/metrics/metrics_collector.go +++ /dev/null @@ -1,184 +0,0 @@ -/* -Copyright 2018 The KubeSphere Authors. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package metrics - -import ( - "encoding/json" - "regexp" - "strings" - - "github.com/emicklei/go-restful" - "github.com/golang/glog" - - "kubesphere.io/kubesphere/pkg/client" -) - -func getPodNameRegexInWorkload(request *restful.Request) string { - promql := MakeWorkloadRule(request) - res := client.SendPrometheusRequest(request, promql) - data := []byte(res) - var dat CommonMetricsResult - jsonErr := json.Unmarshal(data, &dat) - if jsonErr != nil { - glog.Errorln("json parse failed", jsonErr) - } - var podNames []string - for _, x := range dat.Data.Result { - podName := x.KubePodMetric.Pod - podNames = append(podNames, podName) - } - podNamesFilter := "^(" + strings.Join(podNames, "|") + ")$" - return podNamesFilter -} - -func MonitorWorkloadSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { - nsName := strings.Trim(request.PathParameter("ns_name"), " ") - podNamesFilter := getPodNameRegexInWorkload(request) - newPromql := MakePodPromQL(request, []string{metricsName, nsName, "", "", podNamesFilter}) - podMetrics := client.SendPrometheusRequest(request, newPromql) - cleanedJson := ReformatJson(podMetrics, metricsName) - return cleanedJson -} - -func MonitorPodSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { - nsName := strings.Trim(request.PathParameter("ns_name"), " ") - nodeID := strings.Trim(request.PathParameter("node_id"), " ") - podName := strings.Trim(request.PathParameter("pod_name"), " ") - podFilter := strings.Trim(request.QueryParameter("pods_filter"), " ") - params := []string{metricsName, nsName, nodeID, podName, podFilter} - promql := MakePodPromQL(request, params) - if promql != "" { - res := client.SendPrometheusRequest(request, promql) - cleanedJson := ReformatJson(res, metricsName) - return cleanedJson - } - return nil -} - -func MonitorNamespaceSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { - recordingRule := MakeNamespacePromQL(request, metricsName) - res := client.SendPrometheusRequest(request, recordingRule) - cleanedJson := ReformatJson(res, metricsName) - return cleanedJson -} - -func ReformatJson(metric string, metricsName string) *FormatedMetric { - var formatMetric FormatedMetric - err := json.Unmarshal([]byte(metric), &formatMetric) - if err != nil { - glog.Errorln("Unmarshal metric json failed", err) - } - if formatMetric.MetricName == "" { - formatMetric.MetricName = metricsName - } - // retrive metrics success - if formatMetric.Status == "success" { - result := formatMetric.Data.Result - for _, res := range result { - metric, ok := res["metric"] - me := metric.(map[string]interface{}) - if ok { - delete(me, "__name__") - } - } - } - return &formatMetric -} - -func collectNodeorClusterMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { - metric := MonitorNodeorClusterSingleMetric(request, metricsName) - ch <- metric -} - -func collectNamespaceMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { - metric := MonitorNamespaceSingleMetric(request, metricsName) - ch <- metric -} - -func collectWorkloadMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { - metricsName = strings.TrimLeft(metricsName, "workload_") - metric := MonitorWorkloadSingleMetric(request, metricsName) - ch <- metric -} - -func collectPodMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { - metric := MonitorPodSingleMetric(request, metricsName) - ch <- metric -} - -func MonitorAllMetrics(request *restful.Request) FormatedLevelMetric { - metricsName := strings.Trim(request.QueryParameter("metrics_filter"), " ") - if metricsName == "" { - metricsName = ".*" - } - path := request.SelectedRoutePath() - sourceType := path[strings.LastIndex(path, "/")+1 : len(path)-1] - if strings.Contains(path, "workload") { - sourceType = "workload" - } - var ch = make(chan *FormatedMetric, 10) - for _, k := range MetricsNames { - bol, err := regexp.MatchString(metricsName, k) - if !bol { - continue - } - if err != nil { - glog.Errorln("regex match failed", err) - continue - } - if strings.HasPrefix(k, sourceType) { - if sourceType == "node" || sourceType == "cluster" { - go collectNodeorClusterMetrics(request, k, ch) - } else if sourceType == "namespace" { - go collectNamespaceMetrics(request, k, ch) - } else if sourceType == "pod" { - go collectPodMetrics(request, k, ch) - } else if sourceType == "workload" { - go collectWorkloadMetrics(request, k, ch) - } - } - } - var metricsArray []FormatedMetric - var tempJson *FormatedMetric - for _, k := range MetricsNames { - bol, err := regexp.MatchString(metricsName, k) - if !bol { - continue - } - if err != nil { - glog.Errorln("regex match failed") - continue - } - if strings.HasPrefix(k, sourceType) { - tempJson = <-ch - if tempJson != nil { - metricsArray = append(metricsArray, *tempJson) - } - } - } - return FormatedLevelMetric{ - MetricsLevel: sourceType, - Results: metricsArray, - } -} - -func MonitorNodeorClusterSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { - recordingRule := MakeNodeorClusterRule(request, metricsName) - res := client.SendPrometheusRequest(request, recordingRule) - cleanedJson := ReformatJson(res, metricsName) - return cleanedJson -} diff --git a/pkg/models/metrics/metrics_rule_tmpl.go b/pkg/models/metrics/metrics_rule_tmpl.go deleted file mode 100644 index eae10835b..000000000 --- a/pkg/models/metrics/metrics_rule_tmpl.go +++ /dev/null @@ -1,147 +0,0 @@ -/* -Copyright 2018 The KubeSphere Authors. -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package metrics - -type MetricMap map[string]string - -var MetricsNames = []string{ - "cluster_cpu_utilisation", - "cluster_memory_utilisation", - "cluster_net_utilisation", - "cluster_pod_count", - - "node_cpu_utilisation", - "node_memory_utilisation", - "node_memory_available", - "node_memory_total", - "node_net_utilisation", - "node_net_bytes_transmitted", - "node_net_bytes_received", - "node_disk_read_iops", - "node_disk_write_iops", - "node_disk_read_throughput", - "node_disk_write_throughput", - "node_disk_capacity", - "node_disk_available", - "node_disk_utilization", - - "namespace_cpu_utilisation", - "namespace_memory_utilisation", - "namespace_memory_utilisation_wo_cache", - "namespace_net_bytes_transmitted", - "namespace_net_bytes_received", - "namespace_pod_count", - - "pod_cpu_utilisation", - "pod_memory_utilisation", - "pod_memory_utilisation_wo_cache", - "pod_net_bytes_transmitted", - "pod_net_bytes_received", - - "workload_pod_cpu_utilisation", - "workload_pod_memory_utilisation", - "workload_pod_memory_utilisation_wo_cache", - "workload_pod_net_bytes_transmitted", - "workload_pod_net_bytes_received", - //"container_cpu_utilisation", - //"container_memory_utilisation_wo_cache", - //"container_memory_utilisation", - - "tenant_cpu_utilisation", - "tenant_memory_utilisation", - "tenant_memory_utilisation_wo_cache", - "tenant_net_bytes_transmitted", - "tenant_net_bytes_received", - "tenant_pod_count", -} - -var RulePromQLTmplMap = MetricMap{ - //cluster - "cluster_cpu_utilisation": ":node_cpu_utilisation:avg1m", - "cluster_memory_utilisation": ":node_memory_utilisation:", - // Cluster network utilisation (bytes received + bytes transmitted per second) - "cluster_net_utilisation": ":node_net_utilisation:sum_irate", - "cluster_pod_count": `count(kube_pod_info{job="kube-state-metrics"})`, - - //node - "node_cpu_utilisation": "node:node_cpu_utilisation:avg1m", - "node_memory_utilisation": "node:node_memory_utilisation:", - "node_memory_available": "node:node_memory_bytes_available:sum", - "node_memory_total": "node:node_memory_bytes_total:sum", - // Node network utilisation (bytes received + bytes transmitted per second) - "node_net_utilisation": "node:node_net_utilisation:sum_irate", - // Node network bytes transmitted per second - "node_net_bytes_transmitted": "node:node_net_bytes_transmitted:sum_irate", - // Node network bytes received per second - "node_net_bytes_received": "node:node_net_bytes_received:sum_irate", - - // node:data_volume_iops_reads:sum{node=~"i-5xcldxos|i-6soe9zl1"} - "node_disk_read_iops": "node:data_volume_iops_reads:sum", - // node:data_volume_iops_writes:sum{node=~"i-5xcldxos|i-6soe9zl1"} - "node_disk_write_iops": "node:data_volume_iops_writes:sum", - // node:data_volume_throughput_bytes_read:sum{node=~"i-5xcldxos|i-6soe9zl1"} - "node_disk_read_throughput": "node:data_volume_throughput_bytes_read:sum", - // node:data_volume_throughput_bytes_written:sum{node=~"i-5xcldxos|i-6soe9zl1"} - "node_disk_write_throughput": "node:data_volume_throughput_bytes_written:sum", - - "node_disk_capacity": `sum by (node) ((node_filesystem_avail{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, - "node_disk_available": `sum by (node) ((node_filesystem_avail{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, - "node_disk_utilization": `sum by (node) (((node_filesystem_size{mountpoint="/", job="node-exporter"} - node_filesystem_avail{mountpoint="/", job="node-exporter"}) / node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, - - //namespace - "namespace_cpu_utilisation": `namespace:container_cpu_usage_seconds_total:sum_rate{namespace=~"$1"}`, - "namespace_memory_utilisation": `namespace:container_memory_usage_bytes:sum{namespace=~"$1"}`, - "namespace_memory_utilisation_wo_cache": `namespace:container_memory_usage_bytes_wo_cache:sum{namespace=~"$1"}`, - "namespace_net_bytes_transmitted": `sum by (namespace) (irate(container_network_transmit_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[2m]))`, - "namespace_net_bytes_received": `sum by (namespace) (irate(container_network_receive_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[2m]))`, - // count(kube_pod_info) by (namespace) namespace=~"monitoring|default|kube-system" - "namespace_pod_count": `count(kube_pod_info{job="kube-state-metrics", namespace=~"$1"}) by (namespace)`, - - // pod - "pod_cpu_utilisation": `sum(irate(container_cpu_usage_seconds_total{job="kubelet", namespace="$1", pod_name="$2", image!=""}[5m])) by (namespace, pod_name)`, - "pod_memory_utilisation": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name="$2", image!=""}) by (namespace, pod_name)`, - "pod_memory_utilisation_wo_cache": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name="$2", image!=""} - container_memory_cache{job="kubelet", namespace="$1", pod_name="$2",image!=""}) by (namespace, pod_name)`, - "pod_net_bytes_transmitted": `sum by (namespace, pod_name) (irate(container_network_transmit_bytes_total{namespace="$1", pod_name!="", pod_name="$2", interface="eth0", job="kubelet"}[2m]))`, - "pod_net_bytes_received": `sum by (namespace, pod_name) (irate(container_network_receive_bytes_total{namespace="$1", pod_name!="", pod_name="$2", interface="eth0", job="kubelet"}[2m]))`, - - "pod_cpu_utilisation_all": `sum(irate(container_cpu_usage_seconds_total{job="kubelet", namespace="$1", pod_name=~"$2", image!=""}[5m])) by (namespace, pod_name)`, - "pod_memory_utilisation_all": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name=~"$2", image!=""}) by (namespace, pod_name)`, - "pod_memory_utilisation_wo_cache_all": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name=~"$2", image!=""} - container_memory_cache{job="kubelet", namespace="$1", pod_name=~"$2", image!=""}) by (namespace, pod_name)`, - "pod_net_bytes_transmitted_all": `sum by (namespace, pod_name) (irate(container_network_transmit_bytes_total{namespace="$1", pod_name!="", pod_name=~"$2", interface="eth0", job="kubelet"}[2m]))`, - "pod_net_bytes_received_all": `sum by (namespace, pod_name) (irate(container_network_receive_bytes_total{namespace="$1", pod_name!="", pod_name=~"$2", interface="eth0", job="kubelet"}[2m]))`, - - //"pod_cpu_utilisation_node": `sum by (node, pod) (label_join(irate(container_cpu_usage_seconds_total{job="kubelet", image!=""}[5m]), "pod", " ", "pod_name") * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:{node=~"$3"})`, - "pod_cpu_utilisation_node": `sum by (node, pod) (label_join(irate(container_cpu_usage_seconds_total{job="kubelet",pod_name=~"$2", image!=""}[5m]), "pod", " ", "pod_name") * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:{node=~"$3"})`, - "pod_memory_utilisation_node": `sum by (node, pod) (label_join(container_memory_usage_bytes{job="kubelet",pod_name=~"$2", image!=""}, "pod", " ", "pod_name") * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:{node=~"$3"})`, - "pod_memory_utilisation_wo_cache_node": `sum by (node, pod) ((label_join(container_memory_usage_bytes{job="kubelet",pod_name=~"$2", image!=""}, "pod", " ", "pod_name") - label_join(container_memory_cache{job="kubelet",pod_name=~"$2", image!=""}, "pod", " ", "pod_name")) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:{node=~"$3"})`, - - // container - "container_cpu_utilisation": `sum(irate(container_cpu_usage_seconds_total{namespace="$1", pod_name="$2", container_name="$3"}[5m])) by (namespace, pod_name, container_name)`, - //"container_cpu_utilisation_wo_podname": `sum(irate(container_cpu_usage_seconds_total{namespace="$1", container_name=~"$3"}[5m])) by (namespace, pod_name, container_name)`, - "container_cpu_utilisation_all": `sum(irate(container_cpu_usage_seconds_total{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"}[5m])) by (namespace, pod_name, container_name)`, - //"container_cpu_utilisation_all_wo_podname": `sum(irate(container_cpu_usage_seconds_total{namespace="$1", container_name!="POD"}[5m])) by (namespace, pod_name, container_name)`, - - "container_memory_utilisation_wo_cache": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name="$3"} - ignoring(id, image, endpoint, instance, job, name, service) container_memory_cache{namespace="$1", pod_name="$2", container_name="$3"}`, - "container_memory_utilisation_wo_cache_all": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"} - ignoring(id, image, endpoint, instance, job, name, service) container_memory_cache{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"}`, - "container_memory_utilisation": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name="$3"}`, - "container_memory_utilisation_all": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"}`, - - // tenant - "tenant_cpu_utilisation": `sum(namespace:container_cpu_usage_seconds_total:sum_rate{namespace =~"$1"})`, - "tenant_memory_utilisation": `sum(namespace:container_memory_usage_bytes:sum{namespace =~"$1"})`, - "tenant_memory_utilisation_wo_cache": `sum(namespace:container_memory_usage_bytes_wo_cache:sum{namespace =~"$1"})`, - "tenant_net_bytes_transmitted": `sum(sum by (namespace) (irate(container_network_transmit_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[2m])))`, - "tenant_net_bytes_received": `sum(sum by (namespace) (irate(container_network_receive_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[2m])))`, - "tenant_pod_count": `sum(count(kube_pod_info{job="kube-state-metrics", namespace=~"$1"}) by (namespace))`, -} diff --git a/pkg/models/metrics/metricscollector.go b/pkg/models/metrics/metricscollector.go new file mode 100644 index 000000000..06b1e369f --- /dev/null +++ b/pkg/models/metrics/metricscollector.go @@ -0,0 +1,422 @@ +/* +Copyright 2018 The KubeSphere Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package metrics + +import ( + "encoding/json" + "regexp" + "strings" + + "github.com/emicklei/go-restful" + "github.com/golang/glog" + + "time" + + "k8s.io/api/core/v1" + metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "kubesphere.io/kubesphere/pkg/client" + "kubesphere.io/kubesphere/pkg/models" +) + +func getPodNameRegexInWorkload(request *restful.Request) string { + promql := MakeWorkloadRule(request) + res := client.SendPrometheusRequest(request, promql) + data := []byte(res) + var dat CommonMetricsResult + jsonErr := json.Unmarshal(data, &dat) + if jsonErr != nil { + glog.Errorln("json parse failed", jsonErr) + } + var podNames []string + for _, x := range dat.Data.Result { + podName := x.KubePodMetric.Pod + podNames = append(podNames, podName) + } + podNamesFilter := "^(" + strings.Join(podNames, "|") + ")$" + return podNamesFilter +} + +func MonitorWorkloadSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { + nsName := strings.Trim(request.PathParameter("ns_name"), " ") + podNamesFilter := getPodNameRegexInWorkload(request) + newPromql := MakePodPromQL(request, []string{metricsName, nsName, "", "", podNamesFilter}) + podMetrics := client.SendPrometheusRequest(request, newPromql) + cleanedJson := ReformatJson(podMetrics, metricsName) + return cleanedJson +} + +func MonitorPodSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { + nsName := strings.Trim(request.PathParameter("ns_name"), " ") + nodeID := strings.Trim(request.PathParameter("node_id"), " ") + podName := strings.Trim(request.PathParameter("pod_name"), " ") + podFilter := strings.Trim(request.QueryParameter("pods_filter"), " ") + params := []string{metricsName, nsName, nodeID, podName, podFilter} + promql := MakePodPromQL(request, params) + if promql != "" { + res := client.SendPrometheusRequest(request, promql) + cleanedJson := ReformatJson(res, metricsName) + return cleanedJson + } + return nil +} + +func MonitorNamespaceSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { + recordingRule := MakeNamespacePromQL(request, metricsName) + res := client.SendPrometheusRequest(request, recordingRule) + cleanedJson := ReformatJson(res, metricsName) + return cleanedJson +} + +// maybe this function is time consuming +func ReformatJson(metric string, metricsName string) *FormatedMetric { + var formatMetric FormatedMetric + err := json.Unmarshal([]byte(metric), &formatMetric) + if err != nil { + glog.Errorln("Unmarshal metric json failed", err) + } + if formatMetric.MetricName == "" { + formatMetric.MetricName = metricsName + } + // retrive metrics success + if formatMetric.Status == MetricStatusSuccess { + result := formatMetric.Data.Result + for _, res := range result { + metric, ok := res[ResultItemMetric] + me := metric.(map[string]interface{}) + if ok { + delete(me, "__name__") + } + } + } + return &formatMetric +} + +func collectNodeorClusterMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { + metric := MonitorNodeorClusterSingleMetric(request, metricsName) + ch <- metric +} + +func collectNamespaceMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { + metric := MonitorNamespaceSingleMetric(request, metricsName) + ch <- metric +} + +func collectWorkloadMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { + metricsName = strings.TrimLeft(metricsName, "workload_") + metric := MonitorWorkloadSingleMetric(request, metricsName) + ch <- metric +} + +func collectWorkspaceMetrics(request *restful.Request, metricsName string, namespaceList []string, ch chan<- *FormatedMetric) { + mertic := monitorWorkspaceSingleMertic(request, metricsName, namespaceList) + ch <- mertic +} + +func collectPodMetrics(request *restful.Request, metricsName string, ch chan<- *FormatedMetric) { + metric := MonitorPodSingleMetric(request, metricsName) + ch <- metric +} + +func monitorWorkspaceSingleMertic(request *restful.Request, metricsName string, namespaceList []string) *FormatedMetric { + namespaceRe2 := "^(" + strings.Join(namespaceList, "|") + ")$" + newpromql := MakeWorkspacePromQL(metricsName, namespaceRe2) + podMetrics := client.SendPrometheusRequest(request, newpromql) + cleanedJson := ReformatJson(podMetrics, metricsName) + return cleanedJson +} + +func filterNamespace(request *restful.Request, namespaceList []string) []string { + var newNSlist []string + nsFilter := strings.Trim(request.QueryParameter("namespaces_filter"), " ") + if nsFilter == "" { + nsFilter = ".*" + } + for _, ns := range namespaceList { + bol, _ := regexp.MatchString(nsFilter, ns) + if bol { + newNSlist = append(newNSlist, ns) + } + } + return newNSlist +} + +func MonitorAllMetrics(request *restful.Request) FormatedLevelMetric { + metricsName := strings.Trim(request.QueryParameter("metrics_filter"), " ") + if metricsName == "" { + metricsName = ".*" + } + path := request.SelectedRoutePath() + sourceType := path[strings.LastIndex(path, "/")+1 : len(path)-1] + if strings.Contains(path, MetricLevelWorkload) { + sourceType = MetricLevelWorkload + } else if strings.Contains(path, MetricLevelWorkspace) { + sourceType = MetricLevelWorkspace + } + var ch = make(chan *FormatedMetric, 10) + for _, metricName := range MetricsNames { + bol, err := regexp.MatchString(metricsName, metricName) + if !bol { + continue + } + if err != nil { + glog.Errorln("regex match failed", err) + continue + } + if strings.HasPrefix(metricName, sourceType) { + if sourceType == MetricLevelCluster || sourceType == MetricLevelNode { + go collectNodeorClusterMetrics(request, metricName, ch) + } else if sourceType == MetricLevelNamespace { + go collectNamespaceMetrics(request, metricName, ch) + } else if sourceType == MetricLevelPod { + go collectPodMetrics(request, metricName, ch) + } else if sourceType == MetricLevelWorkload { + go collectWorkloadMetrics(request, metricName, ch) + } + } + } + var metricsArray []FormatedMetric + var tempJson *FormatedMetric + for _, k := range MetricsNames { + bol, err := regexp.MatchString(metricsName, k) + if !bol { + continue + } + if err != nil { + glog.Errorln("regex match failed") + continue + } + if strings.HasPrefix(k, sourceType) { + tempJson = <-ch + if tempJson != nil { + metricsArray = append(metricsArray, *tempJson) + } + } + } + return FormatedLevelMetric{ + MetricsLevel: sourceType, + Results: metricsArray, + } +} + +func getWorkspacePodsCountMetrics(request *restful.Request, namespaces []string) *FormatedMetric { + metricName := MetricNameNamespacePodCount + var recordingRule = RulePromQLTmplMap[metricName] + nsFilter := "^(" + strings.Join(namespaces, "|") + ")$" + recordingRule = strings.Replace(recordingRule, "$1", nsFilter, -1) + res := client.SendPrometheusRequest(request, recordingRule) + cleanedJson := ReformatJson(res, metricName) + return cleanedJson +} + +func getWorkspaceWorkloadCountMetrics(namespaces []string) FormatedMetric { + var wlQuotaMetrics models.ResourceQuota + wlQuotaMetrics.NameSpace = strings.Join(namespaces, "|") + wlQuotaMetrics.Data.Used = make(v1.ResourceList, 1) + wlQuotaMetrics.Data.Hard = make(v1.ResourceList, 1) + for _, ns := range namespaces { + quotaMetric, err := models.GetNamespaceQuota(ns) + if err != nil { + glog.Errorln(err) + continue + } + // sum all resources used along namespaces + quotaUsed := quotaMetric.Data.Used + for resourceName, quantity := range quotaUsed { + if _, ok := wlQuotaMetrics.Data.Used[resourceName]; ok { + tmpQuantity := wlQuotaMetrics.Data.Used[v1.ResourceName(resourceName)] + tmpQuantity.Add(quantity) + wlQuotaMetrics.Data.Used[v1.ResourceName(resourceName)] = tmpQuantity + } else { + wlQuotaMetrics.Data.Used[v1.ResourceName(resourceName)] = quantity.DeepCopy() + } + } + + // sum all resources hard along namespaces + quotaHard := quotaMetric.Data.Hard + for resourceName, quantity := range quotaHard { + if _, ok := wlQuotaMetrics.Data.Hard[resourceName]; ok { + tmpQuantity := wlQuotaMetrics.Data.Hard[v1.ResourceName(resourceName)] + tmpQuantity.Add(quantity) + wlQuotaMetrics.Data.Hard[v1.ResourceName(resourceName)] = tmpQuantity + } else { + wlQuotaMetrics.Data.Hard[v1.ResourceName(resourceName)] = quantity.DeepCopy() + } + } + } + wlMetrics := convertQuota2MetricStruct(&wlQuotaMetrics) + return wlMetrics +} + +func getSpecificMetricItem(timestamp int64, metricName string, kind string, count int, err error) FormatedMetric { + var nsMetrics FormatedMetric + nsMetrics.MetricName = metricName + nsMetrics.Data.ResultType = ResultTypeVector + resultItem := make(map[string]interface{}) + tmp := make(map[string]string) + tmp[ResultItemMetricResource] = kind + if err == nil { + nsMetrics.Status = MetricStatusSuccess + } else { + nsMetrics.Status = MetricStatusError + resultItem["errorinfo"] = err.Error() + } + + resultItem[ResultItemMetric] = tmp + resultItem[ResultItemValue] = []interface{}{timestamp, count} + nsMetrics.Data.Result = make([]map[string]interface{}, 1) + nsMetrics.Data.Result[0] = resultItem + return nsMetrics +} + +func MonitorNodeorClusterSingleMetric(request *restful.Request, metricsName string) *FormatedMetric { + // support cluster node statistic, include healthy nodes and unhealthy nodes + var res string + var fMetric FormatedMetric + timestamp := int64(time.Now().Unix()) + + if metricsName == MetricNameClusterHealthyNodeCount { + onlineNodes, _ := getNodeHealthyConditionMetric() + fMetric = getSpecificMetricItem(timestamp, MetricNameClusterHealthyNodeCount, "node_count", len(onlineNodes), nil) + } else if metricsName == MetricNameClusterUnhealthyNodeCount { + _, offlineNodes := getNodeHealthyConditionMetric() + fMetric = getSpecificMetricItem(timestamp, MetricNameClusterUnhealthyNodeCount, "node_count", len(offlineNodes), nil) + } else if metricsName == MetricNameClusterNodeCount { + onlineNodes, offlineNodes := getNodeHealthyConditionMetric() + fMetric = getSpecificMetricItem(timestamp, MetricNameClusterNodeCount, "node_count", len(onlineNodes)+len(offlineNodes), nil) + } else { + recordingRule := MakeNodeorClusterRule(request, metricsName) + res = client.SendPrometheusRequest(request, recordingRule) + fMetric = *ReformatJson(res, metricsName) + } + return &fMetric +} + +func getNodeHealthyConditionMetric() ([]string, []string) { + nodeList, err := client.NewK8sClient().CoreV1().Nodes().List(metaV1.ListOptions{}) + if err != nil { + glog.Errorln(err) + return nil, nil + } + var onlineNodes []string + var offlineNodes []string + for _, node := range nodeList.Items { + nodeName := node.Labels["kubernetes.io/hostname"] + nodeRole := node.Labels["role"] + bol := true + for _, cond := range node.Status.Conditions { + if cond.Type == "Ready" && cond.Status == "Unknown" { + bol = false + break + } + } + if nodeRole != "log" { + if bol { + // reachable node + onlineNodes = append(onlineNodes, nodeName) + } else { + // unreachable node + offlineNodes = append(offlineNodes, nodeName) + } + } + } + return onlineNodes, offlineNodes +} + +func getExistingNamespace(namespaces []string) ([]string, []string) { + namespaceMap, err := getAllNamespace() + var existedNs []string + var noneExistedNs []string + if err != nil { + return namespaces, nil + } + for _, ns := range namespaces { + if _, ok := namespaceMap[ns]; ok { + existedNs = append(existedNs, ns) + } else { + noneExistedNs = append(noneExistedNs, ns) + } + } + return existedNs, noneExistedNs +} + +func getAllNamespace() (map[string]int, error) { + k8sClient := client.NewK8sClient() + nsList, err := k8sClient.CoreV1().Namespaces().List(metaV1.ListOptions{}) + if err != nil { + glog.Errorln(err) + return nil, err + } + namespaceMap := make(map[string]int) + for _, item := range nsList.Items { + namespaceMap[item.Name] = 0 + } + return namespaceMap, nil +} + +func MonitorWorkloadCount(request *restful.Request) FormatedMetric { + namespace := strings.Trim(request.PathParameter("ns_name"), " ") + + quotaMetric, err := models.GetNamespaceQuota(namespace) + fMetric := convertQuota2MetricStruct(quotaMetric) + + // whether the namespace in request parameters exists? + namespaceMap, e := getAllNamespace() + _, ok := namespaceMap[namespace] + if e != nil { + ok = true + } + if !ok || err != nil { + fMetric.Status = MetricStatusError + fMetric.Data.ResultType = "" + errInfo := make(map[string]interface{}) + if err != nil { + errInfo["errormsg"] = err.Error() + } else { + errInfo["errormsg"] = "namespace " + namespace + " does not exist" + } + fMetric.Data.Result = []map[string]interface{}{errInfo} + } + + return fMetric +} + +func convertQuota2MetricStruct(quotaMetric *models.ResourceQuota) FormatedMetric { + var fMetric FormatedMetric + fMetric.MetricName = MetricNameWorkloadCount + fMetric.Status = MetricStatusSuccess + fMetric.Data.ResultType = ResultTypeVector + timestamp := int64(time.Now().Unix()) + var resultItems []map[string]interface{} + + hardMap := make(map[string]string) + for resourceName, v := range quotaMetric.Data.Hard { + hardMap[resourceName.String()] = v.String() + } + + for resourceName, v := range quotaMetric.Data.Used { + resultItem := make(map[string]interface{}) + tmp := make(map[string]string) + tmp[ResultItemMetricResource] = resourceName.String() + resultItem[ResultItemMetric] = tmp + resultItem[ResultItemValue] = []interface{}{timestamp, hardMap[resourceName.String()], v.String()} + resultItems = append(resultItems, resultItem) + } + + fMetric.Data.Result = resultItems + return fMetric +} diff --git a/pkg/models/metrics/metricsconst.go b/pkg/models/metrics/metricsconst.go new file mode 100644 index 000000000..a44d302a5 --- /dev/null +++ b/pkg/models/metrics/metricsconst.go @@ -0,0 +1,236 @@ +/* +Copyright 2018 The KubeSphere Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package metrics + +const ( + ResultTypeVector = "vector" + ResultTypeMatrix = "matrix" + MetricStatusError = "error" + MetricStatusSuccess = "success" + ResultItemMetric = "metric" + ResultItemMetricResource = "resource" + ResultItemValue = "value" +) + +const ( + MetricNameWorkloadCount = "workload_count" + MetricNameNamespacePodCount = "namespace_pod_count" + + MetricNameWorkspaceAllOrganizationCount = "workspace_all_organization_count" + MetricNameWorkspaceAllAccountCount = "workspace_all_account_count" + MetricNameWorkspaceAllProjectCount = "workspace_all_project_count" + MetricNameWorkspaceAllDevopsCount = "workspace_all_devops_project_count" + + MetricNameWorkspaceNamespaceCount = "workspace_namespace_count" + MetricNameWorkspaceDevopsCount = "workspace_devops_project_count" + MetricNameWorkspaceMemberCount = "workspace_member_count" + MetricNameWorkspaceRoleCount = "workspace_role_count" + + MetricNameClusterHealthyNodeCount = "cluster_node_online" + MetricNameClusterUnhealthyNodeCount = "cluster_node_offline" + MetricNameClusterNodeCount = "cluster_node_total" +) + +const ( + WorkspaceResourceKindOrganization = "organization" + WorkspaceResourceKindAccount = "account" + WorkspaceResourceKindNamespace = "namespace" + WorkspaceResourceKindDevops = "devops" + WorkspaceResourceKindMember = "member" + WorkspaceResourceKindRole = "role" +) + +const ( + MetricLevelCluster = "cluster" + MetricLevelNode = "node" + MetricLevelWorkspace = "workspace" + MetricLevelNamespace = "namespace" + MetricLevelPod = "pod" + MetricLevelContainer = "container" + MetricLevelWorkload = "workload" +) + +type MetricMap map[string]string + +var MetricsNames = []string{ + "cluster_cpu_utilisation", + "cluster_cpu_usage", + "cluster_cpu_total", + "cluster_memory_utilisation", + "cluster_pod_count", + "cluster_memory_bytes_available", + "cluster_memory_bytes_total", + "cluster_memory_bytes_usage", + "cluster_net_utilisation", + "cluster_net_bytes_transmitted", + "cluster_net_bytes_received", + "cluster_disk_read_iops", + "cluster_disk_write_iops", + "cluster_disk_read_throughput", + "cluster_disk_write_throughput", + "cluster_disk_size_usage", + "cluster_disk_size_utilisation", + "cluster_disk_size_capacity", + "cluster_disk_size_available", + "cluster_node_online", + "cluster_node_offline", + "cluster_node_total", + + "node_cpu_utilisation", + "node_cpu_total", + "node_cpu_usage", + "node_memory_utilisation", + "node_memory_bytes_usage", + "node_memory_bytes_available", + "node_memory_bytes_total", + "node_net_utilisation", + "node_net_bytes_transmitted", + "node_net_bytes_received", + "node_disk_read_iops", + "node_disk_write_iops", + "node_disk_read_throughput", + "node_disk_write_throughput", + "node_disk_size_capacity", + "node_disk_size_available", + "node_disk_size_usage", + "node_disk_size_utilisation", + "node_pod_count", + "node_pod_quota", + + "namespace_cpu_usage", + "namespace_memory_usage", + "namespace_memory_usage_wo_cache", + "namespace_net_bytes_transmitted", + "namespace_net_bytes_received", + "namespace_pod_count", + + "pod_cpu_usage", + "pod_memory_usage", + "pod_memory_usage_wo_cache", + "pod_net_bytes_transmitted", + "pod_net_bytes_received", + + "workload_pod_cpu_usage", + "workload_pod_memory_usage", + "workload_pod_memory_usage_wo_cache", + "workload_pod_net_bytes_transmitted", + "workload_pod_net_bytes_received", + //"container_cpu_usage", + //"container_memory_usage_wo_cache", + //"container_memory_usage", + + "workspace_cpu_usage", + "workspace_memory_usage", + "workspace_memory_usage_wo_cache", + "workspace_net_bytes_transmitted", + "workspace_net_bytes_received", + "workspace_pod_count", +} + +var RulePromQLTmplMap = MetricMap{ + //cluster + "cluster_cpu_utilisation": ":node_cpu_utilisation:avg1m", + "cluster_cpu_usage": `sum (irate(container_cpu_usage_seconds_total{job="kubelet", image!=""}[5m]))`, + "cluster_cpu_total": "sum(node:node_num_cpu:sum)", + "cluster_memory_utilisation": ":node_memory_utilisation:", + "cluster_pod_count": `count(kube_pod_info unless on(pod) kube_pod_completion_time unless on(node) kube_node_labels{label_role="log"})`, + "cluster_memory_bytes_available": "sum(node:node_memory_bytes_available:sum)", + "cluster_memory_bytes_total": "sum(node:node_memory_bytes_total:sum)", + "cluster_memory_bytes_usage": "sum(node:node_memory_bytes_total:sum) - sum(node:node_memory_bytes_available:sum)", + "cluster_net_utilisation": "sum(node:node_net_utilisation:sum_irate)", + "cluster_net_bytes_transmitted": "sum(node:node_net_bytes_transmitted:sum_irate)", + "cluster_net_bytes_received": "sum(node:node_net_bytes_received:sum_irate)", + "cluster_disk_read_iops": "sum(node:data_volume_iops_reads:sum)", + "cluster_disk_write_iops": "sum(node:data_volume_iops_writes:sum)", + "cluster_disk_read_throughput": "sum(node:data_volume_throughput_bytes_read:sum)", + "cluster_disk_write_throughput": "sum(node:data_volume_throughput_bytes_written:sum)", + "cluster_disk_size_usage": `sum(sum by (node) ((node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:)) - sum(sum by (node) ((node_filesystem_avail{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:))`, + "cluster_disk_size_utilisation": `(sum(sum by (node) ((node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:)) - sum(sum by (node) ((node_filesystem_avail{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:))) / sum(sum by (node) ((node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:))`, + "cluster_disk_size_capacity": `sum(sum by (node) ((node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:))`, + "cluster_disk_size_available": `sum(sum by (node) ((node_filesystem_avail{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:))`, + + //node + "node_cpu_utilisation": "node:node_cpu_utilisation:avg1m", + "node_cpu_total": "node:node_num_cpu:sum", + "node_memory_utilisation": "node:node_memory_utilisation:", + "node_memory_bytes_available": "node:node_memory_bytes_available:sum", + "node_memory_bytes_total": "node:node_memory_bytes_total:sum", + // Node network utilisation (bytes received + bytes transmitted per second) + "node_net_utilisation": "node:node_net_utilisation:sum_irate", + // Node network bytes transmitted per second + "node_net_bytes_transmitted": "node:node_net_bytes_transmitted:sum_irate", + // Node network bytes received per second + "node_net_bytes_received": "node:node_net_bytes_received:sum_irate", + + // node:data_volume_iops_reads:sum{node=~"i-5xcldxos|i-6soe9zl1"} + "node_disk_read_iops": "node:data_volume_iops_reads:sum", + // node:data_volume_iops_writes:sum{node=~"i-5xcldxos|i-6soe9zl1"} + "node_disk_write_iops": "node:data_volume_iops_writes:sum", + // node:data_volume_throughput_bytes_read:sum{node=~"i-5xcldxos|i-6soe9zl1"} + "node_disk_read_throughput": "node:data_volume_throughput_bytes_read:sum", + // node:data_volume_throughput_bytes_written:sum{node=~"i-5xcldxos|i-6soe9zl1"} + "node_disk_write_throughput": "node:data_volume_throughput_bytes_written:sum", + + "node_disk_size_capacity": `sum by (node) ((node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, + "node_disk_size_available": `sum by (node) ((node_filesystem_avail{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, + "node_disk_size_usage": `sum by (node) ((node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1) -sum by (node) ((node_filesystem_avail{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, + "node_disk_size_utilisation": `sum by (node) (((node_filesystem_size{mountpoint="/", job="node-exporter"} - node_filesystem_avail{mountpoint="/", job="node-exporter"}) / node_filesystem_size{mountpoint="/", job="node-exporter"}) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, + "node_pod_count": `count(kube_pod_info$1 unless on(pod) kube_pod_completion_time) by (node)`, + // without log node: unless on(node) kube_node_labels{label_role="log"} + "node_pod_quota": `sum(kube_node_status_capacity_pods$1) by (node)`, + "node_cpu_usage": `sum by (node) (label_join(irate(container_cpu_usage_seconds_total{job="kubelet", image!=""}[5m]), "pod", " ", "pod_name") * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:$1)`, + "node_memory_bytes_usage": "node:node_memory_bytes_total:sum$1 - node:node_memory_bytes_available:sum$1", + + //namespace + "namespace_cpu_usage": `namespace:container_cpu_usage_seconds_total:sum_rate{namespace=~"$1"}`, + "namespace_memory_usage": `namespace:container_memory_usage_bytes:sum{namespace=~"$1"}`, + "namespace_memory_usage_wo_cache": `namespace:container_memory_usage_bytes_wo_cache:sum{namespace=~"$1"}`, + "namespace_net_bytes_transmitted": `sum by (namespace) (irate(container_network_transmit_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[5m]))`, + "namespace_net_bytes_received": `sum by (namespace) (irate(container_network_receive_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[5m]))`, + "namespace_pod_count": `count(kube_pod_info{namespace=~"$1"} unless on(pod) kube_pod_completion_time) by (namespace)`, + + // pod + "pod_cpu_usage": `sum(irate(container_cpu_usage_seconds_total{job="kubelet", namespace="$1", pod_name="$2", image!=""}[5m])) by (namespace, pod_name)`, + "pod_memory_usage": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name="$2", image!=""}) by (namespace, pod_name)`, + "pod_memory_usage_wo_cache": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name="$2", image!=""} - container_memory_cache{job="kubelet", namespace="$1", pod_name="$2",image!=""}) by (namespace, pod_name)`, + "pod_net_bytes_transmitted": `sum by (namespace, pod_name) (irate(container_network_transmit_bytes_total{namespace="$1", pod_name!="", pod_name="$2", interface="eth0", job="kubelet"}[5m]))`, + "pod_net_bytes_received": `sum by (namespace, pod_name) (irate(container_network_receive_bytes_total{namespace="$1", pod_name!="", pod_name="$2", interface="eth0", job="kubelet"}[5m]))`, + + "pod_cpu_usage_all": `sum(irate(container_cpu_usage_seconds_total{job="kubelet", namespace="$1", pod_name=~"$2", image!=""}[5m])) by (namespace, pod_name)`, + "pod_memory_usage_all": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name=~"$2", image!=""}) by (namespace, pod_name)`, + "pod_memory_usage_wo_cache_all": `sum(container_memory_usage_bytes{job="kubelet", namespace="$1", pod_name=~"$2", image!=""} - container_memory_cache{job="kubelet", namespace="$1", pod_name=~"$2", image!=""}) by (namespace, pod_name)`, + "pod_net_bytes_transmitted_all": `sum by (namespace, pod_name) (irate(container_network_transmit_bytes_total{namespace="$1", pod_name!="", pod_name=~"$2", interface="eth0", job="kubelet"}[5m]))`, + "pod_net_bytes_received_all": `sum by (namespace, pod_name) (irate(container_network_receive_bytes_total{namespace="$1", pod_name!="", pod_name=~"$2", interface="eth0", job="kubelet"}[5m]))`, + + "pod_cpu_usage_node": `sum by (node, pod) (label_join(irate(container_cpu_usage_seconds_total{job="kubelet",pod_name=~"$2", image!=""}[5m]), "pod", " ", "pod_name") * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:{node=~"$3"})`, + "pod_memory_usage_node": `sum by (node, pod) (label_join(container_memory_usage_bytes{job="kubelet",pod_name=~"$2", image!=""}, "pod", " ", "pod_name") * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:{node=~"$3"})`, + "pod_memory_usage_wo_cache_node": `sum by (node, pod) ((label_join(container_memory_usage_bytes{job="kubelet",pod_name=~"$2", image!=""}, "pod", " ", "pod_name") - label_join(container_memory_cache{job="kubelet",pod_name=~"$2", image!=""}, "pod", " ", "pod_name")) * on (namespace, pod) group_left(node) node_namespace_pod:kube_pod_info:{node=~"$3"})`, + + // container + "container_cpu_usage": `sum(irate(container_cpu_usage_seconds_total{namespace="$1", pod_name="$2", container_name="$3"}[5m])) by (namespace, pod_name, container_name)`, + "container_cpu_usage_all": `sum(irate(container_cpu_usage_seconds_total{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"}[5m])) by (namespace, pod_name, container_name)`, + + "container_memory_usage_wo_cache": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name="$3"} - ignoring(id, image, endpoint, instance, job, name, service) container_memory_cache{namespace="$1", pod_name="$2", container_name="$3"}`, + "container_memory_usage_wo_cache_all": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"} - ignoring(id, image, endpoint, instance, job, name, service) container_memory_cache{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"}`, + "container_memory_usage": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name="$3"}`, + "container_memory_usage_all": `container_memory_usage_bytes{namespace="$1", pod_name="$2", container_name=~"$3", container_name!="POD"}`, + + // enterprise + "workspace_cpu_usage": `sum(namespace:container_cpu_usage_seconds_total:sum_rate{namespace =~"$1"})`, + "workspace_memory_usage": `sum(namespace:container_memory_usage_bytes:sum{namespace =~"$1"})`, + "workspace_memory_usage_wo_cache": `sum(namespace:container_memory_usage_bytes_wo_cache:sum{namespace =~"$1"})`, + "workspace_net_bytes_transmitted": `sum(sum by (namespace) (irate(container_network_transmit_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[5m])))`, + "workspace_net_bytes_received": `sum(sum by (namespace) (irate(container_network_receive_bytes_total{namespace=~"$1", pod_name!="", interface="eth0", job="kubelet"}[5m])))`, + "workspace_pod_count": `sum(count(kube_pod_info{namespace=~"$1"} unless on(pod) kube_pod_completion_time) by (namespace))`, +} diff --git a/pkg/models/metrics/metrics_rule.go b/pkg/models/metrics/metricsrule.go similarity index 94% rename from pkg/models/metrics/metrics_rule.go rename to pkg/models/metrics/metricsrule.go index 14be69488..ee3ee3b70 100644 --- a/pkg/models/metrics/metrics_rule.go +++ b/pkg/models/metrics/metricsrule.go @@ -67,6 +67,12 @@ func MakeWorkloadRule(request *restful.Request) string { return rule } +func MakeWorkspacePromQL(metricsName string, namespaceRe2 string) string { + promql := RulePromQLTmplMap[metricsName] + promql = strings.Replace(promql, "$1", namespaceRe2, -1) + return promql +} + func MakeContainerPromQL(request *restful.Request) string { nsName := strings.Trim(request.PathParameter("ns_name"), " ") poName := strings.Trim(request.PathParameter("pod_name"), " ") @@ -169,9 +175,8 @@ func MakeNodeorClusterRule(request *restful.Request, metricsName string) string if nodesFilter == "" { nodesFilter = ".*" } - if strings.Contains(metricsName, "disk") && (!(strings.Contains(metricsName, "read") || strings.Contains(metricsName, "write"))) { + if strings.Contains(metricsName, "disk_size") || strings.Contains(metricsName, "pod") || strings.Contains(metricsName, "usage") { // disk size promql - nodesFilter := "" if nodeID != "" { nodesFilter = "{" + "node" + "=" + "\"" + nodeID + "\"" + "}" } else { diff --git a/pkg/models/metrics/metrics_struct.go b/pkg/models/metrics/metricsstruct.go similarity index 100% rename from pkg/models/metrics/metrics_struct.go rename to pkg/models/metrics/metricsstruct.go From 75d8787f64bda107c853cdc8778fa8c8caef1ddc Mon Sep 17 00:00:00 2001 From: richardxz Date: Tue, 23 Oct 2018 00:20:22 -0400 Subject: [PATCH 5/7] fix err in service type --- pkg/models/controllers/services.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/models/controllers/services.go b/pkg/models/controllers/services.go index c07755f4d..33ec7261d 100644 --- a/pkg/models/controllers/services.go +++ b/pkg/models/controllers/services.go @@ -95,7 +95,7 @@ func generateSvcObject(item v1.Service) *Service { createTime = time.Now() } - if len(item.Spec.ClusterIP) == 0 { + if len(item.Spec.ClusterIP) == 0 || item.Spec.ClusterIP == "None" { if len(item.Spec.Selector) == 0 { serviceType = "Headless(Selector)" } From 33dd9fb2dd120077c22bade6245b92897f16feca Mon Sep 17 00:00:00 2001 From: richardxz Date: Wed, 24 Oct 2018 00:03:43 -0400 Subject: [PATCH 6/7] ignore the role/clusterrole which don't have "creator" annotation --- pkg/models/controllers/clusterroles.go | 2 +- pkg/models/controllers/roles.go | 4 ++-- pkg/models/controllers/types.go | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/models/controllers/clusterroles.go b/pkg/models/controllers/clusterroles.go index bd74540ef..ed5a47e5c 100644 --- a/pkg/models/controllers/clusterroles.go +++ b/pkg/models/controllers/clusterroles.go @@ -36,7 +36,7 @@ func (ctl *ClusterRoleCtl) generateObject(item v1.ClusterRole) *ClusterRole { } name := item.Name - if strings.HasPrefix(name, systemPrefix) { + if strings.HasPrefix(name, systemPrefix) || item.Annotations == nil || len(item.Annotations[creator]) == 0 { return nil } diff --git a/pkg/models/controllers/roles.go b/pkg/models/controllers/roles.go index 8116ae5f4..bf294648f 100644 --- a/pkg/models/controllers/roles.go +++ b/pkg/models/controllers/roles.go @@ -30,12 +30,12 @@ import ( func (ctl *RoleCtl) generateObject(item v1.Role) *Role { var displayName string - if item.Annotations != nil && len(item.Annotations[DisplayName]) > 0 { + if item.Annotations != nil && len(item.Annotations[DisplayName]) == 0 { displayName = item.Annotations[DisplayName] } name := item.Name - if strings.HasPrefix(name, systemPrefix) { + if strings.HasPrefix(name, systemPrefix) || item.Annotations == nil || len(item.Annotations[creator]) == 0 { return nil } namespace := item.Namespace diff --git a/pkg/models/controllers/types.go b/pkg/models/controllers/types.go index d73d4bc07..7ccd95b11 100644 --- a/pkg/models/controllers/types.go +++ b/pkg/models/controllers/types.go @@ -49,6 +49,7 @@ const ( Warning = "warning" Error = "error" DisplayName = "displayName" + creator = "creator" Pods = "pods" Deployments = "deployments" From 03a37e70a1ccbd4debd48feab93593ececc48b7b Mon Sep 17 00:00:00 2001 From: richardxz Date: Thu, 25 Oct 2018 03:00:56 -0400 Subject: [PATCH 7/7] add "Terminating" status in pvc's lifecycle --- pkg/models/controllers/pvcs.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/models/controllers/pvcs.go b/pkg/models/controllers/pvcs.go index 38a20dfc8..09e794888 100644 --- a/pkg/models/controllers/pvcs.go +++ b/pkg/models/controllers/pvcs.go @@ -38,8 +38,12 @@ func (ctl *PvcCtl) generateObject(item *v1.PersistentVolumeClaim) *Pvc { name := item.Name namespace := item.Namespace - status := fmt.Sprintf("%s", item.Status.Phase) createTime := item.CreationTimestamp.Time + status := fmt.Sprintf("%s", item.Status.Phase) + if item.DeletionTimestamp != nil { + status = "Terminating" + } + var capacity, storageClass, accessModeStr string if createTime.IsZero() {