fixes bug 328066 "implement bzip2 compression for sending metrics log" r=bryner

This commit is contained in:
darin%meer.net 2006-02-28 03:30:01 +00:00
Родитель 3d5382eec2
Коммит 537f19bfd4
7 изменённых файлов: 291 добавлений и 111 удалений

Просмотреть файл

@ -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.

Просмотреть файл

@ -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)

Просмотреть файл

@ -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);

Просмотреть файл

@ -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
}
};

Просмотреть файл

@ -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<nsIDOMSerializer> 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<nsILocalFile> 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<nsILocalFile> dataFile;
GetDataFile(&dataFile);
NS_ENSURE_STATE(dataFile);
nsCOMPtr<nsIInputStream> 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<nsILocalFile> dataFile;
GetDataFile(&dataFile);
if (dataFile)
dataFile->Remove(PR_FALSE);
nsCOMPtr<nsILocalFile> 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<nsIMetricsService> 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<nsMetricsService> 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<nsIDOMSerializer> 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<nsILocalFile> 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 "<?xml version=\"1.0\"?>\n" \
"<log xmlns=\"http://www.mozilla.org/metrics\">\n"
#define METRICS_XML_TAIL "</log>"
nsCOMPtr<nsIMultiplexInputStream> miStream =
do_CreateInstance(NS_MULTIPLEXINPUTSTREAM_CONTRACTID);
NS_ENSURE_STATE(miStream);
nsCOMPtr<nsIInputStream> 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<nsIInputStream> uploadStream;
NS_NewBufferedInputStream(getter_AddRefs(uploadStream), miStream, 4096);
NS_NewBufferedInputStream(getter_AddRefs(uploadStream), fileStream, 4096);
NS_ENSURE_STATE(uploadStream);
nsCOMPtr<nsIIOService> ios = do_GetIOService();
@ -435,7 +471,7 @@ nsMetricsService::UploadData()
nsCOMPtr<nsIUploadChannel> 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<nsILocalFile> *result)
{
nsCOMPtr<nsILocalFile> input;
nsresult rv = GetDataFile(&input);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> src;
rv = OpenCompleteXMLStream(input, getter_AddRefs(src));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIFile> 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<nsILocalFile> 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[] =
"<?xml version=\"1.0\"?>\n"
"<log xmlns=\"http://www.mozilla.org/metrics\">\n";
static const char METRICS_XML_TAIL[] = "</log>";
nsCOMPtr<nsIInputStream> fileStream;
NS_NewLocalFileInputStream(getter_AddRefs(fileStream), dataFile);
NS_ENSURE_STATE(fileStream);
nsCOMPtr<nsIMultiplexInputStream> miStream =
do_CreateInstance(NS_MULTIPLEXINPUTSTREAM_CONTRACTID);
NS_ENSURE_STATE(miStream);
nsCOMPtr<nsIInputStream> 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,

Просмотреть файл

@ -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<nsIMetricsService> 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<nsILocalFile> *result);
nsresult OpenDataFile(PRUint32 flags, PRFileDesc **result);
nsresult GetDataFileForUpload(nsCOMPtr<nsILocalFile> *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

Просмотреть файл

@ -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.