mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
Re-introduce experimental/graphviz
This commit is contained in:
121
experimental/graphviz/handlers.go
Normal file
121
experimental/graphviz/handlers.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
func dot(w io.Writer, t render.RenderableNodes) {
|
||||
fmt.Fprintf(w, "digraph G {\n")
|
||||
fmt.Fprintf(w, "\toutputorder=edgesfirst;\n")
|
||||
fmt.Fprintf(w, "\toverlap=scale;\n")
|
||||
fmt.Fprintf(w, "\tnode [style=filled];\n")
|
||||
fmt.Fprintf(w, "\t\n")
|
||||
for id, rn := range t {
|
||||
label := rn.LabelMajor
|
||||
if len(label) > 20 {
|
||||
label = label[:20] + "..."
|
||||
}
|
||||
fmt.Fprintf(w, "\t%q [label=%q];\n", id, label)
|
||||
for _, other := range rn.Adjacency {
|
||||
fmt.Fprintf(w, "\t%q -> %q;\n", id, other)
|
||||
}
|
||||
fmt.Fprintf(w, "\t\n")
|
||||
}
|
||||
fmt.Fprintf(w, "}\n")
|
||||
}
|
||||
|
||||
func handleHTML(rpt report.Report) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
logRequest("HTML", r)
|
||||
fmt.Fprintf(w, `<html><head></head><body><img src="/svg?%s" width="95%" height="95%"/></body></html>`, r.URL.RawQuery)
|
||||
}
|
||||
}
|
||||
|
||||
func handleDot(rpt report.Report) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
logRequest("Dot", r)
|
||||
|
||||
r.ParseForm()
|
||||
var (
|
||||
engine = getDefault(r.Form, "engine", "twopi")
|
||||
topology = getDefault(r.Form, "topology", "containers")
|
||||
)
|
||||
log.Printf("engine=%s topology=%s", engine, topology)
|
||||
|
||||
t, err := renderTo(rpt, topology)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
log.Printf("render %s to %d node(s)", topology, len(t))
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
dot(w, t)
|
||||
}
|
||||
}
|
||||
|
||||
func handleSVG(rpt report.Report) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
logRequest("SVG", r)
|
||||
|
||||
r.ParseForm()
|
||||
var (
|
||||
engine = getDefault(r.Form, "engine", "twopi")
|
||||
topology = getDefault(r.Form, "topology", "containers")
|
||||
)
|
||||
log.Printf("engine=%s topology=%s", engine, topology)
|
||||
|
||||
t, err := renderTo(rpt, topology)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
log.Printf("render %s to %d node(s)", topology, len(t))
|
||||
|
||||
vizcmd, err := exec.LookPath(engine)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
dot(&buf, t)
|
||||
|
||||
w.Header().Set("Content-Type", "image/svg+xml")
|
||||
if err := (&exec.Cmd{
|
||||
Path: vizcmd,
|
||||
Args: []string{vizcmd, "-Tsvg"},
|
||||
Stdin: &buf,
|
||||
Stdout: w,
|
||||
Stderr: os.Stderr,
|
||||
}).Run(); err != nil {
|
||||
log.Print(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func logRequest(what string, r *http.Request) {
|
||||
log.Printf("> %s %s %s %s", what, r.RemoteAddr, r.Method, r.URL.String())
|
||||
}
|
||||
|
||||
func getDefault(v url.Values, key, def string) string {
|
||||
if val := v.Get(key); val != "" {
|
||||
return val
|
||||
}
|
||||
return def
|
||||
}
|
||||
1
experimental/graphviz/kubernetes.json
Normal file
1
experimental/graphviz/kubernetes.json
Normal file
File diff suppressed because one or more lines are too long
31
experimental/graphviz/main.go
Normal file
31
experimental/graphviz/main.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
listen = flag.String("listen", ":8080", "HTTP listen address")
|
||||
)
|
||||
flag.Parse()
|
||||
|
||||
log.Printf("reading /api/report from stdin...")
|
||||
var rpt report.Report
|
||||
if err := json.NewDecoder(os.Stdin).Decode(&rpt); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
http.HandleFunc("/", handleHTML(rpt))
|
||||
http.HandleFunc("/dot", handleDot(rpt))
|
||||
http.HandleFunc("/svg", handleSVG(rpt))
|
||||
http.HandleFunc("/favicon.ico", func(w http.ResponseWriter, _ *http.Request) { http.Error(w, "Stop it", http.StatusTeapot) })
|
||||
log.Printf("listening on %s", *listen)
|
||||
http.ListenAndServe(*listen, nil)
|
||||
}
|
||||
22
experimental/graphviz/render.go
Normal file
22
experimental/graphviz/render.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/scope/render"
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
func renderTo(rpt report.Report, topology string) (render.RenderableNodes, error) {
|
||||
renderer, ok := map[string]render.Renderer{
|
||||
"applications": render.FilterUnconnected(render.ProcessWithContainerNameRenderer),
|
||||
"applications-by-name": render.FilterUnconnected(render.ProcessNameRenderer),
|
||||
"containers": render.ContainerWithImageNameRenderer,
|
||||
"containers-by-image": render.ContainerImageRenderer,
|
||||
"hosts": render.HostRenderer,
|
||||
}[topology]
|
||||
if !ok {
|
||||
return render.RenderableNodes{}, fmt.Errorf("unknown topology %v", topology)
|
||||
}
|
||||
return renderer.Render(rpt), nil
|
||||
}
|
||||
Reference in New Issue
Block a user