pgraph: graphviz: Update our graphviz library
This makes things a bit easier to use. Especially when building fancy graphs.
This commit is contained in:
@@ -3573,7 +3573,7 @@ func (obj *StmtProg) SetScope(scope *interfaces.Scope) error {
|
|||||||
// debugging visualizations
|
// debugging visualizations
|
||||||
if obj.data.Debug && orderingGraphSingleton {
|
if obj.data.Debug && orderingGraphSingleton {
|
||||||
obj.data.Logf("running graphviz for ordering graph...")
|
obj.data.Logf("running graphviz for ordering graph...")
|
||||||
if err := orderingGraph.ExecGraphviz("dot", "/tmp/graphviz-ordering.dot", ""); err != nil {
|
if err := orderingGraph.ExecGraphviz("/tmp/graphviz-ordering.dot"); err != nil {
|
||||||
obj.data.Logf("graphviz: errored: %+v", err)
|
obj.data.Logf("graphviz: errored: %+v", err)
|
||||||
}
|
}
|
||||||
// Only generate the top-level one, to prevent overwriting this!
|
// Only generate the top-level one, to prevent overwriting this!
|
||||||
|
|||||||
@@ -951,7 +951,7 @@ func TestAstFunc1(t *testing.T) {
|
|||||||
}
|
}
|
||||||
if runGraphviz {
|
if runGraphviz {
|
||||||
t.Logf("test #%d: Running graphviz...", index)
|
t.Logf("test #%d: Running graphviz...", index)
|
||||||
if err := graph.ExecGraphviz("dot", "/tmp/graphviz.dot", ""); err != nil {
|
if err := graph.ExecGraphviz("/tmp/graphviz.dot"); err != nil {
|
||||||
t.Errorf("test #%d: FAIL", index)
|
t.Errorf("test #%d: FAIL", index)
|
||||||
t.Errorf("test #%d: writing graph failed: %+v", index, err)
|
t.Errorf("test #%d: writing graph failed: %+v", index, err)
|
||||||
return
|
return
|
||||||
@@ -1458,7 +1458,7 @@ func TestAstFunc2(t *testing.T) {
|
|||||||
|
|
||||||
if runGraphviz {
|
if runGraphviz {
|
||||||
t.Logf("test #%d: Running graphviz...", index)
|
t.Logf("test #%d: Running graphviz...", index)
|
||||||
if err := graph.ExecGraphviz("dot", "/tmp/graphviz.dot", ""); err != nil {
|
if err := graph.ExecGraphviz("/tmp/graphviz.dot"); err != nil {
|
||||||
t.Errorf("test #%d: FAIL", index)
|
t.Errorf("test #%d: FAIL", index)
|
||||||
t.Errorf("test #%d: writing graph failed: %+v", index, err)
|
t.Errorf("test #%d: writing graph failed: %+v", index, err)
|
||||||
return
|
return
|
||||||
|
|||||||
12
lib/main.go
12
lib/main.go
@@ -773,11 +773,15 @@ func (obj *Main) Run() error {
|
|||||||
|
|
||||||
Logf("graph: %+v", obj.ge.Graph()) // show graph
|
Logf("graph: %+v", obj.ge.Graph()) // show graph
|
||||||
if obj.Graphviz != "" {
|
if obj.Graphviz != "" {
|
||||||
filter := obj.GraphvizFilter
|
gv := &pgraph.Graphviz{
|
||||||
if filter == "" {
|
Filter: obj.GraphvizFilter,
|
||||||
filter = "dot" // directed graph default
|
Filename: obj.Graphviz,
|
||||||
|
Hostname: hostname,
|
||||||
|
Graphs: map[*pgraph.Graph]*pgraph.GraphvizOpts{
|
||||||
|
obj.ge.Graph(): nil,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if err := obj.ge.Graph().ExecGraphviz(filter, obj.Graphviz, hostname); err != nil {
|
if err := gv.Exec(); err != nil {
|
||||||
Logf("graphviz: %+v", err)
|
Logf("graphviz: %+v", err)
|
||||||
} else {
|
} else {
|
||||||
Logf("graphviz: successfully generated graph!")
|
Logf("graphviz: successfully generated graph!")
|
||||||
|
|||||||
@@ -23,18 +23,74 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/purpleidea/mgmt/util/errwrap"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// graphvizDefaultFilter is the default program to run when none are
|
||||||
|
// specified.
|
||||||
|
graphvizDefaultFilter = "dot"
|
||||||
|
|
||||||
ptrLabels = true
|
ptrLabels = true
|
||||||
ptrLabelsSize = 10
|
ptrLabelsSize = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
// Graphviz outputs the graph in graphviz format.
|
// Graphviz adds some visualization features for pgraph.
|
||||||
|
type Graphviz struct {
|
||||||
|
// Name is the display name of the graph. If specified it overrides an
|
||||||
|
// amalgamation of any graph names shown.
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Graphs is a collection of graphs to print together and the associated
|
||||||
|
// options that should be used to format them during display.
|
||||||
|
Graphs map[*Graph]*GraphvizOpts
|
||||||
|
|
||||||
|
// Filter is the graphviz program to run. The default is "dot".
|
||||||
|
Filter string
|
||||||
|
|
||||||
|
// Filename is the output location for the graph.
|
||||||
|
Filename string
|
||||||
|
|
||||||
|
// Hostname is used as a suffix to the filename when specified.
|
||||||
|
Hostname string
|
||||||
|
}
|
||||||
|
|
||||||
|
// graphs returns a list of the graphs in a probably deterministic order.
|
||||||
|
func (obj *Graphviz) graphs() []*Graph {
|
||||||
|
graphs := []*Graph{}
|
||||||
|
for g := range obj.Graphs {
|
||||||
|
graphs = append(graphs, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(graphs, func(i, j int) bool { return graphs[i].GetName() < graphs[j].GetName() })
|
||||||
|
|
||||||
|
return graphs
|
||||||
|
}
|
||||||
|
|
||||||
|
// name returns a unique name for the combination of graphs.
|
||||||
|
func (obj *Graphviz) name() string {
|
||||||
|
if obj.Name != "" {
|
||||||
|
return obj.Name
|
||||||
|
}
|
||||||
|
names := []string{}
|
||||||
|
//for g := range obj.Graphs {
|
||||||
|
// names = append(names, g.GetName())
|
||||||
|
//}
|
||||||
|
//sort.Strings(names) // deterministic
|
||||||
|
for _, g := range obj.graphs() { // deterministic
|
||||||
|
names = append(names, g.GetName())
|
||||||
|
}
|
||||||
|
return strings.Join(names, "|") // arbitrary join character
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text outputs the graph in graphviz format.
|
||||||
// https://en.wikipedia.org/wiki/DOT_%28graph_description_language%29
|
// https://en.wikipedia.org/wiki/DOT_%28graph_description_language%29
|
||||||
func (g *Graph) Graphviz() (out string) {
|
func (obj *Graphviz) Text() string {
|
||||||
//digraph g {
|
//digraph g {
|
||||||
// label="hello world";
|
// label="hello world";
|
||||||
// node [shape=box];
|
// node [shape=box];
|
||||||
@@ -47,80 +103,67 @@ func (g *Graph) Graphviz() (out string) {
|
|||||||
// B -> C [label=g];
|
// B -> C [label=g];
|
||||||
// D -> E [label=h];
|
// D -> E [label=h];
|
||||||
//}
|
//}
|
||||||
out += fmt.Sprintf("digraph \"%s\" {\n", g.GetName())
|
|
||||||
out += fmt.Sprintf("\tlabel=\"%s\";\n", g.GetName())
|
|
||||||
//out += "\tnode [shape=box];\n"
|
|
||||||
str := ""
|
str := ""
|
||||||
// XXX: add determinism to this loop
|
name := obj.name()
|
||||||
for i := range g.Adjacency() { // reverse paths
|
str += fmt.Sprintf("digraph \"%s\" {\n", name)
|
||||||
v1 := html.EscapeString(i.String()) // 1st vertex
|
str += fmt.Sprintf("\tlabel=\"%s\";\n", name)
|
||||||
if ptrLabels {
|
//if obj.filter() == "dot" || true {
|
||||||
text := fmt.Sprintf("%p", i)
|
str += fmt.Sprintf("\tnewrank=true;\n")
|
||||||
small := fmt.Sprintf("<FONT POINT-SIZE=\"%d\">%s</FONT>", ptrLabelsSize, text)
|
|
||||||
out += fmt.Sprintf("\t\"%p\" [label=<%s<BR />%s>];\n", i, v1, small)
|
|
||||||
} else {
|
|
||||||
out += fmt.Sprintf("\t\"%p\" [label=<%s>];\n", i, v1)
|
|
||||||
}
|
|
||||||
|
|
||||||
for j := range g.Adjacency()[i] {
|
|
||||||
k := g.Adjacency()[i][j]
|
|
||||||
//v2 := html.EscapeString(j.String()) // 2nd vertex
|
|
||||||
e := html.EscapeString(k.String()) // edge
|
|
||||||
// use str for clearer output ordering
|
|
||||||
//if fmtBoldFn(k) { // TODO: add this sort of formatting
|
|
||||||
// str += fmt.Sprintf("\t\"%s\" -> \"%s\" [label=<%s>,style=bold];\n", i, j, k)
|
|
||||||
//} else {
|
|
||||||
if false { // XXX: don't need the labels for edges
|
|
||||||
text := fmt.Sprintf("%p", k)
|
|
||||||
small := fmt.Sprintf("<FONT POINT-SIZE=\"%d\">%s</FONT>", ptrLabelsSize, text)
|
|
||||||
str += fmt.Sprintf("\t\"%p\" -> \"%p\" [label=<%s<BR />%s>];\n", i, j, e, small)
|
|
||||||
} else {
|
|
||||||
str += fmt.Sprintf("\t\"%p\" -> \"%p\" [label=<%s>];\n", i, j, e)
|
|
||||||
}
|
|
||||||
//}
|
//}
|
||||||
}
|
|
||||||
}
|
//str += "\tnode [shape=box];\n"
|
||||||
out += str
|
|
||||||
out += "}\n"
|
for _, g := range obj.graphs() { // deterministic
|
||||||
return
|
str += g.graphvizBody(obj.Graphs[g])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecGraphviz writes out the graphviz data and runs the correct graphviz
|
str += "}\n"
|
||||||
// filter command.
|
|
||||||
func (g *Graph) ExecGraphviz(program, filename, hostname string) error {
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exec writes out the graphviz data and runs the correct graphviz filter
|
||||||
|
// command.
|
||||||
|
func (obj *Graphviz) Exec() error {
|
||||||
|
filter := ""
|
||||||
|
switch obj.Filter {
|
||||||
|
case "":
|
||||||
|
filter = graphvizDefaultFilter
|
||||||
|
|
||||||
|
case "dot", "neato", "twopi", "circo", "fdp", "sfdp", "patchwork", "osage":
|
||||||
|
filter = obj.Filter
|
||||||
|
|
||||||
switch program {
|
|
||||||
case "dot", "neato", "twopi", "circo", "fdp":
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid graphviz program selected")
|
return fmt.Errorf("invalid graphviz filter selected")
|
||||||
}
|
}
|
||||||
|
|
||||||
if filename == "" {
|
if obj.Filename == "" {
|
||||||
return fmt.Errorf("no filename given")
|
return fmt.Errorf("no filename given")
|
||||||
}
|
}
|
||||||
|
|
||||||
if hostname != "" {
|
filename := obj.Filename
|
||||||
filename = fmt.Sprintf("%s@%s", filename, hostname)
|
if obj.Hostname != "" {
|
||||||
|
filename = fmt.Sprintf("%s@%s", obj.Filename, obj.Hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
// run as a normal user if possible when run with sudo
|
// run as a normal user if possible when run with sudo
|
||||||
uid, err1 := strconv.Atoi(os.Getenv("SUDO_UID"))
|
uid, err1 := strconv.Atoi(os.Getenv("SUDO_UID"))
|
||||||
gid, err2 := strconv.Atoi(os.Getenv("SUDO_GID"))
|
gid, err2 := strconv.Atoi(os.Getenv("SUDO_GID"))
|
||||||
|
|
||||||
err := ioutil.WriteFile(filename, []byte(g.Graphviz()), 0644)
|
if err := ioutil.WriteFile(filename, []byte(obj.Text()), 0644); err != nil {
|
||||||
if err != nil {
|
return errwrap.Wrapf(err, "error writing to filename")
|
||||||
return fmt.Errorf("error writing to filename")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err1 == nil && err2 == nil {
|
if err1 == nil && err2 == nil {
|
||||||
if err := os.Chown(filename, uid, gid); err != nil {
|
if err := os.Chown(filename, uid, gid); err != nil {
|
||||||
return fmt.Errorf("error changing file owner")
|
return errwrap.Wrapf(err, "error changing file owner")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
path, err := exec.LookPath(program)
|
path, err := exec.LookPath(filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("the Graphviz program is missing")
|
return errwrap.Wrapf(err, "the Graphviz filter is missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
out := fmt.Sprintf("%s.png", filename)
|
out := fmt.Sprintf("%s.png", filename)
|
||||||
@@ -133,9 +176,97 @@ func (g *Graph) ExecGraphviz(program, filename, hostname string) error {
|
|||||||
Gid: uint32(gid),
|
Gid: uint32(gid),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_, err = cmd.Output()
|
|
||||||
if err != nil {
|
if _, err := cmd.Output(); err != nil {
|
||||||
return fmt.Errorf("error writing to image")
|
return errwrap.Wrapf(err, "error writing to image")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// GraphvizOpts specifies some formatting for each graph.
|
||||||
|
type GraphvizOpts struct {
|
||||||
|
// Style represents the node style string.
|
||||||
|
Style string
|
||||||
|
|
||||||
|
// Font represents the node font to use.
|
||||||
|
// TODO: implement me
|
||||||
|
Font string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (obj *Graph) graphvizBody(opts *GraphvizOpts) string {
|
||||||
|
str := ""
|
||||||
|
style := ""
|
||||||
|
if opts != nil {
|
||||||
|
style = opts.Style
|
||||||
|
}
|
||||||
|
|
||||||
|
// all in deterministic order
|
||||||
|
for _, i := range obj.VerticesSorted() { // reverse paths
|
||||||
|
v1 := html.EscapeString(i.String()) // 1st vertex
|
||||||
|
if ptrLabels {
|
||||||
|
text := fmt.Sprintf("%p", i)
|
||||||
|
small := fmt.Sprintf("<FONT POINT-SIZE=\"%d\">%s</FONT>", ptrLabelsSize, text)
|
||||||
|
str += fmt.Sprintf("\t\"%p\" [label=<%s<BR />%s>];\n", i, v1, small)
|
||||||
|
} else {
|
||||||
|
str += fmt.Sprintf("\t\"%p\" [label=<%s>];\n", i, v1)
|
||||||
|
}
|
||||||
|
|
||||||
|
vs := []Vertex{}
|
||||||
|
for j := range obj.Adjacency()[i] {
|
||||||
|
vs = append(vs, j)
|
||||||
|
}
|
||||||
|
sort.Sort(VertexSlice(vs)) // deterministic order
|
||||||
|
|
||||||
|
for _, j := range vs {
|
||||||
|
k := obj.Adjacency()[i][j]
|
||||||
|
//v2 := html.EscapeString(j.String()) // 2nd vertex
|
||||||
|
e := html.EscapeString(k.String()) // edge
|
||||||
|
// use str for clearer output ordering
|
||||||
|
//if fmtBoldFn(k) { // TODO: add this sort of formatting
|
||||||
|
// str += fmt.Sprintf("\t\"%s\" -> \"%s\" [label=<%s>,style=bold];\n", i, j, k)
|
||||||
|
//} else {
|
||||||
|
if false { // XXX: don't need the labels for edges
|
||||||
|
text := fmt.Sprintf("%p", k)
|
||||||
|
small := fmt.Sprintf("<FONT POINT-SIZE=\"%d\">%s</FONT>", ptrLabelsSize, text)
|
||||||
|
str += fmt.Sprintf("\t\"%p\" -> \"%p\" [label=<%s<BR />%s>];\n", i, j, e, small)
|
||||||
|
} else {
|
||||||
|
if style != "" {
|
||||||
|
str += fmt.Sprintf("\t\"%p\" -> \"%p\" [label=<%s>,style=%s];\n", i, j, e, style)
|
||||||
|
} else {
|
||||||
|
str += fmt.Sprintf("\t\"%p\" -> \"%p\" [label=<%s>];\n", i, j, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// Graphviz outputs the graph in graphviz format.
|
||||||
|
// https://en.wikipedia.org/wiki/DOT_%28graph_description_language%29
|
||||||
|
func (obj *Graph) Graphviz() string {
|
||||||
|
gv := &Graphviz{
|
||||||
|
Graphs: map[*Graph]*GraphvizOpts{
|
||||||
|
obj: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return gv.Text()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecGraphviz writes out the graphviz data and runs the correct graphviz
|
||||||
|
// filter command.
|
||||||
|
func (obj *Graph) ExecGraphviz(filename string) error {
|
||||||
|
gv := &Graphviz{
|
||||||
|
Graphs: map[*Graph]*GraphvizOpts{
|
||||||
|
obj: nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
//Filter: filter,
|
||||||
|
Filename: filename,
|
||||||
|
//Hostname: hostname,
|
||||||
|
}
|
||||||
|
return gv.Exec()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user