Back to index

lightning-sunbird  0.9+nobinonly
nsLoadCollector.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  *  Brian Ryner <bryner@brianryner.com>
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 "nsLoadCollector.h"
00040 #include "nsMetricsService.h"
00041 #include "nsCOMPtr.h"
00042 #include "nsCURILoader.h"
00043 #include "nsIServiceManager.h"
00044 #include "nsIWebProgress.h"
00045 #include "nsIDocShell.h"
00046 #include "nsIDocShellTreeItem.h"
00047 #include "nsIChannel.h"
00048 #include "nsIDOMWindow.h"
00049 #include "nsIInterfaceRequestorUtils.h"
00050 #include "nsServiceManagerUtils.h"
00051 #include "nsIURI.h"
00052 #include "nsIDOMDocument.h"
00053 
00054 // Hack around internal string usage in nsIDocument.h on the branch
00055 #ifdef MOZILLA_1_8_BRANCH
00056 // nsAFlat[C]String is a deprecated synonym for ns[C]String
00057 typedef nsString nsAFlatString;
00058 typedef nsCString nsAFlatCString;
00059 // nsXPIDLCString is a subclass of nsCString, but they're equivalent for the
00060 // purposes of nsIDocument.h.
00061 typedef nsCString nsXPIDLCString;
00062 // This utility method isn't in the string glue on the branch.
00063 inline void CopyASCIItoUCS2(const nsACString &src, nsAString &dest) {
00064   NS_CStringToUTF16(src, NS_CSTRING_ENCODING_ASCII, dest);
00065 }
00066 // Suppress inclusion of these headers
00067 #define nsAString_h___
00068 #define nsString_h___
00069 #define nsReadableUtils_h___
00070 #endif
00071 #include "nsIDocument.h"
00072 #ifdef MOZILLA_1_8_BRANCH
00073 #undef nsAString_h___
00074 #undef nsString_h___
00075 #undef nsReadableUtils_h___
00076 #endif
00077 
00078 // This is needed to gain access to the LOAD_ defines in this file.
00079 #define MOZILLA_INTERNAL_API
00080 #include "nsDocShellLoadTypes.h"
00081 #undef MOZILLA_INTERNAL_API
00082 
00083 //-----------------------------------------------------------------------------
00084 
00085 #if defined(__linux)
00086 #include <sys/types.h>
00087 #include <unistd.h>
00088 #include <stdio.h>
00089 static FILE *sProcFP;
00090 static void GetMemUsage_Shutdown() {
00091   if (sProcFP) {
00092     fclose(sProcFP);
00093     sProcFP = NULL;
00094   }
00095 }
00096 #elif defined(XP_WIN)
00097 #include <windows.h>
00098 #if _MSC_VER > 1200
00099 #include <psapi.h>
00100 #else
00101 typedef struct _PROCESS_MEMORY_COUNTERS {
00102   DWORD cb;
00103   DWORD PageFaultCount;
00104   SIZE_T PeakWorkingSetSize;
00105   SIZE_T WorkingSetSize;
00106   SIZE_T QuotaPeakPagedPoolUsage;
00107   SIZE_T QuotaPagedPoolUsage;
00108   SIZE_T QuotaPeakNonPagedPoolUsage;
00109   SIZE_T QuotaNonPagedPoolUsage;
00110   SIZE_T PagefileUsage;
00111   SIZE_T PeakPagefileUsage;
00112 } PROCESS_MEMORY_COUNTERS;
00113 typedef PROCESS_MEMORY_COUNTERS *PPROCESS_MEMORY_COUNTERS;
00114 #endif
00115 typedef BOOL (WINAPI * GETPROCESSMEMORYINFO_FUNC)(
00116     HANDLE process, PPROCESS_MEMORY_COUNTERS counters, DWORD cb);
00117 static HMODULE sPSModule;
00118 static HANDLE sProcess;
00119 static GETPROCESSMEMORYINFO_FUNC sGetMemInfo;
00120 static void GetMemUsage_Shutdown() {
00121   if (sProcess) {
00122     CloseHandle(sProcess);
00123     sProcess = NULL;
00124   }
00125   if (sPSModule) {
00126     FreeLibrary(sPSModule);
00127     sPSModule = NULL;
00128   }
00129   sGetMemInfo = NULL;
00130 }
00131 #elif defined(XP_MACOSX)
00132 #include <mach/mach.h>
00133 #include <mach/task.h>
00134 static void GetMemUsage_Shutdown() {
00135 }
00136 #endif
00137 
00138 struct MemUsage {
00139   PRInt64 total;
00140   PRInt64 resident;
00141 };
00142 
00143 // This method should be incorporated into NSPR
00144 static PRBool GetMemUsage(MemUsage *result)
00145 {
00146   PRBool setResult = PR_FALSE;
00147 #if defined(__linux)
00148   // Read /proc/<pid>/statm, and look at the first and second fields, which
00149   // report the program size and the number of resident pages for this process,
00150   // respectively.
00151  
00152   char buf[256];
00153   if (!sProcFP) {
00154     pid_t pid = getpid();
00155     snprintf(buf, sizeof(buf), "/proc/%d/statm", pid);
00156     sProcFP = fopen(buf, "rb");
00157   }
00158   if (sProcFP) {
00159     int vmsize, vmrss;
00160 
00161     int count = fscanf(sProcFP, "%d %d", &vmsize, &vmrss);
00162     rewind(sProcFP);
00163 
00164     if (count == 2) {
00165       static int ps = getpagesize();
00166       result->total = PRInt64(vmsize) * ps;
00167       result->resident = PRInt64(vmrss) * ps;
00168       setResult = PR_TRUE;
00169     }
00170   }
00171 #elif defined(XP_WIN)
00172   // Use GetProcessMemoryInfo, which only works on WinNT and later.
00173 
00174   if (!sGetMemInfo) {
00175     sPSModule = LoadLibrary("psapi.dll");
00176     if (sPSModule) {
00177       sGetMemInfo = (GETPROCESSMEMORYINFO_FUNC)
00178           GetProcAddress(sPSModule, "GetProcessMemoryInfo");
00179       if (sGetMemInfo)
00180         sProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
00181                                FALSE, GetCurrentProcessId());
00182       // Don't leave ourselves partially initialized.
00183       if (!sProcess)
00184         GetMemUsage_Shutdown();
00185     }
00186   }
00187   if (sGetMemInfo) {
00188     PROCESS_MEMORY_COUNTERS pmc;
00189     if (sGetMemInfo(sProcess, &pmc, sizeof(pmc))) {
00190       result->total = PRInt64(pmc.PagefileUsage);
00191       result->resident = PRInt64(pmc.WorkingSetSize);
00192       setResult = PR_TRUE;
00193     }
00194   }
00195 #elif defined(XP_MACOSX)
00196   // Use task_info
00197 
00198   task_basic_info_data_t ti;
00199   mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT;
00200   kern_return_t error = task_info(mach_task_self(), TASK_BASIC_INFO,
00201                                   (task_info_t) &ti, &count);
00202   if (error == KERN_SUCCESS) {
00203     result->total = PRInt64(ti.virtual_size);
00204     result->resident = PRInt64(ti.resident_size);
00205     setResult = PR_TRUE;
00206   }
00207 #endif
00208   return setResult;
00209 }
00210 
00211 //-----------------------------------------------------------------------------
00212 
00213 nsLoadCollector::nsLoadCollector()
00214     : mNextDocID(0)
00215 {
00216   mDocumentMap.Init(16);
00217 }
00218 
00219 nsLoadCollector::~nsLoadCollector()
00220 {
00221   GetMemUsage_Shutdown();
00222 }
00223 
00224 NS_IMPL_ISUPPORTS4(nsLoadCollector, nsIMetricsCollector,
00225                    nsIWebProgressListener, nsISupportsWeakReference,
00226                    nsIDocumentObserver)
00227 
00228 NS_IMETHODIMP
00229 nsLoadCollector::OnStateChange(nsIWebProgress *webProgress,
00230                                nsIRequest *request,
00231                                PRUint32 flags,
00232                                nsresult status)
00233 {
00234   NS_ASSERTION(flags & STATE_IS_DOCUMENT,
00235                "incorrect state change notification");
00236 
00237 #ifdef PR_LOGGING
00238   if (MS_LOG_ENABLED()) {
00239     nsCString name;
00240     request->GetName(name);
00241 
00242     MS_LOG(("LoadCollector: progress = %p, request = %p [%s], flags = %x, status = %x",
00243             webProgress, request, name.get(), flags, status));
00244   }
00245 #endif
00246 
00247   nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
00248   if (!channel) {
00249     // We don't care about non-channel requests
00250     return NS_OK;
00251   }
00252 
00253   nsresult rv;
00254   if (flags & STATE_START) {
00255     RequestEntry entry;
00256     NS_ASSERTION(!mRequestMap.Get(request, &entry), "duplicate STATE_START");
00257 
00258     nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(webProgress);
00259     nsCOMPtr<nsIDOMWindow> window = do_GetInterface(docShell);
00260     if (!window) {
00261       // We don't really care about windowless loads
00262       return NS_OK;
00263     }
00264 
00265     rv = nsMetricsUtils::NewPropertyBag(getter_AddRefs(entry.properties));
00266     NS_ENSURE_SUCCESS(rv, rv);
00267     nsIWritablePropertyBag2 *props = entry.properties;
00268 
00269     rv = props->SetPropertyAsUint32(NS_LITERAL_STRING("window"),
00270                                     nsMetricsService::GetWindowID(window));
00271     NS_ENSURE_SUCCESS(rv, rv);
00272 
00273     if (flags & STATE_RESTORING) {
00274       rv = props->SetPropertyAsBool(NS_LITERAL_STRING("bfCacheHit"), PR_TRUE);
00275       NS_ENSURE_SUCCESS(rv, rv);
00276     }
00277 
00278     nsString origin;
00279     PRUint32 loadType;
00280     docShell->GetLoadType(&loadType);
00281 
00282     switch (loadType) {
00283     case LOAD_NORMAL:
00284     case LOAD_NORMAL_REPLACE:
00285     case LOAD_BYPASS_HISTORY:
00286       origin = NS_LITERAL_STRING("typed");
00287       break;
00288     case LOAD_NORMAL_EXTERNAL:
00289       origin = NS_LITERAL_STRING("external");
00290       break;
00291     case LOAD_HISTORY:
00292       origin = NS_LITERAL_STRING("session-history");
00293       break;
00294     case LOAD_RELOAD_NORMAL:
00295     case LOAD_RELOAD_BYPASS_CACHE:
00296     case LOAD_RELOAD_BYPASS_PROXY:
00297     case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE:
00298     case LOAD_RELOAD_CHARSET_CHANGE:
00299       origin = NS_LITERAL_STRING("reload");
00300       break;
00301     case LOAD_LINK:
00302       origin = NS_LITERAL_STRING("link");
00303       break;
00304     case LOAD_REFRESH:
00305       origin = NS_LITERAL_STRING("refresh");
00306       break;
00307     default:
00308       break;
00309     }
00310     if (!origin.IsEmpty()) {
00311       rv = props->SetPropertyAsAString(NS_LITERAL_STRING("origin"), origin);
00312       NS_ENSURE_SUCCESS(rv, rv);
00313     }
00314     entry.startTime = PR_Now();
00315     NS_ENSURE_TRUE(mRequestMap.Put(request, entry), NS_ERROR_OUT_OF_MEMORY);
00316   } else if (flags & STATE_STOP) {
00317     RequestEntry entry;
00318     if (mRequestMap.Get(request, &entry)) {
00319       mRequestMap.Remove(request);
00320 
00321       // Log a <document action="load"> event
00322       nsIWritablePropertyBag2 *props = entry.properties;
00323       rv = props->SetPropertyAsACString(NS_LITERAL_STRING("action"),
00324                                         NS_LITERAL_CSTRING("load"));
00325       NS_ENSURE_SUCCESS(rv, rv);
00326       
00327       // Compute the load time now that we have the end time.
00328       PRInt64 loadTime = (PR_Now() - entry.startTime) / PR_USEC_PER_MSEC;
00329       rv = props->SetPropertyAsUint64(NS_LITERAL_STRING("loadtime"), loadTime);
00330       NS_ENSURE_SUCCESS(rv, rv);
00331 
00332       MemUsage mu;
00333       if (GetMemUsage(&mu)) {
00334         rv = props->SetPropertyAsUint64(NS_LITERAL_STRING("memtotal"), mu.total);
00335         NS_ENSURE_SUCCESS(rv, rv);
00336         rv = props->SetPropertyAsUint64(NS_LITERAL_STRING("memresident"), mu.resident);
00337         NS_ENSURE_SUCCESS(rv, rv);
00338       }
00339 
00340       // Look up the document id, or assign a new one
00341       nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(webProgress);
00342       nsCOMPtr<nsIDOMWindow> window = do_GetInterface(docShell);
00343       if (!window) {
00344         MS_LOG(("Couldn't get window"));
00345         return NS_ERROR_UNEXPECTED;
00346       }
00347 
00348       nsCOMPtr<nsIDOMDocument> domDoc;
00349       window->GetDocument(getter_AddRefs(domDoc));
00350       nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
00351       if (!doc) {
00352         MS_LOG(("Couldn't get document"));
00353         return NS_ERROR_UNEXPECTED;
00354       }
00355 
00356       nsCOMPtr<nsIDocShellTreeItem> item = do_QueryInterface(docShell);
00357       NS_ENSURE_STATE(item);
00358       PRBool subframe = nsMetricsUtils::IsSubframe(item);
00359       if (subframe) {
00360         rv = props->SetPropertyAsBool(NS_LITERAL_STRING("subframe"), PR_TRUE);
00361         NS_ENSURE_SUCCESS(rv, rv);
00362       }
00363 
00364       nsMetricsService *ms = nsMetricsService::get();
00365       DocumentEntry docEntry;
00366       if (!mDocumentMap.Get(doc, &docEntry)) {
00367         docEntry.docID = mNextDocID++;
00368         docEntry.subframe = subframe;
00369 
00370         if (!ms->WindowMap().Get(window, &docEntry.windowID)) {
00371           MS_LOG(("Window not in the window map"));
00372           return NS_ERROR_UNEXPECTED;
00373         }
00374 
00375         NS_ENSURE_TRUE(mDocumentMap.Put(doc, docEntry),
00376                        NS_ERROR_OUT_OF_MEMORY);
00377       }
00378       doc->AddObserver(this);  // set up to log the document destroy
00379 
00380       rv = props->SetPropertyAsUint32(NS_LITERAL_STRING("docid"),
00381                                       docEntry.docID);
00382       NS_ENSURE_SUCCESS(rv, rv);
00383 
00384       // If this was a load of a chrome document, hash the URL of the document
00385       // so it can be identified.
00386 
00387       nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
00388       if (channel) {
00389         nsCOMPtr<nsIURI> uri;
00390         channel->GetURI(getter_AddRefs(uri));
00391         if (uri) {
00392           PRBool isChrome = PR_FALSE;
00393           uri->SchemeIs("chrome", &isChrome);
00394           if (isChrome) {
00395             nsCString spec;
00396             uri->GetSpec(spec);
00397 
00398             nsCString hashedSpec;
00399             rv = ms->HashUTF8(spec, hashedSpec);
00400             NS_ENSURE_SUCCESS(rv, rv);
00401 
00402             rv = props->SetPropertyAsACString(NS_LITERAL_STRING("urlhash"),
00403                                               hashedSpec);
00404             NS_ENSURE_SUCCESS(rv, rv);
00405           }
00406         }
00407       }
00408 
00409       rv = ms->LogEvent(NS_LITERAL_STRING("document"), props);
00410       NS_ENSURE_SUCCESS(rv, rv);
00411     } else {
00412       NS_WARNING("STATE_STOP without STATE_START");
00413     }
00414   }
00415 
00416   return NS_OK;
00417 }
00418 
00419 NS_IMETHODIMP
00420 nsLoadCollector::OnProgressChange(nsIWebProgress *webProgress,
00421                                   nsIRequest *request,
00422                                   PRInt32 curSelfProgress,
00423                                   PRInt32 maxSelfProgress,
00424                                   PRInt32 curTotalProgress,
00425                                   PRInt32 maxTotalProgress)
00426 {
00427   return NS_OK;
00428 }
00429 
00430 NS_IMETHODIMP
00431 nsLoadCollector::OnLocationChange(nsIWebProgress *webProgress,
00432                                   nsIRequest *request, nsIURI *location)
00433 {
00434   return NS_OK;
00435 }
00436 
00437 NS_IMETHODIMP
00438 nsLoadCollector::OnStatusChange(nsIWebProgress *webProgress,
00439                                 nsIRequest *request,
00440                                 nsresult status, const PRUnichar *messaage)
00441 {
00442   return NS_OK;
00443 }
00444 
00445 NS_IMETHODIMP
00446 nsLoadCollector::OnSecurityChange(nsIWebProgress *webProgress,
00447                                   nsIRequest *request, PRUint32 state)
00448 {
00449   return NS_OK;
00450 }
00451 
00452 nsresult
00453 nsLoadCollector::Init()
00454 {
00455   NS_ENSURE_TRUE(mRequestMap.Init(32), NS_ERROR_OUT_OF_MEMORY);
00456   return NS_OK;
00457 }
00458 
00459 NS_IMETHODIMP
00460 nsLoadCollector::OnAttach()
00461 {
00462   // Attach the LoadCollector as a global web progress listener
00463   nsCOMPtr<nsIWebProgress> progress =
00464     do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
00465   NS_ENSURE_STATE(progress);
00466   
00467   nsresult rv = progress->AddProgressListener(
00468       this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
00469   NS_ENSURE_SUCCESS(rv, rv);
00470 
00471   return NS_OK;
00472 }
00473 
00474 /* static */ PLDHashOperator PR_CALLBACK
00475 nsLoadCollector::RemoveDocumentFromMap(const nsIDocument *document,
00476                                        DocumentEntry &entry, void *userData)
00477 {
00478   nsIDocument *mutable_doc = NS_CONST_CAST(nsIDocument*, document);
00479   mutable_doc->RemoveObserver(NS_STATIC_CAST(nsLoadCollector*, userData));
00480   return PL_DHASH_REMOVE;
00481 }
00482 
00483 NS_IMETHODIMP
00484 nsLoadCollector::OnDetach()
00485 {
00486   // Clear the request and document maps so we start fresh
00487   // next time we're attached
00488   mRequestMap.Clear();
00489   mDocumentMap.Enumerate(RemoveDocumentFromMap, this);
00490 
00491   // Remove the progress listener
00492   nsCOMPtr<nsIWebProgress> progress =
00493     do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID);
00494   NS_ENSURE_STATE(progress);
00495   
00496   nsresult rv = progress->RemoveProgressListener(this);
00497   NS_ENSURE_SUCCESS(rv, rv);
00498 
00499   return NS_OK;
00500 }
00501 
00502 NS_IMETHODIMP
00503 nsLoadCollector::OnNewLog()
00504 {
00505   return NS_OK;
00506 }
00507 
00508 NS_IMPL_NSIDOCUMENTOBSERVER_LOAD_STUB(nsLoadCollector)
00509 #ifdef MOZILLA_1_8_BRANCH
00510 NS_IMPL_NSIDOCUMENTOBSERVER_REFLOW_STUB(nsLoadCollector)
00511 #endif
00512 NS_IMPL_NSIDOCUMENTOBSERVER_STATE_STUB(nsLoadCollector)
00513 NS_IMPL_NSIDOCUMENTOBSERVER_CONTENT(nsLoadCollector)
00514 NS_IMPL_NSIDOCUMENTOBSERVER_STYLE_STUB(nsLoadCollector)
00515 
00516 void
00517 nsLoadCollector::BeginUpdate(nsIDocument *document, nsUpdateType updateType)
00518 {
00519 }
00520 
00521 void
00522 nsLoadCollector::EndUpdate(nsIDocument *document, nsUpdateType updateType)
00523 {
00524 }
00525 
00526 void
00527 nsLoadCollector::DocumentWillBeDestroyed(nsIDocument *document)
00528 {
00529   // Look up the document to get its id.
00530   DocumentEntry entry;
00531   if (!mDocumentMap.Get(document, &entry)) {
00532     MS_LOG(("Document not in map!"));
00533     return;
00534   }
00535 
00536   mDocumentMap.Remove(document);
00537 
00538   nsCOMPtr<nsIWritablePropertyBag2> props;
00539   nsMetricsUtils::NewPropertyBag(getter_AddRefs(props));
00540   if (!props) {
00541     return;
00542   }
00543 
00544   props->SetPropertyAsACString(NS_LITERAL_STRING("action"),
00545                                NS_LITERAL_CSTRING("destroy"));
00546   props->SetPropertyAsUint32(NS_LITERAL_STRING("docid"), entry.docID);
00547   props->SetPropertyAsUint32(NS_LITERAL_STRING("window"), entry.windowID);
00548   if (entry.subframe) {
00549     props->SetPropertyAsBool(NS_LITERAL_STRING("subframe"), PR_TRUE);
00550   }
00551 
00552   MemUsage mu;
00553   if (GetMemUsage(&mu)) {
00554     props->SetPropertyAsUint64(NS_LITERAL_STRING("memtotal"), mu.total);
00555     props->SetPropertyAsUint64(NS_LITERAL_STRING("memresident"), mu.resident);
00556   }
00557 
00558   nsMetricsService *ms = nsMetricsService::get();
00559   if (ms) {
00560     ms->LogEvent(NS_LITERAL_STRING("document"), props);
00561 #ifdef PR_LOGGING
00562     nsIURI *uri = document->GetDocumentURI();
00563     if (uri) {
00564       nsCString spec;
00565       uri->GetSpec(spec);
00566       MS_LOG(("LoadCollector: Logged document destroy for %s\n", spec.get()));
00567     }
00568 #endif
00569   }
00570 }