gecko/toolkit/components/telemetry/Telemetry.cpp

351 lines
10 KiB
C++

/* -*- 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 <tglek@mozilla.com>
*
* 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 <map>
#include <string.h>
namespace {
using namespace base;
using namespace mozilla;
using namespace std;
struct ltstr {
bool operator()(const char* s1, const char* s2) const {
return strcmp(s1, s2) < 0;
}
};
class TelemetryImpl : public nsITelemetry
{
NS_DECL_ISUPPORTS
NS_DECL_NSITELEMETRY
public:
private:
// This is used to cache JS string->Telemetry::ID conversions
typedef map<const char*, Telemetry::ID, ltstr> NameHistogramMap;
NameHistogramMap mHistogramMap;
};
// 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::kNoFlags);
break;
case nsITelemetry::HISTOGRAM_LINEAR:
*result = LinearHistogram::FactoryGet(name, min, max, bucketCount, Histogram::kNoFlags);
break;
case nsITelemetry::HISTOGRAM_BOOLEAN:
*result = BooleanHistogram::FactoryGet(name, Histogram::kNoFlags);
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;
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", INT_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)
)) {
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)
{
jsval *argv = JS_ARGV(cx, vp);
JSString *str;
if (!JS_ConvertArguments(cx, argc, argv, "i", &str))
return JS_FALSE;
if (!JSVAL_IS_INT(argv[0]))
return JS_FALSE;
JSObject *obj = JS_THIS_OBJECT(cx, vp);
Histogram *h = static_cast<Histogram*>(JS_GetPrivate(cx, obj));
PRUint32 value = JSVAL_TO_INT(argv[0]);
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<Histogram*>(JS_GetPrivate(cx, obj));
JSObject *snapshot = JS_NewObject(cx, NULL, NULL, NULL);
if (!snapshot)
return NS_ERROR_FAILURE;
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;
}
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;
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 for log(N) lookup
// Note the histogram names are statically allocated
if (Telemetry::HistogramCount && !mHistogramMap.size()) {
for (PRUint32 i = 0; i < Telemetry::HistogramCount; i++) {
mHistogramMap[gHistograms[i].id] = (Telemetry::ID) i;
}
}
NameHistogramMap::iterator it = mHistogramMap.find(PromiseFlatCString(name).get());
if (it == mHistogramMap.end())
return NS_ERROR_FAILURE;
Histogram *h;
nsresult rv = GetHistogramByEnumId(it->second, &h);
if (NS_FAILED(rv))
return rv;
return WrapAndReturnHistogram(h, cx, ret);
}
NS_IMPL_THREADSAFE_ISUPPORTS1(TelemetryImpl, nsITelemetry)
already_AddRefed<nsITelemetry>
CreateTelemetryInstance()
{
nsCOMPtr<nsITelemetry> telemetry = new TelemetryImpl();
return telemetry.forget();
}
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITelemetry, 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,
NULL,
};
} // anonymous namespace
namespace mozilla {
namespace Telemetry {
void
Accumulate(ID aHistogram, PRUint32 aSample)
{
Histogram *h;
nsresult rv = GetHistogramByEnumId(aHistogram, &h);
if (NS_SUCCEEDED(rv))
h->Add(aSample);
}
} // namespace Telemetry
} // namespace mozilla
NSMODULE_DEFN(nsTelemetryModule) = &kTelemetryModule;