mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 02:00:43 +00:00
Start showing traces in the UI
This commit is contained in:
@@ -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())
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user