4 function onDataReceived(data) {
5 //console.log("got data, now rendering");
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")
15 .attr("pointer-events", "all")
17 .call(d3.behavior.zoom().on("zoom", pan_and_zoom))
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");
26 // but the stuff we put inside it should have some room
29 // at this point we assume the data is fixed, but the zoom/pan is not
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
39 .attr("fill", "black")
40 .text("DYHB requests");
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
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
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
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")
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;}));
75 // long downloads are likely to have too much info, start small
78 // d3.time.scale() has no support for ms or us.
79 var xOFF = d3.time.scale().domain([data.bounds.min, data.bounds.max])
81 var x = d3.scale.linear().domain([-last*0.05, last])
83 function tx(d) { return "translate(" +x(d) + ",0)"; }
84 function left(d) { return x(reltime(d.start_time)); }
86 return d.finish_time ? x(reltime(d.finish_time)) : "1px";
89 return d.finish_time ? x(reltime(d.finish_time))-x(reltime(d.start_time)) : "1px";
91 function halfwidth(d) {
93 return (x(reltime(d.finish_time))-x(reltime(d.start_time)))/2;
98 return (x(reltime(d.start_time))+x(reltime(d.finish_time)))/2;
100 return x(reltime(d.start_time)) + 1;
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
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());
117 //d3.event.preventDefault();
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]);
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());
137 var min = data.bounds.min + x.domain()[0];
138 var max = data.bounds.min + x.domain()[1];
140 var finish_time = d.finish_time || d.start_time;
141 if (Math.max(d.start_time, min) < Math.min(finish_time, max))
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);
155 // at this point zoom/pan must be fixed
156 var clipped = clip(data);
159 //chart.select(".dyhb-label")
160 // .attr("x", x(0))//"20px")
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))+","
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))+","
182 dyhb.exit().remove();
184 .attr("width", width)
186 new_dyhb.append("svg:rect")
187 .attr("width", width)
188 .attr("height", dyhb_y.rangeBand())
189 .attr("stroke", "black")
191 .attr("title", function(d){return "shnums: "+d.response_shnums;})
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
198 .attr("font-size", "12px")
201 dyhb.select(".rightbox")
202 .attr("transform", function(d) {return "translate("+width(d)
204 var dyhb_rightboxes = new_dyhb.append("svg:g")
205 .attr("class", "rightbox")
206 .attr("transform", function(d) {return "translate("+width(d)
209 dyhb_rightboxes.append("svg:text")
210 .attr("text-anchor", "start")
211 .attr("y", dyhb_y.rangeBand())
213 .attr("dy", "-0.4em")
214 .attr("fill", "#444")
215 .attr("font-size", "14px")
216 .text(function (d) {return "shnums: "+d.response_shnums;})
220 chart.select(".read-label")
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)+")"; });
229 .attr("width", width);
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)+")"; })
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)
243 .attr("stroke", "black")
244 .attr("title", function(d)
245 {return "read(start="+d.start+", len="+d.length+") -> "
246 +d.bytes_returned+" bytes";})
248 new_read.append("svg:text")
249 .attr("x", halfwidth)
251 .attr("fill", "black")
252 .text(function(d) {return "["+d.start+":+"+d.length+"]";})
256 chart.select(".segment-label")
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)+")"; })
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)
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)+")";})
286 new_segment.append("svg:text")
287 .attr("x", halfwidth)
288 .attr("text-anchor", "middle")
290 .attr("fill", "black")
291 .text(function(d) {return d.segment_number;})
294 var shnum_colors = d3.scale.category10();
296 if (show_misc && "misc" in clipped) {
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");
305 .attr("text-anchor", "start") // anchor at top-left
307 .attr("fill", "black")
308 .text("misc requests")
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)+")"; });
318 .attr("width", width);
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)+")"; })
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)
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)+")";})
338 new_misc.append("svg:text")
339 .attr("x", halfwidth)
340 .attr("text-anchor", "middle")
342 .attr("fill", "black")
343 .text(function(d) {return d.what;})
346 chart.select("text.misc-label").remove();
347 chart.selectAll("g.misc").remove();
350 chart.select(".block-label")
354 var block_row_to_y = {};
355 var dummy = function() {
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;
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+")"; })
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;})
387 function(d) {return (d.response_length > 100) ? 10:4;})
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;})
396 new_blocks.append("svg:text")
397 .attr("x", halfwidth)
399 .attr("fill", "black")
400 .attr("font-size", "8px")
401 .attr("text-anchor", "middle")
402 .text(function(d) {return "sh"+d.shnum;})
405 var num = d3.format(".4g");
407 // horizontal scale markers: vertical lines at rational timestamps
408 var rules = chart.selectAll("g.rule")
410 .attr("transform", tx);
411 rules.select("text").text(x.tickFormat(10));
413 var newrules = rules.enter().insert("svg:g")
414 .attr("class", "rule")
415 .attr("transform", tx)
418 newrules.append("svg:line")
419 .attr("class", "rule-tick")
420 .attr("stroke", "black");
421 chart.selectAll("line.rule-tick")
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")
431 newrules.append("svg:text")
432 .attr("class", "rule-text")
434 .attr("text-anchor", "middle")
435 .attr("fill", "black")
436 .text(x.tickFormat(10));
437 chart.selectAll("text.rule-text")
439 rules.exit().remove();
440 chart.select(".seconds-label")
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+")");
450 globals.redraw = redraw;
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",
456 show_misc = !show_misc;
459 d3.select("#reset_button").on("click",
461 x.domain([-last*0.05, last]).range([0,w]);
469 var datafile = "event_json";
471 datafile = location.hash.slice(1);
472 //console.log("fetching data from "+datafile);
473 $.ajax({url: datafile,
476 success: onDataReceived });