OLD | NEW |
| (Empty) |
1 <!DOCTYPE html> | |
2 <html> | |
3 <head> | |
4 <meta charset="utf-8"> | |
5 <title>User counter</title> | |
6 <style type="text/css"> | |
7 form | |
8 { | |
9 display: inline; | |
10 } | |
11 | |
12 .spacer | |
13 { | |
14 margin: 0 10px; | |
15 } | |
16 | |
17 table | |
18 { | |
19 border-collapse: collapse; | |
20 } | |
21 | |
22 td, th | |
23 { | |
24 padding: 5px; | |
25 border: 1px solid black; | |
26 } | |
27 | |
28 td | |
29 { | |
30 text-align: right; | |
31 } | |
32 </style> | |
33 <script type="text/javascript"> | |
34 const MILLIS_IN_DAY = 24 * 60 * 60 * 1000; | |
35 | |
36 function zeroPad(number, digits) | |
37 { | |
38 var result = number.toString(10); | |
39 while (result.length < digits) | |
40 result = "0" + result; | |
41 return result; | |
42 } | |
43 | |
44 function formatDate(date) | |
45 { | |
46 return zeroPad(date.getFullYear(), 4) + "-" + zeroPad(date.getMonth() + 1,
2) + "-" + zeroPad(date.getDate(), 2); | |
47 } | |
48 | |
49 var curMonth = null; | |
50 var curMonthFilters = null; | |
51 var curMonthAntiadblock = null; | |
52 var curMonthNotifications = null; | |
53 | |
54 function checkStats(data, fromDate, offset, callback) | |
55 { | |
56 var date = new Date(fromDate.getTime() + offset * MILLIS_IN_DAY); | |
57 | |
58 function doDownload(url, descr, callback) | |
59 { | |
60 var request = new XMLHttpRequest(); | |
61 request.open("GET", url); | |
62 request.addEventListener("load", function() | |
63 { | |
64 if (request.status != 200) | |
65 alert("Failed to download " + descr + ", got response " + request.st
atus); | |
66 else | |
67 callback(JSON.parse(request.responseText)); | |
68 }, false); | |
69 request.addEventListener("error", function() | |
70 { | |
71 alert("Failed to download " + descr); | |
72 }, false); | |
73 request.send(null); | |
74 } | |
75 | |
76 function doCheckStats(source, type) | |
77 { | |
78 if (!(date.getDate() in source.day)) | |
79 return; | |
80 | |
81 var intervals = source.day[date.getDate()].previousDownload; | |
82 for (var interval in intervals) | |
83 { | |
84 if (!intervals.hasOwnProperty(interval)) | |
85 continue; | |
86 | |
87 var days; | |
88 if (/^\d+ day/.test(interval)) | |
89 days = parseInt(interval, 10); | |
90 else if (/^\d+ month/.test(interval)) | |
91 days = 31; | |
92 else | |
93 continue; | |
94 | |
95 // Most requests claiming that last download was long ago are bots or
broken installations | |
96 if (days > 30) | |
97 continue; | |
98 | |
99 var hits = intervals[interval].hits; | |
100 for (var i = Math.max(offset, 0); i < data.length; i++) | |
101 { | |
102 if (i - offset < 30 && i - offset + days >= 30) | |
103 data[i]["30day " + type] += hits; | |
104 if (i - offset < 7 && i - offset + days >= 7) | |
105 data[i]["7day " + type] += hits; | |
106 if (i - offset < 1 && i - offset + days >= 1) | |
107 data[i]["1day " + type] += hits; | |
108 } | |
109 } | |
110 } | |
111 | |
112 var month = zeroPad(date.getFullYear(), 4) + zeroPad(date.getMonth() + 1,
2); | |
113 if (month != curMonth) | |
114 { | |
115 doDownload("/raw/subscription/" + month + "/exceptionrules.txt.json", "a
cceptable ads data for month " + month, function(json) | |
116 { | |
117 curMonthFilters = json; | |
118 doDownload("/raw/subscription/" + month + "/antiadblockfilters.txt.jso
n", "anti-adblock data for month " + month, function(json) | |
119 { | |
120 curMonthAntiadblock = json; | |
121 doDownload("/raw/notification/" + month + "/notification.json.json",
"notification data for month " + month, function(json) | |
122 { | |
123 curMonthNotifications = json; | |
124 curMonth = month; | |
125 doCheckStats(curMonthFilters, "acceptable"); | |
126 doCheckStats(curMonthAntiadblock, "antiadblock"); | |
127 doCheckStats(curMonthNotifications, "total"); | |
128 callback(); | |
129 }); | |
130 }); | |
131 }); | |
132 } | |
133 else | |
134 { | |
135 doCheckStats(curMonthFilters, "acceptable"); | |
136 doCheckStats(curMonthAntiadblock, "antiadblock"); | |
137 doCheckStats(curMonthNotifications, "total"); | |
138 callback(); | |
139 } | |
140 } | |
141 | |
142 function showResults(fromDate, data) | |
143 { | |
144 function createCell(row, text) | |
145 { | |
146 var cell = document.createElement("td"); | |
147 cell.appendChild(document.createTextNode(text)); | |
148 row.appendChild(cell); | |
149 } | |
150 | |
151 var resultBody = document.getElementById("resultBody"); | |
152 while (resultBody.lastChild) | |
153 resultBody.removeChild(resultBody.lastChild); | |
154 | |
155 for (var i = 0; i < data.length; i++) | |
156 { | |
157 var date = new Date(fromDate.getTime() + i * MILLIS_IN_DAY); | |
158 | |
159 var row = document.createElement("tr"); | |
160 createCell(row, date.toLocaleDateString()); | |
161 createCell(row, data[i]["1day total"].toLocaleString()); | |
162 createCell(row, data[i]["1day antiadblock"].toLocaleString()); | |
163 createCell(row, data[i]["1day acceptable"].toLocaleString()); | |
164 createCell(row, (100 - data[i]["1day acceptable"] / data[i]["1day total"
] * 100).toFixed(2) + "%"); | |
165 createCell(row, data[i]["7day total"].toLocaleString()); | |
166 createCell(row, data[i]["7day antiadblock"].toLocaleString()); | |
167 createCell(row, data[i]["7day acceptable"].toLocaleString()); | |
168 createCell(row, (100 - data[i]["7day acceptable"] / data[i]["7day total"
] * 100).toFixed(2) + "%"); | |
169 createCell(row, data[i]["30day total"].toLocaleString()); | |
170 createCell(row, data[i]["30day antiadblock"].toLocaleString()); | |
171 createCell(row, data[i]["30day acceptable"].toLocaleString()); | |
172 createCell(row, (100 - data[i]["30day acceptable"] / data[i]["30day tota
l"] * 100).toFixed(2) + "%"); | |
173 resultBody.appendChild(row); | |
174 } | |
175 | |
176 document.getElementById("result").removeAttribute("hidden"); | |
177 document.getElementById("wait").setAttribute("hidden", "hidden"); | |
178 } | |
179 | |
180 function calculate() | |
181 { | |
182 document.getElementById("result").setAttribute("hidden", "hidden"); | |
183 document.getElementById("wait").removeAttribute("hidden"); | |
184 | |
185 var fromDate = document.getElementById("from").value.replace(/^\s+/, "").r
eplace(/\s+$/, ""); | |
186 var match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(fromDate); | |
187 if (!match) | |
188 { | |
189 alert("Wrong start date format."); | |
190 document.getElementById("from").focus(); | |
191 return; | |
192 } | |
193 fromDate = new Date(parseInt(match[1], 10), parseInt(match[2], 10) - 1, pa
rseInt(match[3], 10), 12); | |
194 | |
195 var toDate = document.getElementById("to").value.replace(/^\s+/, "").repla
ce(/\s+$/, ""); | |
196 var match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(toDate); | |
197 if (!match) | |
198 { | |
199 alert("Wrong end date format."); | |
200 document.getElementById("to").focus(); | |
201 return; | |
202 } | |
203 toDate = new Date(parseInt(match[1], 10), parseInt(match[2], 10) - 1, pars
eInt(match[3], 10), 12); | |
204 | |
205 if (fromDate > toDate) | |
206 { | |
207 var tmp = fromDate; | |
208 fromDate = toDate; | |
209 toDate = tmp; | |
210 } | |
211 | |
212 var data = []; | |
213 for (var d = fromDate; formatDate(d) <= formatDate(toDate); d = new Date(d
.getTime() + MILLIS_IN_DAY)) | |
214 data.push({"1day total": 0, "1day antiadblock": 0, "1day acceptable": 0,
"7day total": 0, "7day antiadblock": 0, "7day acceptable": 0, "30day total": 0,
"30day antiadblock": 0, "30day acceptable": 0}); | |
215 | |
216 var offset = -30; | |
217 var callback = function() | |
218 { | |
219 offset++; | |
220 if (offset >= data.length) | |
221 { | |
222 showResults(fromDate, data); | |
223 return; | |
224 } | |
225 checkStats(data, fromDate, offset, callback); | |
226 }; | |
227 callback(); | |
228 } | |
229 | |
230 window.onload = function() | |
231 { | |
232 document.getElementById("from").value = formatDate(new Date(Date.now() - 7
* MILLIS_IN_DAY)); | |
233 document.getElementById("to").value = formatDate(new Date(Date.now() - MIL
LIS_IN_DAY)); | |
234 } | |
235 </script> | |
236 </head> | |
237 <body> | |
238 <p> | |
239 Please enter the date range that you need user statistics for: | |
240 </p> | |
241 <p> | |
242 <form action="javascript://" onsubmit="calculate();"> | |
243 Start date (yyyy-mm-dd format): <input id="from" type="text"> | |
244 <span class="spacer"></span> | |
245 End date (yyyy-mm-dd format): <input id="to" type="text"> | |
246 <span class="spacer"></span> | |
247 <button type="submit">Calculate</button> | |
248 </form> | |
249 </p> | |
250 <p id="wait" hidden> | |
251 Please wait, data is being fetched... | |
252 </p> | |
253 <table id="result" hidden> | |
254 <tr> | |
255 <th rowspan="2">Date</th> | |
256 <th colspan="4">1-day active users</th> | |
257 <th colspan="4">7-day active users</th> | |
258 <th colspan="4">30-day active users</th> | |
259 </tr> | |
260 <tr> | |
261 <th>Total</th> | |
262 <th>Anti-adblock filters</th> | |
263 <th>Acceptable ads</th> | |
264 <th>Opt-out rate</th> | |
265 <th>Total</th> | |
266 <th>Anti-adblock filters</th> | |
267 <th>Acceptable ads</th> | |
268 <th>Opt-out rate</th> | |
269 <th>Total</th> | |
270 <th>Anti-adblock filters</th> | |
271 <th>Acceptable ads</th> | |
272 <th>Opt-out rate</th> | |
273 </tr> | |
274 <tbody id="resultBody"> | |
275 </tbody> | |
276 </table> | |
277 </body> | |
278 </html> | |
OLD | NEW |