fixes bug 328125 "Add support for dynamic configuration and throttling" r=bryner

This commit is contained in:
darin%meer.net 2006-03-17 02:58:16 +00:00
parent 649032f434
commit dd7ffaf0ca
8 changed files with 470 additions and 40 deletions

View File

@ -1,3 +1,3 @@
pref("metrics.upload.interval", 86400000); // 24 hrs in msec
pref("metrics.upload.enable", false);
pref("metrics.upload.uri", "");
pref("metrics.event-count", 0);

View File

@ -63,10 +63,11 @@ REQUIRES = xpcom \
$(NULL)
CPPSRCS = \
nsMetricsService.cpp \
nsLoadCollector.cpp \
nsWindowCollector.cpp \
nsMetricsConfig.cpp \
nsMetricsModule.cpp \
nsMetricsService.cpp \
nsWindowCollector.cpp \
$(NULL)
SHARED_LIBRARY_LIBS += $(DIST)/lib/$(LIB_PREFIX)bz2.$(LIB_SUFFIX)

View File

@ -161,7 +161,7 @@ static PRBool GetMemUsage(MemUsage *result)
//-----------------------------------------------------------------------------
static nsLoadCollector *gLoadCollector = nsnull;
static nsLoadCollector *sLoadCollector = nsnull;
NS_IMPL_ISUPPORTS2(nsLoadCollector,
nsIWebProgressListener, nsISupportsWeakReference)
@ -320,15 +320,16 @@ nsLoadCollector::OnSecurityChange(nsIWebProgress *webProgress,
/* static */ nsresult
nsLoadCollector::Startup()
{
NS_ASSERTION(!gLoadCollector, "nsLoadCollector::Startup called twice");
if (sLoadCollector)
return NS_OK;
gLoadCollector = new nsLoadCollector();
NS_ENSURE_TRUE(gLoadCollector, NS_ERROR_OUT_OF_MEMORY);
NS_ADDREF(gLoadCollector);
sLoadCollector = new nsLoadCollector();
NS_ENSURE_TRUE(sLoadCollector, NS_ERROR_OUT_OF_MEMORY);
NS_ADDREF(sLoadCollector);
nsresult rv = gLoadCollector->Init();
nsresult rv = sLoadCollector->Init();
if (NS_FAILED(rv)) {
NS_RELEASE(gLoadCollector);
NS_RELEASE(sLoadCollector);
return rv;
}
@ -338,19 +339,11 @@ nsLoadCollector::Startup()
/* static */ void
nsLoadCollector::Shutdown()
{
// See comments in nsWindowCollector::Shutdown about why we don't
// null out gLoadCollector here.
gLoadCollector->Release();
NS_RELEASE(sLoadCollector);
GetMemUsage_Shutdown();
}
nsLoadCollector::~nsLoadCollector()
{
NS_ASSERTION(gLoadCollector == this, "two load collectors created?");
gLoadCollector = nsnull;
}
nsresult
nsLoadCollector::Init()
{

View File

@ -82,7 +82,7 @@ class nsLoadCollector : public nsIWebProgressListener,
static void Shutdown();
private:
~nsLoadCollector();
~nsLoadCollector() {}
// Object initialization, to be called by Startup()
nsresult Init();

View File

@ -0,0 +1,216 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* ***** 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 the Metrics extension.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Darin Fisher <darin@meer.net>
*
* 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 "nsMetricsService.h"
#include "nsIDOMDocument.h"
#include "nsIDOMParser.h"
#include "nsIDOMElement.h"
#include "nsIDOM3Node.h"
#include "nsNetUtil.h"
#define NS_DEFAULT_UPLOAD_INTERVAL 3600 // 1 hour
//-----------------------------------------------------------------------------
static const nsAutoString
MakeKey(const nsAString &eventNS, const nsAString &eventName)
{
// Since eventName must be a valid XML NCName, we can use ':' to separate
// eventName from eventNS when formulating our hash key.
NS_ASSERTION(eventName.FindChar(':') == kNotFound, "Not a valid NCName");
return nsAutoString(eventName + NS_LITERAL_STRING(":") + eventNS);
}
// This method leaves the result value unchanged if a parsing error occurs.
static void
ReadIntegerAttr(nsIDOMElement *elem, const nsAString &attrName, PRInt32 *result)
{
nsAutoString attrValue;
elem->GetAttribute(attrName, attrValue);
PRInt32 errcode;
PRInt32 parsedVal = attrValue.ToInteger(&errcode);
if (NS_SUCCEEDED(errcode))
*result = parsedVal;
}
//-----------------------------------------------------------------------------
nsMetricsConfig::nsMetricsConfig()
: mEventLimit(0),
mUploadInterval(NS_DEFAULT_UPLOAD_INTERVAL)
{
}
void
nsMetricsConfig::Reset()
{
mEventSet.Clear();
mEventLimit = 0;
mUploadInterval = NS_DEFAULT_UPLOAD_INTERVAL;
}
nsresult
nsMetricsConfig::Load(nsIFile *file)
{
// The given file references a XML file with the following structure:
//
// <config xmlns="http://www.mozilla.org/metrics"
// xmlns:foo="http://foo.com/metrics">
// <collectors>
// <collector type="ui"/>
// <collector type="load"/>
// <collector type="window"/>
// <collector type="foo:mystat"/>
// </collectors>
// <limit events="200"/>
// <upload interval="600"/>
// </config>
PRInt64 fileSize;
nsresult rv = file->GetFileSize(&fileSize);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_STATE(fileSize <= PR_INT32_MAX);
nsCOMPtr<nsIInputStream> stream;
NS_NewLocalFileInputStream(getter_AddRefs(stream), file);
NS_ENSURE_STATE(stream);
nsCOMPtr<nsIDOMParser> parser = do_CreateInstance(NS_DOMPARSER_CONTRACTID);
NS_ENSURE_STATE(parser);
nsCOMPtr<nsIDOMDocument> doc;
parser->ParseFromStream(stream, nsnull, fileSize, "application/xml",
getter_AddRefs(doc));
NS_ENSURE_STATE(doc);
// At this point, we can clear our old configuration.
Reset();
// Now, walk the DOM. All config elements are optional, and we ignore stuff
// that we don't understand.
nsCOMPtr<nsIDOMElement> elem;
doc->GetDocumentElement(getter_AddRefs(elem));
if (!elem)
return NS_OK;
ForEachChildElement(elem, &nsMetricsConfig::ProcessToplevelElement);
return NS_OK;
}
void
nsMetricsConfig::ForEachChildElement(nsIDOMElement *elem,
ForEachChildElementCallback callback)
{
nsCOMPtr<nsIDOMNode> node, next;
elem->GetFirstChild(getter_AddRefs(node));
while (node) {
nsCOMPtr<nsIDOMElement> childElem = do_QueryInterface(node);
if (childElem) {
// Skip elements that are not in our namespace
nsAutoString namespaceURI;
childElem->GetNamespaceURI(namespaceURI);
if (namespaceURI.EqualsLiteral(NS_METRICS_NAMESPACE))
(this->*callback)(childElem);
}
node->GetNextSibling(getter_AddRefs(next));
node.swap(next);
}
}
void
nsMetricsConfig::ProcessToplevelElement(nsIDOMElement *elem)
{
// Process a top-level element
nsAutoString name;
elem->GetLocalName(name);
if (name.EqualsLiteral("collectors")) {
// Enumerate <collector> elements
ForEachChildElement(elem, &nsMetricsConfig::ProcessCollectorElement);
} else if (name.EqualsLiteral("limit")) {
ReadIntegerAttr(elem, NS_LITERAL_STRING("events"), &mEventLimit);
} else if (name.EqualsLiteral("upload")) {
ReadIntegerAttr(elem, NS_LITERAL_STRING("interval"), &mUploadInterval);
}
}
void
nsMetricsConfig::ProcessCollectorElement(nsIDOMElement *elem)
{
nsAutoString value;
// Make sure we are dealing with a <collector> element.
elem->GetLocalName(value);
if (!value.EqualsLiteral("collector"))
return;
value.Truncate();
elem->GetAttribute(NS_LITERAL_STRING("type"), value);
if (value.IsEmpty())
return;
// Get the namespace URI specified by any prefix of value.
nsCOMPtr<nsIDOM3Node> node = do_QueryInterface(elem);
if (!node)
return;
// Check to see if this value references a specific namespace.
PRInt32 colon = value.FindChar(':');
nsAutoString namespaceURI;
if (colon == kNotFound) {
node->LookupNamespaceURI(EmptyString(), namespaceURI);
// value is the EventName
} else {
// value is NamespacePrefix + ":" + EventName
node->LookupNamespaceURI(StringHead(value, colon), namespaceURI);
value.Cut(0, colon + 1);
}
mEventSet.PutEntry(MakeKey(namespaceURI, value));
}
PRBool
nsMetricsConfig::IsEventEnabled(const nsAString &eventNS,
const nsAString &eventName)
{
return mEventSet.GetEntry(MakeKey(eventNS, eventName)) != nsnull;
}

View File

@ -0,0 +1,111 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* ***** 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 the Metrics extension.
*
* The Initial Developer of the Original Code is Google Inc.
* Portions created by the Initial Developer are Copyright (C) 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Darin Fisher <darin@meer.net>
*
* 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 ***** */
#ifndef nsMetricsConfig_h__
#define nsMetricsConfig_h__
#include "nsTHashtable.h"
#include "nsHashKeys.h"
class nsIDOMElement;
class nsMetricsConfig
{
public:
nsMetricsConfig();
~nsMetricsConfig() {}
/**
* This method must be called before using an instance of this class.
*/
PRBool Init() {
return mEventSet.Init();
}
/**
* Restore the default configuration.
*/
void Reset();
/**
* Load the metrics configuration from disk.
*/
nsresult Load(nsIFile *file);
/**
* Call this method to determine if the given event type is enabled for
* collection.
*/
PRBool IsEventEnabled(const nsAString &eventNS, const nsAString &eventName);
/**
* Get the limit on the number of events that should be collected.
*/
PRInt32 EventLimit() {
return mEventLimit;
}
/**
* Get the upload interval (measured in seconds).
*/
PRInt32 UploadInterval() {
return mUploadInterval;
}
/**
* Set the upload interval (measured in seconds).
*/
void SetUploadInterval(PRInt32 uploadInterval) {
mUploadInterval = uploadInterval;
}
private:
typedef void (nsMetricsConfig::*ForEachChildElementCallback)(nsIDOMElement *);
// Run a callback method for each child element of the given element.
void ForEachChildElement(nsIDOMElement *elem, ForEachChildElementCallback cb);
void ProcessToplevelElement(nsIDOMElement *elem);
void ProcessCollectorElement(nsIDOMElement *elem);
nsTHashtable<nsStringHashKey> mEventSet;
PRInt32 mEventLimit;
PRInt32 mUploadInterval;
};
#endif // nsMetricsConfig_h__

View File

@ -58,7 +58,9 @@
#include "nsIDOMSerializer.h"
#include "nsMultiplexInputStream.h"
#include "nsStringStream.h"
#include "nsStreamUtils.h"
#include "nsVariant.h"
#include "prtime.h"
#include "bzlib.h"
#include "nsIClassInfoImpl.h"
@ -72,6 +74,8 @@
// Flush the event log whenever its size exceeds this number of events.
#define NS_EVENTLOG_FLUSH_POINT 64
#define NS_SECONDS_PER_DAY (60 * 60 * 24)
nsMetricsService* nsMetricsService::sMetricsService = nsnull;
#ifdef PR_LOGGING
PRLogModuleInfo *gMetricsLog;
@ -93,6 +97,14 @@ nsMetricsService::LogEvent(const nsAString &eventNS,
if (mSuspendCount != 0) // Ignore events while suspended
return NS_OK;
// Restrict the number of events logged
if (mEventCount >= mConfig.EventLimit())
return NS_OK;
// Restrict the types of events logged
if (!mConfig.IsEventEnabled(eventNS, eventName))
return NS_OK;
// Create a DOM element for the event and append it to our document.
nsCOMPtr<nsIDOMElement> eventElement;
nsresult rv = mDocument->CreateElementNS(eventNS, eventName,
@ -166,7 +178,8 @@ nsMetricsService::LogEvent(const nsAString &eventNS,
rv = mRoot->AppendChild(eventElement, getter_AddRefs(outChild));
NS_ENSURE_SUCCESS(rv, rv);
if (++mEventCount > NS_EVENTLOG_FLUSH_POINT)
// Flush event log to disk if it has grown too large
if ((++mEventCount % NS_EVENTLOG_FLUSH_POINT) == 0)
Flush();
return NS_OK;
}
@ -205,6 +218,12 @@ nsMetricsService::Flush()
PR_Close(fd);
NS_ENSURE_STATE(succeeded);
// Write current event count to prefs
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
NS_ENSURE_STATE(prefs);
rv = prefs->SetIntPref("metrics.event-count", mEventCount);
NS_ENSURE_SUCCESS(rv, rv);
// Create a new mRoot
rv = CreateRoot();
NS_ENSURE_SUCCESS(rv, rv);
@ -231,8 +250,10 @@ nsMetricsService::Upload()
// original data file, and allow new events to be logged to a new file.
nsCOMPtr<nsILocalFile> dataFile;
GetDataFile(&dataFile);
if (dataFile)
dataFile->Remove(PR_FALSE);
if (dataFile) {
if (NS_FAILED(dataFile->Remove(PR_FALSE)))
NS_WARNING("failed to remove data file");
}
return NS_OK;
}
@ -273,6 +294,13 @@ nsMetricsService::NewChannel(nsIURI *uri, nsIChannel **result)
NS_IMETHODIMP
nsMetricsService::OnStartRequest(nsIRequest *request, nsISupports *context)
{
NS_ENSURE_STATE(!mConfigOutputStream);
nsCOMPtr<nsIFile> file;
GetConfigFile(getter_AddRefs(file));
NS_NewLocalFileOutputStream(getter_AddRefs(mConfigOutputStream), file,
PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE);
return NS_OK;
}
@ -280,9 +308,49 @@ NS_IMETHODIMP
nsMetricsService::OnStopRequest(nsIRequest *request, nsISupports *context,
nsresult status)
{
nsCOMPtr<nsILocalFile> uploadFile = do_QueryInterface(context);
if (uploadFile)
uploadFile->Remove(PR_FALSE);
if (mConfigOutputStream) {
mConfigOutputStream->Close();
mConfigOutputStream = 0;
}
// Load configuration file:
nsCOMPtr<nsIFile> file;
GetConfigFile(getter_AddRefs(file));
if (NS_SUCCEEDED(status))
status = mConfig.Load(file);
if (NS_FAILED(status)) {
// Upon failure, dial back the upload interval
PRInt32 interval = mConfig.UploadInterval();
mConfig.Reset();
interval <<= 2;
if (interval > NS_SECONDS_PER_DAY)
interval = NS_SECONDS_PER_DAY;
mConfig.SetUploadInterval(interval);
if (NS_FAILED(file->Remove(PR_FALSE)))
NS_WARNING("failed to remove config file");
}
// Apply possibly new upload interval:
RegisterUploadTimer();
// Shutdown collectors that are no longer enabled. For now, this only
// applies to the load collector since the window collector may be needed by
// other collectors.
//
// TODO(darin): Come up with a better solution for this. A broadcast
// notification to the collectors might be ideal.
//
if (mConfig.IsEventEnabled(NS_LITERAL_STRING(NS_METRICS_NAMESPACE),
NS_LITERAL_STRING("load"))) {
nsLoadCollector::Startup();
} else {
nsLoadCollector::Shutdown();
}
mUploading = PR_FALSE;
return NS_OK;
@ -293,8 +361,9 @@ nsMetricsService::OnDataAvailable(nsIRequest *request, nsISupports *context,
nsIInputStream *stream, PRUint32 offset,
PRUint32 count)
{
// We don't expect to receive any data from an upload.
return NS_ERROR_ABORT;
PRUint32 n;
return stream->ReadSegments(NS_CopySegmentToStream, mConfigOutputStream,
count, &n);
}
NS_IMETHODIMP
@ -306,15 +375,7 @@ nsMetricsService::Observe(nsISupports *subject, const char *topic,
nsLoadCollector::Shutdown();
nsWindowCollector::Shutdown();
} else if (strcmp(topic, "profile-after-change") == 0) {
PRInt32 interval = 86400000; // 24 hours
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
if (prefs)
prefs->GetIntPref("metrics.upload.interval", &interval);
nsCOMPtr<nsIUpdateTimerManager> mgr =
do_GetService("@mozilla.org/updates/timer-manager;1");
if (mgr)
mgr->RegisterTimer(NS_LITERAL_STRING("metrics-upload"), this, interval);
RegisterUploadTimer();
}
return NS_OK;
}
@ -367,6 +428,18 @@ nsMetricsService::Init()
nsresult rv;
// Initialize configuration.
NS_ENSURE_STATE(mConfig.Init());
nsCOMPtr<nsIFile> file;
GetConfigFile(getter_AddRefs(file));
if (file)
mConfig.Load(file);
nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
NS_ENSURE_STATE(prefs);
rv = prefs->GetIntPref("metrics.event-count", &mEventCount);
NS_ENSURE_SUCCESS(rv, rv);
// Create an XML document to serve as the owner document for elements.
mDocument = do_CreateInstance("@mozilla.org/xml/xml-document;1");
NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
@ -464,7 +537,8 @@ nsMetricsService::UploadData()
// NOTE: nsIUploadChannel requires a buffered stream to upload...
nsCOMPtr<nsIInputStream> fileStream;
NS_NewLocalFileInputStream(getter_AddRefs(fileStream), file);
NS_NewLocalFileInputStream(getter_AddRefs(fileStream), file, -1, -1,
nsIFileInputStream::DELETE_ON_CLOSE);
NS_ENSURE_STATE(fileStream);
PRUint32 streamLen;
@ -497,7 +571,7 @@ nsMetricsService::UploadData()
rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
NS_ENSURE_SUCCESS(rv, rv);
rv = channel->AsyncOpen(this, file);
rv = channel->AsyncOpen(this, nsnull);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
@ -592,7 +666,7 @@ nsMetricsService::OpenCompleteXMLStream(nsILocalFile *dataFile,
// footer.
static const char METRICS_XML_HEAD[] =
"<?xml version=\"1.0\"?>\n"
"<log xmlns=\"http://www.mozilla.org/metrics\">\n";
"<log xmlns=\"" NS_METRICS_NAMESPACE "\">\n";
static const char METRICS_XML_TAIL[] = "</log>";
nsCOMPtr<nsIInputStream> fileStream;
@ -625,6 +699,28 @@ nsMetricsService::OpenCompleteXMLStream(nsILocalFile *dataFile,
return NS_OK;
}
void
nsMetricsService::RegisterUploadTimer()
{
nsCOMPtr<nsIUpdateTimerManager> mgr =
do_GetService("@mozilla.org/updates/timer-manager;1");
if (mgr)
mgr->RegisterTimer(NS_LITERAL_STRING("metrics-upload"), this,
mConfig.UploadInterval() * PR_MSEC_PER_SEC);
}
void
nsMetricsService::GetConfigFile(nsIFile **result)
{
nsCOMPtr<nsIFile> file;
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
if (file)
file->AppendNative(NS_LITERAL_CSTRING("metrics-config.xml"));
*result = nsnull;
file.swap(*result);
}
/* static */ nsresult
nsMetricsUtils::PutUint16(nsIWritablePropertyBag *bag,
const nsAString &propertyName,

View File

@ -41,8 +41,10 @@
#include "nsIMetricsService.h"
#include "nsMetricsModule.h"
#include "nsMetricsConfig.h"
#include "nsIAboutModule.h"
#include "nsIStreamListener.h"
#include "nsIOutputStream.h"
#include "nsILocalFile.h"
#include "nsIObserver.h"
#include "nsITimer.h"
@ -132,10 +134,21 @@ private:
nsresult OpenCompleteXMLStream(nsILocalFile *dataFile,
nsIInputStream **result);
// Hook ourselves up to the timer manager.
void RegisterUploadTimer();
// A reference to the local file containing our current configuration
void GetConfigFile(nsIFile **result);
private:
// Pointer to the metrics service singleton
static nsMetricsService* sMetricsService;
nsMetricsConfig mConfig;
// This output stream is non-null when we are downloading the config file.
nsCOMPtr<nsIOutputStream> mConfigOutputStream;
// XML document containing events to be flushed.
nsCOMPtr<nsIDOMDocument> mDocument;
// Root element of the XML document