]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/web/static/download_status_timeline.js
Display serverids consistently as 8-char pubkey, or 6-char tubid.
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / web / static / download_status_timeline.js
1
2 var globals = {};
3
4 function onDataReceived(data) {
5     //console.log("got data, now rendering");
6     var show_misc = false;
7     //delete data.misc;
8     var timeline = d3.select("#timeline");
9     var w = Number(timeline.style("width").slice(0,-2));
10     // the SVG fills the width of the whole div, but it will extend
11     // as far vertically as necessary (depends upon the data)
12     var chart = timeline.append("svg:svg")
13          .attr("id", "outer_chart")
14          .attr("width", w)
15          .attr("pointer-events", "all")
16         .append("svg:g")
17          .call(d3.behavior.zoom().on("zoom", pan_and_zoom))
18     ;
19     // this "backboard" rect lets us catch mouse events anywhere in the
20     // chart, even between the bars. Without it, we only see events on solid
21     // objects like bars and text, but not in the gaps between.
22     chart.append("svg:rect")
23         .attr("id", "outer_rect")
24         .attr("width", w).attr("height", 200).attr("fill", "none");
25
26     // but the stuff we put inside it should have some room
27     w = w-50;
28
29     // at this point we assume the data is fixed, but the zoom/pan is not
30
31     // create the static things (those which don't exist or not exist based
32     // upon the timeline data). Their locations will be adjusted later,
33     // during redraw, when we know the x+y coordinates
34     chart.append("svg:text")
35         .attr("class", "dyhb-label")
36         //.attr("x", "20px").attr("y", y)
37         .attr("text-anchor", "start") // anchor at top-left
38         .attr("dy", ".71em")
39         .attr("fill", "black")
40         .text("DYHB requests");
41
42     chart.append("svg:text")
43         .attr("class", "read-label")
44         //.attr("x", "20px").attr("y", y)
45         .attr("text-anchor", "start") // anchor at top-left
46         .attr("dy", ".71em")
47         .attr("fill", "black")
48         .text("read() requests");
49     chart.append("svg:text")
50         .attr("class", "segment-label")
51         //.attr("x", "20px").attr("y", y)
52         .attr("text-anchor", "start") // anchor at top-left
53         .attr("dy", ".71em")
54         .attr("fill", "black")
55         .text("segment() requests");
56     chart.append("svg:text")
57         .attr("class", "block-label")
58         //.attr("x", "20px").attr("y", y)
59         .attr("text-anchor", "start") // anchor at top-left
60         .attr("dy", ".71em")
61         .attr("fill", "black")
62         .text("block() requests");
63     chart.append("svg:text")
64         .attr("class", "seconds-label")
65         //.attr("x", w/2).attr("y", y + 35)
66         .attr("text-anchor", "middle")
67         .attr("fill", "black")
68         .text("seconds");
69
70
71     function reltime(t) {return t-data.bounds.min;}
72     var last = data.bounds.max - data.bounds.min;
73     //last = reltime(d3.max(data.dyhb, function(d){return d.finish_time;}));
74     last = last * 1.05;
75     // long downloads are likely to have too much info, start small
76     if (last > 10.0)
77         last = 10.0;
78     // d3.time.scale() has no support for ms or us.
79     var xOFF = d3.time.scale().domain([data.bounds.min, data.bounds.max])
80                  .range([0,w]);
81     var x = d3.scale.linear().domain([-last*0.05, last])
82               .range([0,w]);
83     function tx(d) { return "translate(" +x(d) + ",0)"; }
84     function left(d) { return x(reltime(d.start_time)); }
85     function right(d) {
86         return d.finish_time ? x(reltime(d.finish_time)) : "1px";
87     }
88     function width(d) {
89         return d.finish_time ? x(reltime(d.finish_time))-x(reltime(d.start_time)) : "1px";
90     }
91     function halfwidth(d) {
92         if (d.finish_time)
93             return (x(reltime(d.finish_time))-x(reltime(d.start_time)))/2;
94         return "1px";
95     }
96     function middle(d) {
97         if (d.finish_time)
98                 return (x(reltime(d.start_time))+x(reltime(d.finish_time)))/2;
99             else
100                 return x(reltime(d.start_time)) + 1;
101         }
102     function color(d) { return data.server_info[d.serverid].color; }
103     function servername(d) { return data.server_info[d.serverid].short; }
104     function timeformat(duration) {
105         // TODO: trim to microseconds, maybe humanize
106         return duration;
107     }
108
109     function zoomin() {
110         //console.log("zoom in!");
111         //console.log(x.domain());
112         var old = x.domain();
113         var quarterwidth = (old[1] - old[0])/4;
114         x.domain([old[0]+quarterwidth, old[1]-quarterwidth]);
115         //console.log(x.domain());
116         redraw();
117         //d3.event.preventDefault();
118     }
119
120     function zoomout() {
121         //console.log("zoom out!");
122         var old = x.domain();
123         var halfwidth = (old[1] - old[0])/2;
124         x.domain([old[0]-halfwidth, old[1]+halfwidth]);
125         redraw();
126     }
127
128     function pan_and_zoom() {
129         //console.log("P", x.domain());
130         if (d3.event) d3.event.transform(x);
131         //console.log("p", x.domain());
132         redraw();
133     }
134
135     function clip() {
136         var clipped = {};
137         var min = data.bounds.min + x.domain()[0];
138         var max = data.bounds.min + x.domain()[1];
139         function inside(d) {
140             var finish_time = d.finish_time || d.start_time;
141             if (Math.max(d.start_time, min) < Math.min(finish_time, max))
142                 return true;
143             return false;
144         }
145         clipped.dyhb = data.dyhb.filter(inside);
146         clipped.read = data.read.filter(inside);
147         clipped.segment = data.segment.filter(inside);
148         clipped.block = data.block.filter(inside);
149         if (show_misc && data.misc)
150             clipped.misc = data.misc.filter(inside);
151         return clipped;
152     }
153
154     function redraw() {
155         // at this point zoom/pan must be fixed
156         var clipped = clip(data);
157
158         var y = 0;
159         //chart.select(".dyhb-label")
160         //    .attr("x", x(0))//"20px")
161         //    .attr("y", y);
162         y += 20;
163
164         // DYHB section
165         var dyhb_y = d3.scale.ordinal()
166                         .domain(d3.range(data.dyhb.length))
167                         .rangeBands([y, y+data.dyhb.length*20], .2);
168         y += data.dyhb.length*20;
169         var dyhb = chart.selectAll("g.dyhb") // one per row
170              .data(clipped.dyhb, function(d) { return d.start_time; })
171              .attr("transform", function(d,i) {
172                        return "translate("+x(reltime(d.start_time))+","
173                            +dyhb_y(i)+")";
174                    });
175         var new_dyhb = dyhb.enter().append("svg:g")
176              .attr("class", "dyhb")
177              .attr("transform", function(d,i) {
178                        return "translate("+x(reltime(d.start_time))+","
179                            +dyhb_y(i)+")";
180                    })
181         ;
182         dyhb.exit().remove();
183         dyhb.select("rect")
184              .attr("width", width)
185         ;
186         new_dyhb.append("svg:rect")
187              .attr("width", width)
188              .attr("height", dyhb_y.rangeBand())
189              .attr("stroke", "black")
190              .attr("fill", color)
191              .attr("title", function(d){return "shnums: "+d.response_shnums;})
192         ;
193         new_dyhb.append("svg:text")
194              .attr("text-anchor", "end")
195              .attr("fill", "#444")
196              .attr("x", "-0.3em") // for some reason dx doesn't work
197              .attr("dy", "1.0em")
198              .attr("font-size", "12px")
199              .text(servername)
200         ;
201         dyhb.select(".rightbox")
202              .attr("transform", function(d) {return "translate("+width(d)
203                                              +",0)";});
204         var dyhb_rightboxes = new_dyhb.append("svg:g")
205              .attr("class", "rightbox")
206              .attr("transform", function(d) {return "translate("+width(d)
207                                              +",0)";})
208         ;
209         dyhb_rightboxes.append("svg:text")
210              .attr("text-anchor", "start")
211              .attr("y", dyhb_y.rangeBand())
212              .attr("dx", "0.5em")
213              .attr("dy", "-0.4em")
214              .attr("fill", "#444")
215              .attr("font-size", "14px")
216              .text(function (d) {return "shnums: "+d.response_shnums;})
217         ;
218
219         // read() requests
220         chart.select(".read-label")
221             .attr("x", "20px")
222             .attr("y", y);
223         y += 20;
224         var read = chart.selectAll("g.read")
225              .data(clipped.read, function(d) { return d.start_time; })
226              .attr("transform", function(d) {
227                        return "translate("+left(d)+","+(y+30*d.row)+")"; });
228         read.select("rect")
229              .attr("width", width);
230         read.select("text")
231              .attr("x", halfwidth);
232         var new_read = read.enter().append("svg:g")
233              .attr("class", "read")
234              .attr("transform", function(d) {
235                        return "translate("+left(d)+","+(y+30*d.row)+")"; })
236         ;
237         read.exit().remove();
238         y += 30*(1+d3.max(data.read, function(d){return d.row;}));
239         new_read.append("svg:rect")
240              .attr("width", width)
241              .attr("height", 20)
242              .attr("fill", "red")
243              .attr("stroke", "black")
244              .attr("title", function(d)
245                    {return "read(start="+d.start+", len="+d.length+") -> "
246                     +d.bytes_returned+" bytes";})
247         ;
248         new_read.append("svg:text")
249              .attr("x", halfwidth)
250              .attr("dy", "0.9em")
251              .attr("fill", "black")
252              .text(function(d) {return "["+d.start+":+"+d.length+"]";})
253         ;
254
255         // segment requests
256         chart.select(".segment-label")
257             .attr("x", "20px")
258             .attr("y", y);
259         y += 20;
260         var segment = chart.selectAll("g.segment")
261              .data(clipped.segment, function(d) { return d.start_time; })
262              .attr("transform", function(d) {
263                        return "translate("+left(d)+","+(y+30*d.row)+")"; });
264         segment.select("rect")
265              .attr("width", width);
266         segment.select("text")
267              .attr("x", halfwidth);
268         var new_segment = segment.enter().append("svg:g")
269              .attr("class", "segment")
270              .attr("transform", function(d) {
271                        return "translate("+left(d)+","+(y+30*d.row)+")"; })
272         ;
273         segment.exit().remove();
274         y += 30*(1+d3.max(data.segment, function(d){return d.row;}));
275         new_segment.append("svg:rect")
276              .attr("width", width)
277              .attr("height", 20)
278              .attr("fill", function(d){return d.success ? "#cfc" : "#fcc";})
279              .attr("stroke", "black")
280              .attr("stroke-width", 1)
281              .attr("title", function(d) {
282                        return "seg"+d.segment_number+" ["+d.segment_start
283                            +":+"+d.segment_length+"] (took "
284                            +timeformat(d.finish_time-d.start_time)+")";})
285         ;
286         new_segment.append("svg:text")
287              .attr("x", halfwidth)
288              .attr("text-anchor", "middle")
289              .attr("dy", "0.9em")
290              .attr("fill", "black")
291              .text(function(d) {return d.segment_number;})
292         ;
293
294         var shnum_colors = d3.scale.category10();
295
296         if (show_misc && "misc" in clipped) {
297             // misc requests
298             var misc_label = chart.select("text.misc-label");
299             if (!misc_label.node()) {
300                 chart.append("svg:text")
301                     .attr("class", "misc-label");
302                 misc_label = chart.select("text.misc-label");
303             }
304             misc_label
305                 .attr("text-anchor", "start") // anchor at top-left
306                 .attr("dy", ".71em")
307                 .attr("fill", "black")
308                 .text("misc requests")
309                 .attr("x", "20px")
310                 .attr("y", y)
311             ;
312             y += 20;
313             var misc = chart.selectAll("g.misc")
314                 .data(clipped.misc, function(d) { return d.start_time; })
315                 .attr("transform", function(d) {
316                           return "translate("+left(d)+","+(y+30*d.row)+")"; });
317             misc.select("rect")
318                 .attr("width", width);
319             misc.select("text")
320                 .attr("x", halfwidth);
321             var new_misc = misc.enter().append("svg:g")
322                 .attr("class", "misc")
323                 .attr("transform", function(d) {
324                           return "translate("+left(d)+","+(y+30*d.row)+")"; })
325             ;
326             misc.exit().remove();
327             y += 30*(1+d3.max(data.misc, function(d){return d.row;}));
328             new_misc.append("svg:rect")
329                 .attr("width", width)
330                 .attr("height", 20)
331                 .attr("fill", "white")
332                 .attr("stroke", "black")
333                 .attr("stroke-width", 1)
334                 .attr("title", function(d) {
335                           return d.what+" (took "
336                               +timeformat(d.finish_time-d.start_time)+")";})
337             ;
338             new_misc.append("svg:text")
339                 .attr("x", halfwidth)
340                 .attr("text-anchor", "middle")
341                 .attr("dy", "0.9em")
342                 .attr("fill", "black")
343                 .text(function(d) {return d.what;})
344             ;
345         } else {
346             chart.select("text.misc-label").remove();
347             chart.selectAll("g.misc").remove();
348         }
349         // block requests
350         chart.select(".block-label")
351             .attr("x", "20px")
352             .attr("y", y);
353         y += 20;
354         var block_row_to_y = {};
355         var dummy = function() {
356             var row_y=y;
357             for (var group=0; group < data.block_rownums.length; group++) {
358                 for (var row=0; row < data.block_rownums[group]; row++) {
359                     block_row_to_y[group+"-"+row] = row_y;
360                     row_y += 12; y += 12;
361                 }
362                 row_y += 5; y += 5;
363             }
364         }();
365         var blocks = chart.selectAll("g.block")
366              .data(clipped.block, function(d) { return d.start_time; })
367              .attr("transform", function(d) {
368                        var ry = block_row_to_y[d.row[0]+"-"+d.row[1]];
369                        return "translate("+left(d)+"," +ry+")"; });
370         blocks.select("rect")
371             .attr("width", width);
372         blocks.select("text")
373             .attr("x", halfwidth);
374         var new_blocks = blocks.enter().append("svg:g")
375              .attr("class", "block")
376              .attr("transform", function(d) {
377                        var ry = block_row_to_y[d.row[0]+"-"+d.row[1]];
378                        return "translate("+left(d)+"," +ry+")"; })
379         ;
380         blocks.exit().remove();
381         // everything appended to blocks starts at the top-left of the
382         // correct per-rect location
383         new_blocks.append("svg:rect")
384              .attr("width", width)
385              .attr("y", function(d) {return (d.response_length > 100) ? 0:3;})
386              .attr("height",
387                    function(d) {return (d.response_length > 100) ? 10:4;})
388              .attr("fill", color)
389              .attr("stroke", function(d){return shnum_colors(d.shnum);})
390              .attr("stroke-width", 1)
391              .attr("title", function(d){
392                        return "sh"+d.shnum+"-on-"+servername(d)
393                            +" ["+d.start+":+"+d.length+"] -> "
394                            +d.response_length;})
395         ;
396         new_blocks.append("svg:text")
397              .attr("x", halfwidth)
398              .attr("dy", "0.9em")
399              .attr("fill", "black")
400              .attr("font-size", "8px")
401              .attr("text-anchor", "middle")
402              .text(function(d) {return "sh"+d.shnum;})
403         ;
404
405         var num = d3.format(".4g");
406
407         // horizontal scale markers: vertical lines at rational timestamps
408         var rules = chart.selectAll("g.rule")
409             .data(x.ticks(10))
410             .attr("transform", tx);
411         rules.select("text").text(x.tickFormat(10));
412
413         var newrules = rules.enter().insert("svg:g")
414               .attr("class", "rule")
415               .attr("transform", tx)
416         ;
417
418         newrules.append("svg:line")
419             .attr("class", "rule-tick")
420             .attr("stroke", "black");
421         chart.selectAll("line.rule-tick")
422             .attr("y1", y)
423             .attr("y2", y + 6);
424         newrules.append("svg:line")
425             .attr("class", "rule-red")
426             .attr("stroke", "red")
427             .attr("stroke-opacity", .3);
428         chart.selectAll("line.rule-red")
429             .attr("y1", 0)
430             .attr("y2", y);
431         newrules.append("svg:text")
432             .attr("class", "rule-text")
433             .attr("dy", ".71em")
434             .attr("text-anchor", "middle")
435             .attr("fill", "black")
436             .text(x.tickFormat(10));
437         chart.selectAll("text.rule-text")
438             .attr("y", y + 9);
439         rules.exit().remove();
440         chart.select(".seconds-label")
441             .attr("x", w/2)
442             .attr("y", y + 35);
443         y += 45;
444
445         d3.select("#outer_chart").attr("height", y);
446         d3.select("#outer_rect").attr("height", y);
447         d3.select("#zoom").attr("transform", "translate("+(w-10)+","+10+")");
448     }
449     globals.x = x;
450     globals.redraw = redraw;
451
452     d3.select("#zoom_in_button").on("click", zoomin);
453     d3.select("#zoom_out_button").on("click", zoomout);
454     d3.select("#toggle_misc_button").on("click",
455                                         function() {
456                                             show_misc = !show_misc;
457                                             redraw();
458                                         });
459     d3.select("#reset_button").on("click",
460                                   function() {
461                                       x.domain([-last*0.05, last]).range([0,w]);
462                                       redraw();
463                                       });
464
465     redraw();
466 }
467
468 $(function() {
469       var datafile = "event_json";
470       if (location.hash)
471           datafile = location.hash.slice(1);
472       //console.log("fetching data from "+datafile);
473       $.ajax({url: datafile,
474               method: 'GET',
475               dataType: 'json',
476               success: onDataReceived });
477 });
478