Back to index

lightning-sunbird  0.9+nobinonly
nsMetricsService.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* vim:set ts=2 sw=2 sts=2 et cindent: */
00003 /* ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is the Metrics extension.
00017  *
00018  * The Initial Developer of the Original Code is Google Inc.
00019  * Portions created by the Initial Developer are Copyright (C) 2006
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *  Darin Fisher <darin@meer.net>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsMetricsService.h"
00040 #include "nsMetricsEventItem.h"
00041 #include "nsIMetricsCollector.h"
00042 #include "nsStringUtils.h"
00043 #include "nsXPCOM.h"
00044 #include "nsServiceManagerUtils.h"
00045 #include "nsComponentManagerUtils.h"
00046 #include "nsIFile.h"
00047 #include "nsDirectoryServiceUtils.h"
00048 #include "nsAppDirectoryServiceDefs.h"
00049 #include "nsNetCID.h"
00050 #include "nsIObserverService.h"
00051 #include "nsIUploadChannel.h"
00052 #include "nsIPrefService.h"
00053 #include "nsIPrefBranch2.h"
00054 #include "nsIObserver.h"
00055 #include "nsILocalFile.h"
00056 #include "nsIPropertyBag.h"
00057 #include "nsIProperty.h"
00058 #include "nsIVariant.h"
00059 #include "nsIDOMElement.h"
00060 #include "nsIDOMDocument.h"
00061 #include "nsIDOMSerializer.h"
00062 #include "nsIVariant.h"
00063 #include "blapi.h"
00064 #include "plbase64.h"
00065 #include "nsISimpleEnumerator.h"
00066 #include "nsIInputStreamChannel.h"
00067 #include "nsIFileStreams.h"
00068 #include "nsIBufferedStreams.h"
00069 #include "nsIHttpChannel.h"
00070 #include "nsIHttpProtocolHandler.h"
00071 #include "nsIIOService.h"
00072 #include "nsMultiplexInputStream.h"
00073 #include "prtime.h"
00074 #include "prmem.h"
00075 #include "prprf.h"
00076 #include "prrng.h"
00077 #include "bzlib.h"
00078 #ifndef MOZILLA_1_8_BRANCH
00079 #include "nsIClassInfoImpl.h"
00080 #endif
00081 #include "nsIDocShellTreeItem.h"
00082 #include "nsDocShellCID.h"
00083 #include "nsMemory.h"
00084 #include "nsIBadCertListener.h"
00085 #include "nsIInterfaceRequestor.h"
00086 #include "nsIInterfaceRequestorUtils.h"
00087 #include "nsIX509Cert.h"
00088 #include "nsAutoPtr.h"
00089 #include "nsIDOMWindow.h"
00090 #include "nsIDOMDocumentView.h"
00091 #include "nsIDOMAbstractView.h"
00092 
00093 // We need to suppress inclusion of nsString.h
00094 #define nsString_h___
00095 #include "nsIStringStream.h"
00096 #undef nsString_h___
00097 
00098 // Make our MIME type inform the server of possible compression.
00099 #ifdef NS_METRICS_SEND_UNCOMPRESSED_DATA
00100 #define NS_METRICS_MIME_TYPE "application/vnd.mozilla.metrics"
00101 #else
00102 #define NS_METRICS_MIME_TYPE "application/vnd.mozilla.metrics.bz2"
00103 #endif
00104 
00105 // Flush the event log whenever its size exceeds this number of events.
00106 #define NS_EVENTLOG_FLUSH_POINT 64
00107 
00108 #define NS_SECONDS_PER_DAY (60 * 60 * 24)
00109 
00110 nsMetricsService* nsMetricsService::sMetricsService = nsnull;
00111 #ifdef PR_LOGGING
00112 PRLogModuleInfo *gMetricsLog;
00113 #endif
00114 
00115 static const char kQuitApplicationTopic[] = "quit-application";
00116 static const char kUploadTimePref[] = "metrics.upload.next-time";
00117 static const char kPingTimePref[] = "metrics.upload.next-ping";
00118 static const char kEventCountPref[] = "metrics.event-count";
00119 static const char kEnablePref[] = "metrics.upload.enable";
00120 
00121 const PRUint32 nsMetricsService::kMaxRetries = 3;
00122 const PRUint32 nsMetricsService::kMetricsVersion = 2;
00123 
00124 //-----------------------------------------------------------------------------
00125 
00126 #ifndef NS_METRICS_SEND_UNCOMPRESSED_DATA
00127 
00128 // Compress data read from |src|, and write to |outFd|.
00129 static nsresult
00130 CompressBZ2(nsIInputStream *src, PRFileDesc *outFd)
00131 {
00132   // compress the data chunk-by-chunk
00133 
00134   char inbuf[4096], outbuf[4096];
00135   bz_stream strm;
00136   int ret = BZ_OK;
00137 
00138   memset(&strm, 0, sizeof(strm));
00139 
00140   if (BZ2_bzCompressInit(&strm, 9 /*max blocksize*/, 0, 0) != BZ_OK)
00141     return NS_ERROR_OUT_OF_MEMORY;
00142 
00143   nsresult rv = NS_OK;
00144   int action = BZ_RUN;
00145   for (;;) {
00146     PRUint32 bytesRead = 0;
00147     if (action == BZ_RUN && strm.avail_in == 0) {
00148       // fill inbuf
00149       rv = src->Read(inbuf, sizeof(inbuf), &bytesRead);
00150       if (NS_FAILED(rv))
00151         break;
00152       strm.next_in = inbuf;
00153       strm.avail_in = (int) bytesRead;
00154     }
00155 
00156     strm.next_out = outbuf;
00157     strm.avail_out = sizeof(outbuf);
00158 
00159     ret = BZ2_bzCompress(&strm, action);
00160     if (action == BZ_RUN) {
00161       if (ret != BZ_RUN_OK) {
00162         MS_LOG(("BZ2_bzCompress/RUN failed: %d", ret));
00163         rv = NS_ERROR_UNEXPECTED;
00164         break;
00165       }
00166 
00167       if (bytesRead < sizeof(inbuf)) {
00168         // We're done now, tell libbz2 to finish
00169         action = BZ_FINISH;
00170       }
00171     } else if (ret != BZ_FINISH_OK && ret != BZ_STREAM_END) {
00172       MS_LOG(("BZ2_bzCompress/FINISH failed: %d", ret));
00173       rv = NS_ERROR_UNEXPECTED;
00174       break;
00175     }
00176 
00177     if (strm.avail_out < sizeof(outbuf)) {
00178       PRInt32 n = sizeof(outbuf) - strm.avail_out;
00179       if (PR_Write(outFd, outbuf, n) != n) {
00180         MS_LOG(("Failed to write compressed file"));
00181         rv = NS_ERROR_UNEXPECTED;
00182         break;
00183       }
00184     }
00185 
00186     if (ret == BZ_STREAM_END)
00187       break;
00188   }
00189 
00190   BZ2_bzCompressEnd(&strm);
00191   return rv;
00192 }
00193 
00194 #endif // !defined(NS_METRICS_SEND_UNCOMPRESSED_DATA)
00195 
00196 //-----------------------------------------------------------------------------
00197 
00198 class nsMetricsService::BadCertListener : public nsIBadCertListener,
00199                                           public nsIInterfaceRequestor
00200 {
00201  public:
00202   NS_DECL_ISUPPORTS
00203   NS_DECL_NSIBADCERTLISTENER
00204   NS_DECL_NSIINTERFACEREQUESTOR
00205 
00206   BadCertListener() { }
00207 
00208  private:
00209   ~BadCertListener() { }
00210 };
00211 
00212 // This object has to implement threadsafe addref and release, but this is
00213 // only because the GetInterface call happens on the socket transport thread.
00214 // The actual notifications are proxied to the main thread.
00215 NS_IMPL_THREADSAFE_ISUPPORTS2(nsMetricsService::BadCertListener,
00216                               nsIBadCertListener, nsIInterfaceRequestor)
00217 
00218 NS_IMETHODIMP
00219 nsMetricsService::BadCertListener::ConfirmUnknownIssuer(
00220     nsIInterfaceRequestor *socketInfo, nsIX509Cert *cert,
00221     PRInt16 *certAddType, PRBool *result)
00222 {
00223   *result = PR_FALSE;
00224   return NS_OK;
00225 }
00226 
00227 NS_IMETHODIMP
00228 nsMetricsService::BadCertListener::ConfirmMismatchDomain(
00229     nsIInterfaceRequestor *socketInfo, const nsACString &targetURL,
00230     nsIX509Cert *cert, PRBool *result)
00231 {
00232   *result = PR_FALSE;
00233 
00234   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
00235   NS_ENSURE_STATE(prefs);
00236 
00237   nsCString certHostOverride;
00238   prefs->GetCharPref("metrics.upload.cert-host-override",
00239                      getter_Copies(certHostOverride));
00240 
00241   if (!certHostOverride.IsEmpty()) {
00242     // Accept the given alternate hostname (CN) for the certificate
00243     nsString certHost;
00244     cert->GetCommonName(certHost);
00245     if (certHostOverride.Equals(NS_ConvertUTF16toUTF8(certHost))) {
00246       *result = PR_TRUE;
00247     }
00248   }
00249 
00250   return NS_OK;
00251 }
00252 
00253 NS_IMETHODIMP
00254 nsMetricsService::BadCertListener::ConfirmCertExpired(
00255     nsIInterfaceRequestor *socketInfo, nsIX509Cert *cert, PRBool *result)
00256 {
00257   *result = PR_FALSE;
00258   return NS_OK;
00259 }
00260 
00261 NS_IMETHODIMP
00262 nsMetricsService::BadCertListener::NotifyCrlNextupdate(
00263     nsIInterfaceRequestor *socketInfo,
00264     const nsACString &targetURL, nsIX509Cert *cert)
00265 {
00266   return NS_OK;
00267 }
00268 
00269 NS_IMETHODIMP
00270 nsMetricsService::BadCertListener::GetInterface(const nsIID &uuid,
00271                                                 void **result)
00272 {
00273   NS_ENSURE_ARG_POINTER(result);
00274 
00275   if (uuid.Equals(NS_GET_IID(nsIBadCertListener))) {
00276     *result = NS_STATIC_CAST(nsIBadCertListener *, this);
00277     NS_ADDREF_THIS();
00278     return NS_OK;
00279   }
00280 
00281   *result = nsnull;
00282   return NS_ERROR_NO_INTERFACE;
00283 }
00284 
00285 //-----------------------------------------------------------------------------
00286 
00287 nsMetricsService::nsMetricsService()  
00288     : mMD5Context(nsnull),
00289       mEventCount(0),
00290       mSuspendCount(0),
00291       mUploading(PR_FALSE),
00292       mNextWindowID(0),
00293       mRetryCount(0)
00294 {
00295   NS_ASSERTION(!sMetricsService, ">1 MetricsService object created");
00296   sMetricsService = this;
00297 }
00298 
00299 /* static */ PLDHashOperator PR_CALLBACK
00300 nsMetricsService::DetachCollector(const nsAString &key,
00301                                   nsIMetricsCollector *value, void *userData)
00302 {
00303   value->OnDetach();
00304   return PL_DHASH_NEXT;
00305 }
00306 
00307 nsMetricsService::~nsMetricsService()
00308 {
00309   NS_ASSERTION(sMetricsService == this, ">1 MetricsService object created");
00310 
00311   mCollectorMap.EnumerateRead(DetachCollector, nsnull);
00312   MD5_DestroyContext(mMD5Context, PR_TRUE);
00313 
00314   sMetricsService = nsnull;
00315 }
00316 
00317 NS_IMPL_ISUPPORTS6_CI(nsMetricsService, nsIMetricsService, nsIAboutModule,
00318                       nsIStreamListener, nsIRequestObserver, nsIObserver,
00319                       nsITimerCallback)
00320 
00321 NS_IMETHODIMP
00322 nsMetricsService::CreateEventItem(const nsAString &itemNamespace,
00323                                   const nsAString &itemName,
00324                                   nsIMetricsEventItem **result)
00325 {
00326   *result = nsnull;
00327 
00328   nsMetricsEventItem *item = new nsMetricsEventItem(itemNamespace, itemName);
00329   NS_ENSURE_TRUE(item, NS_ERROR_OUT_OF_MEMORY);
00330 
00331   NS_ADDREF(*result = item);
00332   return NS_OK;
00333 }
00334 
00335 nsresult
00336 nsMetricsService::BuildEventItem(nsIMetricsEventItem *item,
00337                                  nsIDOMElement **itemElement)
00338 {
00339   *itemElement = nsnull;
00340 
00341   nsString itemNS, itemName;
00342   item->GetItemNamespace(itemNS);
00343   item->GetItemName(itemName);
00344 
00345   nsCOMPtr<nsIDOMElement> element;
00346   nsresult rv = mDocument->CreateElementNS(itemNS, itemName,
00347                                            getter_AddRefs(element));
00348   NS_ENSURE_SUCCESS(rv, rv);
00349 
00350   // Attach the given properties as attributes.
00351   nsCOMPtr<nsIPropertyBag> properties;
00352   item->GetProperties(getter_AddRefs(properties));
00353   if (properties) {
00354     nsCOMPtr<nsISimpleEnumerator> enumerator;
00355     rv = properties->GetEnumerator(getter_AddRefs(enumerator));
00356     NS_ENSURE_SUCCESS(rv, rv);
00357 
00358     nsCOMPtr<nsISupports> propertySupports;
00359     while (NS_SUCCEEDED(
00360                enumerator->GetNext(getter_AddRefs(propertySupports)))) {
00361       nsCOMPtr<nsIProperty> property = do_QueryInterface(propertySupports);
00362       if (!property) {
00363         NS_WARNING("PropertyBag enumerator has non-nsIProperty elements");
00364         continue;
00365       }
00366 
00367       nsString name;
00368       rv = property->GetName(name);
00369       if (NS_FAILED(rv)) {
00370         NS_WARNING("Failed to get property name");
00371         continue;
00372       }
00373 
00374       nsCOMPtr<nsIVariant> value;
00375       rv = property->GetValue(getter_AddRefs(value));
00376       if (NS_FAILED(rv) || !value) {
00377         NS_WARNING("Failed to get property value");
00378         continue;
00379       }
00380 
00381       // If the type is boolean, we want to use the strings "true" and "false",
00382       // rather than "1" and "0" which is what nsVariant generates on its own.
00383       PRUint16 dataType;
00384       value->GetDataType(&dataType);
00385 
00386       nsString valueString;
00387       if (dataType == nsIDataType::VTYPE_BOOL) {
00388         PRBool valueBool;
00389         rv = value->GetAsBool(&valueBool);
00390         if (NS_FAILED(rv)) {
00391           NS_WARNING("Variant has bool type but couldn't get bool value");
00392           continue;
00393         }
00394         valueString = valueBool ? NS_LITERAL_STRING("true")
00395                       : NS_LITERAL_STRING("false");
00396       } else {
00397         rv = value->GetAsDOMString(valueString);
00398         if (NS_FAILED(rv)) {
00399           NS_WARNING("Failed to convert property value to string");
00400           continue;
00401         }
00402       }
00403 
00404       rv = element->SetAttribute(name, valueString);
00405       if (NS_FAILED(rv)) {
00406         NS_WARNING("Failed to set attribute value");
00407       }
00408       continue;
00409     }
00410   }
00411 
00412   // Now recursively build the child event items
00413   PRInt32 childCount = 0;
00414   item->GetChildCount(&childCount);
00415   for (PRInt32 i = 0; i < childCount; ++i) {
00416     nsCOMPtr<nsIMetricsEventItem> childItem;
00417     item->ChildAt(i, getter_AddRefs(childItem));
00418     NS_ASSERTION(childItem, "The child list cannot contain null items");
00419 
00420     nsCOMPtr<nsIDOMElement> childElement;
00421     rv = BuildEventItem(childItem, getter_AddRefs(childElement));
00422     NS_ENSURE_SUCCESS(rv, rv);
00423 
00424     nsCOMPtr<nsIDOMNode> nodeReturn;
00425     rv = element->AppendChild(childElement, getter_AddRefs(nodeReturn));
00426     NS_ENSURE_SUCCESS(rv, rv);
00427   }
00428 
00429   element.swap(*itemElement);
00430   return NS_OK;
00431 }
00432 
00433 NS_IMETHODIMP
00434 nsMetricsService::LogEvent(nsIMetricsEventItem *item)
00435 {
00436   NS_ENSURE_ARG_POINTER(item);
00437 
00438   if (mSuspendCount != 0)  // Ignore events while suspended
00439     return NS_OK;
00440 
00441   // Restrict the number of events logged
00442   if (mEventCount >= mConfig.EventLimit())
00443     return NS_OK;
00444 
00445   // Restrict the types of events logged
00446   nsString eventNS, eventName;
00447   item->GetItemNamespace(eventNS);
00448   item->GetItemName(eventName);
00449 
00450   if (!mConfig.IsEventEnabled(eventNS, eventName))
00451     return NS_OK;
00452 
00453   // Create a DOM element for the event and append it to our document.
00454   nsCOMPtr<nsIDOMElement> eventElement;
00455   nsresult rv = BuildEventItem(item, getter_AddRefs(eventElement));
00456   NS_ENSURE_SUCCESS(rv, rv);
00457 
00458   // Add the event timestamp
00459   nsString timeString;
00460   AppendInt(timeString, PR_Now() / PR_USEC_PER_SEC);
00461   rv = eventElement->SetAttribute(NS_LITERAL_STRING("time"), timeString);
00462   NS_ENSURE_SUCCESS(rv, rv);
00463 
00464   // Add the session id
00465   rv = eventElement->SetAttribute(NS_LITERAL_STRING("session"), mSessionID);
00466   NS_ENSURE_SUCCESS(rv, rv);
00467   
00468   nsCOMPtr<nsIDOMNode> outChild;
00469   rv = mRoot->AppendChild(eventElement, getter_AddRefs(outChild));
00470   NS_ENSURE_SUCCESS(rv, rv);
00471 
00472   // Flush event log to disk if it has grown too large
00473   if ((++mEventCount % NS_EVENTLOG_FLUSH_POINT) == 0)
00474     Flush();
00475   return NS_OK;
00476 }
00477 
00478 NS_IMETHODIMP
00479 nsMetricsService::LogSimpleEvent(const nsAString &eventNS,
00480                                  const nsAString &eventName,
00481                                  nsIPropertyBag *eventProperties)
00482 {
00483   NS_ENSURE_ARG_POINTER(eventProperties);
00484 
00485   nsCOMPtr<nsIMetricsEventItem> item;
00486   nsresult rv = CreateEventItem(eventNS, eventName, getter_AddRefs(item));
00487   NS_ENSURE_SUCCESS(rv, rv);
00488 
00489   item->SetProperties(eventProperties);
00490   return LogEvent(item);
00491 }
00492 
00493 NS_IMETHODIMP
00494 nsMetricsService::Flush()
00495 {
00496   nsresult rv;
00497 
00498   PRFileDesc *fd;
00499   rv = OpenDataFile(PR_WRONLY | PR_APPEND | PR_CREATE_FILE, &fd);
00500   NS_ENSURE_SUCCESS(rv, rv);
00501 
00502   // Serialize our document, then strip off the root start and end tags,
00503   // and write it out.
00504 
00505   nsCOMPtr<nsIDOMSerializer> ds =
00506     do_CreateInstance(NS_XMLSERIALIZER_CONTRACTID);
00507   NS_ENSURE_TRUE(ds, NS_ERROR_UNEXPECTED);
00508 
00509   nsString docText;
00510   rv = ds->SerializeToString(mRoot, docText);
00511   NS_ENSURE_SUCCESS(rv, rv);
00512 
00513   // The first '>' will be the end of the root start tag.
00514   docText.Cut(0, FindChar(docText, '>') + 1);
00515 
00516   // The last '<' will be the beginning of the root end tag.
00517   PRInt32 start = RFindChar(docText, '<');
00518   docText.Cut(start, docText.Length() - start);
00519 
00520   NS_ConvertUTF16toUTF8 utf8Doc(docText);
00521   PRInt32 num = utf8Doc.Length();
00522   PRBool succeeded = ( PR_Write(fd, utf8Doc.get(), num) == num );
00523 
00524   PR_Close(fd);
00525   NS_ENSURE_STATE(succeeded);
00526 
00527   // Write current event count to prefs
00528   NS_ENSURE_STATE(PersistEventCount());
00529 
00530   // Create a new mRoot
00531   rv = CreateRoot();
00532   NS_ENSURE_SUCCESS(rv, rv);
00533 
00534   return NS_OK;
00535 }
00536 
00537 NS_IMETHODIMP
00538 nsMetricsService::Upload()
00539 {
00540   if (mUploading) {
00541     // Ignore new uploads issued while uploading.
00542     MS_LOG(("Upload already in progress, aborting"));
00543     return NS_OK;
00544   }
00545 
00546   // If we don't have anything to upload, then don't upload, unless
00547   // the time given by the next-ping pref has passed.
00548   if (mEventCount == 0) {
00549     nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
00550     NS_ENSURE_STATE(prefs);
00551 
00552     PRInt32 pingTime_sec;
00553     if (NS_SUCCEEDED(prefs->GetIntPref(kPingTimePref, &pingTime_sec))) {
00554       PRInt32 now_sec = PRInt32(PR_Now() / PR_USEC_PER_SEC);
00555       if (now_sec < pingTime_sec) {
00556         // No need to upload yet, just reset our timer
00557         MS_LOG(("Suppressing upload while idle"));
00558         InitUploadTimer(PR_FALSE);
00559         return NS_OK;
00560       }
00561     }
00562   }
00563 
00564   // XXX Download filtering rules and apply them.
00565 
00566   nsresult rv = Flush();
00567   NS_ENSURE_SUCCESS(rv, rv);
00568 
00569   rv = UploadData();
00570   if (NS_SUCCEEDED(rv))
00571     mUploading = PR_TRUE;
00572 
00573   // We keep the original data file until we know we've uploaded
00574   // successfully, or get a 4xx (bad request) response from the server.
00575 
00576   // Reset event count and persist.
00577   mEventCount = 0;
00578   NS_ENSURE_STATE(PersistEventCount());
00579 
00580   return NS_OK;
00581 }
00582 
00583 NS_IMETHODIMP
00584 nsMetricsService::Suspend()
00585 {
00586   mSuspendCount++;
00587   return NS_OK;
00588 }
00589 
00590 NS_IMETHODIMP
00591 nsMetricsService::Resume()
00592 {
00593   if (mSuspendCount > 0)
00594     mSuspendCount--;
00595   return NS_OK;
00596 }
00597 
00598 NS_IMETHODIMP
00599 nsMetricsService::NewChannel(nsIURI *uri, nsIChannel **result)
00600 {
00601   nsresult rv = Flush();
00602   NS_ENSURE_SUCCESS(rv, rv);
00603 
00604   nsCOMPtr<nsILocalFile> dataFile;
00605   GetDataFile(&dataFile);
00606   NS_ENSURE_STATE(dataFile);
00607 
00608   nsCOMPtr<nsIInputStreamChannel> streamChannel =
00609       do_CreateInstance(NS_INPUTSTREAMCHANNEL_CONTRACTID);
00610   NS_ENSURE_STATE(streamChannel);
00611 
00612   rv = streamChannel->SetURI(uri);
00613   NS_ENSURE_SUCCESS(rv, rv);
00614 
00615   nsCOMPtr<nsIChannel> channel = do_QueryInterface(streamChannel, &rv);
00616   NS_ENSURE_SUCCESS(rv, rv);
00617 
00618   PRBool val;
00619   if (NS_SUCCEEDED(dataFile->Exists(&val)) && val) {
00620     nsCOMPtr<nsIInputStream> stream;
00621     OpenCompleteXMLStream(dataFile, getter_AddRefs(stream));
00622     NS_ENSURE_STATE(stream);
00623 
00624     rv  = streamChannel->SetContentStream(stream);
00625     rv |= channel->SetContentType(NS_LITERAL_CSTRING("text/xml"));
00626   } else {
00627     nsCOMPtr<nsIStringInputStream> errorStream =
00628         do_CreateInstance("@mozilla.org/io/string-input-stream;1");
00629     NS_ENSURE_STATE(errorStream);
00630 
00631     rv = errorStream->SetData("no metrics data", -1);
00632     NS_ENSURE_SUCCESS(rv, rv);
00633 
00634     rv  = streamChannel->SetContentStream(errorStream);
00635     rv |= channel->SetContentType(NS_LITERAL_CSTRING("text/plain"));
00636   }
00637 
00638   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
00639 
00640   NS_ADDREF(*result = channel);
00641   return NS_OK;
00642 }
00643 
00644 NS_IMETHODIMP
00645 nsMetricsService::OnStartRequest(nsIRequest *request, nsISupports *context)
00646 {
00647   NS_ENSURE_STATE(!mConfigOutputStream);
00648 
00649   nsCOMPtr<nsIFile> file;
00650   GetConfigTempFile(getter_AddRefs(file));
00651 
00652   nsCOMPtr<nsIFileOutputStream> out =
00653       do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID);
00654   NS_ENSURE_STATE(out);
00655 
00656   nsresult rv = out->Init(file, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, -1,
00657                           0);
00658   NS_ENSURE_SUCCESS(rv, rv);
00659 
00660   mConfigOutputStream = out;
00661   return NS_OK;
00662 }
00663 
00664 PRBool
00665 nsMetricsService::LoadNewConfig(nsIFile *newConfig, nsIFile *oldConfig)
00666 {
00667   // Try to load the new config
00668   PRBool exists = PR_FALSE;
00669   newConfig->Exists(&exists);
00670   if (exists && NS_SUCCEEDED(mConfig.Load(newConfig))) {
00671     MS_LOG(("Successfully loaded new config"));
00672 
00673     // Replace the old config file with the new one
00674     oldConfig->Remove(PR_FALSE);
00675 
00676     nsString filename;
00677     oldConfig->GetLeafName(filename);
00678 
00679     nsCOMPtr<nsIFile> directory;
00680     oldConfig->GetParent(getter_AddRefs(directory));
00681 
00682     newConfig->MoveTo(directory, filename);
00683     return PR_TRUE;
00684   }
00685 
00686   MS_LOG(("Couldn't load new config"));
00687 
00688   // We want to disable collection until the next upload interval,
00689   // but we don't want to reset the upload interval to the default
00690   // if the server had supplied one.  So, write out a new config
00691   // that just has the collectors disabled.
00692   mConfig.ClearEvents();
00693 
00694   nsCOMPtr<nsILocalFile> lf = do_QueryInterface(oldConfig);
00695   nsresult rv = mConfig.Save(lf);
00696   if (NS_FAILED(rv)) {
00697     MS_LOG(("failed to save config: %d", rv));
00698   }
00699 
00700   return PR_FALSE;
00701 }
00702 
00703 void
00704 nsMetricsService::RemoveDataFile()
00705 {
00706   nsCOMPtr<nsILocalFile> dataFile;
00707   GetDataFile(&dataFile);
00708   if (!dataFile) {
00709     MS_LOG(("Couldn't get data file to remove"));
00710     return;
00711   }
00712 
00713   nsresult rv = dataFile->Remove(PR_FALSE);
00714   if (NS_SUCCEEDED(rv)) {
00715     MS_LOG(("Removed data file"));
00716   } else {
00717     MS_LOG(("Couldn't remove data file: %d", rv));
00718   }
00719 }
00720 
00721 PRInt32
00722 nsMetricsService::GetRandomUploadInterval()
00723 {
00724   static const int kSecondsPerHour = 60 * 60;
00725   mRetryCount = 0;
00726 
00727   PRInt32 interval_sec = kSecondsPerHour * 12;
00728   PRUint32 random = 0;
00729   if (nsMetricsUtils::GetRandomNoise(&random, sizeof(random))) {
00730     interval_sec += (random % (24 * kSecondsPerHour));
00731   }
00732   // If we couldn't get any random bytes, just use the default of
00733   // 12 hours.
00734 
00735   return interval_sec;
00736 }
00737 
00738 NS_IMETHODIMP
00739 nsMetricsService::OnStopRequest(nsIRequest *request, nsISupports *context,
00740                                 nsresult status)
00741 {
00742   MS_LOG(("OnStopRequest status = %x", status));
00743 
00744   // Close the output stream for the download
00745   if (mConfigOutputStream) {
00746     mConfigOutputStream->Close();
00747     mConfigOutputStream = 0;
00748   }
00749 
00750   // There are several possible outcomes of our upload request:
00751   // 1. The server returns 200 OK
00752   //    We consider the upload a success and delete the old data file.
00753   //
00754   // 2. The server returns a 4xx error
00755   //    There was a problem with the uploaded data, so we delete the data file.
00756   //
00757   // 3. The server returns a 5xx error
00758   //    There was a transient server-side problem.  We keep the data file.
00759   //
00760   // In any of these cases, we parse the server response.  If it contains
00761   // a <config>, then it replaces our current config file.  If not, we reset
00762   // to the default configuration, but preserve the upload interval.
00763   // Currently we don't properly handle a 3xx response, it's treated like
00764   // a 4xx error (delete the data file).
00765   //
00766   // 4. A network error occurs (NS_FAILED(status) is true)
00767   //    We keep the old data and the old config.
00768   //
00769   // In any of the error cases, we increment the retry count and schedule
00770   // a retry for the next upload interval.  To start off, the retry is at
00771   // the upload interval specified by our config.  If we fail kMaxRetries
00772   // times, we'll delete the data file and defer trying again until a randomly
00773   // selected time 12-36 hours from the last attempt.  When the 12-36 hour
00774   // deferred upload is attempted, we reset the state and will again retry up
00775   // to kMaxRetriesTimes at the default upload interval.
00776   //
00777   // Any time an upload is successful, the retry count is reset to 0.
00778 
00779   nsCOMPtr<nsIFile> configTempFile;  // the response we just downloaded
00780   GetConfigTempFile(getter_AddRefs(configTempFile));
00781   NS_ENSURE_STATE(configTempFile);
00782 
00783   nsCOMPtr<nsIFile> configFile;  // our old config
00784   GetConfigFile(getter_AddRefs(configFile));
00785   NS_ENSURE_STATE(configFile);
00786 
00787   PRBool success = PR_FALSE, replacedConfig = PR_FALSE;
00788   if (NS_SUCCEEDED(status)) {
00789     // If the request succeeded (200), we remove the old data file
00790     PRUint32 responseCode = 500;
00791     nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(request);
00792     if (channel) {
00793       channel->GetResponseStatus(&responseCode);
00794     }
00795     MS_LOG(("Server response: %u", responseCode));
00796 
00797     if (responseCode == 200) {
00798       success = PR_TRUE;
00799       RemoveDataFile();
00800     } else if (responseCode < 500) {
00801       // This was a request error, so delete the data file
00802       RemoveDataFile();
00803     }
00804 
00805     replacedConfig = LoadNewConfig(configTempFile, configFile);
00806   } else {
00807     MS_LOG(("Request failed"));
00808   }
00809 
00810   // Clean up the temp file if we didn't rename it
00811   if (!replacedConfig) {
00812     configTempFile->Remove(PR_FALSE);
00813   }
00814 
00815   // Handle success or failure of the request
00816   if (success) {
00817     mRetryCount = 0;
00818 
00819     // Clear the next-upload-time pref, in case it was set somehow.
00820     FlushClearPref(kUploadTimePref);
00821     MS_LOG(("Uploaded successfully and reset retry count"));
00822 
00823     // Set the minimum next-ping time to a random time 12-36 hours from now.
00824     // This is the time at which we'll upload to get a new config, even
00825     // if we have no data.
00826     PRInt32 interval_sec = GetRandomUploadInterval();
00827     MS_LOG(("Next ping no later than %d seconds from now", interval_sec));
00828     FlushIntPref(kPingTimePref, (PR_Now() / PR_USEC_PER_SEC) + interval_sec);
00829   } else if (++mRetryCount >= kMaxRetries) {
00830     RemoveDataFile();
00831 
00832     PRInt32 interval_sec = GetRandomUploadInterval();
00833     MS_LOG(("Reached max retry count, deferring upload for %d seconds",
00834             interval_sec));
00835     FlushIntPref(kUploadTimePref, (PR_Now() / PR_USEC_PER_SEC) + interval_sec);
00836 
00837     // We'll initialize a timer for this interval below by calling
00838     // InitUploadTimer().
00839   }
00840 
00841   // Restart the upload timer for our next upload
00842   InitUploadTimer(PR_FALSE);
00843   EnableCollectors();
00844 
00845   mUploading = PR_FALSE;
00846   return NS_OK;
00847 }
00848 
00849 struct DisabledCollectorsClosure
00850 {
00851   DisabledCollectorsClosure(const nsTArray<nsString> &enabled)
00852       : enabledCollectors(enabled) { }
00853 
00854   // Collectors which are enabled in the new config
00855   const nsTArray<nsString> &enabledCollectors;
00856 
00857   // Collector instances which should no longer be enabled
00858   nsTArray< nsCOMPtr<nsIMetricsCollector> > disabledCollectors;
00859 };
00860 
00861 /* static */ PLDHashOperator PR_CALLBACK
00862 nsMetricsService::PruneDisabledCollectors(const nsAString &key,
00863                                           nsCOMPtr<nsIMetricsCollector> &value,
00864                                           void *userData)
00865 {
00866   DisabledCollectorsClosure *dc =
00867     NS_STATIC_CAST(DisabledCollectorsClosure *, userData);
00868 
00869   // The frozen string API doesn't expose operator==, so we can't use
00870   // IndexOf() here.
00871   for (PRUint32 i = 0; i < dc->enabledCollectors.Length(); ++i) {
00872     if (dc->enabledCollectors[i].Equals(key)) {
00873       // The collector is enabled, continue
00874       return PL_DHASH_NEXT;
00875     }
00876   }
00877 
00878   // We didn't find the collector |key| in the list of enabled collectors,
00879   // so move it from the hash table to the disabledCollectors list.
00880   MS_LOG(("Disabling collector %s", NS_ConvertUTF16toUTF8(key).get()));
00881   dc->disabledCollectors.AppendElement(value);
00882   return PL_DHASH_REMOVE;
00883 }
00884 
00885 /* static */ PLDHashOperator PR_CALLBACK
00886 nsMetricsService::NotifyNewLog(const nsAString &key,
00887                                nsIMetricsCollector *value, void *userData)
00888 {
00889   value->OnNewLog();
00890   return PL_DHASH_NEXT;
00891 }
00892 
00893 void
00894 nsMetricsService::EnableCollectors()
00895 {
00896   // Start and stop collectors based on the current config.
00897   nsTArray<nsString> enabledCollectors;
00898   mConfig.GetEvents(enabledCollectors);
00899 
00900   // We need to find two sets of collectors:
00901   //  (1) collectors which are running but not in |collectors|.
00902   //      We'll call onDetach() on them and let them be released.
00903   //  (2) collectors which are in |collectors| but not running.
00904   //      We need to instantiate these collectors.
00905 
00906   DisabledCollectorsClosure dc(enabledCollectors);
00907   mCollectorMap.Enumerate(PruneDisabledCollectors, &dc);
00908 
00909   // Notify this set of collectors that they're going away, and release them.
00910   PRUint32 i;
00911   for (i = 0; i < dc.disabledCollectors.Length(); ++i) {
00912     dc.disabledCollectors[i]->OnDetach();
00913   }
00914   dc.disabledCollectors.Clear();
00915 
00916   // Now instantiate any newly-enabled collectors.
00917   for (i = 0; i < enabledCollectors.Length(); ++i) {
00918     const nsString &name = enabledCollectors[i];
00919     if (!mCollectorMap.GetWeak(name)) {
00920       nsCString contractID("@mozilla.org/metrics/collector;1?name=");
00921       contractID.Append(NS_ConvertUTF16toUTF8(name));
00922 
00923       nsCOMPtr<nsIMetricsCollector> coll = do_GetService(contractID.get());
00924       if (coll) {
00925         MS_LOG(("Created collector %s", contractID.get()));
00926         mCollectorMap.Put(name, coll);
00927         coll->OnAttach();
00928       } else {
00929         MS_LOG(("Couldn't instantiate collector %s", contractID.get()));
00930       }
00931     }
00932   }
00933 
00934   // Finally, notify all collectors that we've restarted the log.
00935   mCollectorMap.EnumerateRead(NotifyNewLog, nsnull);
00936 }
00937 
00938 // Copied from nsStreamUtils.cpp:
00939 static NS_METHOD
00940 CopySegmentToStream(nsIInputStream *inStr,
00941                     void *closure,
00942                     const char *buffer,
00943                     PRUint32 offset,
00944                     PRUint32 count,
00945                     PRUint32 *countWritten)
00946 {
00947   nsIOutputStream *outStr = NS_STATIC_CAST(nsIOutputStream *, closure);
00948   *countWritten = 0;
00949   while (count) {
00950     PRUint32 n;
00951     nsresult rv = outStr->Write(buffer, count, &n);
00952     if (NS_FAILED(rv))
00953       return rv;
00954     buffer += n;
00955     count -= n;
00956     *countWritten += n;
00957   }
00958   return NS_OK;
00959 }
00960 
00961 NS_IMETHODIMP
00962 nsMetricsService::OnDataAvailable(nsIRequest *request, nsISupports *context,
00963                                   nsIInputStream *stream, PRUint32 offset,
00964                                   PRUint32 count)
00965 {
00966   PRUint32 n;
00967   return stream->ReadSegments(CopySegmentToStream, mConfigOutputStream,
00968                               count, &n);
00969 }
00970 
00971 NS_IMETHODIMP
00972 nsMetricsService::Observe(nsISupports *subject, const char *topic,
00973                           const PRUnichar *data)
00974 {
00975   if (strcmp(topic, kQuitApplicationTopic) == 0) {
00976     Flush();
00977 
00978     // We don't detach the collectors here, to allow them to log events
00979     // as we're shutting down.  The collectors will be detached and released
00980     // when the MetricsService goes away.
00981   } else if (strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
00982     mUploadTimer->Cancel();
00983   } else if (strcmp(topic, "profile-after-change") == 0) {
00984     nsresult rv = ProfileStartup();
00985     NS_ENSURE_SUCCESS(rv, rv);
00986   } else if (strcmp(topic, NS_WEBNAVIGATION_DESTROY) == 0 ||
00987              strcmp(topic, NS_CHROME_WEBNAVIGATION_DESTROY) == 0) {
00988 
00989     // Dispatch our notification before removing the window from the map.
00990     nsCOMPtr<nsIObserverService> obsSvc =
00991       do_GetService("@mozilla.org/observer-service;1");
00992     NS_ENSURE_STATE(obsSvc);
00993 
00994     const char *newTopic;
00995     if (strcmp(topic, NS_WEBNAVIGATION_DESTROY) == 0) {
00996       newTopic = NS_METRICS_WEBNAVIGATION_DESTROY;
00997     } else {
00998       newTopic = NS_METRICS_CHROME_WEBNAVIGATION_DESTROY;
00999     }
01000 
01001     obsSvc->NotifyObservers(subject, newTopic, data);
01002 
01003     // Remove the window from our map.
01004     nsCOMPtr<nsIDOMWindow> window = do_GetInterface(subject);
01005     if (window) {
01006       MS_LOG(("Removing window from map: %p", window.get()));
01007       mWindowMap.Remove(window);
01008     } else {
01009       MS_LOG(("Couldn't get window to remove from map"));
01010     }
01011   } else if (strcmp(topic, NS_HTTP_ON_MODIFY_REQUEST_TOPIC) == 0) {
01012     // Check whether this channel if one of ours.  If it is, clear the cookies.
01013     nsCOMPtr<nsIPropertyBag2> props = do_QueryInterface(subject);
01014     if (props) {
01015       PRBool isMetrics = PR_FALSE;
01016       props->GetPropertyAsBool(
01017           NS_LITERAL_STRING("moz-metrics-request"), &isMetrics);
01018       if (isMetrics) {
01019         nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(subject);
01020         if (channel) {
01021           channel->SetRequestHeader(NS_LITERAL_CSTRING("Cookie"),
01022                                     EmptyCString(), PR_FALSE);
01023         }
01024       }
01025     }
01026   } else if (strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
01027     // The only pref we care about changing is the .enable pref
01028     if (NS_ConvertUTF8toUTF16(kEnablePref).Equals(nsDependentString(data))) {
01029       if (CollectionEnabled()) {
01030         StartCollection();
01031       } else {
01032         StopCollection();
01033       }
01034     }
01035   }
01036   
01037   return NS_OK;
01038 }
01039 
01040 nsresult
01041 nsMetricsService::ProfileStartup()
01042 {
01043   nsCOMPtr<nsIPrefBranch2> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
01044   NS_ENSURE_STATE(prefs);
01045   prefs->AddObserver(kEnablePref, this, PR_FALSE);
01046 
01047   return CollectionEnabled() ? StartCollection() : StopCollection();
01048 }
01049 
01050 nsresult
01051 nsMetricsService::StartCollection()
01052 {
01053   // Initialize configuration by reading our old config file if one exists.
01054   nsCOMPtr<nsIFile> file;
01055   GetConfigFile(getter_AddRefs(file));
01056 
01057   PRBool loaded = PR_FALSE;
01058   if (file) {
01059     PRBool exists;
01060     if (NS_SUCCEEDED(file->Exists(&exists)) && exists) {
01061       loaded = NS_SUCCEEDED(mConfig.Load(file));
01062     }
01063   }
01064   
01065   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
01066   NS_ENSURE_STATE(prefs);
01067   prefs->GetIntPref("metrics.event-count", &mEventCount);
01068 
01069   // Update the session id pref for the new session
01070   static const char kSessionIDPref[] = "metrics.last-session-id";
01071   PRInt32 sessionID = -1;
01072   prefs->GetIntPref(kSessionIDPref, &sessionID);
01073   mSessionID.Cut(0, PR_UINT32_MAX);
01074   AppendInt(mSessionID, ++sessionID);
01075   nsresult rv = FlushIntPref(kSessionIDPref, sessionID);
01076   NS_ENSURE_SUCCESS(rv, rv);
01077   
01078   // Start up the collectors
01079   EnableCollectors();
01080 
01081   // If we didn't load a config file, we should upload as soon as possible.
01082   InitUploadTimer(!loaded);
01083 
01084   return NS_OK;
01085 }
01086 
01087 nsresult
01088 nsMetricsService::StopCollection()
01089 {
01090   // Clear out prefs and files associated with metrics collection
01091   MS_LOG(("Clearing metrics state"));
01092   FlushClearPref(kUploadTimePref);
01093   FlushClearPref(kPingTimePref);
01094   FlushClearPref(kEventCountPref);
01095 
01096   nsCOMPtr<nsIFile> configFile;
01097   GetConfigFile(getter_AddRefs(configFile));
01098   if (configFile) {
01099     configFile->Remove(PR_FALSE);
01100   }
01101 
01102   nsCOMPtr<nsILocalFile> dataFile;
01103   GetDataFile(&dataFile);
01104   if (dataFile) {
01105     dataFile->Remove(PR_FALSE);
01106   }
01107 
01108   // Clear our current config and make sure all collectors are disabled
01109   mConfig.Reset();
01110   EnableCollectors();
01111   CreateRoot();  // clear any unflushed events
01112 
01113   return NS_OK;
01114 }
01115 
01116 NS_IMETHODIMP
01117 nsMetricsService::Notify(nsITimer *timer)
01118 {
01119   // OK, we are ready to upload!
01120   MS_LOG(("Timer fired, uploading metrics log"));
01121 
01122   // Clear the next-upload-time pref
01123   FlushClearPref(kUploadTimePref);
01124 
01125   Upload();
01126   return NS_OK;
01127 }
01128 
01129 /*static*/ nsMetricsService *
01130 nsMetricsService::get()
01131 {
01132   if (!sMetricsService) {
01133     nsCOMPtr<nsIMetricsService> ms =
01134       do_GetService(NS_METRICSSERVICE_CONTRACTID);
01135     if (!sMetricsService)
01136       NS_WARNING("failed to initialize metrics service");
01137   }
01138   return sMetricsService;
01139 }
01140 
01141 /*static*/ NS_METHOD
01142 nsMetricsService::Create(nsISupports *outer, const nsIID &iid, void **result)
01143 {
01144   NS_ENSURE_TRUE(!outer, NS_ERROR_NO_AGGREGATION);
01145 
01146   nsRefPtr<nsMetricsService> ms;
01147   if (!sMetricsService) {
01148     ms = new nsMetricsService();
01149     if (!ms)
01150       return NS_ERROR_OUT_OF_MEMORY;
01151     NS_ASSERTION(sMetricsService, "should be non-null");
01152 
01153     nsresult rv = ms->Init();
01154     if (NS_FAILED(rv))
01155       return rv;
01156   }
01157   return sMetricsService->QueryInterface(iid, result);
01158 }
01159 
01160 nsresult
01161 nsMetricsService::Init()
01162 {
01163   // Anything that requires reading profile prefs must be initialized
01164   // later, once the profile-after-change notification has happened.
01165   // We can create objects and register for notifications now.
01166   
01167 #ifdef PR_LOGGING
01168   gMetricsLog = PR_NewLogModule("nsMetricsService");
01169 #endif
01170 
01171   MS_LOG(("nsMetricsService::Init"));
01172 
01173   // Set up our hashtables
01174   NS_ENSURE_TRUE(mWindowMap.Init(32), NS_ERROR_OUT_OF_MEMORY);
01175   NS_ENSURE_TRUE(mCollectorMap.Init(16), NS_ERROR_OUT_OF_MEMORY);
01176 
01177   mUploadTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
01178   NS_ENSURE_TRUE(mUploadTimer, NS_ERROR_OUT_OF_MEMORY);
01179 
01180   mMD5Context = MD5_NewContext();
01181   NS_ENSURE_TRUE(mMD5Context, NS_ERROR_FAILURE);
01182 
01183   NS_ENSURE_STATE(mConfig.Init());
01184 
01185   // Create an XML document to serve as the owner document for elements.
01186   mDocument = do_CreateInstance("@mozilla.org/xml/xml-document;1");
01187   NS_ENSURE_TRUE(mDocument, NS_ERROR_FAILURE);
01188 
01189   // Create a root log element.
01190   nsresult rv = CreateRoot();
01191   NS_ENSURE_SUCCESS(rv, rv);
01192 
01193   nsCOMPtr<nsIObserverService> obsSvc =
01194       do_GetService("@mozilla.org/observer-service;1", &rv);
01195   NS_ENSURE_SUCCESS(rv, rv);
01196 
01197   // The rest of startup will happen on profile-after-change
01198   rv = obsSvc->AddObserver(this, "profile-after-change", PR_FALSE);
01199   NS_ENSURE_SUCCESS(rv, rv);
01200 
01201   // Listen for quit-application so we can properly flush our data to disk
01202   rv = obsSvc->AddObserver(this, kQuitApplicationTopic, PR_FALSE);
01203   NS_ENSURE_SUCCESS(rv, rv);
01204 
01205   // We wait to cancel our timer until xpcom-shutdown, to catch cases
01206   // where quit-application is never called.
01207   rv = obsSvc->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, PR_FALSE);
01208   NS_ENSURE_SUCCESS(rv, rv);
01209 
01210   // Listen for window destruction so that we can remove the windows
01211   // from our window id map.
01212   rv = obsSvc->AddObserver(this, NS_WEBNAVIGATION_DESTROY, PR_FALSE);
01213   NS_ENSURE_SUCCESS(rv, rv);
01214   rv = obsSvc->AddObserver(this, NS_CHROME_WEBNAVIGATION_DESTROY, PR_FALSE);
01215   NS_ENSURE_SUCCESS(rv, rv);
01216 
01217   // Listen for http-on-modify-request so that we can clear out cookies
01218   // from our requests.
01219   rv = obsSvc->AddObserver(this, NS_HTTP_ON_MODIFY_REQUEST_TOPIC, PR_FALSE);
01220   NS_ENSURE_SUCCESS(rv, rv);
01221 
01222   return NS_OK;
01223 }
01224 
01225 nsresult
01226 nsMetricsService::CreateRoot()
01227 {
01228   nsresult rv;
01229   nsCOMPtr<nsIDOMElement> root;
01230   rv = nsMetricsUtils::CreateElement(mDocument, NS_LITERAL_STRING("log"),
01231                                      getter_AddRefs(root));
01232   NS_ENSURE_SUCCESS(rv, rv);
01233 
01234   mRoot = root;
01235   return NS_OK;
01236 }
01237 
01238 nsresult
01239 nsMetricsService::GetDataFile(nsCOMPtr<nsILocalFile> *result)
01240 {
01241   nsCOMPtr<nsIFile> file;
01242   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
01243                                        getter_AddRefs(file));
01244   NS_ENSURE_SUCCESS(rv, rv);
01245 
01246   rv = file->AppendNative(NS_LITERAL_CSTRING("metrics.xml"));
01247   NS_ENSURE_SUCCESS(rv, rv);
01248 
01249   *result = do_QueryInterface(file, &rv);
01250   return rv;
01251 }
01252 
01253 nsresult
01254 nsMetricsService::OpenDataFile(PRUint32 flags, PRFileDesc **fd)
01255 {
01256   nsCOMPtr<nsILocalFile> dataFile;
01257   nsresult rv = GetDataFile(&dataFile);
01258   NS_ENSURE_SUCCESS(rv, rv);
01259 
01260   return dataFile->OpenNSPRFileDesc(flags, 0600, fd);
01261 }
01262 
01263 nsresult
01264 nsMetricsService::UploadData()
01265 {
01266   // TODO: Prepare a data stream for upload that is prefixed with a PROFILE
01267   //       event.
01268  
01269   if (!CollectionEnabled()) {
01270     MS_LOG(("Upload disabled"));
01271     return NS_ERROR_ABORT;
01272   }
01273  
01274   nsCString spec;
01275   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
01276   if (prefs) {
01277     prefs->GetCharPref("metrics.upload.uri", getter_Copies(spec));
01278   }
01279   if (spec.IsEmpty()) {
01280     MS_LOG(("Upload URI not set"));
01281     return NS_ERROR_ABORT;
01282   }
01283 
01284   nsCOMPtr<nsILocalFile> file;
01285   nsresult rv = GetDataFileForUpload(&file);
01286   NS_ENSURE_SUCCESS(rv, rv);
01287 
01288   // NOTE: nsIUploadChannel requires a buffered stream to upload...
01289 
01290   nsCOMPtr<nsIFileInputStream> fileStream =
01291       do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID);
01292   NS_ENSURE_STATE(fileStream);
01293 
01294   rv = fileStream->Init(file, -1, -1, nsIFileInputStream::DELETE_ON_CLOSE);
01295   NS_ENSURE_SUCCESS(rv, rv);
01296 
01297   PRUint32 streamLen;
01298   rv = fileStream->Available(&streamLen);
01299   NS_ENSURE_SUCCESS(rv, rv);
01300 
01301   if (streamLen == 0)
01302     return NS_ERROR_ABORT;
01303 
01304   nsCOMPtr<nsIBufferedInputStream> uploadStream =
01305       do_CreateInstance(NS_BUFFEREDINPUTSTREAM_CONTRACTID);
01306   NS_ENSURE_STATE(uploadStream);
01307 
01308   rv = uploadStream->Init(fileStream, 4096);
01309   NS_ENSURE_SUCCESS(rv, rv);
01310 
01311   nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID);
01312   NS_ENSURE_STATE(ios);
01313 
01314   nsCOMPtr<nsIChannel> channel;
01315   ios->NewChannel(spec, nsnull, nsnull, getter_AddRefs(channel));
01316   NS_ENSURE_STATE(channel); 
01317 
01318   // Tag the channel so that we know it's one of ours.
01319   nsCOMPtr<nsIWritablePropertyBag2> props = do_QueryInterface(channel);
01320   NS_ENSURE_STATE(props);
01321   props->SetPropertyAsBool(NS_LITERAL_STRING("moz-metrics-request"), PR_TRUE);
01322 
01323   nsCOMPtr<nsIInterfaceRequestor> certListener = new BadCertListener();
01324   NS_ENSURE_TRUE(certListener, NS_ERROR_OUT_OF_MEMORY);
01325 
01326   channel->SetNotificationCallbacks(certListener);
01327 
01328   nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(channel);
01329   NS_ENSURE_STATE(uploadChannel); 
01330 
01331   NS_NAMED_LITERAL_CSTRING(binaryType, NS_METRICS_MIME_TYPE);
01332   rv = uploadChannel->SetUploadStream(uploadStream, binaryType, -1);
01333   NS_ENSURE_SUCCESS(rv, rv);
01334 
01335   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
01336   NS_ENSURE_STATE(httpChannel);
01337   rv = httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
01338   NS_ENSURE_SUCCESS(rv, rv);
01339 
01340   rv = channel->AsyncOpen(this, nsnull);
01341   NS_ENSURE_SUCCESS(rv, rv);
01342 
01343   return NS_OK;
01344 }
01345 
01346 nsresult
01347 nsMetricsService::GetDataFileForUpload(nsCOMPtr<nsILocalFile> *result)
01348 {
01349   nsCOMPtr<nsILocalFile> input;
01350   nsresult rv = GetDataFile(&input);
01351   NS_ENSURE_SUCCESS(rv, rv);
01352 
01353   nsCOMPtr<nsIInputStream> src;
01354   rv = OpenCompleteXMLStream(input, getter_AddRefs(src));
01355   NS_ENSURE_SUCCESS(rv, rv);
01356 
01357   nsCOMPtr<nsIFile> temp;
01358   rv = input->Clone(getter_AddRefs(temp));
01359   NS_ENSURE_SUCCESS(rv, rv);
01360 
01361   nsCString leafName;
01362   rv = temp->GetNativeLeafName(leafName);
01363   NS_ENSURE_SUCCESS(rv, rv);
01364 
01365   leafName.Append(".bz2");
01366   rv = temp->SetNativeLeafName(leafName);
01367   NS_ENSURE_SUCCESS(rv, rv);
01368 
01369   nsCOMPtr<nsILocalFile> ltemp = do_QueryInterface(temp, &rv);
01370   NS_ENSURE_SUCCESS(rv, rv);
01371 
01372   PRFileDesc *destFd = NULL;
01373   rv = ltemp->OpenNSPRFileDesc(PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE, 0600,
01374                                &destFd);
01375 
01376   // Copy file using bzip2 compression:
01377 
01378   if (NS_SUCCEEDED(rv)) {
01379 #ifdef NS_METRICS_SEND_UNCOMPRESSED_DATA
01380     char buf[4096];
01381     PRUint32 n;
01382 
01383     while (NS_SUCCEEDED(rv = src->Read(buf, sizeof(buf), &n)) && n) {
01384       if (PR_Write(destFd, buf, n) != n) {
01385         NS_WARNING("failed to write data");
01386         rv = NS_ERROR_UNEXPECTED;
01387         break;
01388       }
01389     }
01390 #else
01391     rv = CompressBZ2(src, destFd);
01392 #endif
01393   }
01394 
01395   if (destFd)
01396     PR_Close(destFd);
01397 
01398   if (NS_SUCCEEDED(rv)) {
01399     *result = nsnull;
01400     ltemp.swap(*result);
01401   }
01402 
01403   return rv;
01404 }
01405 
01406 nsresult
01407 nsMetricsService::OpenCompleteXMLStream(nsILocalFile *dataFile,
01408                                        nsIInputStream **result)
01409 {
01410   // Construct a full XML document using the header, file contents, and
01411   // footer.  We need to generate a client id now if one doesn't exist.
01412   static const char kClientIDPref[] = "metrics.client-id";
01413 
01414   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
01415   NS_ENSURE_STATE(prefs);
01416   
01417   nsCString clientID;
01418   nsresult rv = prefs->GetCharPref(kClientIDPref, getter_Copies(clientID));
01419   if (NS_FAILED(rv) || clientID.IsEmpty()) {
01420     rv = GenerateClientID(clientID);
01421     NS_ENSURE_SUCCESS(rv, rv);
01422     
01423     rv = FlushCharPref(kClientIDPref, clientID.get());
01424     NS_ENSURE_SUCCESS(rv, rv);
01425   }
01426 
01427   static const char METRICS_XML_HEAD[] =
01428       "<?xml version=\"1.0\"?>\n"
01429       "<log xmlns=\"" NS_METRICS_NAMESPACE "\" "
01430            "version=\"%d\" clientid=\"%s\">\n";
01431   static const char METRICS_XML_TAIL[] = "</log>";
01432 
01433   nsCOMPtr<nsIFileInputStream> fileStream =
01434       do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID);
01435   NS_ENSURE_STATE(fileStream);
01436 
01437   rv = fileStream->Init(dataFile, -1, -1, 0);
01438   NS_ENSURE_SUCCESS(rv, rv);
01439 
01440   nsCOMPtr<nsIMultiplexInputStream> miStream =
01441     do_CreateInstance(NS_MULTIPLEXINPUTSTREAM_CONTRACTID);
01442   NS_ENSURE_STATE(miStream);
01443 
01444   nsCOMPtr<nsIStringInputStream> stringStream =
01445       do_CreateInstance("@mozilla.org/io/string-input-stream;1");
01446   NS_ENSURE_STATE(stringStream);
01447 
01448   char *head = PR_smprintf(METRICS_XML_HEAD, kMetricsVersion, clientID.get());
01449   rv = stringStream->SetData(head, -1);
01450   PR_smprintf_free(head);
01451   NS_ENSURE_SUCCESS(rv, rv);
01452 
01453   rv = miStream->AppendStream(stringStream);
01454   NS_ENSURE_SUCCESS(rv, rv);
01455 
01456   rv = miStream->AppendStream(fileStream);
01457   NS_ENSURE_SUCCESS(rv, rv);
01458 
01459   stringStream = do_CreateInstance("@mozilla.org/io/string-input-stream;1");
01460   NS_ENSURE_STATE(stringStream);
01461 
01462   rv = stringStream->SetData(METRICS_XML_TAIL, sizeof(METRICS_XML_TAIL)-1);
01463   NS_ENSURE_SUCCESS(rv, rv);
01464 
01465   rv = miStream->AppendStream(stringStream);
01466   NS_ENSURE_SUCCESS(rv, rv);
01467 
01468   NS_ADDREF(*result = miStream);
01469   return NS_OK;
01470 }
01471 
01472 void
01473 nsMetricsService::InitUploadTimer(PRBool immediate)
01474 {
01475   // Check whether we've set a delayed upload time due to previous errors.
01476   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
01477   if (!prefs) {
01478     NS_WARNING("couldn't get prefs service");
01479     return;
01480   }
01481 
01482   PRUint32 delay_sec;
01483 
01484   PRInt32 uploadTime_sec;
01485   if (NS_SUCCEEDED(prefs->GetIntPref(kUploadTimePref, &uploadTime_sec))) {
01486     // Set a timer for when we should upload.  If the time to upload has
01487     // passed, we'll set a timer for 0ms.
01488     PRInt32 now_sec = PRInt32(PR_Now() / PR_USEC_PER_SEC);
01489     if (now_sec >= uploadTime_sec) {
01490       delay_sec = 0;
01491     } else {
01492       delay_sec = (uploadTime_sec - now_sec);
01493     }
01494   } else if (immediate) {
01495     delay_sec = 0;
01496   } else {
01497     delay_sec = mConfig.UploadInterval();
01498   }
01499 
01500   nsresult rv = mUploadTimer->InitWithCallback(this,
01501                                                delay_sec * PR_MSEC_PER_SEC,
01502                                                nsITimer::TYPE_ONE_SHOT);
01503   if (NS_SUCCEEDED(rv)) {
01504     MS_LOG(("Initialized upload timer for %d sec", delay_sec));
01505   } else {
01506     MS_LOG(("Failed to initialize upload timer"));
01507   }
01508 }
01509 
01510 void
01511 nsMetricsService::GetConfigFile(nsIFile **result)
01512 {
01513   nsCOMPtr<nsIFile> file;
01514   NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
01515   if (file)
01516     file->AppendNative(NS_LITERAL_CSTRING("metrics-config.xml"));
01517 
01518   *result = nsnull;
01519   file.swap(*result);
01520 }
01521 
01522 void
01523 nsMetricsService::GetConfigTempFile(nsIFile **result)
01524 {
01525   nsCOMPtr<nsIFile> file;
01526   NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(file));
01527   if (file)
01528     file->AppendNative(NS_LITERAL_CSTRING("metrics-config.tmp"));
01529 
01530   *result = nsnull;
01531   file.swap(*result);
01532 }
01533 
01534 nsresult
01535 nsMetricsService::GenerateClientID(nsCString &clientID)
01536 {
01537   // Feed some data into the hasher...
01538 
01539   struct {
01540     PRTime  a;
01541     PRUint8 b[32];
01542   } input;
01543 
01544   input.a = PR_Now();
01545   nsMetricsUtils::GetRandomNoise(input.b, sizeof(input.b));
01546 
01547   return HashBytes(
01548       NS_REINTERPRET_CAST(const PRUint8 *, &input), sizeof(input), clientID);
01549 }
01550 
01551 nsresult
01552 nsMetricsService::HashBytes(const PRUint8 *bytes, PRUint32 length,
01553                             nsACString &result)
01554 {
01555   unsigned char buf[HASH_LENGTH_MAX];
01556   unsigned int resultLength = 0;
01557 
01558   MD5_Begin(mMD5Context);
01559   MD5_Update(mMD5Context, bytes, length);
01560   MD5_End(mMD5Context, buf, &resultLength, sizeof(buf));
01561 
01562   // Base64-encode the result.  The maximum result length is calculated
01563   // as described in plbase64.h.
01564   char *resultBuffer;
01565   if (NS_CStringGetMutableData(
01566           result, ((resultLength + 2) / 3) * 4, &resultBuffer) == 0) {
01567     return NS_ERROR_OUT_OF_MEMORY;
01568   }
01569 
01570   PL_Base64Encode(NS_REINTERPRET_CAST(char*, buf), resultLength, resultBuffer);
01571 
01572   // Size the string to its null-terminated length
01573   result.SetLength(strlen(resultBuffer));
01574   return NS_OK;
01575 }
01576 
01577 PRBool
01578 nsMetricsService::PersistEventCount()
01579 {
01580   return NS_SUCCEEDED(FlushIntPref(kEventCountPref, mEventCount));
01581 }
01582 
01583 /* static */ PRUint32
01584 nsMetricsService::GetWindowID(nsIDOMWindow *window)
01585 {
01586   if (!sMetricsService) {
01587     NS_NOTREACHED("metrics service not created");
01588     return PR_UINT32_MAX;
01589   }
01590 
01591   return sMetricsService->GetWindowIDInternal(window);
01592 }
01593 
01594 NS_IMETHODIMP
01595 nsMetricsService::GetWindowID(nsIDOMWindow *window, PRUint32 *id)
01596 {
01597   *id = GetWindowIDInternal(window);
01598   return NS_OK;
01599 }
01600 
01601 PRUint32
01602 nsMetricsService::GetWindowIDInternal(nsIDOMWindow *window)
01603 {
01604   PRUint32 id;
01605   if (!mWindowMap.Get(window, &id)) {
01606     id = mNextWindowID++;
01607     MS_LOG(("Adding window %p to map with id %d", window, id));
01608     mWindowMap.Put(window, id);
01609   }
01610 
01611   return id;
01612 }
01613 
01614 nsresult
01615 nsMetricsService::HashUTF8(const nsCString &str, nsCString &hashed)
01616 {
01617   if (str.IsEmpty()) {
01618     return NS_ERROR_INVALID_ARG;
01619   }
01620 
01621   return HashBytes(
01622       NS_REINTERPRET_CAST(const PRUint8 *, str.get()), str.Length(), hashed);
01623 }
01624 
01625 /* static */ nsresult
01626 nsMetricsService::FlushIntPref(const char *prefName, PRInt32 prefValue)
01627 {
01628   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
01629   NS_ENSURE_STATE(prefs);
01630 
01631   nsresult rv = prefs->SetIntPref(prefName, prefValue);
01632   NS_ENSURE_SUCCESS(rv, rv);
01633 
01634   nsCOMPtr<nsIPrefService> prefService = do_QueryInterface(prefs);
01635   NS_ENSURE_STATE(prefService);
01636 
01637   rv = prefService->SavePrefFile(nsnull);
01638   NS_ENSURE_SUCCESS(rv, rv);
01639 
01640   return NS_OK;
01641 }
01642 
01643 /* static */ nsresult
01644 nsMetricsService::FlushCharPref(const char *prefName, const char *prefValue)
01645 {
01646   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
01647   NS_ENSURE_STATE(prefs);
01648 
01649   nsresult rv = prefs->SetCharPref(prefName, prefValue);
01650   NS_ENSURE_SUCCESS(rv, rv);
01651 
01652   nsCOMPtr<nsIPrefService> prefService = do_QueryInterface(prefs);
01653   NS_ENSURE_STATE(prefService);
01654 
01655   rv = prefService->SavePrefFile(nsnull);
01656   NS_ENSURE_SUCCESS(rv, rv);
01657 
01658   return NS_OK;
01659 }
01660 
01661 /* static */ nsresult
01662 nsMetricsService::FlushClearPref(const char *prefName)
01663 {
01664   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
01665   NS_ENSURE_STATE(prefs);
01666 
01667   nsresult rv = prefs->ClearUserPref(prefName);
01668   if (NS_FAILED(rv)) {
01669     // There was no user-set value for this pref.
01670     // It's not an error, and we don't need to flush.
01671     return NS_OK;
01672   }
01673 
01674   nsCOMPtr<nsIPrefService> prefService = do_QueryInterface(prefs);
01675   NS_ENSURE_STATE(prefService);
01676 
01677   rv = prefService->SavePrefFile(nsnull);
01678   NS_ENSURE_SUCCESS(rv, rv);
01679 
01680   return NS_OK;
01681 }
01682 
01683 /* static */ PRBool
01684 nsMetricsService::CollectionEnabled()
01685 {
01686   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
01687   NS_ENSURE_TRUE(prefs, PR_FALSE);
01688 
01689   PRBool enabled = PR_FALSE;
01690   prefs->GetBoolPref(kEnablePref, &enabled);
01691   return enabled;
01692 }
01693 
01694 /* static */ nsresult
01695 nsMetricsUtils::NewPropertyBag(nsIWritablePropertyBag2 **result)
01696 {
01697   return CallCreateInstance("@mozilla.org/hash-property-bag;1", result);
01698 }
01699 
01700 /* static */ nsresult
01701 nsMetricsUtils::AddChildItem(nsIMetricsEventItem *parent,
01702                              const nsAString &childName,
01703                              nsIPropertyBag *childProperties)
01704 {
01705   nsCOMPtr<nsIMetricsEventItem> item;
01706   nsMetricsService::get()->CreateEventItem(childName, getter_AddRefs(item));
01707   NS_ENSURE_STATE(item);
01708 
01709   item->SetProperties(childProperties);
01710 
01711   nsresult rv = parent->AppendChild(item);
01712   NS_ENSURE_SUCCESS(rv, rv);
01713 
01714   return NS_OK;
01715 }
01716 
01717 /* static */ PRBool
01718 nsMetricsUtils::GetRandomNoise(void *buf, PRSize size)
01719 {
01720   PRSize nbytes = 0;
01721   while (nbytes < size) {
01722     PRSize n = PR_GetRandomNoise(
01723         NS_STATIC_CAST(char *, buf) + nbytes, size - nbytes);
01724     if (n == 0) {
01725       MS_LOG(("Couldn't get any random bytes"));
01726       return PR_FALSE;
01727     }
01728     nbytes += n;
01729   }
01730   return PR_TRUE;
01731 }
01732 
01733 /* static */ nsresult
01734 nsMetricsUtils::CreateElement(nsIDOMDocument *ownerDoc,
01735                               const nsAString &tag, nsIDOMElement **element)
01736 {
01737   return ownerDoc->CreateElementNS(NS_LITERAL_STRING(NS_METRICS_NAMESPACE),
01738                                    tag, element);
01739 }
01740 
01741 
01742 /* static */ PRBool
01743 nsMetricsUtils::IsSubframe(nsIDocShellTreeItem* docShell)
01744 {
01745   // Consider the docshell to be a subframe if it's is content, not chrome,
01746   // and has a parent docshell which is also content.
01747   if (!docShell) {
01748     return PR_FALSE;
01749   }
01750 
01751   PRInt32 itemType;
01752   docShell->GetItemType(&itemType);
01753   if (itemType != nsIDocShellTreeItem::typeContent) {
01754     return PR_FALSE;
01755   }
01756 
01757   nsCOMPtr<nsIDocShellTreeItem> parent;
01758   docShell->GetSameTypeParent(getter_AddRefs(parent));
01759   return (parent != nsnull);
01760 }
01761 
01762 
01763 /* static */ PRUint32
01764 nsMetricsUtils::FindWindowForNode(nsIDOMNode *node)
01765 {
01766   nsCOMPtr<nsIDOMDocument> ownerDoc;
01767   node->GetOwnerDocument(getter_AddRefs(ownerDoc));
01768   NS_ENSURE_STATE(ownerDoc);
01769 
01770   nsCOMPtr<nsIDOMDocumentView> docView = do_QueryInterface(ownerDoc);
01771   NS_ENSURE_STATE(docView);
01772 
01773   nsCOMPtr<nsIDOMAbstractView> absView;
01774   docView->GetDefaultView(getter_AddRefs(absView));
01775   NS_ENSURE_STATE(absView);
01776 
01777   nsCOMPtr<nsIDOMWindow> window = do_QueryInterface(absView);
01778   NS_ENSURE_STATE(window);
01779 
01780   return nsMetricsService::GetWindowID(window);
01781 }