Add graphviz generation and visualization

This requires graphviz to be installed on your machine. If you run the
command with sudo, it will create the files with the original user
ownership to make it easier to remove them without root.
This commit is contained in:
James Shubin
2015-12-29 01:04:03 -05:00
parent 6b4fa21074
commit 1ba6be2957
5 changed files with 126 additions and 1 deletions

View File

@@ -55,6 +55,10 @@ func NewFileType(name, path, content, state string) *FileType {
} }
} }
func (obj *FileType) GetType() string {
return "File"
}
// File watcher for files and directories // File watcher for files and directories
// Modify with caution, probably important to write some test cases first! // Modify with caution, probably important to write some test cases first!
// obj.Path: file or directory // obj.Path: file or directory

16
main.go
View File

@@ -102,6 +102,12 @@ func run(c *cli.Context) {
log.Fatal("Graph failure") log.Fatal("Graph failure")
} }
log.Printf("Graph: %v\n", G) // show graph log.Printf("Graph: %v\n", G) // show graph
err := G.ExecGraphviz(c.String("graphviz-filter"), c.String("graphviz"))
if err != nil {
log.Printf("Graphviz: %v", err)
} else {
log.Printf("Graphviz: Successfully generated graph!")
}
G.SetVertex() G.SetVertex()
if first { if first {
// G.Start(...) needs to be synchronous or wait, // G.Start(...) needs to be synchronous or wait,
@@ -174,6 +180,16 @@ func main() {
Value: "", Value: "",
Usage: "code definition to run", Usage: "code definition to run",
}, },
cli.StringFlag{
Name: "graphviz, g",
Value: "",
Usage: "output file for graphviz data",
},
cli.StringFlag{
Name: "graphviz-filter, gf",
Value: "dot", // directed graph default
Usage: "graphviz filter to use",
},
// useful for testing multiple instances on same machine // useful for testing multiple instances on same machine
cli.StringFlag{ cli.StringFlag{
Name: "hostname", Name: "hostname",

View File

@@ -19,10 +19,15 @@
package main package main
import ( import (
//"container/list" // doubly linked list "errors"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"os"
"os/exec"
"strconv"
"sync" "sync"
"syscall"
) )
//go:generate stringer -type=graphState -output=graphstate_stringer.go //go:generate stringer -type=graphState -output=graphstate_stringer.go
@@ -81,6 +86,11 @@ func NewEdge(name string) *Edge {
} }
} }
// returns the name of the graph
func (g *Graph) GetName() string {
return g.Name
}
// set name of the graph // set name of the graph
func (g *Graph) SetName(name string) { func (g *Graph) SetName(name string) {
g.Name = name g.Name = name
@@ -208,6 +218,88 @@ func (g *Graph) String() string {
return fmt.Sprintf("Vertices(%d), Edges(%d)", g.NumVertices(), g.NumEdges()) return fmt.Sprintf("Vertices(%d), Edges(%d)", g.NumVertices(), g.NumEdges())
} }
// output the graph in graphviz format
// https://en.wikipedia.org/wiki/DOT_%28graph_description_language%29
func (g *Graph) Graphviz() (out string) {
//digraph g {
// label="hello world";
// node [shape=box];
// A [label="A"];
// B [label="B"];
// C [label="C"];
// D [label="D"];
// E [label="E"];
// A -> B [label=f];
// B -> C [label=g];
// D -> E [label=h];
//}
out += fmt.Sprintf("digraph %v {\n", g.GetName())
out += fmt.Sprintf("\tlabel=\"%v\";\n", g.GetName())
//out += "\tnode [shape=box];\n"
str := ""
for i, _ := range g.Adjacency { // reverse paths
out += fmt.Sprintf("\t%v [label=\"%v[%v]\"];\n", i.GetName(), i.GetType(), i.GetName())
for j, _ := range g.Adjacency[i] {
k := g.Adjacency[i][j]
// use str for clearer output ordering
str += fmt.Sprintf("\t%v -> %v [label=%v];\n", i.GetName(), j.GetName(), k.Name)
}
}
out += str
out += "}\n"
return
}
// write out the graphviz data and run the correct graphviz filter command
func (g *Graph) ExecGraphviz(program, filename string) error {
switch program {
case "dot", "neato", "twopi", "circo", "fdp":
default:
return errors.New("Invalid graphviz program selected!")
}
if filename == "" {
return errors.New("No filename given!")
}
// run as a normal user if possible when run with sudo
uid, err1 := strconv.Atoi(os.Getenv("SUDO_UID"))
gid, err2 := strconv.Atoi(os.Getenv("SUDO_GID"))
err := ioutil.WriteFile(filename, []byte(g.Graphviz()), 0644)
if err != nil {
return errors.New("Error writing to filename!")
}
if err1 == nil && err2 == nil {
if err := os.Chown(filename, uid, gid); err != nil {
return errors.New("Error changing file owner!")
}
}
path, err := exec.LookPath(program)
if err != nil {
return errors.New("Graphviz is missing!")
}
out := fmt.Sprintf("%v.png", filename)
cmd := exec.Command(path, "-Tpng", fmt.Sprintf("-o%v", out), filename)
if err1 == nil && err2 == nil {
cmd.SysProcAttr = &syscall.SysProcAttr{}
cmd.SysProcAttr.Credential = &syscall.Credential{
Uid: uint32(uid),
Gid: uint32(gid),
}
}
_, err = cmd.Output()
if err != nil {
return errors.New("Error writing to image!")
}
return nil
}
// google/golang hackers apparently do not think contains should be a built-in! // google/golang hackers apparently do not think contains should be a built-in!
func Contains(s []*Vertex, element *Vertex) bool { func Contains(s []*Vertex, element *Vertex) bool {
for _, v := range s { for _, v := range s {

View File

@@ -45,6 +45,10 @@ func NewServiceType(name, state, startup string) *ServiceType {
} }
} }
func (obj *ServiceType) GetType() string {
return "Service"
}
// Service watcher // Service watcher
func (obj *ServiceType) Watch() { func (obj *ServiceType) Watch() {
// obj.Name: service name // obj.Name: service name

View File

@@ -26,6 +26,7 @@ import (
type Type interface { type Type interface {
Init() Init()
GetName() string // can't be named "Name()" because of struct field GetName() string // can't be named "Name()" because of struct field
GetType() string
Watch() Watch()
StateOK() bool // TODO: can we rename this to something better? StateOK() bool // TODO: can we rename this to something better?
Apply() bool Apply() bool
@@ -71,6 +72,10 @@ func (obj *BaseType) GetName() string {
return obj.Name return obj.Name
} }
func (obj *BaseType) GetType() string {
return "Base"
}
func (obj *BaseType) GetVertex() *Vertex { func (obj *BaseType) GetVertex() *Vertex {
return obj.vertex return obj.vertex
} }
@@ -198,6 +203,10 @@ func (obj *BaseType) Process(typ Type) {
} }
func (obj *NoopType) GetType() string {
return "Noop"
}
func (obj *NoopType) Watch() { func (obj *NoopType) Watch() {
//vertex := obj.vertex // stored with SetVertex //vertex := obj.vertex // stored with SetVertex
var send = false // send event? var send = false // send event?