Start showing traces in the UI

This commit is contained in:
Tom Wilkie
2015-09-17 05:40:51 +00:00
committed by Tom Wilkie
parent b6e43c8f3f
commit bdeed7219f
5 changed files with 129 additions and 88 deletions

View File

@@ -61,7 +61,7 @@ func (t *tracer) http(port int) {
w.WriteHeader(204)
})
router.Methods("GET").Path("/trace").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
router.Methods("GET").Path("/traces").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
respondWith(w, http.StatusOK, t.store.Traces())
})

View File

@@ -20,9 +20,9 @@ type key struct {
}
type trace struct {
pid int
root *ptrace.Fd
children []*trace
PID int
Root *ptrace.Fd
Children []*trace
}
type store struct {
@@ -63,7 +63,7 @@ func (s *store) RecordConnection(pid int, connection *ptrace.Fd) {
s.Lock()
defer s.Unlock()
newTrace := &trace{pid: pid, root: connection}
newTrace := &trace{PID: pid, Root: connection}
newTraceKey := newKey(connection)
log.Printf("Recording trace: %+v", newTrace)
@@ -75,7 +75,7 @@ func (s *store) RecordConnection(pid int, connection *ptrace.Fd) {
parentNode.Remove()
parentTrace := parentNode.Value.(*trace)
log.Printf(" Found parent trace: %+v", parentTrace)
parentTrace.children = append(parentTrace.children, newTrace)
parentTrace.Children = append(parentTrace.Children, newTrace)
} else {
s.traces.Insert(newTraceKey, newTrace)
}
@@ -89,7 +89,7 @@ func (s *store) RecordConnection(pid int, connection *ptrace.Fd) {
childNode.Remove()
childTrace := childNode.Value.(*trace)
log.Printf(" Found child trace: %+v", childTrace)
newTrace.children = append(newTrace.children, childTrace)
newTrace.Children = append(newTrace.Children, childTrace)
} else {
s.traces.Insert(childTraceKey, newTrace)
}
@@ -102,12 +102,9 @@ func (s *store) Traces() []*trace {
var traces []*trace
var cur = s.traces.First()
for {
for cur != nil {
traces = append(traces, cur.Value.(*trace))
cur = cur.Next()
if cur == nil {
break
}
}
return traces
}

View File

@@ -35,14 +35,14 @@ type Fd struct {
fd int
Start int64
stop int64
Stop int64
sent int64
received int64
FromAddr net.IP
FromPort uint16
toAddr net.IP
toPort uint16
ToAddr net.IP
ToPort uint16
// Fds are connections, and can have a causal-link to other Fds
Children []*Fd
@@ -120,6 +120,11 @@ func getLocalAddr(pid, fd int) (addr net.IP, port uint16, err error) {
return
}
// in milliseconds
func now() int64 {
return time.Now().UnixNano() / 1000000
}
// We want to get the listening address from /proc
func newListeningFd(pid, fd int) (*Fd, error) {
localAddr, localPort, err := getLocalAddr(pid, fd)
@@ -128,8 +133,8 @@ func newListeningFd(pid, fd int) (*Fd, error) {
}
return &Fd{
direction: listening, fd: fd, Start: time.Now().Unix(),
toAddr: localAddr, toPort: uint16(localPort),
direction: listening, fd: fd, Start: now(),
ToAddr: localAddr, ToPort: uint16(localPort),
}, nil
}
@@ -141,9 +146,9 @@ func newConnectionFd(pid, fd int, remoteAddr net.IP, remotePort uint16) (*Fd, er
}
return &Fd{
direction: outgoing, fd: fd, Start: time.Now().Unix(),
direction: outgoing, fd: fd, Start: now(),
FromAddr: localAddr, FromPort: uint16(localPort),
toAddr: remoteAddr, toPort: remotePort,
ToAddr: remoteAddr, ToPort: remotePort,
}, nil
}
@@ -154,12 +159,12 @@ func (fd *Fd) newConnection(addr net.IP, port uint16, newFd int) (*Fd, error) {
}
return &Fd{
direction: incoming, fd: newFd, Start: time.Now().Unix(),
toAddr: fd.toAddr, toPort: fd.toPort,
direction: incoming, fd: newFd, Start: now(),
ToAddr: fd.ToAddr, ToPort: fd.ToPort,
FromAddr: addr, FromPort: port,
}, nil
}
func (fd *Fd) close() {
fd.stop = time.Now().Unix()
fd.Stop = now()
}

View File

@@ -168,7 +168,7 @@ func (t *thread) handleAccept(call, result *syscall.PtraceRegs) {
t.process.newFd(connection)
t.logf("Accepted connection from %s:%d -> %s:%d on fd %d, new fd %d",
addr, port, connection.toAddr, connection.toPort, listeningFdNum, connectionFdNum)
addr, port, connection.ToAddr, connection.ToPort, listeningFdNum, connectionFdNum)
}
func (t *thread) handleConnect(call, result *syscall.PtraceRegs) {
@@ -192,7 +192,7 @@ func (t *thread) handleConnect(call, result *syscall.PtraceRegs) {
}
t.logf("Made connection from %s:%d -> %s:%d on fd %d",
connection.toAddr, connection.toPort, connection.FromAddr,
connection.ToAddr, connection.ToPort, connection.FromAddr,
connection.FromPort, fd)
}

View File

@@ -12,102 +12,141 @@
<script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.3/handlebars.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<script src="sprintf.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<script>
$(function () {
var containers = {};
var traces = {}
var currentContainer = null;
var containersByID = {};
var containers = [];
var traces = [];
Handlebars.registerHelper('ts', function(input) {
var ts = moment(input).format("LTS")
return new Handlebars.SafeString(ts);
});
Handlebars.registerHelper('duration', function(input) {
var ms = input.Stop - input.Start
if (ms < 60000) {
return sprintf("%0.2fs", ms / 1000)
}
var ds = moment.duration(ms).humanize();
return new Handlebars.SafeString(ds);
});
function render() {
var template = $('script#process-template').text();
template = Handlebars.compile(template);
var rendered = template({
containers: containers,
container: currentContainer,
traces: traces
});
$('body').html(rendered);
}
function updateContainers() {
$.get("/containers").done(function (data) {
$("ul.containers").html("")
data.sort(function (a, b) {
if (a.Name > b.Name) {
return 1;
}
if (a.Name < b.Name) {
return -1;
}
// a must be equal to b
return 0;
});
$.each(data, function(i, container) {
containers[container.Id] = container
$("ul.containers").append(sprintf("<li id='%s'>%s</li>", container.Id, container.Name.substring(1)))
});
window.setTimeout(updateContainers, 5 * 1000)
$.get("/containers").done(function (data) {
data.sort(function (a, b) {
if (a.Name > b.Name) {
return 1;
}
if (a.Name < b.Name) {
return -1;
}
// a must be equal to b
return 0;
});
containers = data;
$.each(data, function(i, container) {
containersByID[container.Id] = container
});
render();
window.setTimeout(updateContainers, 5 * 1000);
});
}
updateContainers()
$("ul.containers").on("click", "li", function() {
var container = containers[$(this).attr("id")]
var containerTraces = traces[$(this).attr("id")] || []
function fetchTraces() {
$.get("/traces").done(function (data) {
traces = data;
render();
window.setTimeout(fetchTraces, 2 * 1000);
});
}
fetchTraces();
var template = $('script#process-template').text();
template = Handlebars.compile(template);
var rendered = template({container: container, traces: containerTraces});
$('div.mainview').html(rendered);
$("body").on("click", "ul.containers li", function() {
var container = containersByID[$(this).attr("id")]
currentContainer = container
render()
})
$("div.mainview").on("click", "button.start", function() {
$("body").on("click", "div.mainview button.start", function() {
var id = $(this).parent().data("containerId")
var container = containers[id]
var container = containersByID[id]
$.post(sprintf("/pid/%d", container.State.Pid))
})
$("div.mainview").on("click", "button.stop", function() {
$("body").on("click", "div.mainview button.stop", function() {
var id = $(this).parent().data("containerId")
var container = containers[id]
var container = containersByID[id]
$.ajax({
url: sprintf("/pid/%d", container.State.Pid),
type: 'DELETE',
url: sprintf("/pid/%d", container.State.Pid),
type: 'DELETE',
});
})
})
</script>
<style>
ul.containers li {
cursor: pointer;
}
table {
width: 100%;
}
</style>
<script type="text/x-handlebars-template" id="process-template">
<div class="container-fluid">
<div class="col-md-4">
<h1>Weave Tracer</h1>
<ul class="containers">
{{#containers}}
<li id={{Id}}>{{Name}}</li>
{{/containers}}
</ul>
</div>
<div class="col-md-8 mainview">
{{#if container}}
<h2>{{container.Name}}</h2>
<div class="btn-group" role="group" data-container-id="{{container.Id}}">
<button type="button" class="btn btn-default start">
<span class="glyphicon glyphicon-play" aria-hidden="true"></span> Start</button>
<button type="button" class="btn btn-default stop">
<span class="glyphicon glyphicon-stop" aria-hidden="true"></span> Stop</button>
</div>
<table>
<thead><tr><th>Start time</th><th>Duration</th><th>PID<th>From</th><th>To</th><th>Sub-traces</th></tr></thead>
<tbody>
{{#traces}}
<tr><td>{{ts Root.Start}}</td><td>{{duration Root}}</td>
<td>{{PID}}</td><td>{{Root.FromAddr}}:{{Root.FromPort}}</td>
<td>{{Root.ToAddr}}:{{Root.ToPort}}</td></tr>
{{/traces}}
</tbody>
</table>
{{/if}}
</div>
</div>
</script>
</head>
<body>
<script type="text/x-handlebars-template" id="process-template">
<h2>{{container.Name}}</h2>
<div class="btn-group" role="group" data-container-id="{{container.Id}}">
<button type="button" class="btn btn-default start">
<span class="glyphicon glyphicon-play" aria-hidden="true"></span> Start</button>
<button type="button" class="btn btn-default stop">
<span class="glyphicon glyphicon-stop" aria-hidden="true"></span> Stop</button>
<button type="button" class="btn btn-default fetch">
<span class="glyphicon glyphicon-cloud-download" aria-hidden="true"></span> Get Traces</button>
</div>
<table>
<thead><tr><th>Start time</th><th>Duration</th><th>Sub-traces</th></tr></thead>
<tbody>
{{#traces}}
<tr><td>{{.}}</td></tr>
{{/traces}}
</tbody>
</table>
</script>
<div class="container-fluid">
<div class="col-md-4">
<h1>Weave Tracer</h1>
<ul class="containers">
</ul>
</div>
<div class="col-md-8 mainview">
</div>
</div>
</body>
</html>