michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef mozilla_IOInterposer_h michael@0: #define mozilla_IOInterposer_h michael@0: michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: #include "mozilla/XPCOM.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: /** michael@0: * Interface for I/O interposer observers. This is separate from the michael@0: * IOInterposer because we have multiple uses for these observations. michael@0: */ michael@0: class IOInterposeObserver michael@0: { michael@0: public: michael@0: enum Operation michael@0: { michael@0: OpNone = 0, michael@0: OpCreateOrOpen = (1 << 0), michael@0: OpRead = (1 << 1), michael@0: OpWrite = (1 << 2), michael@0: OpFSync = (1 << 3), michael@0: OpStat = (1 << 4), michael@0: OpClose = (1 << 5), michael@0: OpNextStage = (1 << 6), // Meta - used when leaving startup, entering shutdown michael@0: OpWriteFSync = (OpWrite | OpFSync), michael@0: OpAll = (OpCreateOrOpen | OpRead | OpWrite | OpFSync | OpStat | OpClose), michael@0: OpAllWithStaging = (OpAll | OpNextStage) michael@0: }; michael@0: michael@0: /** A representation of an I/O observation */ michael@0: class Observation michael@0: { michael@0: protected: michael@0: /** michael@0: * This constructor is for use by subclasses that are intended to take michael@0: * timing measurements via RAII. The |aShouldReport| parameter may be michael@0: * used to make the measurement and reporting conditional on the michael@0: * satisfaction of an arbitrary predicate that was evaluated michael@0: * in the subclass. Note that IOInterposer::IsObservedOperation() is michael@0: * always ANDed with aShouldReport, so the subclass does not need to michael@0: * include a call to that function explicitly. michael@0: */ michael@0: Observation(Operation aOperation, const char* aReference, michael@0: bool aShouldReport = true); michael@0: michael@0: public: michael@0: /** michael@0: * Since this constructor accepts start and end times, it does *not* take michael@0: * its own timings, nor does it report itself. michael@0: */ michael@0: Observation(Operation aOperation, const TimeStamp& aStart, michael@0: const TimeStamp& aEnd, const char* aReference); michael@0: michael@0: /** michael@0: * Operation observed, this is one of the individual Operation values. michael@0: * Combinations of these flags are only used when registering observers. michael@0: */ michael@0: Operation ObservedOperation() const michael@0: { michael@0: return mOperation; michael@0: } michael@0: michael@0: /** michael@0: * Return the observed operation as a human-readable string. michael@0: */ michael@0: const char* ObservedOperationString() const; michael@0: michael@0: /** Time at which the I/O operation was started */ michael@0: TimeStamp Start() const michael@0: { michael@0: return mStart; michael@0: } michael@0: michael@0: /** michael@0: * Time at which the I/O operation ended, for asynchronous methods this is michael@0: * the time at which the call initiating the asynchronous request returned. michael@0: */ michael@0: TimeStamp End() const michael@0: { michael@0: return mEnd; michael@0: } michael@0: michael@0: /** michael@0: * Duration of the operation, for asynchronous I/O methods this is the michael@0: * duration of the call initiating the asynchronous request. michael@0: */ michael@0: TimeDuration Duration() const michael@0: { michael@0: return mEnd - mStart; michael@0: } michael@0: michael@0: /** michael@0: * IO reference, function name or name of component (sqlite) that did IO michael@0: * this is in addition the generic operation. This attribute may be platform michael@0: * specific, but should only take a finite number of distinct values. michael@0: * E.g. sqlite-commit, CreateFile, NtReadFile, fread, fsync, mmap, etc. michael@0: * I.e. typically the platform specific function that did the IO. michael@0: */ michael@0: const char* Reference() const michael@0: { michael@0: return mReference; michael@0: } michael@0: michael@0: /** Request filename associated with the I/O operation, null if unknown */ michael@0: virtual const char16_t* Filename() michael@0: { michael@0: return nullptr; michael@0: } michael@0: michael@0: virtual ~Observation() michael@0: { michael@0: } michael@0: michael@0: protected: michael@0: void michael@0: Report(); michael@0: michael@0: Operation mOperation; michael@0: TimeStamp mStart; michael@0: TimeStamp mEnd; michael@0: const char* mReference; // Identifies the source of the Observation michael@0: bool mShouldReport; // Measure and report if true michael@0: }; michael@0: michael@0: /** michael@0: * Invoked whenever an implementation of the IOInterposeObserver should michael@0: * observe aObservation. Implement this and do your thing... michael@0: * But do consider if it is wise to use IO functions in this method, they are michael@0: * likely to cause recursion :) michael@0: * At least, see PoisonIOInterposer.h and register your handle as a debug file michael@0: * even, if you don't initialize the poison IO interposer, someone else might. michael@0: * michael@0: * Remark: Observations may occur on any thread. michael@0: */ michael@0: virtual void Observe(Observation& aObservation) = 0; michael@0: michael@0: virtual ~IOInterposeObserver() michael@0: { michael@0: } michael@0: michael@0: protected: michael@0: /** michael@0: * We don't use NS_IsMainThread() because we need to be able to determine the michael@0: * main thread outside of XPCOM Initialization. IOInterposer observers should michael@0: * call this function instead. michael@0: */ michael@0: static bool IsMainThread(); michael@0: }; michael@0: michael@0: /** michael@0: * These functions are responsible for ensuring that events are routed to the michael@0: * appropriate observers. michael@0: */ michael@0: namespace IOInterposer michael@0: { michael@0: /** michael@0: * This function must be called from the main-thread when no other threads are michael@0: * running before any of the other methods on this class may be used. michael@0: * michael@0: * IO reports can however, safely assume that IsObservedOperation() will michael@0: * return false until the IOInterposer is initialized. michael@0: * michael@0: * Remark, it's safe to call this method multiple times, so just call it when michael@0: * you to utilize IO interposing. michael@0: * michael@0: * Using the IOInterposerInit class is preferred to calling this directly. michael@0: */ michael@0: bool Init(); michael@0: michael@0: /** michael@0: * This function must be called from the main thread, and furthermore michael@0: * it must be called when no other threads are executing. Effectively michael@0: * restricting us to calling it only during shutdown. michael@0: * michael@0: * Callers should take care that no other consumers are subscribed to events, michael@0: * as these events will stop when this function is called. michael@0: * michael@0: * In practice, we don't use this method as the IOInterposer is used for michael@0: * late-write checks. michael@0: */ michael@0: void Clear(); michael@0: michael@0: /** michael@0: * This function immediately disables IOInterposer functionality in a fast, michael@0: * thread-safe manner. Primarily for use by the crash reporter. michael@0: */ michael@0: void Disable(); michael@0: michael@0: /** michael@0: * Report IO to registered observers. michael@0: * Notice that the reported operation must be either OpRead, OpWrite or michael@0: * OpFSync. You are not allowed to report an observation with OpWriteFSync or michael@0: * OpAll, these are just auxiliary values for use with Register(). michael@0: * michael@0: * If the IO call you're reporting does multiple things, write and fsync, you michael@0: * can choose to call Report() twice once with write and once with FSync. You michael@0: * may not call Report() with OpWriteFSync! The Observation::mOperation michael@0: * attribute is meant to be generic, not perfect. michael@0: * michael@0: * Notice that there is no reason to report an observation with an operation michael@0: * which is not being observed. Use IsObservedOperation() to check if the michael@0: * operation you are about to report is being observed. This is especially michael@0: * important if you are constructing expensive observations containing michael@0: * filename and full-path. michael@0: * michael@0: * Remark: Init() must be called before any IO is reported. But michael@0: * IsObservedOperation() will return false until Init() is called. michael@0: */ michael@0: void Report(IOInterposeObserver::Observation& aObservation); michael@0: michael@0: /** michael@0: * Return whether or not an operation is observed. Reporters should not michael@0: * report operations that are not being observed by anybody. This mechanism michael@0: * allows us to avoid reporting I/O when no observers are registered. michael@0: */ michael@0: bool IsObservedOperation(IOInterposeObserver::Operation aOp); michael@0: michael@0: /** michael@0: * Register IOInterposeObserver, the observer object will receive all michael@0: * observations for the given operation aOp. michael@0: * michael@0: * Remark: Init() must be called before observers are registered. michael@0: */ michael@0: void Register(IOInterposeObserver::Operation aOp, michael@0: IOInterposeObserver* aObserver); michael@0: michael@0: /** michael@0: * Unregister an IOInterposeObserver for a given operation michael@0: * Remark: It is always safe to unregister for all operations, even if yoú michael@0: * didn't register for them all. michael@0: * I.e. IOInterposer::Unregister(IOInterposeObserver::OpAll, aObserver) michael@0: * michael@0: * Remark: Init() must be called before observers are unregistered. michael@0: */ michael@0: void Unregister(IOInterposeObserver::Operation aOp, michael@0: IOInterposeObserver* aObserver); michael@0: michael@0: /** michael@0: * Registers the current thread with the IOInterposer. This must be done to michael@0: * ensure that per-thread data is created in an orderly fashion. michael@0: * We could have written this to initialize that data lazily, however this michael@0: * could have unintended consequences if a thread that is not aware of michael@0: * IOInterposer was implicitly registered: its per-thread data would never michael@0: * be deleted because it would not know to unregister itself. michael@0: * michael@0: * @param aIsMainThread true if IOInterposer should treat the current thread michael@0: * as the main thread. michael@0: */ michael@0: void michael@0: RegisterCurrentThread(bool aIsMainThread = false); michael@0: michael@0: /** michael@0: * Unregisters the current thread with the IOInterposer. This is important michael@0: * to call when a thread is shutting down because it cleans up data that michael@0: * is stored in a TLS slot. michael@0: */ michael@0: void michael@0: UnregisterCurrentThread(); michael@0: michael@0: /** michael@0: * Called to inform observers that the process has transitioned out of the michael@0: * startup stage or into the shutdown stage. Main thread only. michael@0: */ michael@0: void michael@0: EnteringNextStage(); michael@0: } // namespace IOInterposer michael@0: michael@0: class IOInterposerInit michael@0: { michael@0: public: michael@0: IOInterposerInit() michael@0: { michael@0: #if defined(MOZ_ENABLE_PROFILER_SPS) michael@0: IOInterposer::Init(); michael@0: #endif michael@0: } michael@0: michael@0: // No destructor needed at the moment -- this stuff stays active for the michael@0: // life of the process. This may change in the future. michael@0: }; michael@0: michael@0: } // namespace mozilla michael@0: michael@0: #endif // mozilla_IOInterposer_h