mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
71de614a1d
We want to collect information on late writes via telemetry. We have been doing this in Nightly for a while now, but want to do so in beta/release. I was actually initially unaware of this limitation of the IOInterposer, but we need the IOInterposer to collect information on late writes, so I would like to enable it for just early beta, in the hopes that we can catch any late writes that may be happening, without adding a performance tax onto release. Accordingly, is perf the only reason that this was restricted to Nightly? And if so, did we measure a perf difference, or was this just general caution regarding the performance impact? Is there anything else to look out for? Differential Revision: https://phabricator.services.mozilla.com/D90894
283 lines
9.5 KiB
C++
283 lines
9.5 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* 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 mozilla_IOInterposer_h
|
|
#define mozilla_IOInterposer_h
|
|
|
|
#include "mozilla/Attributes.h"
|
|
#include "mozilla/TimeStamp.h"
|
|
#include "nsString.h"
|
|
|
|
namespace mozilla {
|
|
|
|
/**
|
|
* Interface for I/O interposer observers. This is separate from the
|
|
* IOInterposer because we have multiple uses for these observations.
|
|
*/
|
|
class IOInterposeObserver {
|
|
public:
|
|
enum Operation {
|
|
OpNone = 0,
|
|
OpCreateOrOpen = (1 << 0),
|
|
OpRead = (1 << 1),
|
|
OpWrite = (1 << 2),
|
|
OpFSync = (1 << 3),
|
|
OpStat = (1 << 4),
|
|
OpClose = (1 << 5),
|
|
OpNextStage =
|
|
(1 << 6), // Meta - used when leaving startup, entering shutdown
|
|
OpWriteFSync = (OpWrite | OpFSync),
|
|
OpAll = (OpCreateOrOpen | OpRead | OpWrite | OpFSync | OpStat | OpClose),
|
|
OpAllWithStaging = (OpAll | OpNextStage)
|
|
};
|
|
|
|
/** A representation of an I/O observation */
|
|
class Observation {
|
|
protected:
|
|
/**
|
|
* This constructor is for use by subclasses that are intended to take
|
|
* timing measurements via RAII. The |aShouldReport| parameter may be
|
|
* used to make the measurement and reporting conditional on the
|
|
* satisfaction of an arbitrary predicate that was evaluated
|
|
* in the subclass. Note that IOInterposer::IsObservedOperation() is
|
|
* always ANDed with aShouldReport, so the subclass does not need to
|
|
* include a call to that function explicitly.
|
|
*/
|
|
Observation(Operation aOperation, const char* aReference,
|
|
bool aShouldReport = true);
|
|
|
|
public:
|
|
/**
|
|
* Since this constructor accepts start and end times, it does *not* take
|
|
* its own timings, nor does it report itself.
|
|
*/
|
|
Observation(Operation aOperation, const TimeStamp& aStart,
|
|
const TimeStamp& aEnd, const char* aReference);
|
|
|
|
/**
|
|
* Operation observed, this is one of the individual Operation values.
|
|
* Combinations of these flags are only used when registering observers.
|
|
*/
|
|
Operation ObservedOperation() const { return mOperation; }
|
|
|
|
/**
|
|
* Return the observed operation as a human-readable string.
|
|
*/
|
|
const char* ObservedOperationString() const;
|
|
|
|
/** Time at which the I/O operation was started */
|
|
TimeStamp Start() const { return mStart; }
|
|
|
|
/**
|
|
* Time at which the I/O operation ended, for asynchronous methods this is
|
|
* the time at which the call initiating the asynchronous request returned.
|
|
*/
|
|
TimeStamp End() const { return mEnd; }
|
|
|
|
/**
|
|
* Duration of the operation, for asynchronous I/O methods this is the
|
|
* duration of the call initiating the asynchronous request.
|
|
*/
|
|
TimeDuration Duration() const { return mEnd - mStart; }
|
|
|
|
/**
|
|
* IO reference, function name or name of component (sqlite) that did IO
|
|
* this is in addition the generic operation. This attribute may be platform
|
|
* specific, but should only take a finite number of distinct values.
|
|
* E.g. sqlite-commit, CreateFile, NtReadFile, fread, fsync, mmap, etc.
|
|
* I.e. typically the platform specific function that did the IO.
|
|
*/
|
|
const char* Reference() const { return mReference; }
|
|
|
|
virtual const char* FileType() const { return "File"; }
|
|
|
|
/** Request filename associated with the I/O operation, empty if unknown */
|
|
virtual void Filename(nsAString& aString) { aString.Truncate(); }
|
|
|
|
virtual ~Observation() = default;
|
|
|
|
protected:
|
|
void Report();
|
|
|
|
Operation mOperation;
|
|
TimeStamp mStart;
|
|
TimeStamp mEnd;
|
|
const char* mReference; // Identifies the source of the Observation
|
|
bool mShouldReport; // Measure and report if true
|
|
};
|
|
|
|
/**
|
|
* Invoked whenever an implementation of the IOInterposeObserver should
|
|
* observe aObservation. Implement this and do your thing...
|
|
* But do consider if it is wise to use IO functions in this method, they are
|
|
* likely to cause recursion :)
|
|
* At least, see PoisonIOInterposer.h and register your handle as a debug file
|
|
* even, if you don't initialize the poison IO interposer, someone else might.
|
|
*
|
|
* Remark: Observations may occur on any thread.
|
|
*/
|
|
virtual void Observe(Observation& aObservation) = 0;
|
|
|
|
virtual ~IOInterposeObserver() = default;
|
|
|
|
protected:
|
|
/**
|
|
* We don't use NS_IsMainThread() because we need to be able to determine the
|
|
* main thread outside of XPCOM Initialization. IOInterposer observers should
|
|
* call this function instead.
|
|
*/
|
|
static bool IsMainThread();
|
|
};
|
|
|
|
/**
|
|
* These functions are responsible for ensuring that events are routed to the
|
|
* appropriate observers.
|
|
*/
|
|
namespace IOInterposer {
|
|
|
|
/**
|
|
* This function must be called from the main-thread when no other threads are
|
|
* running before any of the other methods on this class may be used.
|
|
*
|
|
* IO reports can however, safely assume that IsObservedOperation() will
|
|
* return false until the IOInterposer is initialized.
|
|
*
|
|
* Remark, it's safe to call this method multiple times, so just call it when
|
|
* you to utilize IO interposing.
|
|
*
|
|
* Using the IOInterposerInit class is preferred to calling this directly.
|
|
*/
|
|
bool Init();
|
|
|
|
/**
|
|
* This function must be called from the main thread, and furthermore
|
|
* it must be called when no other threads are executing. Effectively
|
|
* restricting us to calling it only during shutdown.
|
|
*
|
|
* Callers should take care that no other consumers are subscribed to events,
|
|
* as these events will stop when this function is called.
|
|
*
|
|
* In practice, we don't use this method as the IOInterposer is used for
|
|
* late-write checks.
|
|
*/
|
|
void Clear();
|
|
|
|
/**
|
|
* This function immediately disables IOInterposer functionality in a fast,
|
|
* thread-safe manner. Primarily for use by the crash reporter.
|
|
*/
|
|
void Disable();
|
|
|
|
/**
|
|
* This function re-enables IOInterposer functionality in a fast, thread-safe
|
|
* manner. Primarily for use by the crash reporter.
|
|
*/
|
|
void Enable();
|
|
|
|
/**
|
|
* Report IO to registered observers.
|
|
* Notice that the reported operation must be either OpRead, OpWrite or
|
|
* OpFSync. You are not allowed to report an observation with OpWriteFSync or
|
|
* OpAll, these are just auxiliary values for use with Register().
|
|
*
|
|
* If the IO call you're reporting does multiple things, write and fsync, you
|
|
* can choose to call Report() twice once with write and once with FSync. You
|
|
* may not call Report() with OpWriteFSync! The Observation::mOperation
|
|
* attribute is meant to be generic, not perfect.
|
|
*
|
|
* Notice that there is no reason to report an observation with an operation
|
|
* which is not being observed. Use IsObservedOperation() to check if the
|
|
* operation you are about to report is being observed. This is especially
|
|
* important if you are constructing expensive observations containing
|
|
* filename and full-path.
|
|
*
|
|
* Remark: Init() must be called before any IO is reported. But
|
|
* IsObservedOperation() will return false until Init() is called.
|
|
*/
|
|
void Report(IOInterposeObserver::Observation& aObservation);
|
|
|
|
/**
|
|
* Return whether or not an operation is observed. Reporters should not
|
|
* report operations that are not being observed by anybody. This mechanism
|
|
* allows us to avoid reporting I/O when no observers are registered.
|
|
*/
|
|
bool IsObservedOperation(IOInterposeObserver::Operation aOp);
|
|
|
|
/**
|
|
* Register IOInterposeObserver, the observer object will receive all
|
|
* observations for the given operation aOp.
|
|
*
|
|
* Remark: Init() must be called before observers are registered.
|
|
*/
|
|
void Register(IOInterposeObserver::Operation aOp,
|
|
IOInterposeObserver* aObserver);
|
|
|
|
/**
|
|
* Unregister an IOInterposeObserver for a given operation
|
|
* Remark: It is always safe to unregister for all operations, even if yoú
|
|
* didn't register for them all.
|
|
* I.e. IOInterposer::Unregister(IOInterposeObserver::OpAll, aObserver)
|
|
*
|
|
* Remark: Init() must be called before observers are unregistered.
|
|
*/
|
|
void Unregister(IOInterposeObserver::Operation aOp,
|
|
IOInterposeObserver* aObserver);
|
|
|
|
/**
|
|
* Registers the current thread with the IOInterposer. This must be done to
|
|
* ensure that per-thread data is created in an orderly fashion.
|
|
* We could have written this to initialize that data lazily, however this
|
|
* could have unintended consequences if a thread that is not aware of
|
|
* IOInterposer was implicitly registered: its per-thread data would never
|
|
* be deleted because it would not know to unregister itself.
|
|
*
|
|
* @param aIsMainThread true if IOInterposer should treat the current thread
|
|
* as the main thread.
|
|
*/
|
|
void RegisterCurrentThread(bool aIsMainThread = false);
|
|
|
|
/**
|
|
* Unregisters the current thread with the IOInterposer. This is important
|
|
* to call when a thread is shutting down because it cleans up data that
|
|
* is stored in a TLS slot.
|
|
*/
|
|
void UnregisterCurrentThread();
|
|
|
|
/**
|
|
* Called to inform observers that the process has transitioned out of the
|
|
* startup stage or into the shutdown stage. Main thread only.
|
|
*/
|
|
void EnteringNextStage();
|
|
|
|
} // namespace IOInterposer
|
|
|
|
class IOInterposerInit {
|
|
public:
|
|
IOInterposerInit() {
|
|
#if defined(EARLY_BETA_OR_EARLIER)
|
|
IOInterposer::Init();
|
|
#endif
|
|
}
|
|
|
|
~IOInterposerInit() {
|
|
#if defined(EARLY_BETA_OR_EARLIER)
|
|
IOInterposer::Clear();
|
|
#endif
|
|
}
|
|
};
|
|
|
|
class MOZ_RAII AutoIOInterposerDisable final {
|
|
public:
|
|
explicit AutoIOInterposerDisable() { IOInterposer::Disable(); }
|
|
~AutoIOInterposerDisable() { IOInterposer::Enable(); }
|
|
|
|
private:
|
|
};
|
|
|
|
} // namespace mozilla
|
|
|
|
#endif // mozilla_IOInterposer_h
|