mirror of
https://github.com/weaveworks/scope.git
synced 2026-05-21 16:42:59 +00:00
166 lines
5.3 KiB
Go
166 lines
5.3 KiB
Go
package detailed
|
|
|
|
import (
|
|
"bytes"
|
|
"net/url"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"github.com/weaveworks/scope/probe/docker"
|
|
"github.com/weaveworks/scope/report"
|
|
|
|
"github.com/ugorji/go/codec"
|
|
)
|
|
|
|
// MetricLink describes a URL referencing a metric.
|
|
type MetricLink struct {
|
|
// References the metric id
|
|
ID string `json:"id"`
|
|
Label string `json:"label"`
|
|
URL string `json:"url"`
|
|
Priority int `json:"priority"`
|
|
}
|
|
|
|
// Variable name for the query within the metrics graph url
|
|
const urlQueryVarName = ":query"
|
|
|
|
var (
|
|
// Available metric links
|
|
linkTemplates = []MetricLink{
|
|
{ID: docker.CPUTotalUsage, Label: "CPU", Priority: 1},
|
|
{ID: docker.MemoryUsage, Label: "Memory", Priority: 2},
|
|
}
|
|
|
|
// Prometheus queries for topologies
|
|
topologyQueries = map[string]map[string]*template.Template{
|
|
report.Pod: {
|
|
// `container_memory_usage_bytes` is provided by cAdvisor in Kubelets.
|
|
docker.MemoryUsage: parsedTemplate(`sum(container_memory_usage_bytes{pod_name="{{.Label}}"})`),
|
|
// `container_cpu_usage_seconds_total` is provided by cAdvisor in Kubelets.
|
|
docker.CPUTotalUsage: parsedTemplate(`sum(rate(container_cpu_usage_seconds_total{pod_name="{{.Label}}"}[1m]))`),
|
|
},
|
|
report.Deployment: {
|
|
// `container_memory_usage_bytes` is provided by cAdvisor in Kubelets.
|
|
// Pod names are automatically generated by k8s using the deployment name:
|
|
// https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#pod-template-hash-label
|
|
docker.MemoryUsage: parsedTemplate(`sum(container_memory_usage_bytes{pod_name=~"^{{.Label}}-[^-]+-[^-]+$"})`),
|
|
// `container_cpu_usage_seconds_total` is provided by cAdvisor in Kubelets.
|
|
docker.CPUTotalUsage: parsedTemplate(`sum(rate(container_cpu_usage_seconds_total{pod_name=~"^{{.Label}}-[^-]+-[^-]+$"}[1m]))`),
|
|
},
|
|
report.DaemonSet: {
|
|
// `container_memory_usage_bytes` is provided by cAdvisor in Kubelets.
|
|
// Pod names are automatically generated by k8s using the DaemonSet name.
|
|
docker.MemoryUsage: parsedTemplate(`sum(container_memory_usage_bytes{pod_name=~"^{{.Label}}-[^-]+$"})`),
|
|
// `container_cpu_usage_seconds_total` is provided by cAdvisor in Kubelets.
|
|
docker.CPUTotalUsage: parsedTemplate(`sum(rate(container_cpu_usage_seconds_total{pod_name=~"^{{.Label}}-[^-]+$"}[1m]))`),
|
|
},
|
|
report.Service: {
|
|
// These recording rules must be defined in the prometheus config.
|
|
// NB: Pods need to be labeled and selected by their respective Service name, meaning:
|
|
// - The Service's `spec.selector` needs to select on `name`
|
|
// - The Service's `metadata.name` needs to be the same value as `spec.selector.name`
|
|
docker.MemoryUsage: parsedTemplate(`namespace_label_name:container_memory_usage_bytes:sum{label_name="{{.Label}}"}`),
|
|
docker.CPUTotalUsage: parsedTemplate(`namespace_label_name:container_cpu_usage_seconds_total:sum_rate{label_name="{{.Label}}}`),
|
|
},
|
|
}
|
|
)
|
|
|
|
// NodeMetricLinks returns the links of a node. The links are collected
|
|
// by a predefined set but filtered depending on whether a query
|
|
// is configured or not for the particular topology.
|
|
func NodeMetricLinks(_ report.Report, n report.Node) []MetricLink {
|
|
queries := topologyQueries[n.Topology]
|
|
if len(queries) == 0 {
|
|
return nil
|
|
}
|
|
|
|
links := []MetricLink{}
|
|
for _, link := range linkTemplates {
|
|
if _, ok := queries[link.ID]; ok {
|
|
links = append(links, link)
|
|
}
|
|
}
|
|
|
|
return links
|
|
}
|
|
|
|
// RenderMetricLinks executes the templated links by supplying the node summary as data.
|
|
// `metricsGraphURL` supports placeholders such as `:orgID` and `:query`. If the `:query`
|
|
// part is missing, a JSON version will be appended, see `queryParamsAsJSON()` for more info.
|
|
// It returns the modified summary.
|
|
func RenderMetricLinks(summary NodeSummary, n report.Node, metricsGraphURL string) NodeSummary {
|
|
queries := topologyQueries[n.Topology]
|
|
if len(queries) == 0 || len(summary.MetricLinks) == 0 {
|
|
return summary
|
|
}
|
|
|
|
links := []MetricLink{}
|
|
var bs bytes.Buffer
|
|
for _, link := range summary.MetricLinks {
|
|
tpl := queries[link.ID]
|
|
if tpl == nil {
|
|
continue
|
|
}
|
|
|
|
bs.Reset()
|
|
if err := tpl.Execute(&bs, summary); err != nil {
|
|
continue
|
|
}
|
|
|
|
link.URL = buildURL(bs.String(), metricsGraphURL)
|
|
links = append(links, link)
|
|
}
|
|
summary.MetricLinks = links
|
|
|
|
return summary
|
|
}
|
|
|
|
// buildURL puts together the URL by looking at the configured
|
|
// `metricsGraphURL`.
|
|
func buildURL(query, metricsGraphURL string) string {
|
|
if strings.Contains(metricsGraphURL, urlQueryVarName) {
|
|
return strings.Replace(metricsGraphURL, urlQueryVarName, url.PathEscape(query), -1)
|
|
}
|
|
|
|
params, err := queryParamsAsJSON(query)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
if metricsGraphURL[len(metricsGraphURL)-1] != '/' {
|
|
metricsGraphURL += "/"
|
|
}
|
|
|
|
return metricsGraphURL + url.PathEscape(params)
|
|
}
|
|
|
|
// queryParamsAsJSON packs the query into a JSON of the
|
|
// format `{"cells":[{"queries":[$query]}]}`.
|
|
func queryParamsAsJSON(query string) (string, error) {
|
|
type cell struct {
|
|
Queries []string `json:"queries"`
|
|
}
|
|
type queryParams struct {
|
|
Cells []cell `json:"cells"`
|
|
}
|
|
params := &queryParams{[]cell{{[]string{query}}}}
|
|
|
|
buf := &bytes.Buffer{}
|
|
encoder := codec.NewEncoder(buf, &codec.JsonHandle{})
|
|
if err := encoder.Encode(params); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return buf.String(), nil
|
|
}
|
|
|
|
// parsedTemplate initializes unnamed text templates.
|
|
func parsedTemplate(query string) *template.Template {
|
|
tpl, err := template.New("").Parse(query)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return tpl
|
|
}
|