diff --git a/extensions/metrics/public/Makefile.in b/extensions/metrics/public/Makefile.in index 2955d71bee9..e0ea0b9a265 100644 --- a/extensions/metrics/public/Makefile.in +++ b/extensions/metrics/public/Makefile.in @@ -45,7 +45,10 @@ include $(DEPTH)/config/autoconf.mk MODULE = metrics XPI_NAME = metrics -XPIDLSRCS = nsIMetricsService.idl +XPIDLSRCS = \ + nsIMetricsService.idl \ + nsIMetricsCollector.idl \ + $(NULL) EXPORTS = \ nsMetricsModule.h \ diff --git a/extensions/metrics/public/nsIMetricsCollector.idl b/extensions/metrics/public/nsIMetricsCollector.idl new file mode 100644 index 00000000000..2ea95415043 --- /dev/null +++ b/extensions/metrics/public/nsIMetricsCollector.idl @@ -0,0 +1,66 @@ +/* -*- 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): + * Brian Ryner + * + * 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 "nsISupports.idl" + +/** + * The nsIMetricsCollector interface is implemented by any object that collects + * data on behalf of the MetricsService. When the configuration file reequests + * collector "foo" in namespace "http://www.mozilla.org/metrics", + * the contract id + * "@mozilla.org/metrics/collector;1?name=http://www.mozilla.org/metrics:foo" + * is instantiated (using getSerivce). The collector is responsible for + * calling nsIMetricsService::logEvent() when it has something to log. + */ +[scriptable, uuid(b6cba4ad-363a-4780-90fa-9d7cc8115854)] +interface nsIMetricsCollector : nsISupports +{ + /** + * Notification that this collector is no longer enabled. The collector + * should unregister itself from observer and event notifications so that + * the object can be freed. + */ + void onDetach(); + + /** + * Notification that the MetricsService is starting a new event log. + * This happens after any onDetach() notifications that result from parsing + * the new configuration. + */ + void onNewLog(); +}; diff --git a/extensions/metrics/public/nsIMetricsService.idl b/extensions/metrics/public/nsIMetricsService.idl index 883ed22329b..974e24d69cf 100644 --- a/extensions/metrics/public/nsIMetricsService.idl +++ b/extensions/metrics/public/nsIMetricsService.idl @@ -39,6 +39,7 @@ #include "nsISupports.idl" interface nsIPropertyBag; +interface nsIDOMWindow; /** * This file defines the interfaces for the Metrics Service. @@ -129,7 +130,7 @@ interface nsIMetricsEventItem : nsISupports readonly attribute long childCount; }; -[scriptable, uuid(ff320b73-ecd4-4e08-a09d-b37537770df6)] +[scriptable, uuid(289cd0d3-00b4-4554-9e6d-71eded08d1d8)] interface nsIMetricsService : nsISupports { /** @@ -188,4 +189,26 @@ interface nsIMetricsService : nsISupports * re-enable log collection. */ void resume(); + + /** + * Gets a numeric window id corresponding to the given DOMWindow. + * The id remains constant for as long as the window exists. + */ + unsigned long getWindowID(in nsIDOMWindow window); }; + +%{C++ +/** + * Observer notifications + */ + +/** + * These work like NS[_CHROME]_WEBNAVIGATION_DESTROY, except that the + * MetricsService is guaranteed to still know about the window which is being + * destroyed (via getWindowID). Collectors should use these notifications + * instead of the docshell-provided ones. + */ +#define NS_METRICS_WEBNAVIGATION_DESTROY "metrics-webnavigation-destroy" +#define NS_METRICS_CHROME_WEBNAVIGATION_DESTROY \ + "metrics-chrome-webnavigation-destroy" +%} diff --git a/extensions/metrics/src/nsLoadCollector.cpp b/extensions/metrics/src/nsLoadCollector.cpp index 038d14b1e37..9a0838308e4 100644 --- a/extensions/metrics/src/nsLoadCollector.cpp +++ b/extensions/metrics/src/nsLoadCollector.cpp @@ -184,9 +184,15 @@ static PRBool GetMemUsage(MemUsage *result) //----------------------------------------------------------------------------- -static nsLoadCollector *sLoadCollector = nsnull; +nsLoadCollector::nsLoadCollector() +{ +} -NS_IMPL_ISUPPORTS2(nsLoadCollector, +nsLoadCollector::~nsLoadCollector() +{ +} + +NS_IMPL_ISUPPORTS3(nsLoadCollector, nsIMetricsCollector, nsIWebProgressListener, nsISupportsWeakReference) NS_IMETHODIMP @@ -345,43 +351,38 @@ nsLoadCollector::OnSecurityChange(nsIWebProgress *webProgress, return NS_OK; } -/* static */ nsresult -nsLoadCollector::SetEnabled(PRBool enabled) -{ - if (enabled) { - if (!sLoadCollector) { - sLoadCollector = new nsLoadCollector(); - NS_ENSURE_TRUE(sLoadCollector, NS_ERROR_OUT_OF_MEMORY); - NS_ADDREF(sLoadCollector); - - nsresult rv = sLoadCollector->Init(); - if (NS_FAILED(rv)) { - MS_LOG(("Failed to initialize the load collector")); - NS_RELEASE(sLoadCollector); - return rv; - } - } - } else { - NS_IF_RELEASE(sLoadCollector); - GetMemUsage_Shutdown(); - } - return NS_OK; -} - nsresult nsLoadCollector::Init() { NS_ENSURE_TRUE(mRequestMap.Init(32), NS_ERROR_OUT_OF_MEMORY); // Attach the LoadCollector as a global web progress listener - nsresult rv; nsCOMPtr progress = - do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); + do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); + NS_ENSURE_STATE(progress); - rv = progress->AddProgressListener(this, - nsIWebProgress::NOTIFY_STATE_DOCUMENT); + nsresult rv = progress->AddProgressListener( + this, nsIWebProgress::NOTIFY_STATE_DOCUMENT); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } + +NS_IMETHODIMP +nsLoadCollector::OnDetach() +{ + nsCOMPtr progress = + do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); + NS_ENSURE_STATE(progress); + + nsresult rv = progress->RemoveProgressListener(this); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsLoadCollector::OnNewLog() +{ + return NS_OK; +} diff --git a/extensions/metrics/src/nsLoadCollector.h b/extensions/metrics/src/nsLoadCollector.h index 4e4988ec6f5..947abeebc71 100644 --- a/extensions/metrics/src/nsLoadCollector.h +++ b/extensions/metrics/src/nsLoadCollector.h @@ -62,33 +62,27 @@ // window: The id of the window where the document loaded (uint16). // loadtime: The elapsed time for the load, in milliseconds (uint32). +#include "nsIMetricsCollector.h" #include "nsIWebProgressListener.h" #include "nsIWritablePropertyBag2.h" #include "nsWeakReference.h" #include "nsDataHashtable.h" #include "nsAutoPtr.h" -class nsLoadCollector : public nsIWebProgressListener, +class nsLoadCollector : public nsIMetricsCollector, + public nsIWebProgressListener, public nsSupportsWeakReference { public: NS_DECL_ISUPPORTS + NS_DECL_NSIMETRICSCOLLECTOR NS_DECL_NSIWEBPROGRESSLISTENER - // Enables or disables the load collector. - // The collector should be shut down with SetEnabled(PR_FALSE) - // when the metrics service is shut down. - static nsresult SetEnabled(PRBool enabled); - - protected: - // Instances of this class should only be created by SetEnabled(). - nsLoadCollector() {} + nsLoadCollector(); + nsresult Init(); private: - ~nsLoadCollector() {} - - // Object initialization, to be called by SetEnabled(). - nsresult Init(); + ~nsLoadCollector(); struct RequestEntry { nsCOMPtr properties; @@ -99,4 +93,8 @@ class nsLoadCollector : public nsIWebProgressListener, nsDataHashtable mRequestMap; }; +#define NS_LOADCOLLECTOR_CLASSNAME "Load Collector" +#define NS_LOADCOLLECTOR_CID \ +{ 0xa97357a0, 0xa2f3, 0x4b1f, {0x93, 0xd3, 0x36, 0xdc, 0xb7, 0xee, 0x24, 0x63}} + #endif // nsLoadCollector_h_ diff --git a/extensions/metrics/src/nsMetricsConfig.cpp b/extensions/metrics/src/nsMetricsConfig.cpp index da92006a6b9..90005fe21b0 100644 --- a/extensions/metrics/src/nsMetricsConfig.cpp +++ b/extensions/metrics/src/nsMetricsConfig.cpp @@ -47,6 +47,7 @@ #include "nsComponentManagerUtils.h" #include "nsNetCID.h" #include "prprf.h" +#include "nsTArray.h" #define NS_DEFAULT_UPLOAD_INTERVAL 3600 // 1 hour @@ -236,3 +237,16 @@ nsMetricsConfig::IsEventEnabled(const nsAString &eventNS, NS_ASSERTION(mEventSet.IsInitialized(), "nsMetricsConfig::Init not called"); return mEventSet.GetEntry(MakeKey(eventNS, eventName)) != nsnull; } + +/* static */ PLDHashOperator PR_CALLBACK +nsMetricsConfig::CopyKey(nsStringHashKey *entry, void *userData) +{ + NS_STATIC_CAST(nsTArray *, userData)-> + AppendElement(entry->GetKey()); + return PL_DHASH_NEXT; +} + +void +nsMetricsConfig::GetEvents(nsTArray &events) { + mEventSet.EnumerateEntries(CopyKey, &events); +} diff --git a/extensions/metrics/src/nsMetricsConfig.h b/extensions/metrics/src/nsMetricsConfig.h index 36c5b414a5f..25d4c366943 100644 --- a/extensions/metrics/src/nsMetricsConfig.h +++ b/extensions/metrics/src/nsMetricsConfig.h @@ -44,6 +44,7 @@ class nsIDOMElement; class nsIFile; +template class nsTArray; class nsMetricsConfig { @@ -73,6 +74,12 @@ public: PRBool IsEventEnabled(const nsAString &eventNS, const nsAString &eventName) const; + /** + * Call this method to get a list of all events that are enabled. + * The event names are prefixed with the namespace, separated by a colon. + */ + void GetEvents(nsTArray &events); + /** * Get the limit on the number of events that should be collected. */ @@ -109,6 +116,9 @@ private: void ProcessToplevelElement(nsIDOMElement *elem); void ProcessCollectorElement(nsIDOMElement *elem); + static PLDHashOperator PR_CALLBACK CopyKey(nsStringHashKey *key, + void *userData); + nsTHashtable mEventSet; PRInt32 mEventLimit; PRInt32 mUploadInterval; diff --git a/extensions/metrics/src/nsMetricsModule.cpp b/extensions/metrics/src/nsMetricsModule.cpp index 0e9ee63e00d..16186dacd59 100644 --- a/extensions/metrics/src/nsMetricsModule.cpp +++ b/extensions/metrics/src/nsMetricsModule.cpp @@ -38,6 +38,9 @@ #include "nsMetricsModule.h" #include "nsMetricsService.h" +#include "nsLoadCollector.h" +#include "nsWindowCollector.h" +#include "nsProfileCollector.h" #include "nsIGenericFactory.h" #include "nsICategoryManager.h" #include "nsServiceManagerUtils.h" @@ -49,6 +52,9 @@ NS_DECL_CLASSINFO(nsMetricsService) +#define COLLECTOR_CONTRACTID(type) \ + "@mozilla.org/metrics/collector;1?name=" type ":" NS_METRICS_NAMESPACE + static NS_METHOD nsMetricsServiceRegisterSelf(nsIComponentManager *compMgr, nsIFile *path, @@ -67,6 +73,10 @@ nsMetricsServiceRegisterSelf(nsIComponentManager *compMgr, return NS_OK; } +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsLoadCollector, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsWindowCollector, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsProfileCollector) + static const nsModuleComponentInfo components[] = { { NS_METRICSSERVICE_CLASSNAME, @@ -86,6 +96,24 @@ static const nsModuleComponentInfo components[] = { NS_METRICSSERVICE_CID, NS_ABOUT_MODULE_CONTRACTID_PREFIX "metrics", nsMetricsService::Create + }, + { + NS_LOADCOLLECTOR_CLASSNAME, + NS_LOADCOLLECTOR_CID, + COLLECTOR_CONTRACTID("document"), + nsLoadCollectorConstructor + }, + { + NS_WINDOWCOLLECTOR_CLASSNAME, + NS_WINDOWCOLLECTOR_CID, + COLLECTOR_CONTRACTID("window"), + nsWindowCollectorConstructor + }, + { + NS_PROFILECOLLECTOR_CLASSNAME, + NS_PROFILECOLLECTOR_CID, + COLLECTOR_CONTRACTID("profile"), + nsProfileCollectorConstructor } }; diff --git a/extensions/metrics/src/nsMetricsService.cpp b/extensions/metrics/src/nsMetricsService.cpp index 48aa60207c2..bf94d9fc0ad 100644 --- a/extensions/metrics/src/nsMetricsService.cpp +++ b/extensions/metrics/src/nsMetricsService.cpp @@ -38,6 +38,7 @@ #include "nsMetricsService.h" #include "nsMetricsEventItem.h" +#include "nsIMetricsCollector.h" #include "nsStringUtils.h" #include "nsXPCOM.h" #include "nsServiceManagerUtils.h" @@ -53,9 +54,6 @@ #include "nsIPrefBranch.h" #include "nsIObserver.h" #include "nsILocalFile.h" -#include "nsLoadCollector.h" -#include "nsWindowCollector.h" -#include "nsProfileCollector.h" #include "nsIPropertyBag.h" #include "nsIProperty.h" #include "nsIVariant.h" @@ -84,6 +82,7 @@ #include "nsIBadCertListener.h" #include "nsIInterfaceRequestor.h" #include "nsIX509Cert.h" +#include "nsAutoPtr.h" // We need to suppress inclusion of nsString.h #define nsString_h___ @@ -282,9 +281,19 @@ nsMetricsService::nsMetricsService() sMetricsService = this; } +/* static */ PLDHashOperator PR_CALLBACK +nsMetricsService::DetachCollector(const nsAString &key, + nsIMetricsCollector *value, void *userData) +{ + value->OnDetach(); + return PL_DHASH_NEXT; +} + nsMetricsService::~nsMetricsService() { NS_ASSERTION(sMetricsService == this, ">1 MetricsService object created"); + + mCollectorMap.EnumerateRead(DetachCollector, nsnull); sMetricsService = nsnull; } @@ -660,22 +669,92 @@ nsMetricsService::OnStopRequest(nsIRequest *request, nsISupports *context, return NS_OK; } +struct DisabledCollectorsClosure +{ + DisabledCollectorsClosure(const nsTArray &enabled) + : enabledCollectors(enabled) { } + + // Collectors which are enabled in the new config + const nsTArray &enabledCollectors; + + // Collector instances which should no longer be enabled + nsTArray< nsCOMPtr > disabledCollectors; +}; + +/* static */ PLDHashOperator PR_CALLBACK +nsMetricsService::PruneDisabledCollectors(const nsAString &key, + nsCOMPtr &value, + void *userData) +{ + DisabledCollectorsClosure *dc = + NS_STATIC_CAST(DisabledCollectorsClosure *, userData); + + // The frozen string API doesn't expose operator==, so we can't use + // IndexOf() here. + for (PRUint32 i = 0; i < dc->enabledCollectors.Length(); ++i) { + if (dc->enabledCollectors[i].Equals(key)) { + // The collector is enabled, continue + return PL_DHASH_NEXT; + } + } + + // We didn't find the collector |key| in the list of enabled collectors, + // so move it from the hash table to the disabledCollectors list. + MS_LOG(("Disabling collector %s", NS_ConvertUTF16toUTF8(key).get())); + dc->disabledCollectors.AppendElement(value); + return PL_DHASH_REMOVE; +} + +/* static */ PLDHashOperator PR_CALLBACK +nsMetricsService::NotifyNewLog(const nsAString &key, + nsIMetricsCollector *value, void *userData) +{ + value->OnNewLog(); + return PL_DHASH_NEXT; +} + nsresult nsMetricsService::EnableCollectors() { // Start and stop collectors based on the current config. - nsresult rv; - rv = nsProfileCollector::SetEnabled( - IsEventEnabled(NS_LITERAL_STRING("profile"))); - NS_ENSURE_SUCCESS(rv, rv); + nsTArray enabledCollectors; + mConfig.GetEvents(enabledCollectors); - rv = nsLoadCollector::SetEnabled( - IsEventEnabled(NS_LITERAL_STRING("document"))); - NS_ENSURE_SUCCESS(rv, rv); + // We need to find two sets of collectors: + // (1) collectors which are running but not in |collectors|. + // We'll call onDetach() on them and let them be released. + // (2) collectors which are in |collectors| but not running. + // We need to instantiate these collectors. - rv = nsWindowCollector::SetEnabled( - IsEventEnabled(NS_LITERAL_STRING("window"))); - NS_ENSURE_SUCCESS(rv, rv); + DisabledCollectorsClosure dc(enabledCollectors); + mCollectorMap.Enumerate(PruneDisabledCollectors, &dc); + + // Notify this set of collectors that they're going away, and release them. + PRUint32 i; + for (i = 0; i < dc.disabledCollectors.Length(); ++i) { + dc.disabledCollectors[i]->OnDetach(); + } + dc.disabledCollectors.Clear(); + + // Now instantiate any newly-enabled collectors. + for (i = 0; i < enabledCollectors.Length(); ++i) { + const nsString &name = enabledCollectors[i]; + if (!mCollectorMap.GetWeak(name)) { + nsCString contractID("@mozilla.org/metrics/collector;1?name="); + contractID.Append(NS_ConvertUTF16toUTF8(name)); + + nsCOMPtr coll = do_GetService(contractID.get()); + if (coll) { + MS_LOG(("Created collector %s", contractID.get())); + mCollectorMap.Put(name, coll); + } else { + MS_LOG(("Couldn't instantiate collector %s", contractID.get())); + } + } + } + + // Finally, notify all collectors that we've restarted the log. + mCollectorMap.EnumerateRead(NotifyNewLog, nsnull); return NS_OK; } @@ -719,21 +798,30 @@ nsMetricsService::Observe(nsISupports *subject, const char *topic, { if (strcmp(topic, kQuitApplicationTopic) == 0) { Flush(); - nsLoadCollector::SetEnabled(PR_FALSE); - nsWindowCollector::SetEnabled(PR_FALSE); - nsProfileCollector::SetEnabled(PR_FALSE); + + // We don't detach the collectors here, to allow them to log events + // as we're shutting down. The collectors will be detached and released + // when the MetricsService goes away. } else if (strcmp(topic, "profile-after-change") == 0) { nsresult rv = ProfileStartup(); NS_ENSURE_SUCCESS(rv, rv); } else if (strcmp(topic, NS_WEBNAVIGATION_DESTROY) == 0 || strcmp(topic, NS_CHROME_WEBNAVIGATION_DESTROY) == 0) { - // We handle dispatching to the window collector, if it's enabled, - // to avoid having an observer ordering dependency. - nsWindowCollector *wc = nsWindowCollector::GetInstance(); - if (wc) { - wc->Observe(subject, topic, data); + + // Dispatch our notification before removing the window from the map. + nsCOMPtr obsSvc = + do_GetService("@mozilla.org/observer-service;1"); + NS_ENSURE_STATE(obsSvc); + + const char *newTopic; + if (strcmp(topic, NS_WEBNAVIGATION_DESTROY) == 0) { + newTopic = NS_METRICS_WEBNAVIGATION_DESTROY; + } else { + newTopic = NS_METRICS_CHROME_WEBNAVIGATION_DESTROY; } - + + obsSvc->NotifyObservers(subject, newTopic, data); + // Remove the window from our map. mWindowMap.Remove(subject); } @@ -774,8 +862,9 @@ nsMetricsService::ProfileStartup() mCryptoHash = do_CreateInstance("@mozilla.org/security/hash;1"); NS_ENSURE_TRUE(mCryptoHash, NS_ERROR_FAILURE); - // Set up the window id map + // Set up our hashtables NS_ENSURE_TRUE(mWindowMap.Init(32), NS_ERROR_OUT_OF_MEMORY); + NS_ENSURE_TRUE(mCollectorMap.Init(16), NS_ERROR_OUT_OF_MEMORY); // Create an XML document to serve as the owner document for elements. mDocument = do_CreateInstance("@mozilla.org/xml/xml-document;1"); @@ -788,11 +877,6 @@ nsMetricsService::ProfileStartup() // Start up the collectors rv = EnableCollectors(); NS_ENSURE_SUCCESS(rv, rv); - - nsProfileCollector *pc = nsProfileCollector::GetInstance(); - if (pc) { - pc->LogProfile(); - } RegisterUploadTimer(); @@ -1189,10 +1273,23 @@ nsMetricsService::GetWindowID(nsIDOMWindow *window) return PR_UINT32_MAX; } + return sMetricsService->GetWindowIDInternal(window); +} + +NS_IMETHODIMP +nsMetricsService::GetWindowID(nsIDOMWindow *window, PRUint32 *id) +{ + *id = GetWindowIDInternal(window); + return NS_OK; +} + +PRUint32 +nsMetricsService::GetWindowIDInternal(nsIDOMWindow *window) +{ PRUint32 id; - if (!sMetricsService->mWindowMap.Get(window, &id)) { - id = sMetricsService->mNextWindowID++; - sMetricsService->mWindowMap.Put(window, id); + if (!mWindowMap.Get(window, &id)) { + id = mNextWindowID++; + mWindowMap.Put(window, id); } return id; diff --git a/extensions/metrics/src/nsMetricsService.h b/extensions/metrics/src/nsMetricsService.h index 2601f3450f3..c90a7805930 100644 --- a/extensions/metrics/src/nsMetricsService.h +++ b/extensions/metrics/src/nsMetricsService.h @@ -52,12 +52,14 @@ #include "prlog.h" #include "nsIWritablePropertyBag2.h" #include "nsDataHashtable.h" +#include "nsInterfaceHashtable.h" class nsILocalFile; class nsIDOMWindow; class nsIDOMDocument; class nsIDOMNode; class nsICryptoHash; +class nsIMetricsCollector; #ifdef PR_LOGGING // Shared log for the metrics service and collectors. @@ -115,8 +117,7 @@ public: name, item); } - // Get the window id for the given DOMWindow. If a window id has not - // yet been assigned for the window, the next id will be used. + // More convenient non-scriptable version of GetWindowID(). static PRUint32 GetWindowID(nsIDOMWindow *window); // VC6 needs this to be public :-( @@ -181,6 +182,22 @@ private: nsresult HashBytes(const PRUint8 *bytes, PRUint32 length, nsACString &result); + // Does the real work of GetWindowID(). + PRUint32 GetWindowIDInternal(nsIDOMWindow *window); + + static PLDHashOperator PR_CALLBACK + PruneDisabledCollectors(const nsAString &key, + nsCOMPtr &value, + void *userData); + + static PLDHashOperator PR_CALLBACK + DetachCollector(const nsAString &key, + nsIMetricsCollector *value, void *userData); + + static PLDHashOperator PR_CALLBACK + NotifyNewLog(const nsAString &key, + nsIMetricsCollector *value, void *userData); + private: class BadCertListener; @@ -204,6 +221,9 @@ private: // Window to incrementing-id map. The keys are nsIDOMWindow*. nsDataHashtable mWindowMap; + // All of the active observers, keyed by name. + nsInterfaceHashtable mCollectorMap; + PRInt32 mEventCount; PRInt32 mSuspendCount; PRBool mUploading; diff --git a/extensions/metrics/src/nsProfileCollector.cpp b/extensions/metrics/src/nsProfileCollector.cpp index b77bd669fb7..418f9da522d 100644 --- a/extensions/metrics/src/nsProfileCollector.cpp +++ b/extensions/metrics/src/nsProfileCollector.cpp @@ -100,26 +100,30 @@ class nsProfileCollector::ExtensionEnumerator nsCOMPtr mDisabledResource; }; -nsProfileCollector *nsProfileCollector::sInstance = nsnull; - -/* static */ nsresult -nsProfileCollector::SetEnabled(PRBool enabled) +nsProfileCollector::nsProfileCollector() + : mLoggedProfile(PR_FALSE) +{ +} + +nsProfileCollector::~nsProfileCollector() +{ +} + +NS_IMPL_ISUPPORTS1(nsProfileCollector, nsIMetricsCollector) + +NS_IMETHODIMP +nsProfileCollector::OnDetach() { - if (enabled) { - if (!sInstance) { - sInstance = new nsProfileCollector(); - NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY); - } - } else { - delete sInstance; - sInstance = nsnull; - } return NS_OK; } -nsresult -nsProfileCollector::LogProfile() +NS_IMETHODIMP +nsProfileCollector::OnNewLog() { + if (mLoggedProfile) { + return NS_OK; + } + nsMetricsService *ms = nsMetricsService::get(); nsCOMPtr profileItem; @@ -138,6 +142,7 @@ nsProfileCollector::LogProfile() nsresult rv = ms->LogEvent(profileItem); NS_ENSURE_SUCCESS(rv, rv); + mLoggedProfile = PR_TRUE; return NS_OK; } diff --git a/extensions/metrics/src/nsProfileCollector.h b/extensions/metrics/src/nsProfileCollector.h index 3d307a5a889..4a1ce3a5517 100644 --- a/extensions/metrics/src/nsProfileCollector.h +++ b/extensions/metrics/src/nsProfileCollector.h @@ -39,7 +39,7 @@ #ifndef nsProfileCollector_h_ #define nsProfileCollector_h_ -#include "nscore.h" +#include "nsIMetricsCollector.h" class nsISupports; class nsIMetricsEventItem; @@ -50,27 +50,16 @@ class nsAString; // a profile event at application startup. The profile event logs // various information about the user's software and hardware configuration. -class nsProfileCollector +class nsProfileCollector : public nsIMetricsCollector { public: - // Enables or disables the profile collector. - // The collector should be shut down with SetEnabled(PR_FALSE) - // when the metrics service is shut down. - static nsresult SetEnabled(PRBool enabled); + NS_DECL_ISUPPORTS + NS_DECL_NSIMETRICSCOLLECTOR - // Returns the singleton nsProfileCollector object, or NULL - // if the profile collector is not enabled. - static nsProfileCollector *GetInstance() { return sInstance; } - - // Called to log a profile event to the metrics service. - nsresult LogProfile(); - - protected: - // Instances of this class are only created from SetEnabled(). - nsProfileCollector() { } + nsProfileCollector(); private: - ~nsProfileCollector() { } + ~nsProfileCollector(); // These methods each create a particular item and append it to the profile. nsresult LogCPU(nsIMetricsEventItem *profile); @@ -84,8 +73,12 @@ class nsProfileCollector class PluginEnumerator; class ExtensionEnumerator; - // the profile collector singleton - static nsProfileCollector *sInstance; + // Track whether we've logged the profile yet this session. + PRBool mLoggedProfile; }; +#define NS_PROFILECOLLECTOR_CLASSNAME "Profile Collector" +#define NS_PROFILECOLLECTOR_CID \ +{ 0x9d5d472d, 0x88c7, 0x4cb2, {0xa6, 0xfb, 0x1f, 0x8e, 0x4d, 0xb5, 0x7e, 0x7e}} + #endif // nsProfileCollector_h_ diff --git a/extensions/metrics/src/nsWindowCollector.cpp b/extensions/metrics/src/nsWindowCollector.cpp index f8500136550..071e6ca18fb 100644 --- a/extensions/metrics/src/nsWindowCollector.cpp +++ b/extensions/metrics/src/nsWindowCollector.cpp @@ -50,56 +50,24 @@ #include "nsITimer.h" #include "nsComponentManagerUtils.h" -nsWindowCollector *nsWindowCollector::sInstance = nsnull; - -/* static */ nsresult -nsWindowCollector::SetEnabled(PRBool enabled) +nsWindowCollector::nsWindowCollector() { - if (enabled) { - if (!sInstance) { - sInstance = new nsWindowCollector(); - NS_ENSURE_TRUE(sInstance, NS_ERROR_OUT_OF_MEMORY); - NS_ADDREF(sInstance); - - nsresult rv = sInstance->Init(); - if (NS_FAILED(rv)) { - MS_LOG(("Failed to initialize the window collector")); - NS_RELEASE(sInstance); - return rv; - } - } - } else { - // We want to release our reference to sInstance so that it can - // be destroyed. However, window destroy events can happen during xpcom - // shutdown (after we've been notified), and we need to still be able to - // access sInstance to log those correctly. So, this releases the - // reference but keeps sInstance around until the destructor runs. - - if (sInstance) { - sInstance->Release(); - } - } - - return NS_OK; } -NS_IMPL_ISUPPORTS1(nsWindowCollector, nsIObserver) - nsWindowCollector::~nsWindowCollector() { - NS_ASSERTION(sInstance == this, "two window collectors created?"); - sInstance = nsnull; } +NS_IMPL_ISUPPORTS2(nsWindowCollector, nsIMetricsCollector, nsIObserver) + nsresult nsWindowCollector::Init() { - nsresult rv; nsCOMPtr obsSvc = - do_GetService("@mozilla.org/observer-service;1", &rv); - NS_ENSURE_SUCCESS(rv, rv); + do_GetService("@mozilla.org/observer-service;1"); + NS_ENSURE_STATE(obsSvc); - rv = obsSvc->AddObserver(this, NS_WEBNAVIGATION_CREATE, PR_FALSE); + nsresult rv = obsSvc->AddObserver(this, NS_WEBNAVIGATION_CREATE, PR_FALSE); NS_ENSURE_SUCCESS(rv, rv); rv = obsSvc->AddObserver(this, NS_CHROME_WEBNAVIGATION_CREATE, PR_FALSE); NS_ENSURE_SUCCESS(rv, rv); @@ -107,20 +75,60 @@ nsWindowCollector::Init() NS_ENSURE_SUCCESS(rv, rv); rv = obsSvc->AddObserver(this, "domwindowclosed", PR_FALSE); NS_ENSURE_SUCCESS(rv, rv); - - // We receive NS_WEBNAVIGATION_DESTROY and NS_CHROME_WEBNAVIGATION_DESTROY - // directly from the MetricsService. This way, we avoid a dependency - // on the order in which observers fire, which is not guaranteed. + rv = obsSvc->AddObserver(this, NS_METRICS_WEBNAVIGATION_DESTROY, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); + rv = obsSvc->AddObserver(this, + NS_METRICS_CHROME_WEBNAVIGATION_DESTROY, PR_FALSE); + NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } +NS_IMETHODIMP +nsWindowCollector::OnDetach() +{ + nsCOMPtr obsSvc = + do_GetService("@mozilla.org/observer-service;1"); + NS_ENSURE_STATE(obsSvc); + + nsresult rv = obsSvc->RemoveObserver(this, NS_WEBNAVIGATION_CREATE); + NS_ENSURE_SUCCESS(rv, rv); + rv = obsSvc->RemoveObserver(this, NS_CHROME_WEBNAVIGATION_CREATE); + NS_ENSURE_SUCCESS(rv, rv); + rv = obsSvc->RemoveObserver(this, "domwindowopened"); + NS_ENSURE_SUCCESS(rv, rv); + rv = obsSvc->RemoveObserver(this, "domwindowclosed"); + NS_ENSURE_SUCCESS(rv, rv); + rv = obsSvc->RemoveObserver(this, NS_METRICS_WEBNAVIGATION_DESTROY); + NS_ENSURE_SUCCESS(rv, rv); + rv = obsSvc->RemoveObserver(this, NS_METRICS_CHROME_WEBNAVIGATION_DESTROY); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowCollector::OnNewLog() +{ + return NS_OK; +} + +struct WindowOpenClosure +{ + WindowOpenClosure(nsISupports *subj, nsWindowCollector *coll) + : subject(subj), collector(coll) { } + + nsCOMPtr subject; + nsRefPtr collector; +}; + /* static */ void nsWindowCollector::WindowOpenCallback(nsITimer *timer, void *closure) { - if (sInstance) { - sInstance->LogWindowOpen(timer, NS_STATIC_CAST(nsISupports *, closure)); - } + WindowOpenClosure *wc = NS_STATIC_CAST(WindowOpenClosure *, closure); + wc->collector->LogWindowOpen(timer, wc->subject); + + delete wc; } void @@ -129,9 +137,6 @@ nsWindowCollector::LogWindowOpen(nsITimer *timer, nsISupports *subject) mWindowOpenTimers.RemoveElement(timer); nsCOMPtr window = do_QueryInterface(subject); - // Balance the addref we did in Observe() - NS_RELEASE(subject); - if (!window) { return; } @@ -207,11 +212,11 @@ nsWindowCollector::Observe(nsISupports *subject, nsCOMPtr timer = do_CreateInstance(NS_TIMER_CONTRACTID); NS_ENSURE_STATE(timer); - // Addref the window so it can't go away before our timer fires. - NS_ADDREF(subject); + WindowOpenClosure *wc = new WindowOpenClosure(subject, this); + NS_ENSURE_TRUE(wc, NS_ERROR_OUT_OF_MEMORY); rv = timer->InitWithFuncCallback(nsWindowCollector::WindowOpenCallback, - subject, 0, nsITimer::TYPE_ONE_SHOT); + wc, 0, nsITimer::TYPE_ONE_SHOT); NS_ENSURE_SUCCESS(rv, rv); mWindowOpenTimers.AppendElement(timer); @@ -219,8 +224,8 @@ nsWindowCollector::Observe(nsISupports *subject, // Log a window close event. action.Assign("close"); window = do_QueryInterface(subject); - } else if (strcmp(topic, NS_WEBNAVIGATION_DESTROY) == 0 || - strcmp(topic, NS_CHROME_WEBNAVIGATION_DESTROY) == 0) { + } else if (strcmp(topic, NS_METRICS_WEBNAVIGATION_DESTROY) == 0 || + strcmp(topic, NS_METRICS_CHROME_WEBNAVIGATION_DESTROY) == 0) { // Log a window destroy event. action.Assign("destroy"); window = do_GetInterface(subject); diff --git a/extensions/metrics/src/nsWindowCollector.h b/extensions/metrics/src/nsWindowCollector.h index 9028a1b1488..6bad8b02c68 100644 --- a/extensions/metrics/src/nsWindowCollector.h +++ b/extensions/metrics/src/nsWindowCollector.h @@ -39,6 +39,7 @@ #ifndef nsWindowCollector_h_ #define nsWindowCollector_h_ +#include "nsIMetricsCollector.h" #include "nsIObserver.h" #include "nsTArray.h" #include "nsCOMPtr.h" @@ -78,40 +79,30 @@ class nsITimer; // Attributes: // windowid: The id of the destroyed window (uint16). -class nsWindowCollector : public nsIObserver +class nsWindowCollector : public nsIMetricsCollector, + public nsIObserver { public: NS_DECL_ISUPPORTS + NS_DECL_NSIMETRICSCOLLECTOR NS_DECL_NSIOBSERVER - // Enables or disables the window collector. - // The collector should be shut down with SetEnabled(PR_FALSE) - // when the metrics service is shut down. - static nsresult SetEnabled(PRBool enabled); - - // Returns the singleton nsWindowCollector object, or NULL - // if the window collector is not enabled. - static nsWindowCollector *GetInstance() { return sInstance; } - - protected: - // Instances of this class should only be created by SetEnabled(). - nsWindowCollector() {} + nsWindowCollector(); + nsresult Init(); private: ~nsWindowCollector(); - // Object initialization, to be called by Startup() - nsresult Init(); - // timer callback static void WindowOpenCallback(nsITimer *timer, void *closure); void LogWindowOpen(nsITimer *timer, nsISupports *subject); - // the window collector singleton - static nsWindowCollector *sInstance; - // timers that we're using for deferred window open events nsTArray< nsCOMPtr > mWindowOpenTimers; }; +#define NS_WINDOWCOLLECTOR_CLASSNAME "Window Collector" +#define NS_WINDOWCOLLECTOR_CID \ +{ 0x56e37604, 0xd593, 0x47e4, {0x87, 0x1f, 0x76, 0x13, 0x64, 0x8e, 0x74, 0x2b}} + #endif // nsWindowCollector_h_