diff --git a/extensions/metrics/public/nsIMetricsService.idl b/extensions/metrics/public/nsIMetricsService.idl index 14b83cfb0fb..dce47637b0e 100644 --- a/extensions/metrics/public/nsIMetricsService.idl +++ b/extensions/metrics/public/nsIMetricsService.idl @@ -61,6 +61,11 @@ interface nsIMetricsService : nsISupports void logEvent(in DOMString eventNS, in DOMString event, in nsIPropertyBag eventValues); + /** + * Flush data to disk. + */ + void flush(); + /** * Initiate the upload of the current event log. This causes the current * event log to be truncated once the upload completes. diff --git a/extensions/metrics/src/Makefile.in b/extensions/metrics/src/Makefile.in index a7c09ddd96e..e77b11d2c6d 100644 --- a/extensions/metrics/src/Makefile.in +++ b/extensions/metrics/src/Makefile.in @@ -59,6 +59,7 @@ REQUIRES = xpcom \ layout \ widget \ xmlextras \ + libbz2 \ $(NULL) CPPSRCS = \ @@ -68,6 +69,8 @@ CPPSRCS = \ nsMetricsModule.cpp \ $(NULL) +SHARED_LIBRARY_LIBS += $(DIST)/lib/$(LIB_PREFIX)bz2.$(LIB_SUFFIX) + EXTRA_DSO_LDOPTS = $(MOZ_COMPONENT_LIBS) \ $(NULL) diff --git a/extensions/metrics/src/nsLoadCollector.cpp b/extensions/metrics/src/nsLoadCollector.cpp index dca727a4bae..252f58431e3 100644 --- a/extensions/metrics/src/nsLoadCollector.cpp +++ b/extensions/metrics/src/nsLoadCollector.cpp @@ -154,7 +154,7 @@ nsLoadCollector::OnStateChange(nsIWebProgress *webProgress, rv = props->SetPropertyAsUint64(NS_LITERAL_STRING("loadtime"), loadTime); NS_ENSURE_SUCCESS(rv, rv); - nsMetricsService *ms = nsMetricsService::GetMetricsService(); + nsMetricsService *ms = nsMetricsService::get(); rv = ms->LogEvent(NS_LITERAL_STRING("load"), props); mRequestMap.Remove(request); diff --git a/extensions/metrics/src/nsMetricsModule.cpp b/extensions/metrics/src/nsMetricsModule.cpp index 8b2bd950e4e..52d8ae91ae7 100644 --- a/extensions/metrics/src/nsMetricsModule.cpp +++ b/extensions/metrics/src/nsMetricsModule.cpp @@ -44,7 +44,6 @@ #include "nsCOMPtr.h" #include "nsXPCOMCID.h" -NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsMetricsService, Init) NS_DECL_CLASSINFO(nsMetricsService) static NS_METHOD @@ -70,7 +69,7 @@ static const nsModuleComponentInfo components[] = { NS_METRICSSERVICE_CLASSNAME, NS_METRICSSERVICE_CID, NS_METRICSSERVICE_CONTRACTID, - nsMetricsServiceConstructor, + nsMetricsService::Create, nsMetricsServiceRegisterSelf, NULL, NULL, @@ -78,6 +77,12 @@ static const nsModuleComponentInfo components[] = { NULL, &NS_CLASSINFO_NAME(nsMetricsService), nsIClassInfo::MAIN_THREAD_ONLY | nsIClassInfo::SINGLETON + }, + { + NS_METRICSSERVICE_CLASSNAME, + NS_METRICSSERVICE_CID, + NS_ABOUT_MODULE_CONTRACTID_PREFIX "metrics", + nsMetricsService::Create } }; diff --git a/extensions/metrics/src/nsMetricsService.cpp b/extensions/metrics/src/nsMetricsService.cpp index aa394b04f59..cd6ffdf3765 100644 --- a/extensions/metrics/src/nsMetricsService.cpp +++ b/extensions/metrics/src/nsMetricsService.cpp @@ -59,6 +59,14 @@ #include "nsMultiplexInputStream.h" #include "nsStringStream.h" #include "nsVariant.h" +#include "bzlib.h" + +// Make our MIME type inform the server of possible compression. +#ifdef NS_METRICS_SEND_UNCOMPRESSED_DATA +#define NS_METRICS_MIME_TYPE "application/vnd.mozilla.metrics" +#else +#define NS_METRICS_MIME_TYPE "application/vnd.mozilla.metrics.bz2" +#endif // Flush the event log whenever its size exceeds this number of events. #define NS_EVENTLOG_FLUSH_POINT 64 @@ -70,9 +78,9 @@ PRLogModuleInfo *gMetricsLog; //----------------------------------------------------------------------------- -NS_IMPL_ISUPPORTS5_CI(nsMetricsService, nsIMetricsService, - nsIStreamListener, nsIRequestObserver, - nsIObserver, nsITimerCallback) +NS_IMPL_ISUPPORTS6_CI(nsMetricsService, nsIMetricsService, nsIAboutModule, + nsIStreamListener, nsIRequestObserver, nsIObserver, + nsITimerCallback) NS_IMETHODIMP nsMetricsService::LogEvent(const nsAString &eventNS, @@ -142,7 +150,48 @@ nsMetricsService::LogEvent(const nsAString &eventNS, NS_ENSURE_SUCCESS(rv, rv); if (++mEventCount > NS_EVENTLOG_FLUSH_POINT) - FlushData(); + Flush(); + return NS_OK; +} + +NS_IMETHODIMP +nsMetricsService::Flush() +{ + nsresult rv; + + PRFileDesc *fd; + rv = OpenDataFile(PR_WRONLY | PR_APPEND | PR_CREATE_FILE, &fd); + NS_ENSURE_SUCCESS(rv, rv); + + // Serialize our document, then strip off the root start and end tags, + // and write it out. + + nsCOMPtr ds = + do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID); + NS_ENSURE_TRUE(ds, NS_ERROR_UNEXPECTED); + + nsAutoString docText; + rv = ds->SerializeToString(mRoot, docText); + NS_ENSURE_SUCCESS(rv, rv); + + // The first '>' will be the end of the root start tag. + docText.Cut(0, docText.FindChar('>') + 1); + + // The last '<' will be the beginning of the root end tag. + PRInt32 start = docText.RFindChar('<'); + docText.Cut(start, docText.Length() - start); + + NS_ConvertUTF16toUTF8 utf8Doc(docText); + PRInt32 num = utf8Doc.Length(); + PRBool succeeded = ( PR_Write(fd, utf8Doc.get(), num) == num ); + + PR_Close(fd); + NS_ENSURE_STATE(succeeded); + + // Create a new mRoot + rv = CreateRoot(); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; } @@ -152,16 +201,21 @@ nsMetricsService::Upload() if (mUploading) // Ignore new uploads issued while uploading. return NS_OK; - // We suspend logging until the upload completes. + // XXX Download filtering rules and apply them. - nsresult rv = FlushData(); + nsresult rv = Flush(); NS_ENSURE_SUCCESS(rv, rv); rv = UploadData(); - if (NS_SUCCEEDED(rv)) { + if (NS_SUCCEEDED(rv)) mUploading = PR_TRUE; - Suspend(); - } + + // Since UploadData is uploading a copy of the data, we can delete the + // original data file, and allow new events to be logged to a new file. + nsCOMPtr dataFile; + GetDataFile(&dataFile); + if (dataFile) + dataFile->Remove(PR_FALSE); return NS_OK; } @@ -181,6 +235,24 @@ nsMetricsService::Resume() return NS_OK; } +NS_IMETHODIMP +nsMetricsService::NewChannel(nsIURI *uri, nsIChannel **result) +{ + nsresult rv = Flush(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr dataFile; + GetDataFile(&dataFile); + NS_ENSURE_STATE(dataFile); + + nsCOMPtr stream; + OpenCompleteXMLStream(dataFile, getter_AddRefs(stream)); + NS_ENSURE_STATE(stream); + + return NS_NewInputStreamChannel(result, uri, stream, + NS_LITERAL_CSTRING("text/xml"), nsnull); +} + NS_IMETHODIMP nsMetricsService::OnStartRequest(nsIRequest *request, nsISupports *context) { @@ -191,12 +263,10 @@ NS_IMETHODIMP nsMetricsService::OnStopRequest(nsIRequest *request, nsISupports *context, nsresult status) { - nsCOMPtr dataFile; - GetDataFile(&dataFile); - if (dataFile) - dataFile->Remove(PR_FALSE); + nsCOMPtr uploadFile = do_QueryInterface(context); + if (uploadFile) + uploadFile->Remove(PR_FALSE); - Resume(); mUploading = PR_FALSE; return NS_OK; } @@ -215,7 +285,7 @@ nsMetricsService::Observe(nsISupports *subject, const char *topic, const PRUnichar *data) { if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { - FlushData(); + Flush(); nsLoadCollector::Shutdown(); nsWindowCollector::Shutdown(); } else if (strcmp(topic, "profile-after-change") == 0) { @@ -240,6 +310,37 @@ nsMetricsService::Notify(nsITimer *timer) return NS_OK; } +/*static*/ nsMetricsService * +nsMetricsService::get() +{ + if (!sMetricsService) { + nsCOMPtr ms = + do_GetService(NS_METRICSSERVICE_CONTRACTID); + if (!sMetricsService) + NS_WARNING("failed to initialize metrics service"); + } + return sMetricsService; +} + +/*static*/ NS_METHOD +nsMetricsService::Create(nsISupports *outer, const nsIID &iid, void **result) +{ + NS_ENSURE_TRUE(!outer, NS_ERROR_NO_AGGREGATION); + + nsRefPtr ms; + if (!sMetricsService) { + ms = new nsMetricsService(); + if (!ms) + return NS_ERROR_OUT_OF_MEMORY; + NS_ASSERTION(sMetricsService, "should be non-null"); + + nsresult rv = ms->Init(); + if (NS_FAILED(rv)) + return rv; + } + return sMetricsService->QueryInterface(iid, result); +} + nsresult nsMetricsService::Init() { @@ -321,47 +422,6 @@ nsMetricsService::OpenDataFile(PRUint32 flags, PRFileDesc **fd) return dataFile->OpenNSPRFileDesc(flags, 0600, fd); } -nsresult -nsMetricsService::FlushData() -{ - nsresult rv; - - PRFileDesc *fd; - rv = OpenDataFile(PR_WRONLY | PR_APPEND | PR_CREATE_FILE, &fd); - NS_ENSURE_SUCCESS(rv, rv); - - // Serialize our document, then strip off the root start and end tags, - // and write it out. - - nsCOMPtr ds = - do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID); - NS_ENSURE_TRUE(ds, NS_ERROR_UNEXPECTED); - - nsAutoString docText; - rv = ds->SerializeToString(mRoot, docText); - NS_ENSURE_SUCCESS(rv, rv); - - // The first '>' will be the end of the root start tag. - docText.Cut(0, docText.FindChar('>') + 1); - - // The last '<' will be the beginning of the root end tag. - PRInt32 start = docText.RFindChar('<'); - docText.Cut(start, docText.Length() - start); - - NS_ConvertUTF16toUTF8 utf8Doc(docText); - PRInt32 num = utf8Doc.Length(); - PRBool succeeded = ( PR_Write(fd, utf8Doc.get(), num) == num ); - - PR_Close(fd); - NS_ENSURE_STATE(succeeded); - - // Create a new mRoot - rv = CreateRoot(); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - nsresult nsMetricsService::UploadData() { @@ -381,7 +441,7 @@ nsMetricsService::UploadData() return NS_ERROR_ABORT; nsCOMPtr file; - nsresult rv = GetDataFile(&file); + nsresult rv = GetDataFileForUpload(&file); NS_ENSURE_SUCCESS(rv, rv); // NOTE: nsIUploadChannel requires a buffered stream to upload... @@ -397,32 +457,8 @@ nsMetricsService::UploadData() if (streamLen == 0) return NS_ERROR_ABORT; - // Construct a full XML document using the header, file contents, and - // footer. -#define METRICS_XML_HEAD "\n" \ - "\n" -#define METRICS_XML_TAIL "" - - nsCOMPtr miStream = - do_CreateInstance(NS_MULTIPLEXINPUTSTREAM_CONTRACTID); - NS_ENSURE_STATE(miStream); - - nsCOMPtr stringStream; - rv = NS_NewCStringInputStream(getter_AddRefs(stringStream), - NS_LITERAL_CSTRING(METRICS_XML_HEAD)); - NS_ENSURE_SUCCESS(rv, rv); - rv = miStream->AppendStream(stringStream); - NS_ENSURE_SUCCESS(rv, rv); - rv = miStream->AppendStream(fileStream); - NS_ENSURE_SUCCESS(rv, rv); - rv = NS_NewCStringInputStream(getter_AddRefs(stringStream), - NS_LITERAL_CSTRING(METRICS_XML_TAIL)); - NS_ENSURE_SUCCESS(rv, rv); - rv = miStream->AppendStream(stringStream); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr uploadStream; - NS_NewBufferedInputStream(getter_AddRefs(uploadStream), miStream, 4096); + NS_NewBufferedInputStream(getter_AddRefs(uploadStream), fileStream, 4096); NS_ENSURE_STATE(uploadStream); nsCOMPtr ios = do_GetIOService(); @@ -435,7 +471,7 @@ nsMetricsService::UploadData() nsCOMPtr uploadChannel = do_QueryInterface(channel); NS_ENSURE_STATE(uploadChannel); - NS_NAMED_LITERAL_CSTRING(binaryType, "application/vnd.mozilla.metrics"); + NS_NAMED_LITERAL_CSTRING(binaryType, NS_METRICS_MIME_TYPE); rv = uploadChannel->SetUploadStream(uploadStream, binaryType, -1); NS_ENSURE_SUCCESS(rv, rv); @@ -444,12 +480,134 @@ nsMetricsService::UploadData() rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST")); NS_ENSURE_SUCCESS(rv, rv); - rv = channel->AsyncOpen(this, nsnull); + rv = channel->AsyncOpen(this, file); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } +nsresult +nsMetricsService::GetDataFileForUpload(nsCOMPtr *result) +{ + nsCOMPtr input; + nsresult rv = GetDataFile(&input); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr src; + rv = OpenCompleteXMLStream(input, getter_AddRefs(src)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr temp; + rv = input->Clone(getter_AddRefs(temp)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCAutoString leafName; + rv = temp->GetNativeLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + + leafName.AppendLiteral(".bz2"); + rv = temp->SetNativeLeafName(leafName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr ltemp = do_QueryInterface(temp, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + FILE *destFp = NULL; + rv = ltemp->OpenANSIFileDesc("wb", &destFp); + + // Copy file using bzip2 compression: + + if (NS_SUCCEEDED(rv)) { +#ifdef NS_METRICS_SEND_UNCOMPRESSED_DATA + char buf[4096]; + PRUint32 n; + + while (NS_SUCCEEDED(rv = src->Read(buf, sizeof(buf), &n)) && n) { + if (fwrite(buf, 1, n, destFp) != n) { + NS_WARNING("failed to write data"); + rv = NS_ERROR_UNEXPECTED; + break; + } + } +#else + int bzerr = BZ_OK; + BZFILE *destBz = BZ2_bzWriteOpen(&bzerr, destFp, + 9, // block size (1-9) + 0, // verbosity + 0); // work factor + if (destBz) { + char buf[4096]; + PRUint32 n; + + while (NS_SUCCEEDED(rv = src->Read(buf, sizeof(buf), &n)) && n) { + BZ2_bzWrite(&bzerr, destBz, buf, n); + if (bzerr != BZ_OK) { + NS_WARNING("failed to write data"); + rv = NS_ERROR_UNEXPECTED; + break; + } + } + + BZ2_bzWriteClose(&bzerr, destBz, + 0, // abandon + nsnull, // nbytes_in + nsnull); // nbytes_out + } +#endif + } + + if (destFp) + fclose(destFp); + + if (NS_SUCCEEDED(rv)) { + *result = nsnull; + ltemp.swap(*result); + } + + return rv; +} + +nsresult +nsMetricsService::OpenCompleteXMLStream(nsILocalFile *dataFile, + nsIInputStream **result) +{ + // Construct a full XML document using the header, file contents, and + // footer. + static const char METRICS_XML_HEAD[] = + "\n" + "\n"; + static const char METRICS_XML_TAIL[] = ""; + + nsCOMPtr fileStream; + NS_NewLocalFileInputStream(getter_AddRefs(fileStream), dataFile); + NS_ENSURE_STATE(fileStream); + + nsCOMPtr miStream = + do_CreateInstance(NS_MULTIPLEXINPUTSTREAM_CONTRACTID); + NS_ENSURE_STATE(miStream); + + nsCOMPtr stringStream; + NS_NewByteInputStream(getter_AddRefs(stringStream), METRICS_XML_HEAD, + sizeof(METRICS_XML_HEAD)-1); + NS_ENSURE_STATE(stringStream); + + nsresult rv = miStream->AppendStream(stringStream); + NS_ENSURE_SUCCESS(rv, rv); + + rv = miStream->AppendStream(fileStream); + NS_ENSURE_SUCCESS(rv, rv); + + NS_NewByteInputStream(getter_AddRefs(stringStream), METRICS_XML_TAIL, + sizeof(METRICS_XML_TAIL)-1); + NS_ENSURE_STATE(stringStream); + + rv = miStream->AppendStream(stringStream); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ADDREF(*result = miStream); + return NS_OK; +} + /* static */ nsresult nsMetricsUtils::PutUint16(nsIWritablePropertyBag *bag, const nsAString &propertyName, diff --git a/extensions/metrics/src/nsMetricsService.h b/extensions/metrics/src/nsMetricsService.h index 96dd666301e..d7132e9ee7e 100644 --- a/extensions/metrics/src/nsMetricsService.h +++ b/extensions/metrics/src/nsMetricsService.h @@ -41,6 +41,7 @@ #include "nsIMetricsService.h" #include "nsMetricsModule.h" +#include "nsIAboutModule.h" #include "nsIStreamListener.h" #include "nsILocalFile.h" #include "nsIObserver.h" @@ -68,6 +69,7 @@ extern PRLogModuleInfo *gMetricsLog; // periodically. class nsMetricsService : public nsIMetricsService + , public nsIAboutModule , public nsIStreamListener , public nsIObserver , public nsITimerCallback @@ -75,11 +77,31 @@ class nsMetricsService : public nsIMetricsService public: NS_DECL_ISUPPORTS NS_DECL_NSIMETRICSSERVICE + NS_DECL_NSIABOUTMODULE NS_DECL_NSIREQUESTOBSERVER NS_DECL_NSISTREAMLISTENER NS_DECL_NSIOBSERVER NS_DECL_NSITIMERCALLBACK + + // Get the metrics service singleton. This method will call do_GetService if + // necessary to fetch the metrics service. It relies on the service manager + // to keep the singleton instance alive. This method may return null! + static nsMetricsService* get(); + // Create the metrics service singleton, called only by the XPCOM factory for + // this class. + static NS_METHOD Create(nsISupports *outer, const nsIID &iid, void **result); + + // Helper function for logging events in the default namespace + nsresult LogEvent(const nsAString &eventName, + nsHashPropertyBag *eventProperties) + { + return LogEvent(NS_LITERAL_STRING(NS_METRICS_NAMESPACE), + eventName, + NS_STATIC_CAST(nsIWritablePropertyBag*, eventProperties)); + } + +private: nsMetricsService() : mEventCount(0), mSuspendCount(0), @@ -94,34 +116,21 @@ public: NS_ASSERTION(sMetricsService == this, ">1 MetricsService object created"); sMetricsService = nsnull; } - + nsresult Init(); - static nsMetricsService* GetMetricsService() - { - if (!sMetricsService) { - nsCOMPtr ms = - do_GetService(NS_METRICSSERVICE_CONTRACTID); - } - return sMetricsService; - } - - nsresult LogEvent(const nsAString &eventName, - nsHashPropertyBag *eventProperties) - { - return LogEvent(NS_LITERAL_STRING(NS_METRICS_NAMESPACE), - eventName, - NS_STATIC_CAST(nsIWritablePropertyBag*, eventProperties)); - } - -private: // Creates a new root element to hold event nodes nsresult CreateRoot(); - nsresult FlushData(); nsresult UploadData(); nsresult GetDataFile(nsCOMPtr *result); nsresult OpenDataFile(PRUint32 flags, PRFileDesc **result); + nsresult GetDataFileForUpload(nsCOMPtr *result); + + // This method returns an input stream containing the complete XML for the + // data to upload. + nsresult OpenCompleteXMLStream(nsILocalFile *dataFile, + nsIInputStream **result); private: // Pointer to the metrics service singleton diff --git a/extensions/metrics/src/nsWindowCollector.cpp b/extensions/metrics/src/nsWindowCollector.cpp index 4e4c57e7c80..b826eb7f5d1 100644 --- a/extensions/metrics/src/nsWindowCollector.cpp +++ b/extensions/metrics/src/nsWindowCollector.cpp @@ -159,7 +159,7 @@ nsWindowCollector::Observe(nsISupports *subject, NS_ENSURE_SUCCESS(rv, rv); } - nsMetricsService *ms = nsMetricsService::GetMetricsService(); + nsMetricsService *ms = nsMetricsService::get(); rv = ms->LogEvent(NS_LITERAL_STRING("windowcreate"), properties); NS_ENSURE_SUCCESS(rv, rv); } else if (strcmp(topic, "toplevel-window-ready") == 0) { @@ -186,7 +186,7 @@ nsWindowCollector::Observe(nsISupports *subject, NS_ENSURE_SUCCESS(rv, rv); } - nsMetricsService *ms = nsMetricsService::GetMetricsService(); + nsMetricsService *ms = nsMetricsService::get(); rv = ms->LogEvent(NS_LITERAL_STRING("windowopen"), properties); NS_ENSURE_SUCCESS(rv, rv); } else if (strcmp(topic, "domwindowclosed") == 0) { @@ -203,7 +203,7 @@ nsWindowCollector::Observe(nsISupports *subject, GetWindowID(window)); NS_ENSURE_SUCCESS(rv, rv); - nsMetricsService *ms = nsMetricsService::GetMetricsService(); + nsMetricsService *ms = nsMetricsService::get(); rv = ms->LogEvent(NS_LITERAL_STRING("windowclose"), properties); NS_ENSURE_SUCCESS(rv, rv); } else if (strcmp(topic, NS_WEBNAVIGATION_DESTROY) == 0 || @@ -221,7 +221,7 @@ nsWindowCollector::Observe(nsISupports *subject, GetWindowID(window)); NS_ENSURE_SUCCESS(rv, rv); - nsMetricsService *ms = nsMetricsService::GetMetricsService(); + nsMetricsService *ms = nsMetricsService::get(); rv = ms->LogEvent(NS_LITERAL_STRING("windowdestroy"), properties); // Remove the window from our map.