oremj%mozilla.com 9d0efbf712 Bug 386675: Better analysis of Tp results on new graph server( Part #2 of comment #1 ) [p=oremj,r=vlad,r=alice]
git-svn-id: svn://10.0.0.236/trunk@237675 18797224-902f-48f8-a5cc-f745e15eee43
2007-10-12 20:26:32 +00:00

888 lines
29 KiB
JavaScript

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is new-graph code.
*
* The Initial Developer of the Original Code is
* Mozilla Corporation
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Vladimir Vukicevic <vladimir@pobox.com> (Original Author)
* Alice Nodelman <anodelman@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
// all times are in seconds
const ONE_HOUR_SECONDS = 60*60;
const ONE_DAY_SECONDS = 24*ONE_HOUR_SECONDS;
const ONE_WEEK_SECONDS = 7*ONE_DAY_SECONDS;
const ONE_YEAR_SECONDS = 365*ONE_DAY_SECONDS; // leap years whatever.
const MONTH_ABBREV = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ];
const CONTINUOUS_GRAPH = 0;
const DISCRETE_GRAPH = 1;
const DATA_GRAPH = 2;
const bonsaicgi = "bonsaibouncer.cgi";
// more days than this and we'll force user confirmation for the bonsai query
const bonsaiNoForceDays = 90;
// the default average interval
var gAverageInterval = 3*ONE_HOUR_SECONDS;
var gCurrentLoadRange = null;
var gForceBonsai = false;
var gOptions = { autoScaleYAxis: true };
var Tinderbox;
var BigPerfGraph;
var SmallPerfGraph;
var Bonsai;
var graphType;
var ResizableBigGraph;
var SmallGraphSizeRuleIndex;
var BigGraphSizeRuleIndex;
function loadingDone(graphTypePref) {
loadOptions();
//createLoggingPane(true);
graphType = graphTypePref;
if (graphType == CONTINUOUS_GRAPH) {
Tinderbox = new TinderboxData();
SmallPerfGraph = new CalendarTimeGraph("smallgraph");
BigPerfGraph = new CalendarTimeGraph("graph");
BigPerfGraph.drawPoints = true;
onDataLoadChanged();
} else if (graphType == DATA_GRAPH) {
Tinderbox = new ExtraDataTinderboxData();
SmallPerfGraph = new CalendarTimeGraph("smallgraph");
BigPerfGraph = new CalendarTimeGraph("graph");
} else {
Tinderbox = new DiscreteTinderboxData();
Tinderbox.raw = 1;
SmallPerfGraph = new DiscreteGraph("smallgraph");
BigPerfGraph = new DiscreteGraph("graph");
onDiscreteDataLoadChanged();
}
// handle saved options
if ("autoScaleYAxis" in gOptions) {
if (gOptions.autoScaleYAxis) {
getElement("autoscale").checked = true;
onAutoScaleClick(true);
} else {
getElement("autoscale").checked = false;
onAutoScaleClick(false);
}
}
// create CSS "smallgraph-size" and "graph-size" rules that the
// layout depends on
{
var sg = document.getElementById("smallgraph");
var g = document.getElementById("graph");
SmallGraphSizeRuleIndex = document.styleSheets[0].insertRule (
".smallgraph-size { width: " + sg.width + "px; height: " + sg.height + "px; }",
document.styleSheets[0].cssRules.length);
BigGraphSizeRuleIndex = document.styleSheets[0].insertRule (
".graph-size { width: " + g.width + "px; height: " + g.height + "px; }",
document.styleSheets[0].cssRules.length);
}
var resizeFunction = function (nw, nh) {
document.getElementById("graph").width = nw;
document.getElementById("graph").height = nh;
document.styleSheets[0].cssRules[BigGraphSizeRuleIndex].style.width = nw + "px";
document.styleSheets[0].cssRules[BigGraphSizeRuleIndex].style.height = nh + "px";
BigPerfGraph.resize();
if (nw != document.getElementById("smallgraph").width) {
document.getElementById("smallgraph").width = nw;
document.styleSheets[0].cssRules[SmallGraphSizeRuleIndex].style.width = nw + "px";
SmallPerfGraph.resize();
}
saveGraphDimensions(nw, nh);
}
var graphSize = { };
if (loadGraphDimensions(graphSize))
resizeFunction(graphSize.width, graphSize.height);
// make the big graph resizable
ResizableBigGraph = new ResizeGraph();
ResizableBigGraph.init('graph', resizeFunction);
Tinderbox.init();
if (BonsaiService)
Bonsai = new BonsaiService();
SmallPerfGraph.yLabelHeight = 20;
SmallPerfGraph.setSelectionType("range");
BigPerfGraph.setSelectionType("cursor");
BigPerfGraph.setCursorType("snap");
SmallPerfGraph.onSelectionChanged.
subscribe (function (type, args, obj) {
log ("selchanged");
if (args[0] == "range") {
var t1 = SmallPerfGraph.startTime;
var t2 = SmallPerfGraph.endTime;
if (args[1] && args[2]) {
t1 = args[1];
t2 = args[2];
var foundIndexes = [];
// make sure that there are at least two points
// on at least one graph for this
var foundPoints = false;
var dss = BigPerfGraph.dataSets;
for (var i = 0; i < dss.length; i++) {
var idcs = dss[i].indicesForTimeRange(t1, t2);
if (idcs[1] - idcs[0] > 1) {
foundPoints = true;
break;
}
foundIndexes.push(idcs);
}
if (!foundPoints) {
// we didn't find at least two points in at least
// one graph; so munge the time numbers until we do.
log("Orig t1 " + t1 + " t2 " + t2);
for (var i = 0; i < dss.length; i++) {
if (foundIndexes[i][0] > 0) {
t1 = Math.min(dss[i].data[(foundIndexes[i][0] - 1) * 2], t1);
} else if (foundIndexes[i][1]+1 < (ds.data.length/2)) {
t2 = Math.max(dss[i].data[(foundIndexes[i][1] + 1) * 2], t2);
}
}
log("Fixed t1 " + t1 + " t2 " + t2);
}
}
if (document.getElementById("bonsailink"))
document.getElementById("bonsailink").href = makeBonsaiLink(t1, t2);
BigPerfGraph.setTimeRange (t1, t2);
BigPerfGraph.autoScale();
BigPerfGraph.redraw();
}
updateLinkToThis();
updateDumpToCsv();
});
BigPerfGraph.onCursorMoved.subscribe (onCursorMoved);
if (graphType == CONTINUOUS_GRAPH) {
BigPerfGraph.onNewGraph.
subscribe (function(type, args, obj) {
if (args[0].length >= GraphFormModules.length) {
clearLoadingAnimation();
}
});
} else if (graphType == DATA_GRAPH || graphType == DISCRETE_GRAPH) {
BigPerfGraph.onNewGraph.
subscribe (function(type, args, obj) {
showGraphList(args[0]);
});
}
if (document.location.hash) {
handleHash(document.location.hash);
} else {
if (graphType == CONTINUOUS_GRAPH) {
addGraphForm();
} else if (graphType == DATA_GRAPH) {
addExtraDataGraphForm();
} else {
addDiscreteGraphForm();
}
}
}
function loadGraphDimensions(data) {
if (!globalStorage || document.domain == "")
return false;
try {
var store = globalStorage[document.domain];
if (!("graphWidth" in store) || !("graphHeight" in store))
return false;
var w = parseInt(store.graphWidth);
var h = parseInt(store.graphHeight);
if (w != w || h != h || w <= 0 || h <= 0)
return false;
data.width = w;
data.height = h;
return true;
} catch (ex) {
}
return false;
}
function saveGraphDimensions(w, h) {
if (!globalStorage || document.domain == "")
return false;
try {
if (parseInt(w) != w || parseInt(h) != h)
return false;
globalStorage[document.domain].graphWidth = w;
globalStorage[document.domain].graphHeight = h;
return true;
} catch (ex) {
}
return false;
}
function addExtraDataGraphForm(config, name) {
showLoadingAnimation("populating lists");
var ed = new ExtraDataGraphFormModule(config, name);
ed.onLoading.subscribe (function(type,args,obj) { showLoadingAnimation(args[0]);});
ed.onLoadingDone.subscribe (function(type,args,obj) { clearLoadingAnimation();});
if (config) {
ed.addedInitialInfo.subscribe(function(type,args,obj) { graphInitial();});
}
ed.render (getElement("graphforms"));
return ed;
}
function addDiscreteGraphForm(config, name) {
showLoadingAnimation("populating lists");
//log("name: " + name);
var m = new DiscreteGraphFormModule(config, name);
m.onLoading.subscribe (function(type,args,obj) { showLoadingAnimation(args[0]);});
m.onLoadingDone.subscribe (function(type,args,obj) { clearLoadingAnimation();});
if (config) {
m.addedInitialInfo.subscribe(function(type,args,obj) { graphInitial();});
}
m.render (getElement("graphforms"));
//m.setColor(randomColor());
return m;
}
function addGraphForm(config) {
showLoadingAnimation("populating list");
var m = new GraphFormModule(config);
m.render (getElement("graphforms"));
m.setColor(randomColor());
m.onLoading.subscribe (function(type,args,obj) { showLoadingAnimation(args[0]);});
m.onLoadingDone.subscribe (function(type,args,obj) { clearLoadingAnimation();});
return m;
}
function onNoBaseLineClick() {
GraphFormModules.forEach (function (g) { g.baseline = false; });
}
// whether the bonsai data query should redraw the graph or not
var gReadyForRedraw = true;
function onUpdateBonsai() {
BigPerfGraph.deleteAllMarkers();
getElement("bonsaibutton").disabled = true;
if (gCurrentLoadRange) {
if ((gCurrentLoadRange[1] - gCurrentLoadRange[0]) < (bonsaiNoForceDays * ONE_DAY_SECONDS) || gForceBonsai) {
Bonsai.requestCheckinsBetween (gCurrentLoadRange[0], gCurrentLoadRange[1],
function (bdata) {
for (var i = 0; i < bdata.times.length; i++) {
BigPerfGraph.addMarker (bdata.times[i], bdata.who[i] + ": " + bdata.comment[i]);
}
if (gReadyForRedraw)
BigPerfGraph.redraw();
getElement("bonsaibutton").disabled = false;
});
}
}
}
function onGraph() {
showLoadingAnimation("building graph");
showStatus(null);
for each (var g in [BigPerfGraph, SmallPerfGraph]) {
g.clearDataSets();
g.setTimeRange(null, null);
}
gReadyForRedraw = false;
// do the actual graph data request
var baselineModule = null;
GraphFormModules.forEach (function (g) { if (g.baseline) baselineModule = g; });
if (baselineModule) {
Tinderbox.requestDataSetFor (baselineModule.testId,
function (testid, ds) {
try {
//log ("Got results for baseline: '" + testid + "' ds: " + ds);
ds.color = baselineModule.color;
onGraphLoadRemainder(ds);
} catch(e) { log(e); }
});
} else {
onGraphLoadRemainder();
}
}
function onGraphLoadRemainder(baselineDataSet) {
for each (var graphModule in GraphFormModules) {
//log ("onGraphLoadRemainder: ", graphModule.id, graphModule.testId, "color:", graphModule.color, "average:", graphModule.average);
// this would have been loaded earlier
if (graphModule.baseline)
continue;
var autoExpand = true;
if (SmallPerfGraph.selectionType == "range" &&
SmallPerfGraph.selectionStartTime &&
SmallPerfGraph.selectionEndTime)
{
if (gCurrentLoadRange && (SmallPerfGraph.selectionStartTime < gCurrentLoadRange[0] ||
SmallPerfGraph.selectionEndTime > gCurrentLoadRange[1]))
{
SmallPerfGraph.selectionStartTime = Math.max (SmallPerfGraph.selectionStartTime, gCurrentLoadRange[0]);
SmallPerfGraph.selectionEndTime = Math.min (SmallPerfGraph.selectionEndTime, gCurrentLoadRange[1]);
}
BigPerfGraph.setTimeRange (SmallPerfGraph.selectionStartTime, SmallPerfGraph.selectionEndTime);
autoExpand = false;
}
// we need a new closure here so that we can get the right value
// of graphModule in our closure
var makeCallback = function (module, color, title) {
return function (testid, ds) {
try {
log("ds.firstTime " + ds.firstTime + " ds.lastTime " + ds.lastTime);
if (!("firstTime" in ds) || !("lastTime" in ds)) {
// got a data set with no data in this time range, or damaged data
// better to not graph
for each (g in [BigPerfGraph, SmallPerfGraph]) {
g.clearGraph();
}
showStatus("No data in the given time range");
clearLoadingAnimation();
}
else {
ds.color = color;
if (title) {
ds.title = title;
}
if (baselineDataSet)
ds = ds.createRelativeTo(baselineDataSet);
//log ("got ds: (", module.id, ")", ds.firstTime, ds.lastTime, ds.data.length);
var avgds = null;
if (baselineDataSet == null &&
module.average)
{
avgds = ds.createAverage(gAverageInterval);
}
if (avgds)
log ("got avgds: (", module.id, ")", avgds.firstTime, avgds.lastTime, avgds.data.length);
for each (g in [BigPerfGraph, SmallPerfGraph]) {
g.addDataSet(ds);
if (avgds)
g.addDataSet(avgds);
if (g == SmallPerfGraph || autoExpand) {
g.expandTimeRange(Math.max(ds.firstTime, gCurrentLoadRange ? gCurrentLoadRange[0] : ds.firstTime),
Math.min(ds.lastTime, gCurrentLoadRange ? gCurrentLoadRange[1] : ds.lastTime));
}
g.autoScale();
g.redraw();
gReadyForRedraw = true;
}
//if (graphType == CONTINUOUS_GRAPH) {
updateLinkToThis();
updateDumpToCsv();
//}
}
} catch(e) { log(e); }
};
};
if (graphModule.testIds) {
if ( graphType == DISCRETE_GRAPH ) {
var testIds = new Array();
for each ( var testId in graphModule.testIds ) {
testIds[testId[0]] = makeCallback(graphModule, randomColor(), testId[1]);
}
Tinderbox.requestDataSetFor(testIds);
} else {
for each (var testId in graphModule.testIds) {
// log ("working with testId: " + testId);
Tinderbox.requestDataSetFor (testId[0], makeCallback(graphModule, randomColor(), testId[1]));
}
}
}
else {
// log ("working with standard, single testId");
Tinderbox.requestDataSetFor (graphModule.testId, makeCallback(graphModule, graphModule.color));
}
}
}
function onDataLoadChanged() {
log ("loadchanged");
if (getElement("load-days-radio").checked) {
var dval = new Number(getElement("load-days-entry").value);
log ("dval", dval);
if (dval <= 0) {
//getElement("load-days-entry").style.background-color = "red";
return;
} else {
//getElement("load-days-entry").style.background-color = "inherit";
}
var d2 = Math.ceil(Date.now() / 1000);
d2 = (d2 - (d2 % ONE_DAY_SECONDS)) + ONE_DAY_SECONDS;
var d1 = Math.floor(d2 - (dval * ONE_DAY_SECONDS));
log ("drange", d1, d2);
Tinderbox.defaultLoadRange = [d1, d2];
gCurrentLoadRange = [d1, d2];
} else {
Tinderbox.defaultLoadRange = null;
gCurrentLoadRange = null;
}
Tinderbox.clearValueDataSets();
// hack, reset colors
randomColorBias = 0;
}
function onExtraDataLoadChanged() {
log ("loadchanged");
Tinderbox.defaultLoadRange = null;
gCurrentLoadRange = null;
// hack, reset colors
randomColorBias = 0;
}
function onDiscreteDataLoadChanged() {
log ("loadchanged");
Tinderbox.defaultLoadRange = null;
gCurrentLoadRange = null;
// hack, reset colors
randomColorBias = 0;
}
function findGraphModule(testId) {
for each (var gm in GraphFormModules) {
if (gm.testId == testId)
return gm;
}
return null;
}
function updateDumpToCsv() {
var ds = "?"
prefix = ""
for each (var gm in GraphFormModules) {
ds += prefix + gm.getDumpString();
prefix = "&"
}
log ("ds");
getElement("dumptocsv").href = "http://" + document.location.host + "/dumpdata.cgi" + ds;
}
function updateLinkToThis() {
var qs = "";
qs += SmallPerfGraph.getQueryString("sp");
qs += "&";
qs += BigPerfGraph.getQueryString("bp");
if (graphType == CONTINUOUS_GRAPH) {
var ctr = 1;
for each (var gm in GraphFormModules) {
qs += "&" + gm.getQueryString("m" + ctr);
ctr++;
}
}
else {
qs += "&";
qs += "name=" + GraphFormModules[0].name;
for each (var gm in GraphFormModules) {
qs += gm.getQueryString("m");
}
}
getElement("linktothis").href = document.location.pathname + "#" + qs;
}
function handleHash(hash) {
var qsdata = {};
for each (var s in hash.substring(1).split("&")) {
var q = s.split("=");
qsdata[q[0]] = q[1];
}
if (graphType == CONTINUOUS_GRAPH) {
var ctr = 1;
while (("m" + ctr + "tid") in qsdata) {
var prefix = "m" + ctr;
addGraphForm({testid: qsdata[prefix + "tid"],
average: qsdata[prefix + "avg"]});
ctr++;
}
}
else {
var ctr=1;
testids = [];
while (("m" + ctr + "tid") in qsdata) {
var prefix = "m" + ctr;
testids.push(Number(qsdata[prefix + "tid"]));
ctr++;
}
// log("qsdata[name] " + qsdata["name"]);
addDiscreteGraphForm(testids, qsdata["name"]);
}
SmallPerfGraph.handleQueryStringData("sp", qsdata);
BigPerfGraph.handleQueryStringData("bp", qsdata);
var tstart = new Number(qsdata["spstart"]);
var tend = new Number(qsdata["spend"]);
//Tinderbox.defaultLoadRange = [tstart, tend];
if (graphType == CONTINUOUS_GRAPH) {
Tinderbox.requestTestList(function (tests) {
setTimeout (onGraph, 0); // let the other handlers do their thing
});
}
}
function graphInitial() {
GraphFormModules[0].addedInitialInfo.unsubscribeAll();
Tinderbox.requestTestList(null, null, null, null, function (tests) {
setTimeout(onGraph, 0);
});
}
function showStatus(s) {
replaceChildNodes("status", s);
}
function showLoadingAnimation(message) {
//log("starting loading animation: " + message);
td = new SPAN();
el = new IMG({ src: "js/img/Throbber-small.gif"});
appendChildNodes(td, el);
appendChildNodes(td, " loading: " + message + " ");
replaceChildNodes("loading", td);
}
function clearLoadingAnimation() {
//log("ending loading animation");
replaceChildNodes("loading", null);
}
function showGraphList(s) {
replaceChildNodes("graph-label-list",null);
// log("s: " +s);
var tbl = new TABLE({});
var tbl_tr = new TR();
appendChildNodes(tbl_tr, new TD(""));
appendChildNodes(tbl_tr, new TD("avg"));
appendChildNodes(tbl_tr, new TD("max"));
appendChildNodes(tbl_tr, new TD("min"));
appendChildNodes(tbl_tr, new TD("test name"));
appendChildNodes(tbl, tbl_tr);
for each (var ds in s) {
var tbl_tr = new TR();
var rstring = ds.stats + " ";
var colorDiv = new DIV({ id: "whee", style: "display: inline; border: 1px solid black; height: 15; " +
"padding-right: 15; vertical-align: middle; margin: 3px;" });
colorDiv.style.backgroundColor = colorToRgbString(ds.color);
// log("ds.stats" + ds.stats);
appendChildNodes(tbl_tr, colorDiv);
for each (var val in ds.stats) {
appendChildNodes(tbl_tr, new TD(val.toFixed(2)));
}
appendChildNodes(tbl, tbl_tr);
appendChildNodes(tbl_tr, new TD(ds.title));
}
appendChildNodes("graph-label-list", tbl);
if (GraphFormModules.length > 0 &&
GraphFormModules[0].testIds &&
s.length == GraphFormModules[0].testIds.length)
{
clearLoadingAnimation();
}
//replaceChildNodes("graph-label-list",rstring);
}
/* Get some pre-set colors in for the first 5 graphs, thens start randomly generating stuff */
var presetColorIndex = 0;
var presetColors = [
[0.0, 0.0, 0.7, 1.0],
[0.0, 0.5, 0.0, 1.0],
[0.7, 0.0, 0.0, 1.0],
[0.7, 0.0, 0.7, 1.0],
[0.0, 0.7, 0.7, 1.0]
];
var randomColorBias = 0;
function randomColor() {
if (presetColorIndex < presetColors.length) {
return presetColors[presetColorIndex++];
}
var col = [
(Math.random()*0.5) + ((randomColorBias==0) ? 0.5 : 0.2),
(Math.random()*0.5) + ((randomColorBias==1) ? 0.5 : 0.2),
(Math.random()*0.5) + ((randomColorBias==2) ? 0.5 : 0.2),
1.0
];
randomColorBias++;
if (randomColorBias == 3)
randomColorBias = 0;
return col;
}
function lighterColor(col) {
return [
Math.min(0.85, col[0] * 1.2),
Math.min(0.85, col[1] * 1.2),
Math.min(0.85, col[2] * 1.2),
col[3]
];
}
function colorToRgbString(col) {
// log ("in colorToRgbString");
if (col[3] < 1) {
return "rgba("
+ Math.floor(col[0]*255) + ","
+ Math.floor(col[1]*255) + ","
+ Math.floor(col[2]*255) + ","
+ col[3]
+ ")";
}
return "rgb("
+ Math.floor(col[0]*255) + ","
+ Math.floor(col[1]*255) + ","
+ Math.floor(col[2]*255) + ")";
}
function makeBonsaiLink(start, end) {
// harcode PhoenixTinderbox, oh well.
return "http://bonsai.mozilla.org/cvsquery.cgi?treeid=default&module=PhoenixTinderbox&branch=HEAD&branchtype=match&dir=&file=&filetype=match&who=&whotype=match&sortby=Date&hours=2&date=explicit&cvsroot=%2Fcvsroot&mindate=" + Math.floor(start) + "&maxdate=" + Math.ceil(end);
}
function onAutoScaleClick(override) {
var checked;
if (override != null) {
checked = override;
} else {
checked = getElement("autoscale").checked;
}
var graphs = [ BigPerfGraph, SmallPerfGraph ];
for each (var g in graphs) {
if (g.autoScaleYAxis != checked) {
g.autoScaleYAxis = checked;
g.autoScale();
g.redraw();
}
}
gOptions.autoScaleYAxis = checked;
saveOptions();
}
function loadOptions() {
if (!globalStorage || document.domain == "")
return false;
try {
var store = globalStorage[document.domain];
if ("graphOptions" in store) {
var s = (store["graphOptions"]).toString();
var tmp = eval(s);
// don't clobber newly defined options
for (var opt in tmp)
gOptions[opt] = tmp[opt];
}
} catch (ex) {
}
}
// This just needs to be called whenever an option changes, we don't
// have a good mechanism for this, so we just call it from wherever
// we change an option
function saveOptions() {
if (!globalStorage || document.domain == "")
return false;
try {
var store = globalStorage[document.domain];
store["graphOptions"] = uneval(gOptions);
} catch (ex) {
}
}
function onCursorMoved(type, args, obj) {
var time = args[0];
var val = args[1];
var extra_data = args[2];
var extra = "";
var label = "Date: ";
if (graphType == DISCRETE_GRAPH) {
label = "Index: ";
extra = extra_data;
}
if (time != null && val != null) {
// cheat
showStatus(label + formatTime(time) + " Value: " + val.toFixed(2) + " " + extra);
showFloater(time, val);
} else {
showStatus(null);
showFloater(null);
}
}
function showFloater(time, value) {
var fdiv = getElement("floater");
if (time == null) {
fdiv.style.visibility = "hidden";
return;
}
fdiv.style.visibility = "visible";
var dss = BigPerfGraph.dataSets;
if (dss.length == 0)
return;
var s = "";
var dstv = [];
for (var i = 0; i < dss.length; i++) {
if ("averageOf" in dss[i])
continue;
var idx = dss[i].indexForTime(time, true);
if (idx != -1) {
var t = dss[i].data[idx*2];
var v = dss[i].data[idx*2+1];
dstv.push( {time: t, value: v, color: dss[i].color} );
}
}
var columns = [];
for (var i = 0; i < dstv.length; i++) {
var column = [];
for (var j = 0; j < dstv.length; j++) {
if (i == j) {
var v = dstv[i].value;
if (v != Math.floor(v))
v = v.toFixed(2);
column.push("<b>" + v + "</b>");
} else {
var ratio = dstv[j].value / dstv[i].value;
column.push("<span style='font-size: small'>" + (ratio * 100).toFixed(0) + "%</span>");
}
}
columns.push(column);
}
var s = "<table class='floater-table'>";
for (var i = 0; i < dstv.length; i++) {
s += "<tr style='color: " + colorToRgbString(dstv[i].color) + "'>";
for (var j = 0; j < columns.length; j++) {
s += "<td>" + columns[i][j] + "</td>";
}
s += "</tr>";
}
s += "</table>";
// then put the floater in the right spot
var xy = BigPerfGraph.timeValueToXY(time, value);
fdiv.style.left = Math.floor(xy.x + 65) + "px";
fdiv.style.top = Math.floor((BigPerfGraph.frontBuffer.height - xy.y) + 15) + "px";
fdiv.innerHTML = s;
}