/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla code. * * The Initial Developer of the Original Code is * Mozilla Corporation. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Taras Glek * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "base/histogram.h" #include "nsIComponentManager.h" #include "nsIServiceManager.h" #include "nsCOMPtr.h" #include "mozilla/ModuleUtils.h" #include "nsIXPConnect.h" #include "mozilla/Services.h" #include "jsapi.h" #include "nsStringGlue.h" #include "nsITelemetry.h" #include "Telemetry.h" #include "nsTHashtable.h" #include "nsHashKeys.h" #include "nsBaseHashtable.h" #include "nsXULAppAPI.h" namespace { using namespace base; using namespace mozilla; class TelemetryImpl : public nsITelemetry { NS_DECL_ISUPPORTS NS_DECL_NSITELEMETRY public: TelemetryImpl(); ~TelemetryImpl(); static bool CanRecord(); static already_AddRefed CreateTelemetryInstance(); static void ShutdownTelemetry(); private: // This is used for speedy JS string->Telemetry::ID conversions typedef nsBaseHashtableET CharPtrEntryType; typedef nsTHashtable HistogramMapType; HistogramMapType mHistogramMap; bool mCanRecord; static TelemetryImpl *sTelemetry; }; TelemetryImpl* TelemetryImpl::sTelemetry = NULL; // A initializer to initialize histogram collection StatisticsRecorder gStatisticsRecorder; // Hardcoded probes struct TelemetryHistogram { Histogram *histogram; const char *id; PRUint32 min; PRUint32 max; PRUint32 bucketCount; PRUint32 histogramType; }; const TelemetryHistogram gHistograms[] = { #define HISTOGRAM(id, min, max, bucket_count, histogram_type, b) \ { NULL, NS_STRINGIFY(id), min, max, bucket_count, nsITelemetry::HISTOGRAM_ ## histogram_type }, #include "TelemetryHistograms.h" #undef HISTOGRAM }; nsresult HistogramGet(const char *name, PRUint32 min, PRUint32 max, PRUint32 bucketCount, PRUint32 histogramType, Histogram **result) { if (histogramType != nsITelemetry::HISTOGRAM_BOOLEAN) { // Sanity checks for histogram parameters. if (min >= max) return NS_ERROR_ILLEGAL_VALUE; if (bucketCount <= 2) return NS_ERROR_ILLEGAL_VALUE; if (min < 1) return NS_ERROR_ILLEGAL_VALUE; } switch (histogramType) { case nsITelemetry::HISTOGRAM_EXPONENTIAL: *result = Histogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag); break; case nsITelemetry::HISTOGRAM_LINEAR: *result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kUmaTargetedHistogramFlag); break; case nsITelemetry::HISTOGRAM_BOOLEAN: *result = BooleanHistogram::FactoryGet(name, Histogram::kUmaTargetedHistogramFlag); break; default: return NS_ERROR_INVALID_ARG; } return NS_OK; } // O(1) histogram lookup by numeric id nsresult GetHistogramByEnumId(Telemetry::ID id, Histogram **ret) { static Histogram* knownHistograms[Telemetry::HistogramCount] = {0}; Histogram *h = knownHistograms[id]; if (h) { *ret = h; return NS_OK; } const TelemetryHistogram &p = gHistograms[id]; nsresult rv = HistogramGet(p.id, p.min, p.max, p.bucketCount, p.histogramType, &h); if (NS_FAILED(rv)) return NS_ERROR_FAILURE; *ret = knownHistograms[id] = h; return NS_OK; } bool FillRanges(JSContext *cx, JSObject *array, Histogram *h) { for (size_t i = 0; i < h->bucket_count(); i++) { if (!JS_DefineElement(cx, array, i, INT_TO_JSVAL(h->ranges(i)), NULL, NULL, JSPROP_ENUMERATE)) return false; } return true; } JSBool ReflectHistogramSnapshot(JSContext *cx, JSObject *obj, Histogram *h) { Histogram::SampleSet ss; h->SnapshotSample(&ss); JSObject *counts_array; JSObject *rarray; jsval static_histogram = h->flags() && Histogram::kUmaTargetedHistogramFlag ? JSVAL_TRUE : JSVAL_FALSE; const size_t count = h->bucket_count(); if (!(JS_DefineProperty(cx, obj, "min", INT_TO_JSVAL(h->declared_min()), NULL, NULL, JSPROP_ENUMERATE) && JS_DefineProperty(cx, obj, "max", INT_TO_JSVAL(h->declared_max()), NULL, NULL, JSPROP_ENUMERATE) && JS_DefineProperty(cx, obj, "histogram_type", INT_TO_JSVAL(h->histogram_type()), NULL, NULL, JSPROP_ENUMERATE) && JS_DefineProperty(cx, obj, "sum", DOUBLE_TO_JSVAL(ss.sum()), NULL, NULL, JSPROP_ENUMERATE) && (rarray = JS_NewArrayObject(cx, count, NULL)) && JS_DefineProperty(cx, obj, "ranges", OBJECT_TO_JSVAL(rarray), NULL, NULL, JSPROP_ENUMERATE) && FillRanges(cx, rarray, h) && (counts_array = JS_NewArrayObject(cx, count, NULL)) && JS_DefineProperty(cx, obj, "counts", OBJECT_TO_JSVAL(counts_array), NULL, NULL, JSPROP_ENUMERATE) && JS_DefineProperty(cx, obj, "static", static_histogram, NULL, NULL, JSPROP_ENUMERATE) )) { return JS_FALSE; } for (size_t i = 0; i < count; i++) { if (!JS_DefineElement(cx, counts_array, i, INT_TO_JSVAL(ss.counts(i)), NULL, NULL, JSPROP_ENUMERATE)) { return JS_FALSE; } } return JS_TRUE; } JSBool JSHistogram_Add(JSContext *cx, uintN argc, jsval *vp) { if (!argc) { JS_ReportError(cx, "Expected one argument"); return JS_FALSE; } jsval v = JS_ARGV(cx, vp)[0]; int32 value; if (!(JSVAL_IS_NUMBER(v) || JSVAL_IS_BOOLEAN(v))) { JS_ReportError(cx, "Not a number"); return JS_FALSE; } if (!JS_ValueToECMAInt32(cx, v, &value)) { return JS_FALSE; } if (TelemetryImpl::CanRecord()) { JSObject *obj = JS_THIS_OBJECT(cx, vp); Histogram *h = static_cast(JS_GetPrivate(cx, obj)); if (h->histogram_type() == Histogram::BOOLEAN_HISTOGRAM) h->Add(!!value); else h->Add(value); } return JS_TRUE; } JSBool JSHistogram_Snapshot(JSContext *cx, uintN argc, jsval *vp) { JSObject *obj = JS_THIS_OBJECT(cx, vp); Histogram *h = static_cast(JS_GetPrivate(cx, obj)); JSObject *snapshot = JS_NewObject(cx, NULL, NULL, NULL); if (!snapshot) return JS_FALSE; JS_SET_RVAL(cx, vp, OBJECT_TO_JSVAL(snapshot)); return ReflectHistogramSnapshot(cx, snapshot, h); } nsresult WrapAndReturnHistogram(Histogram *h, JSContext *cx, jsval *ret) { static JSClass JSHistogram_class = { "JSHistogram", /* name */ JSCLASS_HAS_PRIVATE, /* flags */ JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, JSCLASS_NO_OPTIONAL_MEMBERS }; JSObject *obj = JS_NewObject(cx, &JSHistogram_class, NULL, NULL); if (!obj) return NS_ERROR_FAILURE; *ret = OBJECT_TO_JSVAL(obj); return (JS_SetPrivate(cx, obj, h) && JS_DefineFunction (cx, obj, "add", JSHistogram_Add, 1, 0) && JS_DefineFunction (cx, obj, "snapshot", JSHistogram_Snapshot, 1, 0)) ? NS_OK : NS_ERROR_FAILURE; } TelemetryImpl::TelemetryImpl(): mCanRecord(XRE_GetProcessType() == GeckoProcessType_Default) { mHistogramMap.Init(Telemetry::HistogramCount); } TelemetryImpl::~TelemetryImpl() { mHistogramMap.Clear(); } NS_IMETHODIMP TelemetryImpl::NewHistogram(const nsACString &name, PRUint32 min, PRUint32 max, PRUint32 bucketCount, PRUint32 histogramType, JSContext *cx, jsval *ret) { Histogram *h; nsresult rv = HistogramGet(PromiseFlatCString(name).get(), min, max, bucketCount, histogramType, &h); if (NS_FAILED(rv)) return rv; h->ClearFlags(Histogram::kUmaTargetedHistogramFlag); return WrapAndReturnHistogram(h, cx, ret); } NS_IMETHODIMP TelemetryImpl::GetHistogramSnapshots(JSContext *cx, jsval *ret) { JSObject *root_obj = JS_NewObject(cx, NULL, NULL, NULL); if (!root_obj) return NS_ERROR_FAILURE; *ret = OBJECT_TO_JSVAL(root_obj); StatisticsRecorder::Histograms h; StatisticsRecorder::GetHistograms(&h); for (StatisticsRecorder::Histograms::iterator it = h.begin(); it != h.end();++it) { Histogram *h = *it; JSObject *hobj = JS_NewObject(cx, NULL, NULL, NULL); if (!(hobj && JS_DefineProperty(cx, root_obj, h->histogram_name().c_str(), OBJECT_TO_JSVAL(hobj), NULL, NULL, JSPROP_ENUMERATE) && ReflectHistogramSnapshot(cx, hobj, h))) { return NS_ERROR_FAILURE; } } return NS_OK; } NS_IMETHODIMP TelemetryImpl::GetHistogramById(const nsACString &name, JSContext *cx, jsval *ret) { // Cache names // Note the histogram names are statically allocated if (!mHistogramMap.Count()) { for (PRUint32 i = 0; i < Telemetry::HistogramCount; i++) { CharPtrEntryType *entry = mHistogramMap.PutEntry(gHistograms[i].id); if (NS_UNLIKELY(!entry)) { mHistogramMap.Clear(); return NS_ERROR_OUT_OF_MEMORY; } entry->mData = (Telemetry::ID) i; } } CharPtrEntryType *entry = mHistogramMap.GetEntry(PromiseFlatCString(name).get()); if (!entry) return NS_ERROR_FAILURE; Histogram *h; nsresult rv = GetHistogramByEnumId(entry->mData, &h); if (NS_FAILED(rv)) return rv; return WrapAndReturnHistogram(h, cx, ret); } NS_IMETHODIMP TelemetryImpl::GetCanRecord(PRBool *ret) { *ret = mCanRecord; return NS_OK; } NS_IMETHODIMP TelemetryImpl::SetCanRecord(PRBool canRecord) { mCanRecord = !!canRecord; return NS_OK; } bool TelemetryImpl::CanRecord() { return !sTelemetry || sTelemetry->mCanRecord; } already_AddRefed TelemetryImpl::CreateTelemetryInstance() { NS_ABORT_IF_FALSE(sTelemetry == NULL, "CreateTelemetryInstance may only be called once, via GetService()"); sTelemetry = new TelemetryImpl(); // AddRef for the local reference NS_ADDREF(sTelemetry); // AddRef for the caller NS_ADDREF(sTelemetry); return sTelemetry; } void TelemetryImpl::ShutdownTelemetry() { NS_IF_RELEASE(sTelemetry); } NS_IMPL_THREADSAFE_ISUPPORTS1(TelemetryImpl, nsITelemetry) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITelemetry, TelemetryImpl::CreateTelemetryInstance) #define NS_TELEMETRY_CID \ {0xaea477f2, 0xb3a2, 0x469c, {0xaa, 0x29, 0x0a, 0x82, 0xd1, 0x32, 0xb8, 0x29}} NS_DEFINE_NAMED_CID(NS_TELEMETRY_CID); const Module::CIDEntry kTelemetryCIDs[] = { { &kNS_TELEMETRY_CID, false, NULL, nsITelemetryConstructor }, { NULL } }; const Module::ContractIDEntry kTelemetryContracts[] = { { "@mozilla.org/base/telemetry;1", &kNS_TELEMETRY_CID }, { NULL } }; const Module kTelemetryModule = { Module::kVersion, kTelemetryCIDs, kTelemetryContracts, NULL, NULL, NULL, TelemetryImpl::ShutdownTelemetry }; } // anonymous namespace namespace mozilla { namespace Telemetry { void Accumulate(ID aHistogram, PRUint32 aSample) { if (!TelemetryImpl::CanRecord()) { return; } Histogram *h; nsresult rv = GetHistogramByEnumId(aHistogram, &h); if (NS_SUCCEEDED(rv)) h->Add(aSample); } } // namespace Telemetry } // namespace mozilla NSMODULE_DEFN(nsTelemetryModule) = &kTelemetryModule; /** * The XRE_TelemetryAdd function is to be used by embedding applications * that can't use mozilla::Telemetry::Accumulate() directly. */ void XRE_TelemetryAccumulate(int aID, PRUint32 aSample) { mozilla::Telemetry::Accumulate((mozilla::Telemetry::ID) aID, aSample); }