123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- package detailed
- import (
- "context"
- "fmt"
- "strings"
- opentracing "github.com/opentracing/opentracing-go"
- "github.com/weaveworks/scope/probe/awsecs"
- "github.com/weaveworks/scope/probe/docker"
- "github.com/weaveworks/scope/probe/kubernetes"
- "github.com/weaveworks/scope/probe/overlay"
- "github.com/weaveworks/scope/probe/process"
- "github.com/weaveworks/scope/render"
- "github.com/weaveworks/scope/report"
- )
- // Shapes that are allowed
- const (
- ImageNameNone = "<none>"
- // Keys we use to render container names
- AmazonECSContainerNameLabel = "com.amazonaws.ecs.container-name"
- KubernetesContainerNameLabel = "io.kubernetes.container.name"
- MarathonAppIDEnv = "MARATHON_APP_ID"
- )
- // NodeSummaryGroup is a topology-typed group of children for a Node.
- type NodeSummaryGroup struct {
- ID string `json:"id"`
- Label string `json:"label"`
- Nodes []NodeSummary `json:"nodes"`
- TopologyID string `json:"topologyId"`
- Columns []Column `json:"columns"`
- }
- // Column provides special json serialization for column ids, so they include
- // their label for the frontend.
- type Column struct {
- ID string `json:"id"`
- Label string `json:"label"`
- DefaultSort bool `json:"defaultSort"`
- Datatype string `json:"dataType"`
- }
- // BasicNodeSummary is basic summary information about a Node,
- // sufficient for rendering links to the node.
- type BasicNodeSummary struct {
- ID string `json:"id"`
- Label string `json:"label"`
- LabelMinor string `json:"labelMinor"`
- Rank string `json:"rank"`
- Shape string `json:"shape,omitempty"`
- Tag string `json:"tag,omitempty"`
- Stack bool `json:"stack,omitempty"`
- Pseudo bool `json:"pseudo,omitempty"`
- }
- // NodeSummary is summary information about a Node.
- type NodeSummary struct {
- BasicNodeSummary
- Metadata []report.MetadataRow `json:"metadata,omitempty"`
- Parents []Parent `json:"parents,omitempty"`
- Metrics []report.MetricRow `json:"metrics,omitempty"`
- Tables []report.Table `json:"tables,omitempty"`
- Adjacency report.IDList `json:"adjacency,omitempty"`
- }
- var renderers = map[string]func(BasicNodeSummary, report.Node) BasicNodeSummary{
- render.Pseudo: pseudoNodeSummary,
- report.Process: processNodeSummary,
- report.Container: containerNodeSummary,
- report.ContainerImage: containerImageNodeSummary,
- report.Pod: podNodeSummary,
- report.Service: podGroupNodeSummary,
- report.Deployment: podGroupNodeSummary,
- report.DaemonSet: podGroupNodeSummary,
- report.StatefulSet: podGroupNodeSummary,
- report.CronJob: podGroupNodeSummary,
- report.Job: podGroupNodeSummary,
- report.ECSTask: ecsTaskNodeSummary,
- report.ECSService: ecsServiceNodeSummary,
- report.SwarmService: swarmServiceNodeSummary,
- report.Host: hostNodeSummary,
- report.Overlay: weaveNodeSummary,
- report.Endpoint: nil, // Do not render
- report.PersistentVolume: persistentVolumeNodeSummary,
- report.PersistentVolumeClaim: persistentVolumeClaimNodeSummary,
- report.StorageClass: storageClassNodeSummary,
- report.VolumeSnapshot: volumeSnapshotNodeSummary,
- report.VolumeSnapshotData: volumeSnapshotDataNodeSummary,
- }
- // For each report.Topology, map to a 'primary' API topology. This can then be used in a variety of places.
- var primaryAPITopology = map[string]string{
- report.Process: "processes",
- report.Container: "containers",
- report.ContainerImage: "containers-by-image",
- report.Pod: "pods",
- report.Deployment: "kube-controllers",
- report.DaemonSet: "kube-controllers",
- report.StatefulSet: "kube-controllers",
- report.CronJob: "kube-controllers",
- report.Job: "kube-controllers",
- report.Service: "services",
- report.ECSTask: "ecs-tasks",
- report.ECSService: "ecs-services",
- report.SwarmService: "swarm-services",
- report.Host: "hosts",
- report.PersistentVolume: "pods",
- report.PersistentVolumeClaim: "pods",
- report.StorageClass: "pods",
- report.VolumeSnapshot: "pods",
- report.VolumeSnapshotData: "pods",
- }
- // MakeBasicNodeSummary returns a basic summary of a node, if
- // possible. This summary is sufficient for rendering links to the node.
- func MakeBasicNodeSummary(r report.Report, n report.Node) (BasicNodeSummary, bool) {
- summary := BasicNodeSummary{ // This is unlikely to look very good, but is a reasonable fallback
- ID: n.ID,
- Label: n.ID,
- Shape: report.Triangle,
- }
- if t, ok := r.Topology(n.Topology); ok {
- summary.Shape = t.GetShape()
- summary.Tag = t.Tag
- }
- // Do we have a renderer for the topology?
- if renderer, ok := renderers[n.Topology]; ok {
- if renderer == nil { // we don't want to render this
- return summary, false
- }
- return renderer(summary, n), true
- }
- // Is it a group topology?
- if strings.HasPrefix(n.Topology, "group:") {
- return groupNodeSummary(summary, r, n), true
- }
- // Is it any known topology?
- if _, ok := r.Topology(n.Topology); ok {
- // We should never get here, since all known topologies are in
- // 'renderers'.
- return summary, true
- }
- // We have no idea how to render this.
- return summary, false
- }
- // MakeNodeSummary summarizes a node, if possible.
- func MakeNodeSummary(rc RenderContext, n report.Node) (NodeSummary, bool) {
- base, ok := MakeBasicNodeSummary(rc.Report, n)
- if !ok {
- return NodeSummary{}, false
- }
- summary := NodeSummary{
- BasicNodeSummary: base,
- Parents: Parents(rc.Report, n),
- Adjacency: n.Adjacency,
- }
- // Only include metadata, metrics, tables when it's not a group node
- if _, ok := n.LookupCounter(n.Topology); !ok {
- if topology, ok := rc.Topology(n.Topology); ok {
- summary.Metadata = topology.MetadataTemplates.MetadataRows(n)
- summary.Metrics = topology.MetricTemplates.MetricRows(n)
- summary.Tables = topology.TableTemplates.Tables(n)
- }
- }
- return RenderMetricURLs(summary, n, rc.Report, rc.MetricsGraphURL), true
- }
- // SummarizeMetrics returns a copy of the NodeSummary where the metrics are
- // replaced with their summaries
- func (n NodeSummary) SummarizeMetrics() NodeSummary {
- summarizedMetrics := make([]report.MetricRow, len(n.Metrics))
- for i, m := range n.Metrics {
- summarizedMetrics[i] = m.Summary()
- }
- n.Metrics = summarizedMetrics
- return n
- }
- func pseudoNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- pseudoID, _ := render.ParsePseudoNodeID(n.ID)
- base.Pseudo = true
- base.Rank = pseudoID
- switch {
- case n.ID == render.IncomingInternetID:
- // render as an internet node
- base.Label = render.InboundMajor
- base.LabelMinor = render.InboundMinor
- base.Shape = report.Cloud
- case n.ID == render.OutgoingInternetID:
- // render as an internet node
- base.Label = render.OutboundMajor
- base.LabelMinor = render.OutboundMinor
- base.Shape = report.Cloud
- case strings.HasPrefix(n.ID, render.ServiceNodeIDPrefix):
- // render as a known service node
- base.Label = n.ID[len(render.ServiceNodeIDPrefix):]
- base.LabelMinor = ""
- base.Shape = report.Cloud
- case strings.HasPrefix(n.ID, render.UncontainedIDPrefix):
- // render as an uncontained node
- base.Label = render.UncontainedMajor
- base.LabelMinor = n.ID[len(render.UncontainedIDPrefix):]
- base.Shape = report.Square
- base.Stack = true
- case strings.HasPrefix(n.ID, render.UnmanagedIDPrefix):
- // render as an unmanaged node
- base.Label = render.UnmanagedMajor
- base.LabelMinor = n.ID[len(render.UnmanagedIDPrefix):]
- base.Shape = report.Square
- base.Stack = true
- default:
- // try rendering it as an endpoint
- if _, addr, _, ok := report.ParseEndpointNodeID(n.ID); ok {
- base.Label = addr
- base.Shape = report.Circle
- } else {
- // last resort
- base.Label = pseudoID
- }
- }
- return base
- }
- func processNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- var (
- hostID, pid, _ = report.ParseProcessNodeID(n.ID)
- processName, _ = n.Latest.Lookup(process.Name)
- containerName, _ = n.Latest.Lookup(docker.ContainerName)
- )
- switch {
- case processName != "" && containerName != "":
- base.Label = processName
- base.LabelMinor = fmt.Sprintf("%s (%s:%s)", hostID, containerName, pid)
- base.Rank = processName
- case processName != "":
- base.Label = processName
- base.LabelMinor = fmt.Sprintf("%s (%s)", hostID, pid)
- base.Rank = processName
- case containerName != "":
- base.Label = pid
- base.LabelMinor = fmt.Sprintf("%s (%s)", hostID, containerName)
- base.Rank = hostID
- default:
- base.Label = pid
- base.LabelMinor = hostID
- base.Rank = hostID
- }
- return base
- }
- func containerNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- var (
- containerName = getRenderableContainerName(n)
- hostName = report.ExtractHostID(n)
- imageName, _ = n.Latest.Lookup(docker.ImageName)
- )
- base.Label = containerName
- base.LabelMinor = hostName
- if imageName != "" {
- base.Rank = docker.ImageNameWithoutTag(imageName)
- } else if hostName != "" {
- base.Rank = hostName
- } else {
- base.Rank = base.Label
- }
- return base
- }
- func containerImageNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- var (
- imageName, _ = n.Latest.Lookup(docker.ImageName)
- imageNameWithoutTag = docker.ImageNameWithoutTag(imageName)
- )
- switch {
- case imageNameWithoutTag != "" && imageNameWithoutTag != ImageNameNone:
- base.Label = imageNameWithoutTag
- case imageName != "" && imageName != ImageNameNone:
- base.Label = imageName
- default:
- // The id can be an image id or an image name. Ideally we'd
- // truncate the former but not the latter, but short of
- // heuristic regexp match we cannot tell the difference.
- base.Label, _ = report.ParseContainerImageNodeID(n.ID)
- }
- base.LabelMinor = pluralize(n, report.Container, "container", "containers")
- base.Rank = base.Label
- base.Stack = true
- return base
- }
- func addKubernetesLabelAndRank(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- var (
- name, _ = n.Latest.Lookup(kubernetes.Name)
- namespace, _ = n.Latest.Lookup(kubernetes.Namespace)
- )
- if name != "" {
- base.Label = name
- } else {
- base.Label, _, _ = report.ParseNodeID(n.ID)
- }
- base.Rank = namespace + "/" + base.Label
- return base
- }
- func podNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- base = addKubernetesLabelAndRank(base, n)
- base.LabelMinor = pluralize(n, report.Container, "container", "containers")
- return base
- }
- var podGroupNodeTypeName = map[string]string{
- report.Deployment: "Deployment",
- report.DaemonSet: "DaemonSet",
- report.StatefulSet: "StatefulSet",
- report.CronJob: "CronJob",
- report.Job: "Job",
- }
- func podGroupNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- base = addKubernetesLabelAndRank(base, n)
- base.Stack = true
- // NB: pods are the highest aggregation level for which we display
- // counts.
- count := pluralize(n, report.Pod, "pod", "pods")
- if typeName, ok := podGroupNodeTypeName[n.Topology]; ok {
- base.LabelMinor = fmt.Sprintf("%s of %s", typeName, count)
- } else {
- base.LabelMinor = count
- }
- return base
- }
- func ecsTaskNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- base.Label, _ = n.Latest.Lookup(awsecs.TaskFamily)
- if base.Label == "" {
- base.Label, _ = report.ParseECSTaskNodeID(n.ID)
- }
- return base
- }
- func ecsServiceNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- _, base.Label, _ = report.ParseECSServiceNodeID(n.ID)
- base.Stack = true
- return base
- }
- func swarmServiceNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- base.Label, _ = n.Latest.Lookup(docker.ServiceName)
- if base.Label == "" {
- base.Label, _ = report.ParseSwarmServiceNodeID(n.ID)
- }
- return base
- }
- func hostNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- var (
- hostname, _ = report.ParseHostNodeID(n.ID)
- parts = strings.SplitN(hostname, ".", 2)
- )
- if len(parts) == 2 {
- base.Label, base.LabelMinor, base.Rank = parts[0], parts[1], parts[1]
- } else {
- base.Label = hostname
- }
- return base
- }
- func weaveNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- var (
- nickname, _ = n.Latest.Lookup(overlay.WeavePeerNickName)
- _, peerName = report.ParseOverlayNodeID(n.ID)
- )
- if nickname != "" {
- base.Label = nickname
- } else {
- base.Label = peerName
- }
- base.LabelMinor = peerName
- return base
- }
- func persistentVolumeNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- base = addKubernetesLabelAndRank(base, n)
- return base
- }
- func persistentVolumeClaimNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- base = addKubernetesLabelAndRank(base, n)
- return base
- }
- func storageClassNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- base = addKubernetesLabelAndRank(base, n)
- return base
- }
- func volumeSnapshotNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- base = addKubernetesLabelAndRank(base, n)
- return base
- }
- func volumeSnapshotDataNodeSummary(base BasicNodeSummary, n report.Node) BasicNodeSummary {
- base = addKubernetesLabelAndRank(base, n)
- return base
- }
- // groupNodeSummary renders the summary for a group node. n.Topology is
- // expected to be of the form: group:container:hostname
- func groupNodeSummary(base BasicNodeSummary, r report.Report, n report.Node) BasicNodeSummary {
- base.Label, base.Rank = n.ID, n.ID
- if topology, _, ok := render.ParseGroupNodeTopology(n.Topology); ok {
- if t, ok := r.Topology(topology); ok {
- base.Shape = t.GetShape()
- base.Tag = t.Tag
- if t.Label != "" {
- base.LabelMinor = pluralize(n, topology, t.Label, t.LabelPlural)
- }
- }
- }
- base.Stack = true
- return base
- }
- func pluralize(n report.Node, key, singular, plural string) string {
- c, ok := n.LookupCounter(key)
- if !ok {
- c = 0
- }
- if c == 1 {
- return fmt.Sprintf("%d %s", c, singular)
- }
- return fmt.Sprintf("%d %s", c, plural)
- }
- type nodeSummariesByID []NodeSummary
- func (s nodeSummariesByID) Len() int { return len(s) }
- func (s nodeSummariesByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
- func (s nodeSummariesByID) Less(i, j int) bool { return s[i].ID < s[j].ID }
- // NodeSummaries is a set of NodeSummaries indexed by ID.
- type NodeSummaries map[string]NodeSummary
- // Summaries converts RenderableNodes into a set of NodeSummaries
- func Summaries(ctx context.Context, rc RenderContext, rns report.Nodes) NodeSummaries {
- span, ctx := opentracing.StartSpanFromContext(ctx, "detailed.Summaries")
- defer span.Finish()
- result := NodeSummaries{}
- for id, node := range rns {
- if summary, ok := MakeNodeSummary(rc, node); ok {
- for i, m := range summary.Metrics {
- summary.Metrics[i] = m.Summary()
- }
- result[id] = summary
- }
- }
- return result
- }
- // getRenderableContainerName obtains a user-friendly container name, to render in the UI
- func getRenderableContainerName(nmd report.Node) string {
- for _, key := range []string{
- // Amazon's ecs-agent produces huge Docker container names, destructively
- // derived from mangling Container Definition names in Task
- // Definitions.
- //
- // However, the ecs-agent provides a label containing the original Container
- // Definition name.
- docker.LabelPrefix + AmazonECSContainerNameLabel,
- // Kubernetes also mangles its Docker container names and provides a
- // label with the original container name. However, note that this label
- // is only provided by Kubernetes versions >= 1.2 (see
- // https://github.com/kubernetes/kubernetes/pull/17234/ )
- docker.LabelPrefix + KubernetesContainerNameLabel,
- // Marathon doesn't set any Docker labels and this is the only meaningful
- // attribute we can find to make Scope useful without Mesos plugin
- docker.EnvPrefix + MarathonAppIDEnv,
- docker.ContainerName,
- docker.ContainerHostname,
- } {
- if label, ok := nmd.Latest.Lookup(key); ok {
- return label
- }
- }
- containerID, _ := report.ParseContainerNodeID(nmd.ID)
- if len(containerID) > 12 {
- containerID = containerID[:12]
- }
- return containerID
- }
|