Files
weave-scope/render/detailed/node.go

281 lines
7.4 KiB
Go

package detailed
import (
"sort"
"github.com/ugorji/go/codec"
"github.com/weaveworks/scope/probe/awsecs"
"github.com/weaveworks/scope/probe/docker"
"github.com/weaveworks/scope/probe/kubernetes"
"github.com/weaveworks/scope/probe/process"
"github.com/weaveworks/scope/report"
)
// Node is the data type that's yielded to the JavaScript layer when
// we want deep information about an individual node.
type Node struct {
NodeSummary
Controls []ControlInstance `json:"controls"`
Children []NodeSummaryGroup `json:"children,omitempty"`
Connections []ConnectionsSummary `json:"connections,omitempty"`
}
// ControlInstance contains a control description, and all the info
// needed to execute it.
type ControlInstance struct {
ProbeID string
NodeID string
Control report.Control
}
// MarshalJSON shouldn't be used, use CodecEncodeSelf instead
func (ControlInstance) MarshalJSON() ([]byte, error) {
panic("MarshalJSON shouldn't be used, use CodecEncodeSelf instead")
}
// UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead
func (*ControlInstance) UnmarshalJSON(b []byte) error {
panic("UnmarshalJSON shouldn't be used, use CodecDecodeSelf instead")
}
type wiredControlInstance struct {
ProbeID string `json:"probeId"`
NodeID string `json:"nodeId"`
ID string `json:"id"`
Human string `json:"human"`
Icon string `json:"icon"`
Confirmation string `json:"confirmation,omitempty"`
Rank int `json:"rank"`
}
// CodecEncodeSelf marshals this ControlInstance. It takes the basic Metric
// rendering, then adds some row-specific fields.
func (c *ControlInstance) CodecEncodeSelf(encoder *codec.Encoder) {
encoder.Encode(wiredControlInstance{
ProbeID: c.ProbeID,
NodeID: c.NodeID,
ID: c.Control.ID,
Human: c.Control.Human,
Icon: c.Control.Icon,
Confirmation: c.Control.Confirmation,
Rank: c.Control.Rank,
})
}
// CodecDecodeSelf implements codec.Selfer
func (c *ControlInstance) CodecDecodeSelf(decoder *codec.Decoder) {
var in wiredControlInstance
decoder.Decode(&in)
*c = ControlInstance{
ProbeID: in.ProbeID,
NodeID: in.NodeID,
Control: report.Control{
ID: in.ID,
Human: in.Human,
Icon: in.Icon,
Confirmation: in.Confirmation,
Rank: in.Rank,
},
}
}
// RenderContext carries contextual data that is needed when rendering parts of the report.
type RenderContext struct {
report.Report
MetricsGraphURL string
}
// MakeNode transforms a renderable node to a detailed node. It uses
// aggregate metadata, plus the set of origin node IDs, to produce tables.
func MakeNode(topologyID string, rc RenderContext, ns report.Nodes, n report.Node) Node {
summary, _ := MakeNodeSummary(rc, n)
return Node{
NodeSummary: summary,
Controls: controls(rc.Report, n),
Children: children(rc, n),
Connections: []ConnectionsSummary{
incomingConnectionsSummary(topologyID, rc.Report, n, ns),
outgoingConnectionsSummary(topologyID, rc.Report, n, ns),
},
}
}
func controlsFor(topology report.Topology, nodeID string) []ControlInstance {
result := []ControlInstance{}
node, ok := topology.Nodes[nodeID]
if !ok {
return result
}
probeID, ok := node.Latest.Lookup(report.ControlProbeID)
if !ok {
return result
}
for _, controlID := range node.ActiveControls() {
if control, ok := topology.Controls[controlID]; ok {
if control.ProbeID != "" { // does this Control have an override for the node probe?
probeID = control.ProbeID
}
result = append(result, ControlInstance{
ProbeID: probeID,
NodeID: nodeID,
Control: control,
})
}
}
return result
}
func controls(r report.Report, n report.Node) []ControlInstance {
if t, ok := r.Topology(n.Topology); ok {
return controlsFor(t, n.ID)
}
return []ControlInstance{}
}
// We only need to include topologies here where the nodes may appear
// as children of other nodes in some topology.
var nodeSummaryGroupSpecs = []struct {
topologyID string
NodeSummaryGroup
}{
{
topologyID: report.Pod,
NodeSummaryGroup: NodeSummaryGroup{
Label: "Pods",
Columns: []Column{
{ID: kubernetes.State, Label: "State"},
{ID: report.Container, Label: "# Containers", Datatype: report.Number},
{ID: kubernetes.IP, Label: "IP", Datatype: report.IP},
},
},
},
{
topologyID: report.ECSTask,
NodeSummaryGroup: NodeSummaryGroup{
Label: "Tasks",
Columns: []Column{
{ID: awsecs.CreatedAt, Label: "Created At", Datatype: report.DateTime},
},
},
},
{
topologyID: report.Container,
NodeSummaryGroup: NodeSummaryGroup{
Label: "Containers",
Columns: []Column{
{ID: docker.CPUTotalUsage, Label: "CPU", Datatype: report.Number},
{ID: docker.MemoryUsage, Label: "Memory", Datatype: report.Number},
},
},
},
{
topologyID: report.Process,
NodeSummaryGroup: NodeSummaryGroup{
Label: "Processes",
Columns: []Column{
{ID: process.PID, Label: "PID", Datatype: report.Number},
{ID: process.CPUUsage, Label: "CPU", Datatype: report.Number},
{ID: process.MemoryUsage, Label: "Memory", Datatype: report.Number},
},
},
},
{
topologyID: report.ContainerImage,
NodeSummaryGroup: NodeSummaryGroup{
Label: "Container images",
Columns: []Column{},
},
},
{
topologyID: report.PersistentVolume,
NodeSummaryGroup: NodeSummaryGroup{
Label: "Persistent Volumes",
Columns: []Column{},
},
},
{
topologyID: report.PersistentVolumeClaim,
NodeSummaryGroup: NodeSummaryGroup{
Label: "Persistent Volume Claims",
Columns: []Column{},
},
},
{
topologyID: report.StorageClass,
NodeSummaryGroup: NodeSummaryGroup{
Label: "Storage Classes",
Columns: []Column{},
},
},
{
topologyID: report.VolumeSnapshot,
NodeSummaryGroup: NodeSummaryGroup{
Label: "Volume Snapshots",
Columns: []Column{},
},
},
{
topologyID: report.VolumeSnapshotData,
NodeSummaryGroup: NodeSummaryGroup{
Label: "Volume Snapshot Data",
Columns: []Column{},
},
},
}
func children(rc RenderContext, n report.Node) []NodeSummaryGroup {
summaries := map[string][]NodeSummary{}
n.Children.ForEach(func(child report.Node) {
if child.ID == n.ID {
return
}
summary, ok := MakeNodeSummary(rc, child)
if !ok {
return
}
summaries[child.Topology] = append(summaries[child.Topology], summary.SummarizeMetrics())
})
nodeSummaryGroups := []NodeSummaryGroup{}
// Apply specific group specs in the order they're listed
for _, spec := range nodeSummaryGroupSpecs {
if len(summaries[spec.topologyID]) == 0 {
continue
}
apiTopology, ok := primaryAPITopology[spec.topologyID]
if !ok {
continue
}
sort.Sort(nodeSummariesByID(summaries[spec.topologyID]))
group := spec.NodeSummaryGroup
group.Nodes = summaries[spec.topologyID]
group.TopologyID = apiTopology
nodeSummaryGroups = append(nodeSummaryGroups, group)
delete(summaries, spec.topologyID)
}
// As a fallback, in case a topology has no group spec defined, add any remaining at the end
for topologyID, nodeSummaries := range summaries {
if len(nodeSummaries) == 0 {
continue
}
topology, ok := rc.Topology(topologyID)
if !ok {
continue
}
apiTopology, ok := primaryAPITopology[topologyID]
if !ok {
continue
}
sort.Sort(nodeSummariesByID(nodeSummaries))
group := NodeSummaryGroup{
TopologyID: apiTopology,
Label: topology.LabelPlural,
Columns: []Column{},
}
nodeSummaryGroups = append(nodeSummaryGroups, group)
}
return nodeSummaryGroups
}