Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: modules/statsmaster/files/usercounts.html

Issue 5143068516810752: Added new stats server and user counter page (Closed)
Patch Set: Centralized stats processing Created Dec. 20, 2013, 3:47 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: modules/statsmaster/files/usercounts.html
===================================================================
new file mode 100644
--- /dev/null
+++ b/modules/statsmaster/files/usercounts.html
@@ -0,0 +1,256 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>User counter</title>
+ <style type="text/css">
+ form
+ {
+ display: inline;
+ }
+
+ .spacer
+ {
+ margin: 0 10px;
+ }
+
+ table
+ {
+ border-collapse: collapse;
+ }
+
+ td, th
+ {
+ padding: 5px;
+ border: 1px solid black;
+ }
+
+ td
+ {
+ text-align: right;
+ }
+ </style>
+ <script type="text/javascript">
+ const MILLIS_IN_DAY = 24 * 60 * 60 * 1000;
+
+ function zeroPad(number, digits)
+ {
+ var result = number.toString(10);
+ while (result.length < digits)
+ result = "0" + result;
+ return result;
+ }
+
+ function formatDate(date)
+ {
+ return zeroPad(date.getFullYear(), 4) + "-" + zeroPad(date.getMonth() + 1, 2) + "-" + zeroPad(date.getDate(), 2);
+ }
+
+ var curMonth = null;
+ var curMonthFilters = null;
+ var curMonthNotifications = null;
+
+ function checkStats(data, fromDate, offset, callback)
+ {
+ var date = new Date(fromDate.getTime() + offset * MILLIS_IN_DAY);
+
+ function doDownload(url, descr, callback)
+ {
+ var request = new XMLHttpRequest();
+ request.open("GET", url);
+ request.addEventListener("load", function()
+ {
+ if (request.status != 200)
+ alert("Failed to download " + descr + ", got response " + request.status);
+ else
+ callback(JSON.parse(request.responseText));
+ }, false);
+ request.addEventListener("error", function()
+ {
+ alert("Failed to download " + descr);
+ }, false);
+ request.send(null);
+ }
+
+ function doCheckStats(source, type)
+ {
+ if (!(date.getDate() in source.day))
+ return;
+
+ var intervals = source.day[date.getDate()].previousDownload;
+ for (var interval in intervals)
+ {
+ if (!intervals.hasOwnProperty(interval))
+ continue;
+
+ var days;
+ if (/^\d+ day/.test(interval))
+ days = parseInt(interval, 10);
+ else if (/^\d+ month/.test(interval))
+ days = 31;
+ else
+ continue;
+
+ // Most requests claiming that last download was long ago are bots or broken installations
+ if (days > 30)
+ continue;
+
+ var hits = intervals[interval].hits;
+ for (var i = Math.max(offset, 0); i < data.length; i++)
+ {
+ if (i - offset < 30 && i - offset + days >= 30)
+ data[i]["30day " + type] += hits;
+ if (i - offset < 7 && i - offset + days >= 7)
+ data[i]["7day " + type] += hits;
+ }
+ }
+ }
+
+ var month = zeroPad(date.getFullYear(), 4) + zeroPad(date.getMonth() + 1, 2);
+ if (month != curMonth)
+ {
+ doDownload("/raw/subscription/" + month + "/exceptionrules.txt.json", "acceptable ads data for month " + month, function(json)
+ {
+ curMonthFilters = json;
+ doDownload("/raw/notification/" + month + "/notification.json.json", "notification data for month " + month, function(json)
+ {
+ curMonthNotifications = json;
+ curMonth = month;
+ doCheckStats(curMonthFilters, "acceptable");
+ doCheckStats(curMonthNotifications, "total");
+ callback();
+ });
+ });
+ }
+ else
+ {
+ doCheckStats(curMonthFilters, "acceptable");
+ doCheckStats(curMonthNotifications, "total");
+ callback();
+ }
+ }
+
+ function showResults(fromDate, data)
+ {
+ function createCell(row, text)
+ {
+ var cell = document.createElement("td");
+ cell.appendChild(document.createTextNode(text));
+ row.appendChild(cell);
+ }
+
+ var resultBody = document.getElementById("resultBody");
+ while (resultBody.lastChild)
+ resultBody.removeChild(resultBody.lastChild);
+
+ for (var i = 0; i < data.length; i++)
+ {
+ var date = new Date(fromDate.getTime() + i * MILLIS_IN_DAY);
+
+ var row = document.createElement("tr");
+ createCell(row, date.toLocaleDateString());
+ createCell(row, data[i]["7day total"].toLocaleString());
+ createCell(row, data[i]["7day acceptable"].toLocaleString());
+ createCell(row, (100 - data[i]["7day acceptable"] / data[i]["7day total"] * 100).toFixed(2) + "%");
+ createCell(row, data[i]["30day total"].toLocaleString());
+ createCell(row, data[i]["30day acceptable"].toLocaleString());
+ createCell(row, (100 - data[i]["30day acceptable"] / data[i]["30day total"] * 100).toFixed(2) + "%");
+ resultBody.appendChild(row);
+ }
+
+ document.getElementById("result").removeAttribute("hidden");
+ document.getElementById("wait").setAttribute("hidden", "hidden");
+ }
+
+ function calculate()
+ {
+ document.getElementById("result").setAttribute("hidden", "hidden");
+ document.getElementById("wait").removeAttribute("hidden");
+
+ var fromDate = document.getElementById("from").value.replace(/^\s+/, "").replace(/\s+$/, "");
+ var match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(fromDate);
+ if (!match)
+ {
+ alert("Wrong start date format.");
+ document.getElementById("from").focus();
+ return;
+ }
+ fromDate = new Date(parseInt(match[1], 10), parseInt(match[2], 10) - 1, parseInt(match[3], 10), 12);
+
+ var toDate = document.getElementById("to").value.replace(/^\s+/, "").replace(/\s+$/, "");
+ var match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(toDate);
+ if (!match)
+ {
+ alert("Wrong end date format.");
+ document.getElementById("to").focus();
+ return;
+ }
+ toDate = new Date(parseInt(match[1], 10), parseInt(match[2], 10) - 1, parseInt(match[3], 10), 12);
+
+ if (fromDate > toDate)
+ {
+ var tmp = fromDate;
+ fromDate = toDate;
+ toDate = tmp;
+ }
+
+ var data = [];
+ for (var d = fromDate; formatDate(d) <= formatDate(toDate); d = new Date(d.getTime() + MILLIS_IN_DAY))
+ data.push({"7day total": 0, "7day acceptable": 0, "30day total": 0, "30day acceptable": 0});
+
+ var offset = -30;
+ var callback = function()
+ {
+ offset++;
+ if (offset >= data.length)
+ {
+ showResults(fromDate, data);
+ return;
+ }
+ checkStats(data, fromDate, offset, callback);
+ };
+ callback();
+ }
+
+ window.onload = function()
+ {
+ document.getElementById("from").value = formatDate(new Date(Date.now() - 7 * MILLIS_IN_DAY));
+ document.getElementById("to").value = formatDate(new Date(Date.now() - MILLIS_IN_DAY));
+ }
+ </script>
+</head>
+<body>
+ <p>
+ Please enter the date range that you need user statistics for:
+ </p>
+ <p>
+ <form action="javascript://" onsubmit="calculate();">
+ Start date (yyyy-mm-dd format): <input id="from" type="text">
+ <span class="spacer"></span>
+ End date (yyyy-mm-dd format): <input id="to" type="text">
+ <span class="spacer"></span>
+ <button type="submit">Calculate</button>
+ </form>
+ </p>
+ <p id="wait" hidden>
+ Please wait, data is being fetched...
+ </p>
+ <table id="result" hidden>
+ <tr>
+ <th rowspan="2">Date</th>
+ <th colspan="3">7-day active users</th>
+ <th colspan="3">30-day active users</th>
+ </tr>
+ <tr>
+ <th>Total</th>
+ <th>Acceptable ads</th>
+ <th>Opt-out rate</th>
+ <th>Total</th>
+ <th>Acceptable ads</th>
+ <th>Opt-out rate</th>
+ </tr>
+ <tbody id="resultBody">
+ </tbody>
+ </table>
+</body>
+</html>

Powered by Google App Engine
This is Rietveld