diff options
Diffstat (limited to 'trunk/etherpad/src/etherpad/control/statscontrol.js')
-rw-r--r-- | trunk/etherpad/src/etherpad/control/statscontrol.js | 1214 |
1 files changed, 1214 insertions, 0 deletions
diff --git a/trunk/etherpad/src/etherpad/control/statscontrol.js b/trunk/etherpad/src/etherpad/control/statscontrol.js new file mode 100644 index 0000000..3659107 --- /dev/null +++ b/trunk/etherpad/src/etherpad/control/statscontrol.js @@ -0,0 +1,1214 @@ +/** + * Copyright 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import("fastJSON"); +import("netutils"); +import("funhtml.*"); +import("stringutils.{html,sprintf,startsWith,md5}"); +import("jsutils.*"); +import("sqlbase.sqlbase"); +import("sqlbase.sqlcommon"); +import("sqlbase.sqlobj"); +import("dispatch.{Dispatcher,PrefixMatcher,DirMatcher,forward}"); + +import("etherpad.globals.*"); +import("etherpad.utils.*"); +import("etherpad.sessions.getSession"); +import("etherpad.sessions"); +import("etherpad.statistics.statistics"); +import("etherpad.log"); +import("etherpad.usage_stats.usage_stats"); +import("etherpad.helpers"); + +//---------------------------------------------------------------- +// Usagestats +//---------------------------------------------------------------- + +var _defaultPrefs = { + topNCount: 5, + granularity: 1440 +} + +function onRequest() { + keys(_defaultPrefs).forEach(function(prefName) { + if (request.params[prefName]) { + _prefs()[prefName] = request.params[prefName]; + } + }); + if (request.isPost) { + response.redirect( + request.path+ + (request.query ? "?"+request.query : "")+ + (request.params.fragment ? "#"+request.params.fragment : "")); + } +} + +function _prefs() { + if (! sessions.getSession().statsPrefs) { + sessions.getSession().statsPrefs = {} + } + return sessions.getSession().statsPrefs; +} + +function _pref(pname) { + return _prefs()[pname] || _defaultPrefs[pname]; +} + +function _topN() { + return _pref('topNCount'); +} +function _showLiveStats() { + return _timescale() < 1440; + // return _pref('granularity') == 'live'; +} +function _showHistStats() { + return _timescale() >= 1440 + // return _pref('showLiveOrHistorical') == 'hist'; +} +function _timescale() { + return Number(_pref('granularity')) || 1; +} + +// types: +// compare - compare one or more single-value stats +// top - show top values over time +// histogram - show histogram over time + +var statDisplays = { + users: [ + { name: "visitors", + description: "User visits, total over a %t period", + type: "compare", + stats: [ {stat: "site_pageviews", + description: "Page views", + color: "FFA928" }, + {stat: "site_unique_ips", + description: "Unique IPs", + color: "00FF00" } ] }, + + // free pad usage + { name: "free pad usage, 1 day", + description: "Free pad.spline.inf.fu-berlin.de users, total over a %t period", + type: "compare", + stats: [ {stat: "active_user_ids", + description: "All users", + color: "FFA928" }, + {stat: "users_1day_returning_7days", + description: "Users returning after 7 days", + color: "00FF00"}, + {stat: "users_1day_returning_30days", + description: "Users returning after 30 days", + color: "FF0000"} ] }, + { name: "free pad usage, 7 day", + description: "Free pad.spline.inf.fu-berlin.de users over the last 7 days", + type: "compare", + options: { hideLive: true, latestUseHistorical: true}, + stats: [ {stat: "active_user_ids_7days", + description: "All users", + color: "FFA928" }, + {stat: "users_7day_returning_7days", + description: "Users returning after 7 days", + color: "00FF00"}, + {stat: "users_7day_returning_30days", + description: "Users returning after 30 days", + color: "FF0000"} ] }, + { name: "free pad usage, 30 day", + description: "Free pad.spline.inf.fu-berlin.de users over the last 30 days", + type: "compare", + options: { hideLive: true, latestUseHistorical: true}, + stats: [ {stat: "active_user_ids_30days", + description: "All users", + color: "FFA928" }, + {stat: "users_30day_returning_7days", + description: "Users returning after 7 days", + color: "00FF00"}, + {stat: "users_30day_returning_30days", + description: "Users returning after 30 days", + color: "FF0000"} ] }, + + // pro pad usage + { name: "active pro accounts, 1 day", + description: "Active pro accounts, total over a %t period", + type: "compare", + stats: [ {stat: "active_pro_accounts", + description: "All accounts", + color: "FFA928" }, + {stat: "pro_accounts_1day_returning_7days", + description: "Accounts older than 7 days", + color: "00FF00"}, + {stat: "pro_accounts_1day_returning_30days", + description: "Accounts older than 30 days", + color: "FF0000"} ] }, + { name: "active pro accounts, 7 day", + description: "Active pro accounts over the last 7 days", + type: "compare", + options: { hideLive: true, latestUseHistorical: true}, + stats: [ {stat: "active_pro_accounts_7days", + description: "All accounts", + color: "FFA928" }, + {stat: "pro_accounts_7day_returning_7days", + description: "Accounts older than 7 days", + color: "00FF00"}, + {stat: "pro_accounts_7day_returning_30days", + description: "Accounts older than 30 days", + color: "FF0000"} ] }, + { name: "active pro accounts, 30 day", + description: "Active pro accounts over the last 30 days", + type: "compare", + options: { hideLive: true, latestUseHistorical: true}, + stats: [ {stat: "active_pro_accounts_30days", + description: "All accounts", + color: "FFA928" }, + {stat: "pro_accounts_30day_returning_7days", + description: "Accounts older than 7 days", + color: "00FF00"}, + {stat: "pro_accounts_30day_returning_30days", + description: "Accounts older than 30 days", + color: "FF0000"} ] }, + + // other stats + { name: "pad connections", + description: "Number of active comet connections, mean over a %t period", + type: "top", + options: {showOthers: false}, + stats: ["streaming_connections"] }, + { name: "referers", + description: "Referers, number of hits over a %t period", + type: "top", + options: {showOthers: false}, + stats: ["top_referers"] }, + ], + product: [ + { name: "pads", + description: "Newly-created and active pads, total over a %t period", + type: "compare", + stats: [ {stat: "active_pads", + description: "Active pads", + color: "FFA928" }, + {stat: "new_pads", + description: "New pads", + color: "FF0000" }] }, + { name: "chats", + description: "Chat messages and active chatters, total over a %t period", + type: "compare", + stats: [ {stat: "chat_messages", + description: "Messages", + color: "FFA928" }, + {stat: "active_chatters", + description: "Chatters", + color: "FF0000" }] }, + { name: "import/export", + description: "Imports and Exports, total over a %t period", + type: "compare", + stats: [ {stat: {f: '+', args: ["imports_exports_counts:export", "imports_exports_counts:import"]}, + description: "Total", + color: "FFA928" }, + {stat: "imports_exports_counts:export", + description: "Exports", + color: "FF0000"}, + {stat: "imports_exports_counts:import", + description: "Imports", + color: "00FF00"}] }, + { name: "revenue", + description: "Revenue, total over a %t period", + type: "compare", + stats: [ {stat: "revenue", + description: "Revenue", + color: "FFA928"}] } + ], + performance: [ + { name: "dynamic page latencies", + description: "Slowest dynamic pages: mean load time in milliseconds over a %t period", + type: "top", + options: {showOthers: false}, + stats: ["execution_latencies"] }, + { name: "pad startup latencies", + description: "Pad startup times: percent load time in milliseconds over a %t period", + type: "histogram", + stats: ["pad_startup_times"] }, + { name: "stream post latencies", + description: "Comet post latencies, percentiles in milliseconds over a %t period", + type: "histogram", + stats: ["streaming_latencies"] }, + ], + health: [ + { name: "disconnect causes", + description: "Causes of disconnects, total over a %t period", + type: "top", + stats: ["disconnect_causes"] }, + { name: "paths with 404s", + description: "'Not found' responses, by path, number served over a %t period", + type: "top", + stats: ["paths_404"] }, + { name: "exceptions", + description: "Total number of server exceptions over a %t period", + type: "compare", + stats: [ {stat: "exceptions", + description: "Exceptions", + color: "FF1928" } ] }, + { name: "paths with 500s", + type: "top", + description: "'500' responses, by path, number served over a %t period", + type: "top", + stats: ["paths_500"] }, + { name: "paths with exceptions", + description: "responses with exceptions, by path, number served over a %t period", + type: "top", + stats: ["paths_exception"] }, + { name: "disconnects with client-side errors", + description: "user disconnects with an error on the client side, number over a %t period", + type: "compare", + stats: [ { stat: "disconnects_with_clientside_errors", + description: "Disconnects with errors", + color: "FFA928" } ] }, + { name: "unnecessary disconnects", + description: "disconnects that were avoidable, number over a %t period", + type: "compare", + stats: [ { stat: "streaming_disconnects:disconnected_userids", + description: "Number of unique users disconnected", + color: "FFA928" }, + { stat: "streaming_disconnects:total_disconnects", + description: "Total number of disconnects", + color: "FF0000" } ] }, + ] +} + +function getUsedStats(statStructure) { + var stats = {}; + function getStructureValues(statStructure) { + if (typeof(statStructure) == 'string') { + stats[statStructure] = true; + } else { + statStructure.args.forEach(getStructureValues); + } + } + getStructureValues(statStructure); + return keys(stats); +} + +function getStatData(statStructure, values_f) { + function getStructureValues(statStructure) { + if (typeof(statStructure) == 'string') { + return values_f(statStructure); + } else if (typeof(statStructure) == 'number') { + return statStructure; + } else { + var args = statStructure.args.map(getStructureValues); + return { + f: statStructure.f, + args: args + } + } + } + + var mappedStructure = getStructureValues(statStructure); + + function evalStructure(statStructure) { + if ((typeof(statStructure) == 'number') || (statStructure instanceof Array)) { + return statStructure; + } else { + var merge_f = statStructure.f; + if (typeof(merge_f) == 'string') { + switch (merge_f) { + case '+': + merge_f = function() { + var sum = 0; + for (var i = 0; i < arguments.length; ++i) { + sum += arguments[i]; + } + return sum; + } + break; + case '*': + merge_f = function() { + var product = 0; + for (var i = 0; i < arguments.length; ++i) { + product *= arguments[i]; + } + return product; + } + break; + case '/': + merge_f = function(a, b) { return a / b; } + break; + case '-': + merge_f = function(a, b) { return a - b; } + break; + } + } + var evaluatedArguments = statStructure.args.map(evalStructure); + var length = -1; + evaluatedArguments.forEach(function(arg) { + if (typeof(arg) == 'object' && (arg instanceof Array)) { + length = arg.length; + } + }); + evaluatedArguments = evaluatedArguments.map(function(arg) { + if (typeof(arg) == 'number') { + var newArg = new Array(length); + for (var i = 0; i < newArg.length; ++i) { + newArg[i] = arg; + } + return newArg + } else { + return arg; + } + }); + return mergeArrays.apply(this, [merge_f].concat(evaluatedArguments)); + } + } + return evalStructure(mappedStructure); +} + +var googleChartSimpleEncoding = "ABCDEFGHIJLKMNOPQRSTUVQXYZabcdefghijklmnopqrstuvwxyz0123456789-."; +function _enc(value) { + return googleChartSimpleEncoding[Math.floor(value/64)] + googleChartSimpleEncoding[value%64]; +} + +function drawSparkline(dataSets, labels, colors, minutes) { + var max = 1; + var maxLength = 0; + dataSets.forEach(function(dataSet, i) { + if (dataSet.length > maxLength) { + maxLength = dataSet.length; + } + dataSet.forEach(function(point) { + if (point > max) { + max = point; + } + }); + }); + var data = dataSets.map(function(dataSet) { + var chars = dataSet.map(function(x) { + if (x !== undefined) { + return _enc(Math.round(x/max*4095)); + } else { + return "__"; + } + }).join(""); + while (chars.length < maxLength*2) { + chars = "__"+chars; + } + return chars; + }).join(","); + var timeLabels; + if (minutes < 60*24) { + timeLabels = [4,3,2,1,0].map(function(t) { + var minutesPerTick = minutes/4; + var d = new Date(Date.now() - minutesPerTick*60000*t); + return (d.getHours()%12 || 12)+":"+(d.getMinutes() < 10 ? "0" : "")+d.getMinutes()+(d.getHours() < 12 ? "am":"pm"); + }).join("|"); + } else { + timeLabels = [4,3,2,1,0].map(function(t) { + var daysPerTick = (minutes/(60*24))/4; + var d = new Date(Date.now() - t*daysPerTick*24*60*60*1000); + return (d.getMonth()+1)+"/"+d.getDate(); + }).join("|"); + } + var pointLabels = dataSets.map(function(dataSet, i) { + return ["t"+dataSet[dataSet.length-1],colors[i],i,maxLength-1,12,0].join(","); + }).join("|"); + labels = labels.map(function(label) { + return encodeURIComponent((label.length > 73) ? label.slice(0, 70) + "..." : label); + }); + var step = Math.round(max/10); + step = Math.round(step/Math.pow(10, String(step).length-1))*Math.pow(10, String(step).length-1); + var srcUrl = + "http://chart.apis.google.com/chart?chs=600x300&cht=lc&chd=e:"+data+ + "&chxt=y,x&chco="+colors.join(",")+"&chxr=0,0,"+max+","+step+"&chxl=1:|"+timeLabels+ + "&chdl="+labels.join("|")+"&chdlp=b&chm="+pointLabels; + return toHTML(IMG({src: srcUrl})); +} + +var liveDataNumSamples = 20; + +function extractStatValuesFunction(nameToValues_f) { + return function(statName) { + var value; + if (statName.indexOf(":") >= 0) { + [statName, value] = statName.split(":"); + } + var h = nameToValues_f(statName); + if (value) { + h = h.map(function(topValues) { + if (! topValues) { return; } + var tv = topValues.topValues; + for (var i = 0; i < tv.length; ++i) { + if (tv[i].value == value) { + return tv[i].count; + } + } + return 0; + }); + } + return h; + } +} + +function sparkline_compare(history_f, minutesPerSample, stat) { + var histories = stat.stats.map(function(stat) { + var samples = getStatData(stat.stat, extractStatValuesFunction(history_f)); + return [samples, stat.description, stat.color]; + }); + return drawSparkline(histories.map(function(history) { return history[0] }), + histories.map(function(history) { return history[1] }), + histories.map(function(history) { return history[2] }), + minutesPerSample*histories[0][0].length); +} + +function sparkline_top(history_f, minutesPerSample, stat) { + var showOthers = ! stat.options || stat.options.showOthers != false; + var history = stat.stats.map(history_f)[0]; + + if (history.length == 0) { + return "<b>no data</b>"; + } + var topRecents = {}; + var topRecents_arr = []; + history.forEach(function(tv) { + if (! tv) { return; } + if (tv.topValues.length > 0) { + topRecents_arr = tv.topValues.map(function(x) { return x.value; }); + } + }); + + if (topRecents_arr.length == 0) { + return "<b>no data</b>"; + } + topRecents_arr = topRecents_arr.slice(0, _topN()); + topRecents_arr.forEach(function(value, i) { + topRecents[value] = i; + }); + + if (showOthers) { + topRecents_arr.push("Other"); + } + var max = 1; + var values = topRecents_arr.map(function() { return history.map(function() { return 0 }); }); + + history.forEach(function(tv, i) { + if (! tv) { return; } + tv.topValues.forEach(function(entry) { + if (entry.count > max) { + max = entry.count; + } + if (entry.value in topRecents) { + values[topRecents[entry.value]][i] = entry.count; + } else if (showOthers) { + values[values.length-1][i] += entry.count; + } + }); + }); + return drawSparkline( + values, + topRecents_arr, + ["FF0000", "00FF00", "0000FF", "FF00FF", "00FFFF"].slice(0, topRecents_arr.length-1).concat("FFA928"), + minutesPerSample*history.length); +} + +function sparkline_histogram(history_f, minutesPerSample, stat) { + var history = stat.stats.map(history_f)[0]; + + if (history.length == 0) { + return "<b>no data</b>"; + } + var percentiles = [50, 90, 95, 99]; + var data = percentiles.map(function() { return []; }) + history.forEach(function(hist) { + percentiles.forEach(function(pct, i) { + data[i].push((hist ? hist[""+pct] : undefined)); + }); + }); + return drawSparkline( + data, + percentiles.map(function(pct) { return ""+pct+"%"; }), + ["FF0000","FF00FF","FFA928","00FF00"].reverse(), + minutesPerSample*history.length); +} + +function liveHistoryFunction(minutesPerSample) { + return function(statName) { + return statistics.liveSnapshot(statName).history(minutesPerSample, liveDataNumSamples); + } +} + +function _listStats(statName, count) { + var options = { orderBy: '-timestamp,id' }; + if (count !== undefined) { + options.limit = count; + } + return sqlobj.selectMulti('statistics', {name: statName}, options); +} + +function ancientHistoryFunction(time) { + return function(statName) { + var seenDates = {}; + var samples = _listStats(statName); + + samples = samples.reverse().map(function(json) { + if (seenDates[""+json.timestamp]) { return; } + seenDates[""+json.timestamp] = true; + return {timestamp: json.timestamp, json: json.value}; + }).filter(function(x) { return x !== undefined }); + + samples = samples.reverse().slice(0, Math.round(time/(24*60))); + var samplesWithEmptyValues = []; + for (var i = 0; i < samples.length-1; ++i) { + var current = samples[i]; + var next = samples[i+1]; + samplesWithEmptyValues.push(current.json); + for (var j = current.timestamp+86400*1000; j < next.timestamp; j += 86400*1000) { + samplesWithEmptyValues.push(undefined); + } + } + if (samples.length > 0) { + samplesWithEmptyValues.push(samples[samples.length-1].json); + } + samplesWithEmptyValues = samplesWithEmptyValues.map(function(json) { + if (! json) { return; } + var obj = fastJSON.parse(json); + if (keys(obj).length == 1 && 'value' in obj) { + obj = obj.value; + } + return obj; + }); + + return samplesWithEmptyValues.reverse(); + } +} + +function sparkline(history_f, minutesPerSample, stat) { + if (this["sparkline_"+stat.type]) { + return this["sparkline_"+stat.type](history_f, minutesPerSample, stat); + } else { + return "<b>No sparkline handler!</b>"; + } +} + +function liveLatestFunction(minutesPerSample) { + return function(statName) { + return [statistics.liveSnapshot(statName).latest(minutesPerSample)]; + } +} + +function liveTotal(statName) { + return [statistics.liveSnapshot(statName).total]; +} + +function historyLatest(statName) { + return _listStats(statName, 1).map(function(x) { + var value = fastJSON.parse(x.value); + if (keys(value).length == 1 && 'value' in value) { + value = value.value; + } + return value; + }); +} + +function latest_compare(latest_f, stat) { + return stat.stats.map(function(stat) { + var sample = getStatData(stat.stat, extractStatValuesFunction(latest_f))[0]; + return { value: sample, description: stat.description }; + }); +} + +function latest_top(latest_f, stat) { + var showOthers = ! stat.options || stat.options.showOthers != false; + + var sample = stat.stats.map(latest_f)[0][0]; + if (! sample) { + return []; + } + var total = sample.count; + + var values = sample.topValues.slice(0, _topN()).map(function(v) { + total -= v.count; + return { value: v.count, description: v.value }; + }); + if (showOthers) { + values.push({value: total, description: "Other"}); + } + return values; +} + +function latest_histogram(latest_f, stat) { + var sample = stat.stats.map(latest_f)[0][0]; + + if (! sample) { + return "<b>no data</b>"; + } + + var percentiles = [0, 1, 5, 10, 25, 50, 75, 90, 95, 99, 100].filter(function(pct) { return ((""+pct) in sample) }); + + var xpos = percentiles.map(function(x, i) { return sample[x] }); + var xMax = 0; + var xMin = 1e12; + xpos.forEach(function(x) { xMax = (x > xMax ? x : xMax); xMin = (x < xMin ? x : xMin); }); + xposNormalized = xpos.map(function(x) { return Math.round((x-xMin)/(xMax-xMin || 1)*100); }); + + var ypos = percentiles.slice(1).map(function(y, i) { return (y-percentiles[i])/(xpos[i+1] || 1); }); + var yMax = 0; + ypos.forEach(function(y) { yMax = (y > yMax ? y : yMax); }); + yposNormalized = ypos.map(function(y) { return Math.round(y/yMax*100); }); + + // var proposedLabels = mergeArrays(function(x, y) { return {pos: x, label: y}; }, xposNormalized, xpos); + // var keepLabels = [{pos: 0, label: 0}]; + // proposedLabels.forEach(function(label) { + // if (label.pos - keepLabels[keepLabels.length-1].pos > 10) { + // keepLabels.push(label); + // } + // }); + // + // var labelPos = keepLabels.map(function(x) { return x.pos }); + // var labels = keepLabels.map(function(x) { return x.label }); + + return toHTML(IMG({src: + "http://chart.apis.google.com/chart?chs=340x100&cht=lxy&chd=t:"+xposNormalized.join(",")+"|0,"+yposNormalized.join(",")+ + "&chxt=x&chxr=0,"+xMin+","+xMax+","+Math.floor((xMax-xMin)/5) // "l=0:|"+labels.join("|")+"&chxp=0,"+labelPos.join(",") + })); +} + +function latest(latest_f, stat) { + if (this["latest_"+stat.type]) { + return this["latest_"+stat.type](latest_f, stat); + } else { + return "<b>No latest handler!</b>"; + } +} + +function dropdown(name, options, selected) { + var select; + if (typeof(name) == 'string') { + select = SELECT({name: name}); + } else { + select = SELECT(name); + } + + function addOption(value, content) { + var opt = OPTION({value: value}, content || value); + if (value == selected) { + opt.attribs.selected = "selected"; + } + select.push(opt); + } + + if (options instanceof Array) { + options.forEach(f_limitArgs(this, addOption, 1)); + } else { + eachProperty(options, addOption); + } + return select; +} + +function render_main() { + var categoriesToStats = {}; + + eachProperty(statDisplays, function(catName, statArray) { + categoriesToStats[catName] = statArray.map(_renderableStat); + }); + + renderHtml('statistics/stat_page.ejs', + {eachProperty: eachProperty, + statCategoryNames: keys(categoriesToStats), + categoriesToStats: categoriesToStats, + optionsForm: _optionsForm() }); +} + +function _optionsForm() { + return FORM({id: "statprefs", method: "POST"}, "Show data with granularity: ", + // dropdown({name: 'showLiveOrHistorical', onchange: 'formChanged();'}, + // {live: 'live', hist: 'historical'}, + // _pref('showLiveOrHistorical')), + // (_showLiveStats() ? + // SPAN(" with granularity ", + dropdown({name: 'granularity', onchange: 'formChanged();'}, + {"1": '1 minute', "5": '5 minutes', "60": '1 hour', "1440": '1 day'}, + _pref('granularity')), // ), + // : ""), + " top N:", + INPUT({type: "text", name: "topNCount", value: _topN()}), + INPUT({type: "submit", name: "Set", value: "set N"}), + INPUT({type: "hidden", name: "fragment", id: "fragment", value: "health"})); +} + +// function render_main() { +// var body = BODY(); +// +// var cat = request.params.cat; +// if (!cat) { +// cat = 'health'; +// } +// +// body.push(A({id: "backtoadmin", href: "/ep/admin/"}, html("«"), " back to admin")); +// body.push(_renderTopnav(cat)); +// +// body.push(form); +// +// if (request.params.stat) { +// body.push(A({className: "viewall", +// href: qpath({stat: null})}, html("«"), " view all")); +// } +// +// var statNames = statDisplays[cat]; +// statNames.forEach(function(sn) { +// if (!request.params.stat || (request.params.stat == sn)) { +// body.push(_renderableStat(sn)); +// } +// }); +// +// helpers.includeCss('admin/admin-stats.css'); +// response.write(HTML(HEAD(html(helpers.cssIncludes())), body)); +// } + +function _getLatest(stat) { + var minutesPerSample = _timescale(); + + if (_showLiveStats()) { + return latest(liveLatestFunction(minutesPerSample), stat); + } else { + return latest(liveTotal, stat); + } +} + +function _getGraph(stat) { + var minutesPerSample = _timescale(); + + if (_showLiveStats()) { + return html(sparkline(liveHistoryFunction(minutesPerSample), minutesPerSample, stat)); + } else { + return html(sparkline(ancientHistoryFunction(60*24*60), 24*60, stat)); + } +} + +function _getDataLinks(stat) { + if (_showLiveStats()) { + return; + } + + function listToLinks(list) { + var links = []; //SPAN({className: "datalink"}, "(data for "); + list.forEach(function(statName) { + links.push(toHTML(A({href: "/ep/admin/usagestats/data?statName="+statName}, statName))); + }); +// links.push(")"); + return links; + } + + switch (stat.type) { + case 'compare': + var stats = []; + stat.stats.map(function(stat) { return getUsedStats(stat.stat); }).forEach(function(list) { + stats = stats.concat(list); + }); + return listToLinks(stats); + case 'top': + return listToLinks(stat.stats); + case 'histogram': + return listToLinks(stat.stats); + } +} + +function _renderableStat(stat) { + var minutesPerSample = _timescale(); + + var period = (_showLiveStats() ? minutesPerSample : 24*60); + + if (period < 24*60 && stat.hideLive) { + return; + } + + if (period < 60) { + period = ""+period+"-minute"; + } else if (period < 24*60) { + period = ""+period/(60)+"-hour"; + } else if (period >= 24*60) { + period = ""+period/(24*60)+"-day"; + } + var graph = _getGraph(stat); + var id = stat.name.replace(/[^a-zA-Z0-9]/g, ""); + + var displayName = stat.description.replace("%t", period); + var latest = _getLatest(stat); + var dataLinks = _getDataLinks(stat); + + return { + id: id, + specialState: "", + displayName: displayName, + name: stat.name, + graph: graph, + latest: latest, + dataLinks: dataLinks + } +} + +function render_data() { + var sn = request.params.statName; + var t = TABLE({border: 1, cellpadding: 2, style: "font-family: monospace;"}); + _listStats(sn).forEach(function(s) { + var tr = TR(); + tr.push(TD((s.id))); + tr.push(TD((new Date(s.timestamp * 1000)).toString())); + tr.push(TD(s.value)); + t.push(tr); + }); + response.write(HTML(BODY(t))); +} + + +// function renderStat(body, statName) { +// var div = DIV({className: 'statbox'}); +// div.push(A({className: "stat-title", href: qpath({stat: statName})}, +// statName, descriptions[statName] || "")); +// if (_showHistStats()) { +// div.push( +// DIV({className: "stat-graph"}, +// A({href: '/ep/admin/usagestats/graph?size=1080x420&statName='+statName}, +// IMG({src: '/ep/admin/usagestats/graph?size=400x200&statName='+statName, +// style: 'border: 1px solid #ccc; margin: 10px 0 0 20px;'})), +// BR(), +// DIV({style: 'text-align: right;'}, +// A({style: 'text-decoration: none; font-size: .8em;', +// href: '/ep/admin/usagestats/data?statName='+statName}, "(data)"))) +// ); +// } +// if (_showLiveStats()) { +// var data = statistics.getStatData(statName); +// var displayData = statistics.liveSnapshot(data); +// var t = TABLE({border: 0}); +// var tcount = 0; +// ["minute", "hour", "fourHour", "day"].forEach(function(timescale) { +// if (! _showTimescale(timescale)) { return; } +// var tr = TR(); +// t.push(tr); +// tr.push(TD({valign: "top"}, B("Last ", timescale))); +// var td = TD(); +// var cell = SPAN(); +// tr.push(td); +// td.push(cell); +// switch (data.plotType) { +// case 'line': +// cell.push(B(displayData[timescale])); break; +// case 'topValues': +// var top = displayData[timescale].topValues; +// if (tcount != 0) { +// tr[0].attribs.style = cell.attribs.style = "border-top: 2px solid black;"; +// } +// // println(statName+" / top length: "+top.length); +// for (var i = 0; i < Math.min(_topN(), top.length); ++i) { +// cell.push(B(top[i].count), ": ", top[i].value, BR()); +// } +// break; +// case 'histogram': +// var percentiles = displayData[timescale]; +// var pcts = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; +// var max = percentiles["100"] || 1000; +// cell.push(IMG({src: "http://chart.apis.google.com/chart?chs=340x100&cht=bvs&chd=t:"+ +// pcts.map(function(pct) { return Math.round(percentiles[""+pct]/max*100); }).join(",")+ +// "&chxt=x,y&chxl=0:|"+ +// pcts.map(function(pct) { return ""+pct+"%"; }).join("|")+ +// "&chxr=0,0,100|1,0,"+max+""})) +// // td.push("50%: ", B(percentiles["50"]), " ", +// // "90%: ", B(percentiles["90"]), " ", +// // "max: ", B(percentiles["100"])); +// break; +// } +// tcount++; +// }); +// div.push(DIV({className: "stat-table"}, t)); +// div.push(html(helpers.clearFloats())); +// } +// body.push(div); +// } +// ======= +// >>>>>>> Stashed changes:etherpad/src/etherpad/control/statscontrol.js + + +// old output. + +// +// function getStatsForCategory(category) { +// var statnames = statistics.getAllStatNames(); +// +// var matchingStatNames = []; +// statnames.forEach(function(sn) { +// if (statistics.getStatData(sn).category == category) { +// matchingStatNames.push(sn); +// } +// }); +// +// return matchingStatNames; +// } +// +// function renderCategoryList() { +// var body = BODY(); +// +// catNames = getCategoryNames(); +// body.push(P("Please select a statistics category:")); +// catNames.sort().forEach(function(catname) { +// body.push(P(A({href: "/ep/admin/usagestats/?cat="+catname}, catname))); +// }); +// response.write(body); +// } +// +// function getCategoryNames() { +// var statnames = statistics.getAllStatNames(); +// var catNames = {}; +// statnames.forEach(function(sn) { +// catNames[statistics.getStatData(sn).category] = true; +// }); +// return keys(catNames); +// } +// +// function dropdown(name, options, selected) { +// var select; +// if (typeof(name) == 'string') { +// select = SELECT({name: name}); +// } else { +// select = SELECT(name); +// } +// +// function addOption(value, content) { +// var opt = OPTION({value: value}, content || value); +// if (value == selected) { +// opt.attribs.selected = "selected"; +// } +// select.push(opt); +// } +// +// if (options instanceof Array) { +// options.forEach(f_limitArgs(this, addOption, 1)); +// } else { +// eachProperty(options, addOption); +// } +// return select; +// } +// +// function getCategorizedStats() { +// var statnames = statistics.getAllStatNames(); +// var categories = {} +// statnames.forEach(function(sn) { +// var category = statistics.getStatData(sn).category +// if (! categories[category]) { +// categories[category] = []; +// } +// categories[category].push(statistics.getStatData(sn)); +// }); +// return categories; +// } +// +// function render_ajax() { +// var categoriesToStats = getCategorizedStats(); +// +// eachProperty(categoriesToStats, function(catName, statArray) { +// categoriesToStats[catName] = statArray.map(function(statObject) { +// return { +// specialState: "", +// displayName: statObject.name, +// name: statObject.name, +// data: liveStatDisplayHtml(statObject) +// } +// }) +// }); +// +// renderHtml('statistics/stat_page.ejs', +// {eachProperty: eachProperty, +// statCategoryNames: keys(categoriesToStats), +// categoriesToStats: categoriesToStats }); +// } + +// function render_main() { +// var body = BODY(); +// +// var statNames = statistics.getAllStatNames(); //getStatsForCategory(request.params.cat); +// statNames.forEach(function(sn) { +// renderStat(body, sn); +// }); +// response.write(body); +// } +// +// var descriptions = { +// execution_latencies: ", mean response time in milliseconds", +// static_file_latencies: ", mean response time in milliseconds", +// pad_startup_times: ", max response time in milliseconds of fastest N% of requests" +// }; +// +// function liveStatDisplayHtml(statObject) { +// var displayData = statistics.liveSnapshot(statObject); +// switch (statObject.plotType) { +// case 'line': +// return displayData; +// case 'topValues': +// var data = {} +// eachProperty(displayData, function(timescale, tsdata) { +// data[timescale] = "" +// var top = tsdata.topValues; +// for (var i = 0; i < Math.min(_topN(), top.length); ++i) { +// data[timescale] += [B(top[i].count), ": ", top[i].value, BR()].map(toHTML).join(""); +// } +// if (data[timescale] == "") { +// data[timescale] = "(no data)"; +// } +// }); +// return data; +// case 'histogram': +// var imgs = {} +// eachProperty(displayData, function(timescale, tsdata) { +// var percentiles = tsdata; +// var pcts = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; +// var max = percentiles["100"] || 1000; +// imgs[timescale] = +// toHTML(IMG({src: "http://chart.apis.google.com/chart?chs=400x100&cht=bvs&chd=t:"+ +// pcts.map(function(pct) { return Math.round(percentiles[""+pct]/max*100); }).join(",")+ +// "&chxt=x,y&chxl=0:|"+ +// pcts.map(function(pct) { return ""+pct+"%"; }).join("|")+ +// "&chxr=0,0,100|1,0,"+max+""})); +// }); +// return imgs; +// } +// } +// +// function renderStat(body, statName) { +// var div = DIV({style: 'float: left; text-align: center; margin: 3px; border: 1px solid black;'}) +// div.push(P(statName, descriptions[statName] || "")); +// if (_showLiveStats()) { +// var data = statistics.getStatData(statName); +// var displayData = statistics.liveSnapshot(data); +// var t = TABLE(); +// var tcount = 0; +// ["minute", "hour", "fourHour", "day"].forEach(function(timescale) { +// if (! _showTimescale(timescale)) { return; } +// var tr = TR(); +// t.push(tr); +// tr.push(TD("last ", timescale)); +// var td = TD(); +// tr.push(td); +// switch (data.plotType) { +// case 'line': +// td.push(B(displayData[timescale])); break; +// case 'topValues': +// var top = displayData[timescale].topValues; +// if (tcount != 0) { +// tr[0].attribs.style = td.attribs.style = "border-top: 1px solid gray;"; +// } +// // println(statName+" / top length: "+top.length); +// for (var i = 0; i < Math.min(_topN(), top.length); ++i) { +// td.push(B(top[i].count), ": ", top[i].value, BR()); +// } +// break; +// case 'histogram': +// var percentiles = displayData[timescale]; +// var pcts = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; +// var max = percentiles["100"] || 1000; +// td.push(IMG({src: "http://chart.apis.google.com/chart?chs=340x100&cht=bvs&chd=t:"+ +// pcts.map(function(pct) { return Math.round(percentiles[""+pct]/max*100); }).join(",")+ +// "&chxt=x,y&chxl=0:|"+ +// pcts.map(function(pct) { return ""+pct+"%"; }).join("|")+ +// "&chxr=0,0,100|1,0,"+max+""})) +// // td.push("50%: ", B(percentiles["50"]), " ", +// // "90%: ", B(percentiles["90"]), " ", +// // "max: ", B(percentiles["100"])); +// break; +// } +// tcount++; +// }); +// div.push(t) +// } +// if (_showHistStats()) { +// div.push(A({href: '/ep/admin/usagestats/graph?size=1080x420&statName='+statName}, +// IMG({src: '/ep/admin/usagestats/graph?size=400x200&statName='+statName, +// style: 'border: 1px solid #ccc; margin: 10px 0 0 20px;'})), +// BR(), +// DIV({style: 'text-align: right;'}, +// A({style: 'text-decoration: none; font-size: .8em;', +// href: '/ep/admin/usagestats/data?statName='+statName}, "(data)"))); +// } +// body.push(div); +// } +// +// function render_graph() { +// var sn = request.params.statName; +// if (!sn) { +// render404(); +// } +// usage_stats.respondWithGraph(sn); +// } +// +// +// function render_exceptions() { +// var logNames = ["frontend/exception", "backend/exceptions"]; +// } + +// function render_updatehistory() { +// +// sqlcommon.withConnection(function(conn) { +// var stmnt = "delete from statistics;"; +// var s = conn.createStatement(); +// sqlcommon.closing(s, function() { +// s.execute(stmnt); +// }); +// }); +// +// var processed = {}; +// +// function _domonth(y, m) { +// for (var i = 0; i < 32; i++) { +// _processStatsDay(y, m, i, processed); +// } +// } +// +// _domonth(2008, 10); +// _domonth(2008, 11); +// _domonth(2008, 12); +// _domonth(2009, 1); +// _domonth(2009, 2); +// _domonth(2009, 3); +// _domonth(2009, 4); +// _domonth(2009, 5); +// _domonth(2009, 6); +// _domonth(2009, 7); +// +// response.redirect('/ep/admin/usagestats'); +// } + +// function _processStatsDay(year, month, date, processed) { +// var now = new Date(); +// var day = new Date(); +// +// for (var i = 0; i < 10; i++) { +// day.setFullYear(year); +// day.setDate(date); +// day.setMonth(month-1); +// } +// +// if ((+day < +now) && +// (!((day.getFullYear() == now.getFullYear()) && +// (day.getMonth() == now.getMonth()) && +// (day.getDate() == now.getDate())))) { +// +// var dayNoon = statistics.noon(day); +// +// if (processed[dayNoon]) { +// return; +// } else { +// statistics.processLogDay(new Date(dayNoon)); +// processed[dayNoon] = true; +// } +// } else { +// /* nothing */ +// } +// } + |