-
Notifications
You must be signed in to change notification settings - Fork 24
/
graph.go
137 lines (122 loc) · 4.82 KB
/
graph.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package circuitfactory
import (
"fmt"
"strings"
"github.com/emicklei/dot"
policymonitoringv1 "github.com/fluxninja/aperture/v2/api/gen/proto/go/aperture/policy/monitoring/v1"
)
// MermaidGraph returns Components and Links as a mermaid graph.
func MermaidGraph(graph *policymonitoringv1.Graph) string {
var sb strings.Builder
// subgraph for each component
sb.WriteString("flowchart LR\n")
renderComponentSubGraph := func(component *policymonitoringv1.ComponentView) string {
linkExists := func(componentID, portName string, links []*policymonitoringv1.Link) bool {
for _, link := range links {
if link.Source.ComponentId == componentID && link.Source.PortName == portName {
return true
} else if link.Target.ComponentId == componentID && link.Target.PortName == portName {
return true
}
}
return false
}
var s strings.Builder
if component.ComponentName == "Variable" || component.ComponentName == fakeConstantComponentName {
outPort := component.OutPorts[0].PortName
// render constant as a circle with value
s.WriteString(fmt.Sprintf("%s((%s))\n", component.ComponentId+outPort, component.ComponentDescription))
return s.String()
}
name := component.ComponentName
// only show component description
if component.ComponentDescription != "" {
description := component.ComponentDescription
// truncate description if too long (mermaid limitation)
if len(description) > 27 {
description = description[:27] + "..."
}
name = fmt.Sprintf("<center>%s<br/>%s</center>", name, description)
}
s.WriteString(fmt.Sprintf("subgraph %s[%s]\n", component.ComponentId, name))
if len(component.InPorts) > 0 {
// add subgraph for inports
s.WriteString(fmt.Sprintf("subgraph %s_inports[ ]\n", component.ComponentId))
// style to make inports invisible
s.WriteString(fmt.Sprintf("style %s_inports fill:none,stroke:none\n", component.ComponentId))
// InPorts and OutPorts are nodes in the subgraph
for _, inputPort := range component.InPorts {
// check if link exists for this port
if linkExists(component.ComponentId, inputPort.PortName, graph.InternalLinks) {
s.WriteString(fmt.Sprintf("%s[%s]\n", component.ComponentId+inputPort.PortName, inputPort.PortName))
}
}
s.WriteString("end\n")
}
if len(component.OutPorts) > 0 {
// add subgraph for outports
s.WriteString(fmt.Sprintf("subgraph %s_outports[ ]\n", component.ComponentId))
// style to make outports invisible
s.WriteString(fmt.Sprintf("style %s_outports fill:none,stroke:none\n", component.ComponentId))
for _, outputPort := range component.OutPorts {
if linkExists(component.ComponentId, outputPort.PortName, graph.InternalLinks) {
s.WriteString(fmt.Sprintf("%s[%s]\n", component.ComponentId+outputPort.PortName, outputPort.PortName))
}
}
s.WriteString("end\n")
}
s.WriteString("end\n")
return s.String()
}
for _, c := range graph.InternalComponents {
sb.WriteString(renderComponentSubGraph(c))
}
// links
for _, link := range graph.InternalLinks {
if link.GetSignalName() != "" {
// Signal Link
sb.WriteString(fmt.Sprintf("%s --> |%s| %s\n", link.Source.ComponentId+link.Source.PortName, link.GetSignalName(), link.Target.ComponentId+link.Target.PortName))
} else {
// Constant Link
sb.WriteString(fmt.Sprintf("%s --> %s\n", link.Source.ComponentId+link.Source.PortName, link.Target.ComponentId+link.Target.PortName))
}
}
return sb.String()
}
// DOTGraph returns Components and Links as a DOT graph description.
func DOTGraph(graph *policymonitoringv1.Graph) string {
g := dot.NewGraph(dot.Directed)
g.AttributesMap.Attr("splines", "ortho")
g.AttributesMap.Attr("rankdir", "LR")
components := graph.InternalComponents
links := graph.InternalLinks
// indexed by component id
clusters := make(map[string]*dot.Graph)
for i := range components {
name := fmt.Sprintf("%s[%s]", components[i].ComponentName, strings.SplitN(components[i].ComponentId, ".", 1)[0])
cluster := g.Subgraph(name, dot.ClusterOption{})
cluster.AttributesMap.Attr("margin", "50.0")
clusters[components[i].ComponentId] = cluster
var anyIn, anyOut dot.Node
for _, inPort := range components[i].InPorts {
anyIn = cluster.Node(inPort.PortName)
cluster.AddToSameRank("input", anyIn)
}
for j := range components[i].OutPorts {
anyOut = cluster.Node(components[i].OutPorts[j].PortName)
cluster.AddToSameRank("output", anyOut)
}
if len(components[i].InPorts) > 0 && len(components[i].OutPorts) > 0 {
cluster.Edge(anyIn, anyOut).Attr("style", "invis")
}
}
for i := range links {
edge := g.Edge(clusters[links[i].Source.ComponentId].Node(links[i].Source.PortName),
clusters[links[i].Target.ComponentId].Node(links[i].Target.PortName))
if links[i].GetSignalName() != "" {
// Signal Link
edge.Attr("label", links[i].GetSignalName())
}
}
return g.String()
}