1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Mozilla Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 2011
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Taras Glek <tglek@mozilla.com>
24 : * Vladan Djeric <vdjeric@mozilla.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "base/histogram.h"
41 : #include "base/pickle.h"
42 : #include "nsIComponentManager.h"
43 : #include "nsIServiceManager.h"
44 : #include "nsCOMPtr.h"
45 : #include "mozilla/ModuleUtils.h"
46 : #include "nsIXPConnect.h"
47 : #include "mozilla/Services.h"
48 : #include "jsapi.h"
49 : #include "nsStringGlue.h"
50 : #include "nsITelemetry.h"
51 : #include "nsIFile.h"
52 : #include "nsILocalFile.h"
53 : #include "Telemetry.h"
54 : #include "nsTHashtable.h"
55 : #include "nsHashKeys.h"
56 : #include "nsBaseHashtable.h"
57 : #include "nsXULAppAPI.h"
58 : #include "nsThreadUtils.h"
59 : #include "mozilla/Mutex.h"
60 : #include "mozilla/FileUtils.h"
61 :
62 : namespace {
63 :
64 : using namespace base;
65 : using namespace mozilla;
66 :
67 : template<class EntryType>
68 : class AutoHashtable : public nsTHashtable<EntryType>
69 : {
70 : public:
71 : AutoHashtable(PRUint32 initSize = PL_DHASH_MIN_SIZE);
72 : ~AutoHashtable();
73 : typedef bool (*ReflectEntryFunc)(EntryType *entry, JSContext *cx, JSObject *obj);
74 : bool ReflectHashtable(ReflectEntryFunc entryFunc, JSContext *cx, JSObject *obj);
75 : private:
76 : struct EnumeratorArgs {
77 : JSContext *cx;
78 : JSObject *obj;
79 : ReflectEntryFunc entryFunc;
80 : };
81 : static PLDHashOperator ReflectEntryStub(EntryType *entry, void *arg);
82 : };
83 :
84 : template<class EntryType>
85 5681 : AutoHashtable<EntryType>::AutoHashtable(PRUint32 initSize)
86 : {
87 5681 : this->Init(initSize);
88 5681 : }
89 :
90 : template<class EntryType>
91 5679 : AutoHashtable<EntryType>::~AutoHashtable()
92 : {
93 5679 : this->Clear();
94 5679 : }
95 :
96 : template<typename EntryType>
97 : PLDHashOperator
98 50 : AutoHashtable<EntryType>::ReflectEntryStub(EntryType *entry, void *arg)
99 : {
100 50 : EnumeratorArgs *args = static_cast<EnumeratorArgs *>(arg);
101 50 : if (!args->entryFunc(entry, args->cx, args->obj)) {
102 0 : return PL_DHASH_STOP;
103 : }
104 50 : return PL_DHASH_NEXT;
105 : }
106 :
107 : /**
108 : * Reflect the individual entries of table into JS, usually by defining
109 : * some property and value of obj. entryFunc is called for each entry.
110 : */
111 : template<typename EntryType>
112 : bool
113 25 : AutoHashtable<EntryType>::ReflectHashtable(ReflectEntryFunc entryFunc,
114 : JSContext *cx, JSObject *obj)
115 : {
116 25 : EnumeratorArgs args = { cx, obj, entryFunc };
117 25 : PRUint32 num = this->EnumerateEntries(ReflectEntryStub, static_cast<void*>(&args));
118 25 : return num == this->Count();
119 : }
120 :
121 : class TelemetryImpl : public nsITelemetry
122 : {
123 : NS_DECL_ISUPPORTS
124 : NS_DECL_NSITELEMETRY
125 :
126 : public:
127 : TelemetryImpl();
128 : ~TelemetryImpl();
129 :
130 : static bool CanRecord();
131 : static already_AddRefed<nsITelemetry> CreateTelemetryInstance();
132 : static void ShutdownTelemetry();
133 : static void RecordSlowStatement(const nsACString &statement,
134 : const nsACString &dbName,
135 : PRUint32 delay);
136 : static nsresult GetHistogramEnumId(const char *name, Telemetry::ID *id);
137 : struct StmtStats {
138 : PRUint32 hitCount;
139 : PRUint32 totalTime;
140 : };
141 : typedef nsBaseHashtableET<nsCStringHashKey, StmtStats> SlowSQLEntryType;
142 :
143 : private:
144 : static bool StatementReflector(SlowSQLEntryType *entry, JSContext *cx,
145 : JSObject *obj);
146 : bool AddSQLInfo(JSContext *cx, JSObject *rootObj, bool mainThread);
147 :
148 : // Like GetHistogramById, but returns the underlying C++ object, not the JS one.
149 : nsresult GetHistogramByName(const nsACString &name, Histogram **ret);
150 : bool ShouldReflectHistogram(Histogram *h);
151 : void IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs);
152 : typedef StatisticsRecorder::Histograms::iterator HistogramIterator;
153 :
154 : struct AddonHistogramInfo {
155 : PRUint32 min;
156 : PRUint32 max;
157 : PRUint32 bucketCount;
158 : PRUint32 histogramType;
159 : Histogram *h;
160 : };
161 : typedef nsBaseHashtableET<nsCStringHashKey, AddonHistogramInfo> AddonHistogramEntryType;
162 : typedef AutoHashtable<AddonHistogramEntryType> AddonHistogramMapType;
163 : typedef nsBaseHashtableET<nsCStringHashKey, AddonHistogramMapType *> AddonEntryType;
164 : typedef AutoHashtable<AddonEntryType> AddonMapType;
165 : static bool AddonHistogramReflector(AddonHistogramEntryType *entry,
166 : JSContext *cx, JSObject *obj);
167 : static bool AddonReflector(AddonEntryType *entry, JSContext *cx, JSObject *obj);
168 : static bool CreateHistogramForAddon(const nsACString &name,
169 : AddonHistogramInfo &info);
170 : AddonMapType mAddonMap;
171 :
172 : // This is used for speedy string->Telemetry::ID conversions
173 : typedef nsBaseHashtableET<nsCharPtrHashKey, Telemetry::ID> CharPtrEntryType;
174 : typedef AutoHashtable<CharPtrEntryType> HistogramMapType;
175 : HistogramMapType mHistogramMap;
176 : bool mCanRecord;
177 : static TelemetryImpl *sTelemetry;
178 : AutoHashtable<SlowSQLEntryType> mSlowSQLOnMainThread;
179 : AutoHashtable<SlowSQLEntryType> mSlowSQLOnOtherThread;
180 : // This gets marked immutable in debug builds, so we can't use
181 : // AutoHashtable here.
182 : nsTHashtable<nsCStringHashKey> mTrackedDBs;
183 : Mutex mHashMutex;
184 : };
185 :
186 : TelemetryImpl* TelemetryImpl::sTelemetry = NULL;
187 :
188 : // A initializer to initialize histogram collection
189 1464 : StatisticsRecorder gStatisticsRecorder;
190 :
191 : // Hardcoded probes
192 : struct TelemetryHistogram {
193 : const char *id;
194 : PRUint32 min;
195 : PRUint32 max;
196 : PRUint32 bucketCount;
197 : PRUint32 histogramType;
198 : const char *comment;
199 : };
200 :
201 : // Perform the checks at the beginning of HistogramGet at compile time, so
202 : // that if people add incorrect histogram definitions, they get compiler
203 : // errors.
204 : #define HISTOGRAM(id, min, max, bucket_count, histogram_type, b) \
205 : PR_STATIC_ASSERT(nsITelemetry::HISTOGRAM_ ## histogram_type == nsITelemetry::HISTOGRAM_BOOLEAN || \
206 : nsITelemetry::HISTOGRAM_ ## histogram_type == nsITelemetry::HISTOGRAM_FLAG || \
207 : (min < max && bucket_count > 2 && min >= 1));
208 :
209 : #include "TelemetryHistograms.h"
210 :
211 : #undef HISTOGRAM
212 :
213 : const TelemetryHistogram gHistograms[] = {
214 : #define HISTOGRAM(id, min, max, bucket_count, histogram_type, comment) \
215 : { NS_STRINGIFY(id), min, max, bucket_count, \
216 : nsITelemetry::HISTOGRAM_ ## histogram_type, comment },
217 :
218 : #include "TelemetryHistograms.h"
219 :
220 : #undef HISTOGRAM
221 : };
222 : bool gCorruptHistograms[Telemetry::HistogramCount];
223 :
224 : bool
225 72 : TelemetryHistogramType(Histogram *h, PRUint32 *result)
226 : {
227 72 : switch (h->histogram_type()) {
228 : case Histogram::HISTOGRAM:
229 42 : *result = nsITelemetry::HISTOGRAM_EXPONENTIAL;
230 42 : break;
231 : case Histogram::LINEAR_HISTOGRAM:
232 28 : *result = nsITelemetry::HISTOGRAM_LINEAR;
233 28 : break;
234 : case Histogram::BOOLEAN_HISTOGRAM:
235 2 : *result = nsITelemetry::HISTOGRAM_BOOLEAN;
236 2 : break;
237 : case Histogram::FLAG_HISTOGRAM:
238 0 : *result = nsITelemetry::HISTOGRAM_FLAG;
239 : default:
240 0 : return false;
241 : }
242 72 : return true;
243 : }
244 :
245 : nsresult
246 32385 : HistogramGet(const char *name, PRUint32 min, PRUint32 max, PRUint32 bucketCount,
247 : PRUint32 histogramType, Histogram **result)
248 : {
249 32385 : if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN
250 : && histogramType != nsITelemetry::HISTOGRAM_FLAG) {
251 : // Sanity checks for histogram parameters.
252 26154 : if (min >= max)
253 0 : return NS_ERROR_ILLEGAL_VALUE;
254 :
255 26154 : if (bucketCount <= 2)
256 2 : return NS_ERROR_ILLEGAL_VALUE;
257 :
258 26152 : if (min < 1)
259 2 : return NS_ERROR_ILLEGAL_VALUE;
260 : }
261 :
262 32381 : switch (histogramType) {
263 : case nsITelemetry::HISTOGRAM_EXPONENTIAL:
264 19785 : *result = Histogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
265 19785 : break;
266 : case nsITelemetry::HISTOGRAM_LINEAR:
267 6365 : *result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag);
268 6365 : break;
269 : case nsITelemetry::HISTOGRAM_BOOLEAN:
270 6225 : *result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
271 6225 : break;
272 : case nsITelemetry::HISTOGRAM_FLAG:
273 6 : *result = FlagHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag);
274 6 : break;
275 : default:
276 0 : return NS_ERROR_INVALID_ARG;
277 : }
278 32381 : return NS_OK;
279 : }
280 :
281 : // O(1) histogram lookup by numeric id
282 : nsresult
283 1354137 : GetHistogramByEnumId(Telemetry::ID id, Histogram **ret)
284 : {
285 : static Histogram* knownHistograms[Telemetry::HistogramCount] = {0};
286 1354137 : Histogram *h = knownHistograms[id];
287 1354137 : if (h) {
288 1321836 : *ret = h;
289 1321836 : return NS_OK;
290 : }
291 :
292 32301 : const TelemetryHistogram &p = gHistograms[id];
293 32301 : nsresult rv = HistogramGet(p.id, p.min, p.max, p.bucketCount, p.histogramType, &h);
294 32301 : if (NS_FAILED(rv))
295 0 : return rv;
296 :
297 32301 : *ret = knownHistograms[id] = h;
298 32301 : return NS_OK;
299 : }
300 :
301 : bool
302 402 : FillRanges(JSContext *cx, JSObject *array, Histogram *h)
303 : {
304 229987 : for (size_t i = 0; i < h->bucket_count(); i++) {
305 229585 : if (!JS_DefineElement(cx, array, i, INT_TO_JSVAL(h->ranges(i)), NULL, NULL, JSPROP_ENUMERATE))
306 0 : return false;
307 : }
308 402 : return true;
309 : }
310 :
311 : enum reflectStatus {
312 : REFLECT_OK,
313 : REFLECT_CORRUPT,
314 : REFLECT_FAILURE
315 : };
316 :
317 : enum reflectStatus
318 402 : ReflectHistogramAndSamples(JSContext *cx, JSObject *obj, Histogram *h,
319 : const Histogram::SampleSet &ss)
320 : {
321 : // We don't want to reflect corrupt histograms.
322 402 : if (h->FindCorruption(ss) != Histogram::NO_INCONSISTENCIES) {
323 0 : return REFLECT_CORRUPT;
324 : }
325 :
326 402 : if (!(JS_DefineProperty(cx, obj, "min", INT_TO_JSVAL(h->declared_min()), NULL, NULL, JSPROP_ENUMERATE)
327 402 : && JS_DefineProperty(cx, obj, "max", INT_TO_JSVAL(h->declared_max()), NULL, NULL, JSPROP_ENUMERATE)
328 402 : && JS_DefineProperty(cx, obj, "histogram_type", INT_TO_JSVAL(h->histogram_type()), NULL, NULL, JSPROP_ENUMERATE)
329 804 : && JS_DefineProperty(cx, obj, "sum", DOUBLE_TO_JSVAL(ss.sum()), NULL, NULL, JSPROP_ENUMERATE))) {
330 0 : return REFLECT_FAILURE;
331 : }
332 :
333 402 : const size_t count = h->bucket_count();
334 402 : JSObject *rarray = JS_NewArrayObject(cx, count, nsnull);
335 402 : if (!rarray) {
336 0 : return REFLECT_FAILURE;
337 : }
338 804 : JS::AutoObjectRooter aroot(cx, rarray);
339 402 : if (!(FillRanges(cx, rarray, h)
340 : && JS_DefineProperty(cx, obj, "ranges", OBJECT_TO_JSVAL(rarray),
341 402 : NULL, NULL, JSPROP_ENUMERATE))) {
342 0 : return REFLECT_FAILURE;
343 : }
344 :
345 402 : JSObject *counts_array = JS_NewArrayObject(cx, count, NULL);
346 402 : if (!counts_array) {
347 0 : return REFLECT_FAILURE;
348 : }
349 804 : JS::AutoObjectRooter croot(cx, counts_array);
350 402 : if (!JS_DefineProperty(cx, obj, "counts", OBJECT_TO_JSVAL(counts_array),
351 402 : NULL, NULL, JSPROP_ENUMERATE)) {
352 0 : return REFLECT_FAILURE;
353 : }
354 229987 : for (size_t i = 0; i < count; i++) {
355 459170 : if (!JS_DefineElement(cx, counts_array, i, INT_TO_JSVAL(ss.counts(i)),
356 229585 : NULL, NULL, JSPROP_ENUMERATE)) {
357 0 : return REFLECT_FAILURE;
358 : }
359 : }
360 :
361 402 : return REFLECT_OK;
362 : }
363 :
364 : enum reflectStatus
365 378 : ReflectHistogramSnapshot(JSContext *cx, JSObject *obj, Histogram *h)
366 : {
367 756 : Histogram::SampleSet ss;
368 378 : h->SnapshotSample(&ss);
369 378 : return ReflectHistogramAndSamples(cx, obj, h, ss);
370 : }
371 :
372 : JSBool
373 117 : JSHistogram_Add(JSContext *cx, unsigned argc, jsval *vp)
374 : {
375 117 : if (!argc) {
376 0 : JS_ReportError(cx, "Expected one argument");
377 0 : return JS_FALSE;
378 : }
379 :
380 117 : jsval v = JS_ARGV(cx, vp)[0];
381 : int32 value;
382 :
383 117 : if (!(JSVAL_IS_NUMBER(v) || JSVAL_IS_BOOLEAN(v))) {
384 0 : JS_ReportError(cx, "Not a number");
385 0 : return JS_FALSE;
386 : }
387 :
388 117 : if (!JS_ValueToECMAInt32(cx, v, &value)) {
389 0 : return JS_FALSE;
390 : }
391 :
392 117 : if (TelemetryImpl::CanRecord()) {
393 116 : JSObject *obj = JS_THIS_OBJECT(cx, vp);
394 116 : if (!obj) {
395 0 : return JS_FALSE;
396 : }
397 :
398 116 : Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
399 116 : if (h->histogram_type() == Histogram::BOOLEAN_HISTOGRAM)
400 9 : h->Add(!!value);
401 : else
402 107 : h->Add(value);
403 : }
404 117 : return JS_TRUE;
405 : }
406 :
407 : JSBool
408 51 : JSHistogram_Snapshot(JSContext *cx, unsigned argc, jsval *vp)
409 : {
410 51 : JSObject *obj = JS_THIS_OBJECT(cx, vp);
411 51 : if (!obj) {
412 0 : return JS_FALSE;
413 : }
414 :
415 51 : Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
416 51 : JSObject *snapshot = JS_NewObject(cx, nsnull, nsnull, nsnull);
417 51 : if (!snapshot)
418 0 : return JS_FALSE;
419 102 : JS::AutoObjectRooter sroot(cx, snapshot);
420 :
421 51 : switch (ReflectHistogramSnapshot(cx, snapshot, h)) {
422 : case REFLECT_FAILURE:
423 0 : return JS_FALSE;
424 : case REFLECT_CORRUPT:
425 0 : JS_ReportError(cx, "Histogram is corrupt");
426 0 : return JS_FALSE;
427 : case REFLECT_OK:
428 51 : JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(snapshot));
429 51 : return JS_TRUE;
430 : default:
431 0 : MOZ_NOT_REACHED("unhandled reflection status");
432 : return JS_FALSE;
433 : }
434 : }
435 :
436 : nsresult
437 177 : WrapAndReturnHistogram(Histogram *h, JSContext *cx, jsval *ret)
438 : {
439 : static JSClass JSHistogram_class = {
440 : "JSHistogram", /* name */
441 : JSCLASS_HAS_PRIVATE, /* flags */
442 : JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub,
443 : JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
444 : JSCLASS_NO_OPTIONAL_MEMBERS
445 : };
446 :
447 177 : JSObject *obj = JS_NewObject(cx, &JSHistogram_class, NULL, NULL);
448 177 : if (!obj)
449 0 : return NS_ERROR_FAILURE;
450 354 : JS::AutoObjectRooter root(cx, obj);
451 177 : if (!(JS_DefineFunction (cx, obj, "add", JSHistogram_Add, 1, 0)
452 177 : && JS_DefineFunction (cx, obj, "snapshot", JSHistogram_Snapshot, 1, 0))) {
453 0 : return NS_ERROR_FAILURE;
454 : }
455 177 : *ret = OBJECT_TO_JSVAL(obj);
456 177 : JS_SetPrivate(obj, h);
457 177 : return NS_OK;
458 : }
459 :
460 1419 : TelemetryImpl::TelemetryImpl():
461 : mHistogramMap(Telemetry::HistogramCount),
462 1419 : mCanRecord(XRE_GetProcessType() == GeckoProcessType_Default),
463 2838 : mHashMutex("Telemetry::mHashMutex")
464 : {
465 : // A whitelist to prevent Telemetry reporting on Addon & Thunderbird DBs
466 : const char *trackedDBs[] = {
467 : "addons.sqlite", "chromeappsstore.sqlite", "content-prefs.sqlite",
468 : "cookies.sqlite", "downloads.sqlite", "extensions.sqlite",
469 : "formhistory.sqlite", "index.sqlite", "permissions.sqlite", "places.sqlite",
470 : "search.sqlite", "signons.sqlite", "urlclassifier3.sqlite",
471 : "webappsstore.sqlite"
472 1419 : };
473 :
474 1419 : mTrackedDBs.Init();
475 21285 : for (size_t i = 0; i < ArrayLength(trackedDBs); i++)
476 19866 : mTrackedDBs.PutEntry(nsDependentCString(trackedDBs[i]));
477 :
478 : #ifdef DEBUG
479 : // Mark immutable to prevent asserts on simultaneous access from multiple threads
480 1419 : mTrackedDBs.MarkImmutable();
481 : #endif
482 1419 : }
483 :
484 1419 : TelemetryImpl::~TelemetryImpl() {
485 1419 : }
486 :
487 : NS_IMETHODIMP
488 9 : TelemetryImpl::NewHistogram(const nsACString &name, PRUint32 min, PRUint32 max, PRUint32 bucketCount, PRUint32 histogramType, JSContext *cx, jsval *ret)
489 : {
490 : Histogram *h;
491 9 : nsresult rv = HistogramGet(PromiseFlatCString(name).get(), min, max, bucketCount, histogramType, &h);
492 9 : if (NS_FAILED(rv))
493 4 : return rv;
494 5 : h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
495 5 : return WrapAndReturnHistogram(h, cx, ret);
496 : }
497 :
498 : bool
499 0 : TelemetryImpl::StatementReflector(SlowSQLEntryType *entry, JSContext *cx,
500 : JSObject *obj)
501 : {
502 0 : const nsACString &sql = entry->GetKey();
503 0 : jsval hitCount = UINT_TO_JSVAL(entry->mData.hitCount);
504 0 : jsval totalTime = UINT_TO_JSVAL(entry->mData.totalTime);
505 :
506 0 : JSObject *arrayObj = JS_NewArrayObject(cx, 2, nsnull);
507 0 : if (!arrayObj) {
508 0 : return false;
509 : }
510 0 : JS::AutoObjectRooter root(cx, arrayObj);
511 0 : return (JS_SetElement(cx, arrayObj, 0, &hitCount)
512 0 : && JS_SetElement(cx, arrayObj, 1, &totalTime)
513 : && JS_DefineProperty(cx, obj,
514 : sql.BeginReading(),
515 : OBJECT_TO_JSVAL(arrayObj),
516 0 : NULL, NULL, JSPROP_ENUMERATE));
517 : }
518 :
519 : bool
520 14 : TelemetryImpl::AddSQLInfo(JSContext *cx, JSObject *rootObj, bool mainThread)
521 : {
522 14 : JSObject *statsObj = JS_NewObject(cx, NULL, NULL, NULL);
523 14 : if (!statsObj)
524 0 : return false;
525 28 : JS::AutoObjectRooter root(cx, statsObj);
526 :
527 : AutoHashtable<SlowSQLEntryType> &sqlMap = (mainThread
528 : ? mSlowSQLOnMainThread
529 14 : : mSlowSQLOnOtherThread);
530 14 : if (!sqlMap.ReflectHashtable(StatementReflector, cx, statsObj)) {
531 0 : return false;
532 : }
533 :
534 : return JS_DefineProperty(cx, rootObj,
535 : mainThread ? "mainThread" : "otherThreads",
536 : OBJECT_TO_JSVAL(statsObj),
537 14 : NULL, NULL, JSPROP_ENUMERATE);
538 : }
539 :
540 : nsresult
541 893 : TelemetryImpl::GetHistogramEnumId(const char *name, Telemetry::ID *id)
542 : {
543 893 : if (!sTelemetry) {
544 0 : return NS_ERROR_FAILURE;
545 : }
546 :
547 : // Cache names
548 : // Note the histogram names are statically allocated
549 893 : TelemetryImpl::HistogramMapType *map = &sTelemetry->mHistogramMap;
550 893 : if (!map->Count()) {
551 2816 : for (PRUint32 i = 0; i < Telemetry::HistogramCount; i++) {
552 2805 : CharPtrEntryType *entry = map->PutEntry(gHistograms[i].id);
553 2805 : if (NS_UNLIKELY(!entry)) {
554 0 : map->Clear();
555 0 : return NS_ERROR_OUT_OF_MEMORY;
556 : }
557 2805 : entry->mData = (Telemetry::ID) i;
558 : }
559 : }
560 :
561 893 : CharPtrEntryType *entry = map->GetEntry(name);
562 893 : if (!entry) {
563 283 : return NS_ERROR_INVALID_ARG;
564 : }
565 610 : *id = entry->mData;
566 610 : return NS_OK;
567 : }
568 :
569 : nsresult
570 173 : TelemetryImpl::GetHistogramByName(const nsACString &name, Histogram **ret)
571 : {
572 : Telemetry::ID id;
573 173 : nsresult rv = GetHistogramEnumId(PromiseFlatCString(name).get(), &id);
574 173 : if (NS_FAILED(rv)) {
575 3 : return rv;
576 : }
577 :
578 170 : rv = GetHistogramByEnumId(id, ret);
579 170 : if (NS_FAILED(rv))
580 0 : return rv;
581 :
582 170 : return NS_OK;
583 : }
584 :
585 : NS_IMETHODIMP
586 72 : TelemetryImpl::HistogramFrom(const nsACString &name, const nsACString &existing_name,
587 : JSContext *cx, jsval *ret)
588 : {
589 : Histogram *existing;
590 72 : nsresult rv = GetHistogramByName(existing_name, &existing);
591 72 : if (NS_FAILED(rv))
592 0 : return rv;
593 :
594 : PRUint32 histogramType;
595 72 : bool success = TelemetryHistogramType(existing, &histogramType);
596 72 : if (!success)
597 0 : return NS_ERROR_INVALID_ARG;
598 :
599 : Histogram *clone;
600 144 : rv = HistogramGet(PromiseFlatCString(name).get(), existing->declared_min(),
601 144 : existing->declared_max(), existing->bucket_count(),
602 288 : histogramType, &clone);
603 72 : if (NS_FAILED(rv))
604 0 : return rv;
605 :
606 144 : Histogram::SampleSet ss;
607 72 : existing->SnapshotSample(&ss);
608 72 : clone->AddSampleSet(ss);
609 72 : return WrapAndReturnHistogram(clone, cx, ret);
610 : }
611 :
612 : void
613 8 : TelemetryImpl::IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs)
614 : {
615 331 : for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
616 323 : Histogram *h = *it;
617 :
618 : Telemetry::ID id;
619 323 : nsresult rv = GetHistogramEnumId(h->histogram_name().c_str(), &id);
620 : // This histogram isn't a static histogram, just ignore it.
621 323 : if (NS_FAILED(rv)) {
622 123 : continue;
623 : }
624 :
625 200 : if (gCorruptHistograms[id]) {
626 0 : continue;
627 : }
628 :
629 400 : Histogram::SampleSet ss;
630 200 : h->SnapshotSample(&ss);
631 200 : Histogram::Inconsistencies check = h->FindCorruption(ss);
632 200 : bool corrupt = (check != Histogram::NO_INCONSISTENCIES);
633 :
634 200 : if (corrupt) {
635 0 : Telemetry::ID corruptID = Telemetry::HistogramCount;
636 0 : if (check & Histogram::RANGE_CHECKSUM_ERROR) {
637 0 : corruptID = Telemetry::RANGE_CHECKSUM_ERRORS;
638 0 : } else if (check & Histogram::BUCKET_ORDER_ERROR) {
639 0 : corruptID = Telemetry::BUCKET_ORDER_ERRORS;
640 0 : } else if (check & Histogram::COUNT_HIGH_ERROR) {
641 0 : corruptID = Telemetry::TOTAL_COUNT_HIGH_ERRORS;
642 0 : } else if (check & Histogram::COUNT_LOW_ERROR) {
643 0 : corruptID = Telemetry::TOTAL_COUNT_LOW_ERRORS;
644 : }
645 0 : Telemetry::Accumulate(corruptID, 1);
646 : }
647 :
648 200 : gCorruptHistograms[id] = corrupt;
649 : }
650 8 : }
651 :
652 : bool
653 323 : TelemetryImpl::ShouldReflectHistogram(Histogram *h)
654 : {
655 323 : const char *name = h->histogram_name().c_str();
656 : Telemetry::ID id;
657 323 : nsresult rv = GetHistogramEnumId(name, &id);
658 323 : if (NS_FAILED(rv)) {
659 : // GetHistogramEnumId generally should not fail. But a lookup
660 : // failure shouldn't prevent us from reflecting histograms into JS.
661 : //
662 : // However, these two histograms are created by Histogram itself for
663 : // tracking corruption. We have our own histograms for that, so
664 : // ignore these two.
665 246 : if (strcmp(name, "Histogram.InconsistentCountHigh") == 0
666 123 : || strcmp(name, "Histogram.InconsistentCountLow") == 0) {
667 0 : return false;
668 : }
669 123 : return true;
670 : } else {
671 200 : return !gCorruptHistograms[id];
672 : }
673 : }
674 :
675 : // Compute the name to pass into Histogram for the addon histogram
676 : // 'name' from the addon 'id'. We can't use 'name' directly because it
677 : // might conflict with other histograms in other addons or even with our
678 : // own.
679 : void
680 2 : AddonHistogramName(const nsACString &id, const nsACString &name,
681 : nsACString &ret)
682 : {
683 2 : ret.Append(id);
684 2 : ret.Append(NS_LITERAL_CSTRING(":"));
685 2 : ret.Append(name);
686 2 : }
687 :
688 : NS_IMETHODIMP
689 6 : TelemetryImpl::RegisterAddonHistogram(const nsACString &id,
690 : const nsACString &name,
691 : PRUint32 min, PRUint32 max,
692 : PRUint32 bucketCount,
693 : PRUint32 histogramType)
694 : {
695 6 : AddonEntryType *addonEntry = mAddonMap.GetEntry(id);
696 6 : if (!addonEntry) {
697 3 : addonEntry = mAddonMap.PutEntry(id);
698 3 : if (NS_UNLIKELY(!addonEntry)) {
699 0 : return NS_ERROR_OUT_OF_MEMORY;
700 : }
701 3 : addonEntry->mData = new AddonHistogramMapType();
702 : }
703 :
704 6 : AddonHistogramMapType *histogramMap = addonEntry->mData;
705 6 : AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name);
706 : // Can't re-register the same histogram.
707 6 : if (histogramEntry) {
708 1 : return NS_ERROR_FAILURE;
709 : }
710 :
711 5 : histogramEntry = histogramMap->PutEntry(name);
712 5 : if (NS_UNLIKELY(!histogramEntry)) {
713 0 : return NS_ERROR_OUT_OF_MEMORY;
714 : }
715 :
716 5 : AddonHistogramInfo &info = histogramEntry->mData;
717 5 : info.min = min;
718 5 : info.max = max;
719 5 : info.bucketCount = bucketCount;
720 5 : info.histogramType = histogramType;
721 :
722 5 : return NS_OK;
723 : }
724 :
725 : NS_IMETHODIMP
726 3 : TelemetryImpl::GetAddonHistogram(const nsACString &id, const nsACString &name,
727 : JSContext *cx, jsval *ret)
728 : {
729 3 : AddonEntryType *addonEntry = mAddonMap.GetEntry(id);
730 : // The given id has not been registered.
731 3 : if (!addonEntry) {
732 1 : return NS_ERROR_INVALID_ARG;
733 : }
734 :
735 2 : AddonHistogramMapType *histogramMap = addonEntry->mData;
736 2 : AddonHistogramEntryType *histogramEntry = histogramMap->GetEntry(name);
737 : // The given histogram name has not been registered.
738 2 : if (!histogramEntry) {
739 0 : return NS_ERROR_INVALID_ARG;
740 : }
741 :
742 2 : AddonHistogramInfo &info = histogramEntry->mData;
743 2 : if (!info.h) {
744 4 : nsCAutoString actualName;
745 2 : AddonHistogramName(id, name, actualName);
746 2 : if (!CreateHistogramForAddon(actualName, info)) {
747 0 : return NS_ERROR_FAILURE;
748 : }
749 : }
750 2 : return WrapAndReturnHistogram(info.h, cx, ret);
751 : }
752 :
753 : NS_IMETHODIMP
754 1 : TelemetryImpl::UnregisterAddonHistograms(const nsACString &id)
755 : {
756 1 : AddonEntryType *addonEntry = mAddonMap.GetEntry(id);
757 1 : if (addonEntry) {
758 : // Histogram's destructor is private, so this is the best we can do.
759 : // The histograms the addon created *will* stick around, but they
760 : // will be deleted if and when the addon registers histograms with
761 : // the same names.
762 1 : delete addonEntry->mData;
763 1 : mAddonMap.RemoveEntry(id);
764 : }
765 :
766 1 : return NS_OK;
767 : }
768 :
769 : NS_IMETHODIMP
770 8 : TelemetryImpl::GetHistogramSnapshots(JSContext *cx, jsval *ret)
771 : {
772 8 : JSObject *root_obj = JS_NewObject(cx, NULL, NULL, NULL);
773 8 : if (!root_obj)
774 0 : return NS_ERROR_FAILURE;
775 8 : *ret = OBJECT_TO_JSVAL(root_obj);
776 :
777 : // Ensure that all the HISTOGRAM_FLAG histograms have been created, so
778 : // that their values are snapshotted.
779 2048 : for (size_t i = 0; i < Telemetry::HistogramCount; ++i) {
780 2040 : if (gHistograms[i].histogramType == nsITelemetry::HISTOGRAM_FLAG) {
781 : Histogram *h;
782 32 : DebugOnly<nsresult> rv = GetHistogramByEnumId(Telemetry::ID(i), &h);
783 16 : MOZ_ASSERT(NS_SUCCEEDED(rv));
784 : }
785 : };
786 :
787 16 : StatisticsRecorder::Histograms hs;
788 8 : StatisticsRecorder::GetHistograms(&hs);
789 :
790 : // We identify corrupt histograms first, rather than interspersing it
791 : // in the loop below, to ensure that our corruption statistics don't
792 : // depend on histogram enumeration order.
793 : //
794 : // Of course, we hope that all of these corruption-statistics
795 : // histograms are not themselves corrupt...
796 8 : IdentifyCorruptHistograms(hs);
797 :
798 : // OK, now we can actually reflect things.
799 331 : for (HistogramIterator it = hs.begin(); it != hs.end(); ++it) {
800 323 : Histogram *h = *it;
801 323 : if (!ShouldReflectHistogram(h)) {
802 0 : continue;
803 : }
804 :
805 323 : JSObject *hobj = JS_NewObject(cx, NULL, NULL, NULL);
806 323 : if (!hobj) {
807 0 : return NS_ERROR_FAILURE;
808 : }
809 646 : JS::AutoObjectRooter root(cx, hobj);
810 323 : switch (ReflectHistogramSnapshot(cx, hobj, h)) {
811 : case REFLECT_CORRUPT:
812 : // We can still hit this case even if ShouldReflectHistograms
813 : // returns true. The histogram lies outside of our control
814 : // somehow; just skip it.
815 0 : continue;
816 : case REFLECT_FAILURE:
817 0 : return NS_ERROR_FAILURE;
818 : case REFLECT_OK:
819 646 : if (!JS_DefineProperty(cx, root_obj, h->histogram_name().c_str(),
820 646 : OBJECT_TO_JSVAL(hobj), NULL, NULL, JSPROP_ENUMERATE)) {
821 0 : return NS_ERROR_FAILURE;
822 : }
823 : }
824 : }
825 8 : return NS_OK;
826 : }
827 :
828 : bool
829 3 : TelemetryImpl::CreateHistogramForAddon(const nsACString &name,
830 : AddonHistogramInfo &info)
831 : {
832 : Histogram *h;
833 3 : nsresult rv = HistogramGet(PromiseFlatCString(name).get(),
834 : info.min, info.max, info.bucketCount,
835 3 : info.histogramType, &h);
836 3 : if (NS_FAILED(rv)) {
837 0 : return false;
838 : }
839 : // Don't let this histogram be reported via the normal means
840 : // (e.g. Telemetry.registeredHistograms); we'll make it available in
841 : // other ways.
842 3 : h->ClearFlags(Histogram::kUmaTargetedHistogramFlag);
843 3 : info.h = h;
844 3 : return true;
845 : }
846 :
847 : bool
848 8 : TelemetryImpl::AddonHistogramReflector(AddonHistogramEntryType *entry,
849 : JSContext *cx, JSObject *obj)
850 : {
851 8 : AddonHistogramInfo &info = entry->mData;
852 :
853 : // Never even accessed the histogram.
854 8 : if (!info.h) {
855 : // Have to force creation of HISTOGRAM_FLAG histograms.
856 5 : if (info.histogramType != nsITelemetry::HISTOGRAM_FLAG)
857 4 : return true;
858 :
859 1 : if (!CreateHistogramForAddon(entry->GetKey(), info)) {
860 0 : return false;
861 : }
862 : }
863 :
864 4 : JSObject *snapshot = JS_NewObject(cx, NULL, NULL, NULL);
865 4 : if (!snapshot) {
866 : // Just consider this to be skippable.
867 0 : return true;
868 : }
869 8 : JS::AutoObjectRooter r(cx, snapshot);
870 4 : switch (ReflectHistogramSnapshot(cx, snapshot, info.h)) {
871 : case REFLECT_FAILURE:
872 : case REFLECT_CORRUPT:
873 0 : return false;
874 : case REFLECT_OK:
875 4 : const nsACString &histogramName = entry->GetKey();
876 8 : if (!JS_DefineProperty(cx, obj,
877 4 : PromiseFlatCString(histogramName).get(),
878 : OBJECT_TO_JSVAL(snapshot), NULL, NULL,
879 4 : JSPROP_ENUMERATE)) {
880 0 : return false;
881 : }
882 4 : break;
883 : }
884 4 : return true;
885 : }
886 :
887 : bool
888 5 : TelemetryImpl::AddonReflector(AddonEntryType *entry,
889 : JSContext *cx, JSObject *obj)
890 : {
891 5 : const nsACString &addonId = entry->GetKey();
892 5 : JSObject *subobj = JS_NewObject(cx, NULL, NULL, NULL);
893 5 : if (!subobj) {
894 0 : return false;
895 : }
896 10 : JS::AutoObjectRooter r(cx, subobj);
897 :
898 5 : AddonHistogramMapType *map = entry->mData;
899 10 : if (!(map->ReflectHashtable(AddonHistogramReflector, cx, subobj)
900 : && JS_DefineProperty(cx, obj,
901 5 : PromiseFlatCString(addonId).get(),
902 : OBJECT_TO_JSVAL(subobj), NULL, NULL,
903 10 : JSPROP_ENUMERATE))) {
904 0 : return false;
905 : }
906 5 : return true;
907 : }
908 :
909 : NS_IMETHODIMP
910 5 : TelemetryImpl::GetAddonHistogramSnapshots(JSContext *cx, jsval *ret)
911 : {
912 5 : *ret = JSVAL_VOID;
913 5 : JSObject *obj = JS_NewObject(cx, NULL, NULL, NULL);
914 5 : if (!obj) {
915 0 : return NS_ERROR_FAILURE;
916 : }
917 10 : JS::AutoObjectRooter r(cx, obj);
918 :
919 5 : if (!mAddonMap.ReflectHashtable(AddonReflector, cx, obj)) {
920 0 : return NS_ERROR_FAILURE;
921 : }
922 5 : *ret = OBJECT_TO_JSVAL(obj);
923 5 : return NS_OK;
924 : }
925 :
926 : NS_IMETHODIMP
927 7 : TelemetryImpl::GetSlowSQL(JSContext *cx, jsval *ret)
928 : {
929 7 : JSObject *root_obj = JS_NewObject(cx, NULL, NULL, NULL);
930 7 : if (!root_obj)
931 0 : return NS_ERROR_FAILURE;
932 7 : *ret = OBJECT_TO_JSVAL(root_obj);
933 :
934 14 : MutexAutoLock hashMutex(mHashMutex);
935 : // Add info about slow SQL queries on the main thread
936 7 : if (!AddSQLInfo(cx, root_obj, true))
937 0 : return NS_ERROR_FAILURE;
938 : // Add info about slow SQL queries on other threads
939 7 : if (!AddSQLInfo(cx, root_obj, false))
940 0 : return NS_ERROR_FAILURE;
941 :
942 7 : return NS_OK;
943 : }
944 :
945 : NS_IMETHODIMP
946 8 : TelemetryImpl::GetRegisteredHistograms(JSContext *cx, jsval *ret)
947 : {
948 8 : size_t count = ArrayLength(gHistograms);
949 8 : JSObject *info = JS_NewObject(cx, NULL, NULL, NULL);
950 8 : if (!info)
951 0 : return NS_ERROR_FAILURE;
952 16 : JS::AutoObjectRooter root(cx, info);
953 :
954 2048 : for (size_t i = 0; i < count; ++i) {
955 2040 : JSString *comment = JS_InternString(cx, gHistograms[i].comment);
956 :
957 2040 : if (!(comment
958 : && JS_DefineProperty(cx, info, gHistograms[i].id,
959 : STRING_TO_JSVAL(comment), NULL, NULL,
960 2040 : JSPROP_ENUMERATE))) {
961 0 : return NS_ERROR_FAILURE;
962 : }
963 : }
964 :
965 8 : *ret = OBJECT_TO_JSVAL(info);
966 8 : return NS_OK;
967 : }
968 :
969 : NS_IMETHODIMP
970 101 : TelemetryImpl::GetHistogramById(const nsACString &name, JSContext *cx, jsval *ret)
971 : {
972 : Histogram *h;
973 101 : nsresult rv = GetHistogramByName(name, &h);
974 101 : if (NS_FAILED(rv))
975 3 : return rv;
976 :
977 98 : return WrapAndReturnHistogram(h, cx, ret);
978 : }
979 :
980 : class TelemetrySessionData : public nsITelemetrySessionData
981 : {
982 : NS_DECL_ISUPPORTS
983 : NS_DECL_NSITELEMETRYSESSIONDATA
984 :
985 : public:
986 : static nsresult LoadFromDisk(nsIFile *, TelemetrySessionData **ptr);
987 : static nsresult SaveToDisk(nsIFile *, const nsACString &uuid);
988 :
989 : TelemetrySessionData(const char *uuid);
990 : ~TelemetrySessionData();
991 :
992 : private:
993 : typedef nsBaseHashtableET<nsUint32HashKey, Histogram::SampleSet> EntryType;
994 : typedef AutoHashtable<EntryType> SessionMapType;
995 : static bool SampleReflector(EntryType *entry, JSContext *cx, JSObject *obj);
996 : SessionMapType mSampleSetMap;
997 : nsCString mUUID;
998 :
999 : bool DeserializeHistogramData(Pickle &pickle, void **iter);
1000 : static bool SerializeHistogramData(Pickle &pickle);
1001 :
1002 : // The file format version. Should be incremented whenever we change
1003 : // how individual SampleSets are stored in the file.
1004 : static const unsigned int sVersion = 1;
1005 : };
1006 :
1007 36 : NS_IMPL_THREADSAFE_ISUPPORTS1(TelemetrySessionData, nsITelemetrySessionData)
1008 :
1009 2 : TelemetrySessionData::TelemetrySessionData(const char *uuid)
1010 2 : : mUUID(uuid)
1011 : {
1012 2 : }
1013 :
1014 2 : TelemetrySessionData::~TelemetrySessionData()
1015 : {
1016 2 : }
1017 :
1018 : NS_IMETHODIMP
1019 1 : TelemetrySessionData::GetUuid(nsACString &uuid)
1020 : {
1021 1 : uuid = mUUID;
1022 1 : return NS_OK;
1023 : }
1024 :
1025 : bool
1026 37 : TelemetrySessionData::SampleReflector(EntryType *entry, JSContext *cx,
1027 : JSObject *snapshots)
1028 : {
1029 : // Don't reflect histograms with no data associated with them.
1030 37 : if (entry->mData.sum() == 0) {
1031 13 : return true;
1032 : }
1033 :
1034 : // This has the undesirable effect of creating a histogram for the
1035 : // current session with the given ID. But there's no good way to
1036 : // compute the ranges and buckets from scratch.
1037 24 : Histogram *h = nsnull;
1038 24 : nsresult rv = GetHistogramByEnumId(Telemetry::ID(entry->GetKey()), &h);
1039 24 : if (NS_FAILED(rv)) {
1040 0 : return true;
1041 : }
1042 :
1043 24 : JSObject *snapshot = JS_NewObject(cx, NULL, NULL, NULL);
1044 24 : if (!snapshot) {
1045 0 : return false;
1046 : }
1047 48 : JS::AutoObjectRooter root(cx, snapshot);
1048 24 : switch (ReflectHistogramAndSamples(cx, snapshot, h, entry->mData)) {
1049 : case REFLECT_OK:
1050 : return JS_DefineProperty(cx, snapshots,
1051 24 : h->histogram_name().c_str(),
1052 : OBJECT_TO_JSVAL(snapshot), NULL, NULL,
1053 48 : JSPROP_ENUMERATE);
1054 : case REFLECT_CORRUPT:
1055 : // Just ignore this one.
1056 0 : return true;
1057 : case REFLECT_FAILURE:
1058 0 : return false;
1059 : default:
1060 0 : MOZ_NOT_REACHED("unhandled reflection status");
1061 : return false;
1062 : }
1063 : }
1064 :
1065 : NS_IMETHODIMP
1066 1 : TelemetrySessionData::GetSnapshots(JSContext *cx, jsval *ret)
1067 : {
1068 1 : JSObject *snapshots = JS_NewObject(cx, NULL, NULL, NULL);
1069 1 : if (!snapshots) {
1070 0 : return NS_ERROR_FAILURE;
1071 : }
1072 2 : JS::AutoObjectRooter root(cx, snapshots);
1073 :
1074 1 : if (!mSampleSetMap.ReflectHashtable(SampleReflector, cx, snapshots)) {
1075 0 : return NS_ERROR_FAILURE;
1076 : }
1077 :
1078 1 : *ret = OBJECT_TO_JSVAL(snapshots);
1079 1 : return NS_OK;
1080 : }
1081 :
1082 : bool
1083 2 : TelemetrySessionData::DeserializeHistogramData(Pickle &pickle, void **iter)
1084 : {
1085 2 : PRUint32 count = 0;
1086 2 : if (!pickle.ReadUInt32(iter, &count)) {
1087 0 : return false;
1088 : }
1089 :
1090 76 : for (size_t i = 0; i < count; ++i) {
1091 : int stored_length;
1092 : const char *name;
1093 74 : if (!pickle.ReadData(iter, &name, &stored_length)) {
1094 0 : return false;
1095 : }
1096 :
1097 : Telemetry::ID id;
1098 74 : nsresult rv = TelemetryImpl::GetHistogramEnumId(name, &id);
1099 74 : if (NS_FAILED(rv)) {
1100 : // We serialized a non-static histogram or we serialized a
1101 : // histogram that is no longer defined in TelemetryHistograms.h.
1102 : // Just drop its data on the floor. If we can't deserialize the
1103 : // data, though, we're in trouble.
1104 68 : Histogram::SampleSet ss;
1105 34 : if (!ss.Deserialize(iter, pickle)) {
1106 0 : return false;
1107 : }
1108 : } else {
1109 40 : EntryType *entry = mSampleSetMap.GetEntry(id);
1110 40 : if (!entry) {
1111 40 : entry = mSampleSetMap.PutEntry(id);
1112 40 : if (NS_UNLIKELY(!entry)) {
1113 0 : return false;
1114 : }
1115 40 : if (!entry->mData.Deserialize(iter, pickle)) {
1116 0 : return false;
1117 : }
1118 : }
1119 : }
1120 : }
1121 :
1122 2 : return true;
1123 : }
1124 :
1125 : nsresult
1126 2 : TelemetrySessionData::LoadFromDisk(nsIFile *file, TelemetrySessionData **ptr)
1127 : {
1128 2 : *ptr = nsnull;
1129 : nsresult rv;
1130 4 : nsCOMPtr<nsILocalFile> f(do_QueryInterface(file, &rv));
1131 2 : if (NS_FAILED(rv)) {
1132 0 : return rv;
1133 : }
1134 :
1135 4 : AutoFDClose fd;
1136 2 : rv = f->OpenNSPRFileDesc(PR_RDONLY, 0, &fd);
1137 2 : if (NS_FAILED(rv)) {
1138 0 : return NS_ERROR_FAILURE;
1139 : }
1140 :
1141 : // If there's not even enough data to read the header for the pickle,
1142 : // don't bother. Conveniently, this handles the error case as well.
1143 2 : PRInt32 size = PR_Available(fd);
1144 2 : if (size < static_cast<PRInt32>(sizeof(Pickle::Header))) {
1145 0 : return NS_ERROR_FAILURE;
1146 : }
1147 :
1148 6 : nsAutoArrayPtr<char> data(new char[size]);
1149 2 : PRInt32 amount = PR_Read(fd, data, size);
1150 2 : if (amount != size) {
1151 0 : return NS_ERROR_FAILURE;
1152 : }
1153 :
1154 4 : Pickle pickle(data, size);
1155 2 : void *iter = NULL;
1156 :
1157 : // Make sure that how much data the pickle thinks it has corresponds
1158 : // with how much data we actually read.
1159 2 : const Pickle::Header *header = pickle.headerT<Pickle::Header>();
1160 2 : if (header->payload_size != static_cast<PRUint32>(amount) - sizeof(*header)) {
1161 0 : return NS_ERROR_FAILURE;
1162 : }
1163 :
1164 : unsigned int storedVersion;
1165 2 : if (!(pickle.ReadUInt32(&iter, &storedVersion)
1166 2 : && storedVersion == sVersion)) {
1167 0 : return NS_ERROR_FAILURE;
1168 : }
1169 :
1170 : const char *uuid;
1171 : int uuidLength;
1172 2 : if (!pickle.ReadData(&iter, &uuid, &uuidLength)) {
1173 0 : return NS_ERROR_FAILURE;
1174 : }
1175 :
1176 4 : nsAutoPtr<TelemetrySessionData> sessionData(new TelemetrySessionData(uuid));
1177 2 : if (!sessionData->DeserializeHistogramData(pickle, &iter)) {
1178 0 : return NS_ERROR_FAILURE;
1179 : }
1180 :
1181 2 : *ptr = sessionData.forget();
1182 2 : return NS_OK;
1183 : }
1184 :
1185 : bool
1186 2 : TelemetrySessionData::SerializeHistogramData(Pickle &pickle)
1187 : {
1188 4 : StatisticsRecorder::Histograms hs;
1189 2 : StatisticsRecorder::GetHistograms(&hs);
1190 :
1191 2 : if (!pickle.WriteUInt32(hs.size())) {
1192 0 : return false;
1193 : }
1194 :
1195 152 : for (StatisticsRecorder::Histograms::const_iterator it = hs.begin();
1196 76 : it != hs.end();
1197 : ++it) {
1198 74 : const Histogram *h = *it;
1199 74 : const char *name = h->histogram_name().c_str();
1200 :
1201 148 : Histogram::SampleSet ss;
1202 74 : h->SnapshotSample(&ss);
1203 :
1204 74 : if (!(pickle.WriteData(name, strlen(name)+1)
1205 74 : && ss.Serialize(&pickle))) {
1206 0 : return false;
1207 : }
1208 : }
1209 :
1210 2 : return true;
1211 : }
1212 :
1213 : nsresult
1214 2 : TelemetrySessionData::SaveToDisk(nsIFile *file, const nsACString &uuid)
1215 : {
1216 : nsresult rv;
1217 4 : nsCOMPtr<nsILocalFile> f(do_QueryInterface(file, &rv));
1218 2 : if (NS_FAILED(rv)) {
1219 0 : return rv;
1220 : }
1221 :
1222 4 : AutoFDClose fd;
1223 2 : rv = f->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0600, &fd);
1224 2 : if (NS_FAILED(rv)) {
1225 0 : return rv;
1226 : }
1227 :
1228 4 : Pickle pickle;
1229 2 : if (!pickle.WriteUInt32(sVersion)) {
1230 0 : return NS_ERROR_FAILURE;
1231 : }
1232 :
1233 : // Include the trailing NULL for the UUID to make reading easier.
1234 : const char *data;
1235 2 : size_t length = uuid.GetData(&data);
1236 2 : if (!(pickle.WriteData(data, length+1)
1237 2 : && SerializeHistogramData(pickle))) {
1238 0 : return NS_ERROR_FAILURE;
1239 : }
1240 :
1241 2 : PRInt32 amount = PR_Write(fd, static_cast<const char*>(pickle.data()),
1242 4 : pickle.size());
1243 2 : if (amount != pickle.size()) {
1244 0 : return NS_ERROR_FAILURE;
1245 : }
1246 :
1247 2 : return NS_OK;
1248 : }
1249 :
1250 : class SaveHistogramEvent : public nsRunnable
1251 8 : {
1252 : public:
1253 2 : SaveHistogramEvent(nsIFile *file, const nsACString &uuid,
1254 : nsITelemetrySaveSessionDataCallback *callback)
1255 2 : : mFile(file), mUUID(uuid), mCallback(callback)
1256 2 : {}
1257 :
1258 2 : NS_IMETHOD Run()
1259 : {
1260 2 : nsresult rv = TelemetrySessionData::SaveToDisk(mFile, mUUID);
1261 2 : mCallback->Handle(!!NS_SUCCEEDED(rv));
1262 2 : return rv;
1263 : }
1264 :
1265 : private:
1266 : nsCOMPtr<nsIFile> mFile;
1267 : nsCString mUUID;
1268 : nsCOMPtr<nsITelemetrySaveSessionDataCallback> mCallback;
1269 : };
1270 :
1271 : NS_IMETHODIMP
1272 2 : TelemetryImpl::SaveHistograms(nsIFile *file, const nsACString &uuid,
1273 : nsITelemetrySaveSessionDataCallback *callback,
1274 : bool isSynchronous)
1275 : {
1276 4 : nsCOMPtr<nsIRunnable> event = new SaveHistogramEvent(file, uuid, callback);
1277 2 : if (isSynchronous) {
1278 1 : return event ? event->Run() : NS_ERROR_FAILURE;
1279 : } else {
1280 1 : return NS_DispatchToCurrentThread(event);
1281 : }
1282 : }
1283 :
1284 : class LoadHistogramEvent : public nsRunnable
1285 8 : {
1286 : public:
1287 2 : LoadHistogramEvent(nsIFile *file,
1288 : nsITelemetryLoadSessionDataCallback *callback)
1289 2 : : mFile(file), mCallback(callback)
1290 2 : {}
1291 :
1292 2 : NS_IMETHOD Run()
1293 : {
1294 2 : TelemetrySessionData *sessionData = nsnull;
1295 2 : nsresult rv = TelemetrySessionData::LoadFromDisk(mFile, &sessionData);
1296 2 : if (NS_FAILED(rv)) {
1297 0 : mCallback->Handle(nsnull);
1298 : } else {
1299 4 : nsCOMPtr<nsITelemetrySessionData> data(sessionData);
1300 2 : mCallback->Handle(data);
1301 : }
1302 2 : return rv;
1303 : }
1304 :
1305 : private:
1306 : nsCOMPtr<nsIFile> mFile;
1307 : nsCOMPtr<nsITelemetryLoadSessionDataCallback> mCallback;
1308 : };
1309 :
1310 : NS_IMETHODIMP
1311 2 : TelemetryImpl::LoadHistograms(nsIFile *file,
1312 : nsITelemetryLoadSessionDataCallback *callback,
1313 : bool isSynchronous)
1314 : {
1315 4 : nsCOMPtr<nsIRunnable> event = new LoadHistogramEvent(file, callback);
1316 2 : if (isSynchronous) {
1317 1 : return event ? event->Run() : NS_ERROR_FAILURE;
1318 : } else {
1319 1 : return NS_DispatchToCurrentThread(event);
1320 : }
1321 : }
1322 :
1323 : NS_IMETHODIMP
1324 0 : TelemetryImpl::GetCanRecord(bool *ret) {
1325 0 : *ret = mCanRecord;
1326 0 : return NS_OK;
1327 : }
1328 :
1329 : NS_IMETHODIMP
1330 2 : TelemetryImpl::SetCanRecord(bool canRecord) {
1331 2 : mCanRecord = !!canRecord;
1332 2 : return NS_OK;
1333 : }
1334 :
1335 : bool
1336 1353989 : TelemetryImpl::CanRecord() {
1337 1353989 : return !sTelemetry || sTelemetry->mCanRecord;
1338 : }
1339 :
1340 : NS_IMETHODIMP
1341 3 : TelemetryImpl::GetCanSend(bool *ret) {
1342 : #if defined(MOZILLA_OFFICIAL) && defined(MOZ_TELEMETRY_REPORTING)
1343 : *ret = true;
1344 : #else
1345 3 : *ret = false;
1346 : #endif
1347 3 : return NS_OK;
1348 : }
1349 :
1350 : already_AddRefed<nsITelemetry>
1351 1419 : TelemetryImpl::CreateTelemetryInstance()
1352 : {
1353 1419 : NS_ABORT_IF_FALSE(sTelemetry == NULL, "CreateTelemetryInstance may only be called once, via GetService()");
1354 1419 : sTelemetry = new TelemetryImpl();
1355 : // AddRef for the local reference
1356 1419 : NS_ADDREF(sTelemetry);
1357 : // AddRef for the caller
1358 1419 : NS_ADDREF(sTelemetry);
1359 1419 : return sTelemetry;
1360 : }
1361 :
1362 : void
1363 1419 : TelemetryImpl::ShutdownTelemetry()
1364 : {
1365 1419 : NS_IF_RELEASE(sTelemetry);
1366 1419 : }
1367 :
1368 : void
1369 13 : TelemetryImpl::RecordSlowStatement(const nsACString &statement,
1370 : const nsACString &dbName,
1371 : PRUint32 delay)
1372 : {
1373 13 : MOZ_ASSERT(sTelemetry);
1374 13 : if (!sTelemetry->mCanRecord || !sTelemetry->mTrackedDBs.GetEntry(dbName))
1375 7 : return;
1376 :
1377 6 : AutoHashtable<SlowSQLEntryType> *slowSQLMap = NULL;
1378 6 : if (NS_IsMainThread())
1379 5 : slowSQLMap = &(sTelemetry->mSlowSQLOnMainThread);
1380 : else
1381 1 : slowSQLMap = &(sTelemetry->mSlowSQLOnOtherThread);
1382 :
1383 12 : MutexAutoLock hashMutex(sTelemetry->mHashMutex);
1384 6 : SlowSQLEntryType *entry = slowSQLMap->GetEntry(statement);
1385 6 : if (!entry) {
1386 6 : entry = slowSQLMap->PutEntry(statement);
1387 6 : if (NS_UNLIKELY(!entry))
1388 : return;
1389 6 : entry->mData.hitCount = 0;
1390 6 : entry->mData.totalTime = 0;
1391 : }
1392 6 : entry->mData.hitCount++;
1393 6 : entry->mData.totalTime += delay;
1394 : }
1395 :
1396 17838 : NS_IMPL_THREADSAFE_ISUPPORTS1(TelemetryImpl, nsITelemetry)
1397 1419 : NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITelemetry, TelemetryImpl::CreateTelemetryInstance)
1398 :
1399 : #define NS_TELEMETRY_CID \
1400 : {0xaea477f2, 0xb3a2, 0x469c, {0xaa, 0x29, 0x0a, 0x82, 0xd1, 0x32, 0xb8, 0x29}}
1401 : NS_DEFINE_NAMED_CID(NS_TELEMETRY_CID);
1402 :
1403 : const Module::CIDEntry kTelemetryCIDs[] = {
1404 : { &kNS_TELEMETRY_CID, false, NULL, nsITelemetryConstructor },
1405 : { NULL }
1406 : };
1407 :
1408 : const Module::ContractIDEntry kTelemetryContracts[] = {
1409 : { "@mozilla.org/base/telemetry;1", &kNS_TELEMETRY_CID },
1410 : { NULL }
1411 : };
1412 :
1413 : const Module kTelemetryModule = {
1414 : Module::kVersion,
1415 : kTelemetryCIDs,
1416 : kTelemetryContracts,
1417 : NULL,
1418 : NULL,
1419 : NULL,
1420 : TelemetryImpl::ShutdownTelemetry
1421 : };
1422 :
1423 : } // anonymous namespace
1424 :
1425 : namespace mozilla {
1426 : namespace Telemetry {
1427 :
1428 : void
1429 1353872 : Accumulate(ID aHistogram, PRUint32 aSample)
1430 : {
1431 1353872 : if (!TelemetryImpl::CanRecord()) {
1432 0 : return;
1433 : }
1434 : Histogram *h;
1435 1353872 : nsresult rv = GetHistogramByEnumId(aHistogram, &h);
1436 1353871 : if (NS_SUCCEEDED(rv))
1437 1353871 : h->Add(aSample);
1438 : }
1439 :
1440 : void
1441 646764 : AccumulateTimeDelta(ID aHistogram, TimeStamp start, TimeStamp end)
1442 : {
1443 : Accumulate(aHistogram,
1444 646764 : static_cast<PRUint32>((end - start).ToMilliseconds()));
1445 646764 : }
1446 :
1447 : bool
1448 0 : CanRecord()
1449 : {
1450 0 : return TelemetryImpl::CanRecord();
1451 : }
1452 :
1453 : base::Histogram*
1454 56 : GetHistogramById(ID id)
1455 : {
1456 56 : Histogram *h = NULL;
1457 56 : GetHistogramByEnumId(id, &h);
1458 56 : return h;
1459 : }
1460 :
1461 : void
1462 13 : RecordSlowSQLStatement(const nsACString &statement,
1463 : const nsACString &dbName,
1464 : PRUint32 delay)
1465 : {
1466 13 : TelemetryImpl::RecordSlowStatement(statement, dbName, delay);
1467 13 : }
1468 :
1469 1419 : void Init()
1470 : {
1471 : // Make the service manager hold a long-lived reference to the service
1472 : nsCOMPtr<nsITelemetry> telemetryService =
1473 2838 : do_GetService("@mozilla.org/base/telemetry;1");
1474 1419 : MOZ_ASSERT(telemetryService);
1475 1419 : }
1476 :
1477 : } // namespace Telemetry
1478 : } // namespace mozilla
1479 :
1480 : NSMODULE_DEFN(nsTelemetryModule) = &kTelemetryModule;
1481 :
1482 : /**
1483 : * The XRE_TelemetryAdd function is to be used by embedding applications
1484 : * that can't use mozilla::Telemetry::Accumulate() directly.
1485 : */
1486 : void
1487 46 : XRE_TelemetryAccumulate(int aID, PRUint32 aSample)
1488 : {
1489 46 : mozilla::Telemetry::Accumulate((mozilla::Telemetry::ID) aID, aSample);
1490 4438 : }
|