Bug 867762: NSPR and SQLite Main Thread I/O Interposing. r=BenWa
authorAaron Klotz <aklotz@mozilla.com>
Fri, 14 Jun 2013 12:01:02 -0600
changeset 135101 1a0ff6b97e035ddc3be189a8db8a0a75493177c1
parent 135100 5f243d3f45beae0ed65011357e2f4274a89dd164
child 135102 a6d60a556ed4dbcc24e475c0c82ead4304d882f5
push id29519
push useraklotz@mozilla.com
push dateFri, 14 Jun 2013 18:02:10 +0000
treeherdermozilla-inbound@1a0ff6b97e03 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersBenWa
bugs867762
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 867762: NSPR and SQLite Main Thread I/O Interposing. r=BenWa
storage/src/TelemetryVFS.cpp
tools/profiler/IOInterposer.cpp
tools/profiler/IOInterposer.h
tools/profiler/NSPRInterposer.cpp
tools/profiler/NSPRInterposer.h
tools/profiler/ProfilerIOInterposeObserver.cpp
tools/profiler/ProfilerIOInterposeObserver.h
tools/profiler/SQLiteInterposer.cpp
tools/profiler/SQLiteInterposer.h
tools/profiler/TableTicker.h
tools/profiler/moz.build
tools/profiler/platform.cpp
--- a/storage/src/TelemetryVFS.cpp
+++ b/storage/src/TelemetryVFS.cpp
@@ -7,16 +7,17 @@
 #include <string.h>
 #include "mozilla/Telemetry.h"
 #include "mozilla/Preferences.h"
 #include "sqlite3.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Util.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/QuotaObject.h"
+#include "mozilla/SQLiteInterposer.h"
 
 /**
  * This preference is a workaround to allow users/sysadmins to identify
  * that the profile exists on an NFS share whose implementation
  * is incompatible with SQLite's default locking implementation.
  * Bug 433129 attempted to automatically identify such file-systems, 
  * but a reliable way was not found and it was determined that the fallback 
  * locking is slower than POSIX locking, so we do not want to do it by default.
@@ -61,32 +62,45 @@ public:
   /** 
    * IOThreadAutoTimer measures time spent in IO. Additionally it
    * automatically determines whether IO is happening on the main
    * thread and picks an appropriate histogram.
    *
    * @param id takes a telemetry histogram id. The id+1 must be an
    * equivalent histogram for the main thread. Eg, MOZ_SQLITE_OPEN_MS 
    * is followed by MOZ_SQLITE_OPEN_MAIN_THREAD_MS.
+   *
+   * @param evtFn optionally takes a function pointer to one of the
+   * SQLiteInterposer event handler functions (OnRead, OnWrite, OnFSync).
+   * Once the end TimeStamp has been determined, if the I/O occurred on the
+   * main thread then evtFn will be called with the calculated duration.
    */
-  IOThreadAutoTimer(Telemetry::ID id)
+  IOThreadAutoTimer(Telemetry::ID id,
+                    SQLiteInterposer::EventHandlerFn evtFn = nullptr)
     : start(TimeStamp::Now()),
-      id(id)
+      id(id),
+      evtFn(evtFn)
   {
   }
 
   ~IOThreadAutoTimer() {
+    TimeStamp end(TimeStamp::Now());
     uint32_t mainThread = NS_IsMainThread() ? 1 : 0;
     Telemetry::AccumulateTimeDelta(static_cast<Telemetry::ID>(id + mainThread),
-                                   start);
+                                   start, end);
+    if (evtFn && mainThread) {
+      double duration = (end - start).ToMilliseconds();
+      evtFn(duration);
+    }
   }
 
 private:
   const TimeStamp start;
   const Telemetry::ID id;
+  SQLiteInterposer::EventHandlerFn evtFn;
 };
 
 struct telemetry_file {
   // Base class.  Must be first
   sqlite3_file base;
 
   // histograms pertaining to this file
   Histograms *histograms;
@@ -117,17 +131,17 @@ xClose(sqlite3_file *pFile)
 
 /*
 ** Read data from a telemetry_file.
 */
 int
 xRead(sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst)
 {
   telemetry_file *p = (telemetry_file *)pFile;
-  IOThreadAutoTimer ioTimer(p->histograms->readMS);
+  IOThreadAutoTimer ioTimer(p->histograms->readMS, &SQLiteInterposer::OnRead);
   int rc;
   rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst);
   // sqlite likes to read from empty files, this is normal, ignore it.
   if (rc != SQLITE_IOERR_SHORT_READ)
     Telemetry::Accumulate(p->histograms->readB, rc == SQLITE_OK ? iAmt : 0);
   return rc;
 }
 
@@ -136,17 +150,17 @@ xRead(sqlite3_file *pFile, void *zBuf, i
 */
 int
 xWrite(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst)
 {
   telemetry_file *p = (telemetry_file *)pFile;
   if (p->quotaObject && !p->quotaObject->MaybeAllocateMoreSpace(iOfst, iAmt)) {
     return SQLITE_FULL;
   }
-  IOThreadAutoTimer ioTimer(p->histograms->writeMS);
+  IOThreadAutoTimer ioTimer(p->histograms->writeMS, &SQLiteInterposer::OnWrite);
   int rc;
   rc = p->pReal->pMethods->xWrite(p->pReal, zBuf, iAmt, iOfst);
   Telemetry::Accumulate(p->histograms->writeB, rc == SQLITE_OK ? iAmt : 0);
   return rc;
 }
 
 /*
 ** Truncate a telemetry_file.
@@ -167,17 +181,17 @@ xTruncate(sqlite3_file *pFile, sqlite_in
 
 /*
 ** Sync a telemetry_file.
 */
 int
 xSync(sqlite3_file *pFile, int flags)
 {
   telemetry_file *p = (telemetry_file *)pFile;
-  IOThreadAutoTimer ioTimer(p->histograms->syncMS);
+  IOThreadAutoTimer ioTimer(p->histograms->syncMS, &SQLiteInterposer::OnFSync);
   return p->pReal->pMethods->xSync(p->pReal, flags);
 }
 
 /*
 ** Return the current file-size of a telemetry_file.
 */
 int
 xFileSize(sqlite3_file *pFile, sqlite_int64 *pSize)
new file mode 100644
--- /dev/null
+++ b/tools/profiler/IOInterposer.cpp
@@ -0,0 +1,154 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IOInterposer.h"
+#include "NSPRInterposer.h"
+#include "SQLiteInterposer.h"
+
+using namespace mozilla;
+
+static StaticAutoPtr<IOInterposer> sSingleton;
+
+/* static */ IOInterposer*
+IOInterposer::GetInstance()
+{
+  if (!sSingleton) {
+    // We can't actually use this assertion because we initialize this code
+    // before XPCOM is initialized, so NS_IsMainThread() always returns false.
+    // MOZ_ASSERT(NS_IsMainThread());
+    sSingleton = new IOInterposer();
+    sSingleton->Init();
+  }
+
+  return sSingleton.get();
+}
+
+/* static */ void
+IOInterposer::ClearInstance()
+{
+  // We can't actually use this assertion because we execute this code
+  // after XPCOM is shut down, so NS_IsMainThread() always returns false.
+  // MOZ_ASSERT(NS_IsMainThread());
+  sSingleton = nullptr;
+}
+
+IOInterposer::IOInterposer()
+  :mMutex("IOInterposer::mMutex")
+{
+  // We can't actually use this assertion because we initialize this code
+  // before XPCOM is initialized, so NS_IsMainThread() always returns false.
+  // MOZ_ASSERT(NS_IsMainThread());
+}
+
+IOInterposer::~IOInterposer()
+{
+  // We can't actually use this assertion because we execute this code
+  // after XPCOM is shut down, so NS_IsMainThread() always returns false.
+  // MOZ_ASSERT(NS_IsMainThread());
+  Enable(false);
+  NSPRInterposer::ClearInstance();
+  SQLiteInterposer::ClearInstance();
+}
+
+bool
+IOInterposer::Init()
+{
+  // We can't actually use this assertion because we initialize this code
+  // before XPCOM is initialized, so NS_IsMainThread() always returns false.
+  // MOZ_ASSERT(NS_IsMainThread());
+  mozilla::MutexAutoLock lock(mMutex);
+  IOInterposerModule* nsprModule = NSPRInterposer::GetInstance(this,
+      IOInterposeObserver::OpAll);
+  if (!nsprModule) {
+    return false;
+  }
+
+  IOInterposerModule* sqlModule = SQLiteInterposer::GetInstance(this,
+      IOInterposeObserver::OpAll);
+  if (!sqlModule) {
+    return false;
+  }
+
+  mModules.AppendElement(nsprModule);
+  mModules.AppendElement(sqlModule);
+  return true;
+}
+
+void
+IOInterposer::Enable(bool aEnable)
+{
+  mozilla::MutexAutoLock lock(mMutex);
+  for (PRUint32 i = 0; i < mModules.Length(); ++i ) {
+    mModules[i]->Enable(aEnable);
+  }
+}
+
+void
+IOInterposer::Register(IOInterposeObserver::Operation aOp,
+                       IOInterposeObserver* aObserver)
+{
+  // We can't actually use this assertion because we initialize this code
+  // before XPCOM is initialized, so NS_IsMainThread() always returns false.
+  // MOZ_ASSERT(NS_IsMainThread());
+  if (aOp & IOInterposeObserver::OpRead) {
+    mReadObservers.AppendElement(aObserver);
+  }
+  if (aOp & IOInterposeObserver::OpWrite) {
+    mWriteObservers.AppendElement(aObserver);
+  }
+  if (aOp & IOInterposeObserver::OpFSync) {
+    mFSyncObservers.AppendElement(aObserver);
+  }
+}
+
+void
+IOInterposer::Deregister(IOInterposeObserver::Operation aOp,
+                         IOInterposeObserver* aObserver)
+{
+  // We can't actually use this assertion because we execute this code
+  // after XPCOM is shut down, so NS_IsMainThread() always returns false.
+  // MOZ_ASSERT(NS_IsMainThread());
+  if (aOp & IOInterposeObserver::OpRead) {
+    mReadObservers.RemoveElement(aObserver);
+  }
+  if (aOp & IOInterposeObserver::OpWrite) {
+    mWriteObservers.RemoveElement(aObserver);
+  }
+  if (aOp & IOInterposeObserver::OpFSync) {
+    mFSyncObservers.RemoveElement(aObserver);
+  }
+}
+
+void
+IOInterposer::Observe(IOInterposeObserver::Operation aOp, double& aDuration,
+                      const char* aModuleInfo)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  switch (aOp) {
+    case IOInterposeObserver::OpRead:
+      {
+        for (PRUint32 i = 0; i < mReadObservers.Length(); ++i) {
+          mReadObservers[i]->Observe(aOp, aDuration, aModuleInfo);
+        }
+      }
+      break;
+    case IOInterposeObserver::OpWrite:
+      {
+        for (PRUint32 i = 0; i < mWriteObservers.Length(); ++i) {
+          mWriteObservers[i]->Observe(aOp, aDuration, aModuleInfo);
+        }
+      }
+      break;
+    case IOInterposeObserver::OpFSync:
+      {
+        for (PRUint32 i = 0; i < mFSyncObservers.Length(); ++i) {
+          mFSyncObservers[i]->Observe(aOp, aDuration, aModuleInfo);
+        }
+      }
+      break;
+    default:
+      break;
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/tools/profiler/IOInterposer.h
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef IOINTERPOSER_H_
+#define IOINTERPOSER_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/XPCOM.h"
+
+namespace mozilla {
+
+/**
+ * Interface for I/O interposer observers. This is separate from
+ * IOInterposer itself because in the future we might use the I/O
+ * Interposer for shutdown write poisioning.
+ */
+class IOInterposeObserver
+{
+public:
+  enum Operation
+  {
+    OpNone = 0,
+    OpRead = (1 << 0),
+    OpWrite = (1 << 1),
+    OpFSync = (1 << 2),
+    OpWriteFSync = (OpWrite | OpFSync),
+    OpAll = (OpRead | OpWrite | OpFSync)
+  };
+  virtual void Observe(Operation aOp, double& aDuration,
+                       const char* aModuleInfo) = 0;
+};
+
+/**
+ * Interface for I/O Interposer modules that actually generate the I/O
+ * Interposer events. Some concrete examples include NSPR, SQLite, ETW
+ */
+class IOInterposerModule
+{
+public:
+  virtual ~IOInterposerModule() {}
+  virtual void Enable(bool aEnable) = 0;
+
+protected:
+  IOInterposerModule() {}
+
+private:
+  IOInterposerModule(const IOInterposerModule&);
+  IOInterposerModule& operator=(const IOInterposerModule&);
+};
+
+/**
+ * This class is reponsible for managing the state of I/O interposer
+ * modules and ensuring that events are routed to the appropriate
+ * observers. All methods except for Enable should be called from the
+ * main thread.
+ */
+class IOInterposer MOZ_FINAL : public IOInterposeObserver
+{
+public:
+  ~IOInterposer();
+
+  /**
+   * It is safe to call this method off of the main thread.
+   *
+   * @param aEnable true to enable all modules, false to disable all modules
+   */
+  void Enable(bool aEnable);
+
+  void Register(IOInterposeObserver::Operation aOp,
+                IOInterposeObserver* aObserver);
+  void Deregister(IOInterposeObserver::Operation aOp,
+                  IOInterposeObserver* aObserver);
+  void Observe(IOInterposeObserver::Operation aOp, double& aDuration,
+               const char* aModuleInfo);
+
+  static IOInterposer* GetInstance();
+
+  /**
+   * This function must be called from the main thread, and furthermore
+   * it must be called when no other threads are executing. Effectivly this
+   * restricts us to calling it only during shutdown.
+   */
+  static void ClearInstance();
+
+private:
+  IOInterposer();
+  bool Init();
+  IOInterposer(const IOInterposer&);
+  IOInterposer& operator=(const IOInterposer&);
+
+  // mMutex guards access to mModules, particularly to provide
+  // mutual exclusion during Enable()
+  mozilla::Mutex  mMutex;
+  nsTArray<IOInterposerModule*>   mModules;
+  nsTArray<IOInterposeObserver*>  mReadObservers;
+  nsTArray<IOInterposeObserver*>  mWriteObservers;
+  nsTArray<IOInterposeObserver*>  mFSyncObservers;
+};
+
+} // namespace mozilla
+
+#endif // IOINTERPOSER_H_
+
new file mode 100644
--- /dev/null
+++ b/tools/profiler/NSPRInterposer.cpp
@@ -0,0 +1,139 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NSPRInterposer.h"
+
+#include "private/pprio.h"
+
+using namespace mozilla;
+
+StaticAutoPtr<NSPRInterposer> NSPRInterposer::sSingleton;
+const char* NSPRAutoTimer::sModuleInfo = "NSPR";
+
+/* static */ IOInterposerModule*
+NSPRInterposer::GetInstance(IOInterposeObserver* aObserver,
+                            IOInterposeObserver::Operation aOpsToInterpose)
+{
+  // We can't actually use this assertion because we initialize this code
+  // before XPCOM is initialized, so NS_IsMainThread() always returns false.
+  // MOZ_ASSERT(NS_IsMainThread());
+  if (!sSingleton) {
+    nsAutoPtr<NSPRInterposer> newObj(new NSPRInterposer());
+    if (!newObj->Init(aObserver, aOpsToInterpose)) {
+      return nullptr;
+    }
+    sSingleton = newObj.forget();
+  }
+  return sSingleton;
+}
+
+/* static */ void
+NSPRInterposer::ClearInstance()
+{
+  // We can't actually use this assertion because we execute this code
+  // after XPCOM is shut down, so NS_IsMainThread() always returns false.
+  // MOZ_ASSERT(NS_IsMainThread());
+  sSingleton = nullptr;
+}
+
+NSPRInterposer::NSPRInterposer()
+  :mObserver(nullptr),
+   mFileIOMethods(nullptr),
+   mEnabled(false),
+   mOrigReadFn(nullptr),
+   mOrigWriteFn(nullptr),
+   mOrigFSyncFn(nullptr)
+{
+}
+
+NSPRInterposer::~NSPRInterposer()
+{
+  // We can't actually use this assertion because we execute this code
+  // after XPCOM is shut down, so NS_IsMainThread() always returns false.
+  // MOZ_ASSERT(NS_IsMainThread());
+  Enable(false);
+  mFileIOMethods->read = mOrigReadFn;
+  mFileIOMethods->write = mOrigWriteFn;
+  mFileIOMethods->fsync = mOrigFSyncFn;
+  sSingleton = nullptr;
+}
+
+bool
+NSPRInterposer::Init(IOInterposeObserver* aObserver,
+                     IOInterposeObserver::Operation aOpsToInterpose)
+{
+  // We can't actually use this assertion because we initialize this code
+  // before XPCOM is initialized, so NS_IsMainThread() always returns false.
+  // MOZ_ASSERT(NS_IsMainThread());
+  if (!aObserver || !(aOpsToInterpose & IOInterposeObserver::OpAll)) {
+    return false;
+  }
+  mObserver = aObserver;
+  // Yes, this is not very kosher; now show me an example of function
+  // interposing an unsuspecting target that /is/.
+  mFileIOMethods = const_cast<PRIOMethods*>(PR_GetFileMethods());
+  if (!mFileIOMethods) {
+    return false;
+  }
+  mOrigReadFn = mFileIOMethods->read;
+  mOrigWriteFn = mFileIOMethods->write;
+  mOrigFSyncFn = mFileIOMethods->fsync;
+  if (!mOrigReadFn || !mOrigWriteFn || !mOrigFSyncFn) {
+    return false;
+  }
+  if (aOpsToInterpose & IOInterposeObserver::OpRead) {
+    mFileIOMethods->read = &NSPRInterposer::Read;
+  }
+  if (aOpsToInterpose & IOInterposeObserver::OpWrite) {
+    mFileIOMethods->write = &NSPRInterposer::Write;
+  }
+  if (aOpsToInterpose & IOInterposeObserver::OpFSync) {
+    mFileIOMethods->fsync = &NSPRInterposer::FSync;
+  }
+  return true;
+}
+
+void
+NSPRInterposer::Enable(bool aEnable)
+{
+  mEnabled = aEnable ? 1 : 0;
+}
+
+PRInt32 PR_CALLBACK
+NSPRInterposer::Read(PRFileDesc* aFd, void* aBuf, PRInt32 aAmt)
+{
+  // If we don't have valid pointers, something is very wrong.
+  NS_ASSERTION(sSingleton, "NSPRInterposer::sSingleton not available!");
+  NS_ASSERTION(sSingleton->mOrigReadFn, "mOrigReadFn not available!");
+  NS_ASSERTION(sSingleton->mObserver, "NSPRInterposer not initialized!");
+
+  NSPRAutoTimer timer(IOInterposeObserver::OpRead);
+  return sSingleton->mOrigReadFn(aFd, aBuf, aAmt);
+}
+
+
+PRInt32 PR_CALLBACK
+NSPRInterposer::Write(PRFileDesc* aFd, const void* aBuf, PRInt32 aAmt)
+{
+  // If we don't have valid pointers, something is very wrong.
+  NS_ASSERTION(sSingleton, "NSPRInterposer::sSingleton not available!");
+  NS_ASSERTION(sSingleton->mOrigWriteFn, "mOrigWriteFn not available!");
+  NS_ASSERTION(sSingleton->mObserver, "NSPRInterposer not initialized!");
+
+  NSPRAutoTimer timer(IOInterposeObserver::OpWrite);
+  return sSingleton->mOrigWriteFn(aFd, aBuf, aAmt);
+}
+
+PRStatus PR_CALLBACK
+NSPRInterposer::FSync(PRFileDesc* aFd)
+{
+  // If we don't have valid pointers, something is very wrong.
+  NS_ASSERTION(sSingleton, "NSPRInterposer::sSingleton not available!");
+  NS_ASSERTION(sSingleton->mOrigFSyncFn, "mOrigFSyncFn not available!");
+  NS_ASSERTION(sSingleton->mObserver, "NSPRInterposer not initialized!");
+
+  NSPRAutoTimer timer(IOInterposeObserver::OpFSync);
+  return sSingleton->mOrigFSyncFn(aFd);
+}
+
new file mode 100644
--- /dev/null
+++ b/tools/profiler/NSPRInterposer.h
@@ -0,0 +1,100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSPRINTERPOSER_H_
+#define NSPRINTERPOSER_H_
+
+#ifdef MOZ_ENABLE_PROFILER_SPS
+
+#include "IOInterposer.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/TimeStamp.h"
+#include "prio.h"
+
+namespace mozilla {
+
+/**
+ * This class provides main thread I/O interposing for NSPR file reads, writes,
+ * and fsyncs. Most methods must be called from the main thread, except for:
+ *   Enable
+ *   Read
+ *   Write
+ *   FSync
+ */
+class NSPRInterposer MOZ_FINAL : public IOInterposerModule
+{
+public:
+  static IOInterposerModule* GetInstance(IOInterposeObserver* aObserver, 
+      IOInterposeObserver::Operation aOpsToInterpose);
+
+  /**
+   * This function must be called from the main thread, and furthermore
+   * it must be called when no other threads are executing in Read, Write,
+   * or FSync. Effectivly this restricts us to calling it only after all other
+   * threads have been stopped during shutdown.
+   */
+  static void ClearInstance();
+
+  ~NSPRInterposer();
+  void Enable(bool aEnable);
+
+private:
+  NSPRInterposer();
+  bool Init(IOInterposeObserver* aObserver,
+            IOInterposeObserver::Operation aOpsToInterpose);
+
+  static PRInt32 PR_CALLBACK Read(PRFileDesc* aFd, void* aBuf, PRInt32 aAmt);
+  static PRInt32 PR_CALLBACK Write(PRFileDesc* aFd, const void* aBuf,
+                                   PRInt32 aAmt);
+  static PRStatus PR_CALLBACK FSync(PRFileDesc* aFd);
+  static StaticAutoPtr<NSPRInterposer> sSingleton;
+
+  friend class NSPRAutoTimer;
+
+  IOInterposeObserver*  mObserver;
+  PRIOMethods*          mFileIOMethods;
+  Atomic<uint32_t,ReleaseAcquire> mEnabled;
+  PRReadFN              mOrigReadFn;
+  PRWriteFN             mOrigWriteFn;
+  PRFsyncFN             mOrigFSyncFn;
+};
+
+/**
+ * RAII class for timing the duration of an NSPR I/O call and reporting the
+ * result to the IOInterposeObserver.
+ */
+class NSPRAutoTimer
+{
+public:
+  NSPRAutoTimer(IOInterposeObserver::Operation aOp)
+    :mOp(aOp),
+     mShouldObserve(NSPRInterposer::sSingleton->mEnabled && NS_IsMainThread())
+  {
+    if (mShouldObserve) {
+      mStart = mozilla::TimeStamp::Now();
+    }
+  }
+
+  ~NSPRAutoTimer()
+  {
+    if (mShouldObserve) {
+      double duration = (mozilla::TimeStamp::Now() - mStart).ToMilliseconds();
+      NSPRInterposer::sSingleton->mObserver->Observe(mOp, duration,
+                                                     sModuleInfo);
+    }
+  }
+
+private:
+  IOInterposeObserver::Operation  mOp;
+  bool                            mShouldObserve;
+  mozilla::TimeStamp              mStart;
+  static const char*              sModuleInfo;
+};
+
+} // namespace mozilla
+
+#endif // MOZ_ENABLE_PROFILER_SPS
+
+#endif // NSPRINTERPOSER_H_
+
new file mode 100644
--- /dev/null
+++ b/tools/profiler/ProfilerIOInterposeObserver.cpp
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ProfilerIOInterposeObserver.h"
+
+#include "GeckoProfiler.h"
+
+using namespace mozilla;
+
+ProfilerIOInterposeObserver::ProfilerIOInterposeObserver()
+{
+  IOInterposer* interposer = IOInterposer::GetInstance();
+  if (interposer) {
+    interposer->Register(IOInterposeObserver::OpAll, this);
+  }
+}
+
+ProfilerIOInterposeObserver::~ProfilerIOInterposeObserver()
+{
+  IOInterposer* interposer = IOInterposer::GetInstance();
+  if (interposer) {
+    interposer->Deregister(IOInterposeObserver::OpAll, this);
+  }
+}
+
+void ProfilerIOInterposeObserver::Observe(IOInterposeObserver::Operation aOp,
+                                          double& aDuration,
+                                          const char* aModuleInfo)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  const char* ops[] = {"none", "read", "write", "invalid", "fsync"};
+  PROFILER_MARKER(ops[aOp]);
+}
+
new file mode 100644
--- /dev/null
+++ b/tools/profiler/ProfilerIOInterposeObserver.h
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PROFILERIOINTERPOSEOBSERVER_H
+#define PROFILERIOINTERPOSEOBSERVER_H
+
+#ifdef MOZ_ENABLE_PROFILER_SPS
+
+#include "mozilla/IOInterposer.h"
+
+namespace mozilla {
+
+/**
+ * This class is the observer that calls into the profiler whenever
+ * main thread I/O occurs. It should only ever be called on the main thread.
+ */
+class ProfilerIOInterposeObserver MOZ_FINAL : public IOInterposeObserver
+{
+public:
+  ProfilerIOInterposeObserver();
+  ~ProfilerIOInterposeObserver();
+
+  virtual void Observe(Operation aOp, double& aDuration,
+                       const char* aModuleInfo);
+};
+
+} // namespace mozilla
+
+#endif // MOZ_ENABLE_PROFILER_SPS
+
+#endif // PROFILERIOINTERPOSEOBSERVER_H
+
new file mode 100644
--- /dev/null
+++ b/tools/profiler/SQLiteInterposer.cpp
@@ -0,0 +1,114 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SQLiteInterposer.h"
+
+using namespace mozilla;
+
+static StaticAutoPtr<SQLiteInterposer> sSingleton;
+static const char* sModuleInfo = "SQLite";
+
+/* static */ IOInterposerModule*
+SQLiteInterposer::GetInstance(IOInterposeObserver* aObserver,
+                              IOInterposeObserver::Operation aOpsToInterpose)
+{
+  // We can't actually use this assertion because we initialize this code
+  // before XPCOM is initialized, so NS_IsMainThread() always returns false.
+  // MOZ_ASSERT(NS_IsMainThread());
+  if (!sSingleton) {
+    nsAutoPtr<SQLiteInterposer> newObj(new SQLiteInterposer());
+    if (!newObj->Init(aObserver, aOpsToInterpose)) {
+      return nullptr;
+    }
+    sSingleton = newObj.forget();
+  }
+  return sSingleton;
+}
+
+/* static */ void
+SQLiteInterposer::ClearInstance()
+{
+  // We can't actually use this assertion because we execute this code
+  // after XPCOM is shut down, so NS_IsMainThread() always returns false.
+  // MOZ_ASSERT(NS_IsMainThread());
+  sSingleton = nullptr;
+}
+
+SQLiteInterposer::SQLiteInterposer()
+  :mObserver(nullptr),
+   mOps(IOInterposeObserver::OpNone),
+   mEnabled(false)
+{
+}
+
+SQLiteInterposer::~SQLiteInterposer()
+{
+  // We can't actually use this assertion because we execute this code
+  // after XPCOM is shut down, so NS_IsMainThread() always returns false.
+  // MOZ_ASSERT(NS_IsMainThread());
+  mOps = IOInterposeObserver::OpNone;
+  sSingleton = nullptr;
+  Enable(false);
+}
+
+bool
+SQLiteInterposer::Init(IOInterposeObserver* aObserver,
+                       IOInterposeObserver::Operation aOpsToInterpose)
+{
+  // We can't actually use this assertion because we initialize this code
+  // before XPCOM is initialized, so NS_IsMainThread() always returns false.
+  // MOZ_ASSERT(NS_IsMainThread());
+  if (!aObserver || !(aOpsToInterpose & IOInterposeObserver::OpAll)) {
+    return false;
+  }
+  mObserver = aObserver;
+  mOps = aOpsToInterpose;
+  return true;
+}
+
+void
+SQLiteInterposer::Enable(bool aEnable)
+{
+  mEnabled = aEnable ? 1 : 0;
+}
+
+/* static */ void
+SQLiteInterposer::OnRead(double& aDuration)
+{
+  if (!NS_IsMainThread() || !sSingleton) {
+    return;
+  }
+  if (sSingleton->mEnabled &&
+      (sSingleton->mOps & IOInterposeObserver::OpRead)) {
+    sSingleton->mObserver->Observe(IOInterposeObserver::OpRead, aDuration,
+                                   sModuleInfo);
+  }
+}
+
+/* static */ void
+SQLiteInterposer::OnWrite(double& aDuration)
+{
+  if (!NS_IsMainThread() || !sSingleton) {
+    return;
+  }
+  if (sSingleton->mEnabled && 
+      (sSingleton->mOps & IOInterposeObserver::OpWrite)) {
+    sSingleton->mObserver->Observe(IOInterposeObserver::OpWrite, aDuration,
+                                   sModuleInfo);
+  }
+}
+
+/* static */ void
+SQLiteInterposer::OnFSync(double& aDuration)
+{
+  if (!NS_IsMainThread() || !sSingleton) {
+    return;
+  }
+  if (sSingleton->mEnabled &&
+      (sSingleton->mOps & IOInterposeObserver::OpFSync)) {
+    sSingleton->mObserver->Observe(IOInterposeObserver::OpFSync, aDuration,
+                                   sModuleInfo);
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/tools/profiler/SQLiteInterposer.h
@@ -0,0 +1,74 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SQLITEINTERPOSER_H_
+#define SQLITEINTERPOSER_H_
+
+#ifdef MOZ_ENABLE_PROFILER_SPS
+
+#include "IOInterposer.h"
+#include "mozilla/Atomics.h"
+
+namespace mozilla {
+
+/**
+ * This class provides main thread I/O interposing for SQLite reads, writes,
+ * and fsyncs. Most methods must be called from the main thread, except for:
+ *   Enable
+ *   OnRead
+ *   OnWrite
+ *   OnFSync
+ */
+class SQLiteInterposer MOZ_FINAL : public IOInterposerModule
+{
+public:
+  static IOInterposerModule* GetInstance(IOInterposeObserver* aObserver, 
+      IOInterposeObserver::Operation aOpsToInterpose);
+
+  /**
+   * This function must be called from the main thread, and furthermore
+   * it must be called when no other threads are executing in OnRead, OnWrite,
+   * or OnFSync. Effectivly this restricts us to calling it only after all other
+   * threads have been stopped during shutdown.
+   */
+  static void ClearInstance();
+
+  ~SQLiteInterposer();
+  void Enable(bool aEnable);
+
+  typedef void (*EventHandlerFn)(double&);
+  static void OnRead(double& aDuration);
+  static void OnWrite(double& aDuration);
+  static void OnFSync(double& aDuration);
+
+private:
+  SQLiteInterposer();
+  bool Init(IOInterposeObserver* aObserver,
+            IOInterposeObserver::Operation aOpsToInterpose);
+
+  IOInterposeObserver*            mObserver;
+  IOInterposeObserver::Operation  mOps;
+  Atomic<uint32_t,ReleaseAcquire> mEnabled;
+};
+
+} // namespace mozilla
+
+#else // MOZ_ENABLE_PROFILER_SPS
+
+namespace mozilla {
+
+class SQLiteInterposer MOZ_FINAL
+{
+public:
+  typedef void (*EventHandlerFn)(double&);
+  static inline void OnRead(double& aDuration) {}
+  static inline void OnWrite(double& aDuration) {}
+  static inline void OnFSync(double& aDuration) {}
+};
+
+} // namespace mozilla
+
+#endif // MOZ_ENABLE_PROFILER_SPS
+
+#endif // SQLITEINTERPOSER_H_
--- a/tools/profiler/TableTicker.h
+++ b/tools/profiler/TableTicker.h
@@ -38,16 +38,17 @@ class TableTicker: public Sampler {
     //XXX: It's probably worth splitting the jank profiler out from the regular profiler at some point
     mJankOnly = hasFeature(aFeatures, aFeatureCount, "jank");
     mProfileJS = hasFeature(aFeatures, aFeatureCount, "js");
     mProfileJava = hasFeature(aFeatures, aFeatureCount, "java");
     mProfileThreads = hasFeature(aFeatures, aFeatureCount, "threads");
     mUnwinderThread = hasFeature(aFeatures, aFeatureCount, "unwinder") || sps_version2();
     mAddLeafAddresses = hasFeature(aFeatures, aFeatureCount, "leaf");
     mPrivacyMode = hasFeature(aFeatures, aFeatureCount, "privacy");
+    mAddMainThreadIO = hasFeature(aFeatures, aFeatureCount, "mainthreadio");
 
     sStartTime = TimeStamp::Now();
 
     {
       mozilla::MutexAutoLock lock(*sRegisteredThreadsMutex);
 
       // Create ThreadProfile for each registered thread
       for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
@@ -124,16 +125,17 @@ class TableTicker: public Sampler {
   virtual JSObject *ToJSObject(JSContext *aCx);
   JSCustomObject *GetMetaJSCustomObject(JSAObjectBuilder& b);
 
   bool HasUnwinderThread() const { return mUnwinderThread; }
   bool ProfileJS() const { return mProfileJS; }
   bool ProfileJava() const { return mProfileJava; }
   bool ProfileThreads() const { return mProfileThreads; }
   bool InPrivacyMode() const { return mPrivacyMode; }
+  bool AddMainThreadIO() const { return mAddMainThreadIO; }
 
 protected:
   // Called within a signal. This function must be reentrant
   virtual void UnwinderTick(TickSample* sample);
 
   // Called within a signal. This function must be reentrant
   virtual void InplaceTick(TickSample* sample);
 
@@ -148,10 +150,11 @@ protected:
   bool mAddLeafAddresses;
   bool mUseStackWalk;
   bool mJankOnly;
   bool mProfileJS;
   bool mProfileThreads;
   bool mUnwinderThread;
   bool mProfileJava;
   bool mPrivacyMode;
+  bool mAddMainThreadIO;
 };
 
--- a/tools/profiler/moz.build
+++ b/tools/profiler/moz.build
@@ -23,16 +23,20 @@ if CONFIG['MOZ_ENABLE_PROFILER_SPS']:
         'TableTicker.cpp',
         'SaveProfileTask.cpp',
         'BreakpadSampler.cpp',
         'UnwinderThread2.cpp',
         'ProfileEntry.cpp',
         'local_debug_info_symbolizer.cc',
         'JSObjectBuilder.cpp',
         'JSCustomObjectBuilder.cpp',
+        'IOInterposer.cpp',
+        'NSPRInterposer.cpp',
+        'ProfilerIOInterposeObserver.cpp',
+        'SQLiteInterposer.cpp',
     ]
 
     if CONFIG['OS_TARGET'] in ('Android', 'Linux'):
         CPP_SOURCES += [
             'shared-libraries-linux.cc',
             'platform-linux.cc',
         ]
     elif CONFIG['OS_TARGET'] == 'Darwin':
@@ -45,9 +49,14 @@ if CONFIG['MOZ_ENABLE_PROFILER_SPS']:
             'shared-libraries-win32.cc',
             'platform-win32.cc',
         ]
 
 EXPORTS += [
     'GeckoProfiler.h',
 ]
 
+EXPORTS.mozilla += [
+    'IOInterposer.h',
+    'SQLiteInterposer.h',
+]
+
 XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']
--- a/tools/profiler/platform.cpp
+++ b/tools/profiler/platform.cpp
@@ -2,16 +2,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <ostream>
 #include <fstream>
 #include <sstream>
 #include <errno.h>
 
+#include "IOInterposer.h"
+#include "ProfilerIOInterposeObserver.h"
 #include "platform.h"
 #include "PlatformMacros.h"
 #include "prenv.h"
 #include "mozilla/ThreadLocal.h"
 #include "PseudoStack.h"
 #include "TableTicker.h"
 #include "UnwinderThread2.h"
 #include "nsIObserverService.h"
@@ -51,16 +53,18 @@ unsigned int sCurrentEventGeneration = 0
  * a problem if 2^32 events happen between samples that we need
  * to know are associated with different events */
 
 std::vector<ThreadInfo*>* Sampler::sRegisteredThreads = nullptr;
 mozilla::Mutex* Sampler::sRegisteredThreadsMutex = nullptr;
 
 TableTicker* Sampler::sActiveSampler;
 
+static mozilla::ProfilerIOInterposeObserver* sInterposeObserver = nullptr;
+
 void Sampler::Startup() {
   sRegisteredThreads = new std::vector<ThreadInfo*>();
   sRegisteredThreadsMutex = new mozilla::Mutex("sRegisteredThreads mutex");
 }
 
 void Sampler::Shutdown() {
   while (sRegisteredThreads->size() > 0) {
     delete sRegisteredThreads->back();
@@ -276,16 +280,19 @@ void mozilla_sampler_init(void* stackTop
   // Read mode settings from MOZ_PROFILER_MODE and interval
   // settings from MOZ_PROFILER_INTERVAL and stack-scan threshhold
   // from MOZ_PROFILER_STACK_SCAN.
   read_profiler_env_vars();
 
   // Allow the profiler to be started using signals
   OS::RegisterStartHandler();
 
+  // Initialize (but don't enable) I/O interposing
+  sInterposeObserver = new mozilla::ProfilerIOInterposeObserver();
+
   // We can't open pref so we use an environment variable
   // to know if we should trigger the profiler on startup
   // NOTE: Default
   const char *val = PR_GetEnv("MOZ_PROFILER_STARTUP");
   if (!val || !*val) {
     return;
   }
 
@@ -318,16 +325,20 @@ void mozilla_sampler_shutdown()
         t->ToStreamAsJSON(stream);
         stream.close();
       }
     }
   }
 
   profiler_stop();
 
+  delete sInterposeObserver;
+  sInterposeObserver = nullptr;
+  mozilla::IOInterposer::ClearInstance();
+
   Sampler::Shutdown();
 
   // We can't delete the Stack because we can be between a
   // sampler call_enter/call_exit point.
   // TODO Need to find a safe time to delete Stack
 }
 
 void mozilla_sampler_save()
@@ -388,16 +399,18 @@ const char** mozilla_sampler_get_feature
     "jank",
     // Tell the JS engine to emmit pseudostack entries in the
     // pro/epilogue.
     "js",
     // Profile the registered secondary threads.
     "threads",
     // Do not include user-identifiable information
     "privacy",
+    // Add main thread I/O to the profile
+    "mainthreadio",
     NULL
   };
 
   return features;
 }
 
 // Values are only honored on the first start
 void mozilla_sampler_start(int aProfileEntries, int aInterval,
@@ -450,16 +463,20 @@ void mozilla_sampler_start(int aProfileE
     // Java sampling doesn't accuratly keep up with 1ms sampling
     if (javaInterval < 10) {
       aInterval = 10;
     }
     mozilla::AndroidBridge::Bridge()->StartJavaProfiling(javaInterval, 1000);
   }
 #endif
 
+  if (t->AddMainThreadIO()) {
+    mozilla::IOInterposer::GetInstance()->Enable(true);
+  }
+
   sIsProfiling = true;
 
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (os)
     os->NotifyObservers(nullptr, "profiler-started", nullptr);
 }
 
 void mozilla_sampler_stop()
@@ -492,16 +509,18 @@ void mozilla_sampler_stop()
     ASSERT(stack != NULL);
     stack->disableJSSampling();
   }
 
   if (unwinderThreader) {
     uwt__deinit();
   }
 
+  mozilla::IOInterposer::GetInstance()->Enable(false);
+
   sIsProfiling = false;
 
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (os)
     os->NotifyObservers(nullptr, "profiler-stopped", nullptr);
 }
 
 bool mozilla_sampler_is_active()