Commit 603af813 authored by Austin Clements's avatar Austin Clements

cmd/trace: list and link to worst mutator utilization windows

This adds the ability to click a point on the MMU graph to show a list
of the worst 10 mutator utilization windows of the selected size. This
list in turn links to the trace viewer to drill down on specifically
what happened in each specific window.

Change-Id: Ic1b72d8b37fbf2212211c513cf36b34788b30133
Reviewed-on: https://go-review.googlesource.com/c/60795
Run-TryBot: Austin Clements <austin@google.com>
Reviewed-by: default avatarHyang-Ah Hana Kim <hyangah@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent cbe04e8d
...@@ -189,7 +189,7 @@ var templMain = template.Must(template.New("").Parse(` ...@@ -189,7 +189,7 @@ var templMain = template.Must(template.New("").Parse(`
<body> <body>
{{if $}} {{if $}}
{{range $e := $}} {{range $e := $}}
<a href="/trace?start={{$e.Start}}&end={{$e.End}}">View trace ({{$e.Name}})</a><br> <a href="{{$e.URL}}">View trace ({{$e.Name}})</a><br>
{{end}} {{end}}
<br> <br>
{{else}} {{else}}
......
...@@ -13,6 +13,7 @@ import ( ...@@ -13,6 +13,7 @@ import (
"log" "log"
"math" "math"
"net/http" "net/http"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
...@@ -21,6 +22,7 @@ import ( ...@@ -21,6 +22,7 @@ import (
func init() { func init() {
http.HandleFunc("/mmu", httpMMU) http.HandleFunc("/mmu", httpMMU)
http.HandleFunc("/mmuPlot", httpMMUPlot) http.HandleFunc("/mmuPlot", httpMMUPlot)
http.HandleFunc("/mmuDetails", httpMMUDetails)
} }
var mmuCache struct { var mmuCache struct {
...@@ -98,6 +100,9 @@ var templMMU = `<!doctype html> ...@@ -98,6 +100,9 @@ var templMMU = `<!doctype html>
google.charts.load('current', {'packages':['corechart']}); google.charts.load('current', {'packages':['corechart']});
google.charts.setOnLoadCallback(refreshChart); google.charts.setOnLoadCallback(refreshChart);
var chart;
var curve;
function niceDuration(ns) { function niceDuration(ns) {
if (ns < 1e3) { return ns + 'ns'; } if (ns < 1e3) { return ns + 'ns'; }
else if (ns < 1e6) { return ns / 1e3 + 'µs'; } else if (ns < 1e6) { return ns / 1e3 + 'µs'; }
...@@ -114,7 +119,7 @@ var templMMU = `<!doctype html> ...@@ -114,7 +119,7 @@ var templMMU = `<!doctype html>
} }
function drawChart(plotData) { function drawChart(plotData) {
var curve = plotData.curve; curve = plotData.curve;
var data = new google.visualization.DataTable(); var data = new google.visualization.DataTable();
data.addColumn('number', 'Window duration'); data.addColumn('number', 'Window duration');
data.addColumn('number', 'Minimum mutator utilization'); data.addColumn('number', 'Minimum mutator utilization');
...@@ -148,13 +153,85 @@ var templMMU = `<!doctype html> ...@@ -148,13 +153,85 @@ var templMMU = `<!doctype html>
var container = $('#mmu_chart'); var container = $('#mmu_chart');
container.empty(); container.empty();
var chart = new google.visualization.LineChart(container[0]); chart = new google.visualization.LineChart(container[0]);
chart = new google.visualization.LineChart(document.getElementById('mmu_chart'));
chart.draw(data, options); chart.draw(data, options);
google.visualization.events.addListener(chart, 'select', selectHandler);
}
function selectHandler() {
var items = chart.getSelection();
if (items.length === 0) {
return;
}
var details = $('#details');
details.empty();
var windowNS = curve[items[0].row][0];
var url = '/mmuDetails?window=' + windowNS;
$.getJSON(url)
.fail(function(xhr, status, error) {
details.text(status + ': ' + url + ' could not be loaded');
})
.done(function(worst) {
details.text('Lowest mutator utilization in ' + niceDuration(windowNS) + ' windows:');
for (var i = 0; i < worst.length; i++) {
details.append($('<br/>'));
var text = worst[i].MutatorUtil.toFixed(3) + ' at time ' + niceDuration(worst[i].Time);
details.append($('<a/>').text(text).attr('href', worst[i].URL));
}
});
} }
</script> </script>
</head> </head>
<body> <body>
<div id="mmu_chart" style="width: 900px; height: 500px">Loading plot...</div> <div id="mmu_chart" style="width: 900px; height: 500px">Loading plot...</div>
<div id="details">Select a point for details.</div>
</body> </body>
</html> </html>
` `
// httpMMUDetails serves details of an MMU graph at a particular window.
func httpMMUDetails(w http.ResponseWriter, r *http.Request) {
_, mmuCurve, err := getMMUCurve()
if err != nil {
http.Error(w, fmt.Sprintf("failed to parse events: %v", err), http.StatusInternalServerError)
return
}
windowStr := r.FormValue("window")
window, err := strconv.ParseUint(windowStr, 10, 64)
if err != nil {
http.Error(w, fmt.Sprintf("failed to parse window parameter %q: %v", windowStr, err), http.StatusBadRequest)
return
}
worst := mmuCurve.Examples(time.Duration(window), 10)
// Construct a link for each window.
var links []linkedUtilWindow
for _, ui := range worst {
links = append(links, newLinkedUtilWindow(ui, time.Duration(window)))
}
err = json.NewEncoder(w).Encode(links)
if err != nil {
log.Printf("failed to serialize trace: %v", err)
return
}
}
type linkedUtilWindow struct {
trace.UtilWindow
URL string
}
func newLinkedUtilWindow(ui trace.UtilWindow, window time.Duration) linkedUtilWindow {
// Find the range containing this window.
var r Range
for _, r = range ranges {
if r.EndTime > ui.Time {
break
}
}
return linkedUtilWindow{ui, fmt.Sprintf("%s#%v:%v", r.URL(), float64(ui.Time)/1e6, float64(ui.Time+int64(window))/1e6)}
}
...@@ -272,9 +272,15 @@ func httpJSONTrace(w http.ResponseWriter, r *http.Request) { ...@@ -272,9 +272,15 @@ func httpJSONTrace(w http.ResponseWriter, r *http.Request) {
// Range is a named range // Range is a named range
type Range struct { type Range struct {
Name string Name string
Start int Start int
End int End int
StartTime int64
EndTime int64
}
func (r Range) URL() string {
return fmt.Sprintf("/trace?start=%d&end=%d", r.Start, r.End)
} }
// splitTrace splits the trace into a number of ranges, // splitTrace splits the trace into a number of ranges,
...@@ -345,10 +351,14 @@ func splittingTraceConsumer(max int) (*splitter, traceConsumer) { ...@@ -345,10 +351,14 @@ func splittingTraceConsumer(max int) (*splitter, traceConsumer) {
start := 0 start := 0
for i, ev := range sizes { for i, ev := range sizes {
if sum+ev.Sz > max { if sum+ev.Sz > max {
startTime := time.Duration(sizes[start].Time * 1000)
endTime := time.Duration(ev.Time * 1000)
ranges = append(ranges, Range{ ranges = append(ranges, Range{
Name: fmt.Sprintf("%v-%v", time.Duration(sizes[start].Time*1000), time.Duration(ev.Time*1000)), Name: fmt.Sprintf("%v-%v", startTime, endTime),
Start: start, Start: start,
End: i + 1, End: i + 1,
StartTime: int64(startTime),
EndTime: int64(endTime),
}) })
start = i + 1 start = i + 1
sum = minSize sum = minSize
...@@ -363,9 +373,11 @@ func splittingTraceConsumer(max int) (*splitter, traceConsumer) { ...@@ -363,9 +373,11 @@ func splittingTraceConsumer(max int) (*splitter, traceConsumer) {
if end := len(sizes) - 1; start < end { if end := len(sizes) - 1; start < end {
ranges = append(ranges, Range{ ranges = append(ranges, Range{
Name: fmt.Sprintf("%v-%v", time.Duration(sizes[start].Time*1000), time.Duration(sizes[end].Time*1000)), Name: fmt.Sprintf("%v-%v", time.Duration(sizes[start].Time*1000), time.Duration(sizes[end].Time*1000)),
Start: start, Start: start,
End: end, End: end,
StartTime: int64(sizes[start].Time * 1000),
EndTime: int64(sizes[end].Time * 1000),
}) })
} }
s.Ranges = ranges s.Ranges = ranges
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment