// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef BASE_METRICS_PERSISTENT_HISTOGRAM_ALLOCATOR_H_ #define BASE_METRICS_PERSISTENT_HISTOGRAM_ALLOCATOR_H_ #include #include #include #include #include #include "base/base_export.h" #include "base/feature_list.h" #include "base/memory/raw_ptr.h" #include "base/metrics/histogram_base.h" #include "base/metrics/persistent_memory_allocator.h" #include "base/metrics/ranges_manager.h" #include "base/process/process_handle.h" #include "base/strings/string_piece.h" #include "base/synchronization/lock.h" #include "build/build_config.h" namespace base { class BucketRanges; class FilePath; class PersistentSampleMapRecords; class PersistentSparseHistogramDataManager; class WritableSharedMemoryRegion; // Feature definition for enabling histogram persistence. BASE_EXPORT extern const Feature kPersistentHistogramsFeature; // A data manager for sparse histograms so each instance of such doesn't have // to separately iterate over the entire memory segment. Though this class // will generally be accessed through the PersistentHistogramAllocator above, // it can be used independently on any PersistentMemoryAllocator (making it // useable for testing). This object supports only one instance of a sparse // histogram for a given id. Tests that create multiple identical histograms, // perhaps to simulate multiple processes, should create a separate manager // for each. class BASE_EXPORT PersistentSparseHistogramDataManager { public: // Constructs the data manager. The allocator must live longer than any // managers that reference it. explicit PersistentSparseHistogramDataManager( PersistentMemoryAllocator* allocator); PersistentSparseHistogramDataManager( const PersistentSparseHistogramDataManager&) = delete; PersistentSparseHistogramDataManager& operator=( const PersistentSparseHistogramDataManager&) = delete; ~PersistentSparseHistogramDataManager(); // Returns the object that manages the persistent-sample-map records for a // given |id|. Only one |user| of this data is allowed at a time. This does // an automatic Acquire() on the records. The user must call Release() on // the returned object when it is finished with it. Ownership of the records // object stays with this manager. PersistentSampleMapRecords* UseSampleMapRecords(uint64_t id, const void* user); // Convenience method that gets the object for a given reference so callers // don't have to also keep their own pointer to the appropriate allocator. template T* GetAsObject(PersistentMemoryAllocator::Reference ref) { return allocator_->GetAsObject(ref); } private: friend class PersistentSampleMapRecords; // Gets the object holding records for a given sample-map id. PersistentSampleMapRecords* GetSampleMapRecordsWhileLocked(uint64_t id) EXCLUSIVE_LOCKS_REQUIRED(lock_); // Loads sample-map records looking for those belonging to the specified // |load_id|. Records found for other sample-maps are held for later use // without having to iterate again. This should be called only from a // PersistentSampleMapRecords object because those objects have a contract // that there are no other threads accessing the internal records_ field // of the object that is passed in. bool LoadRecords(PersistentSampleMapRecords* sample_map_records); // Weak-pointer to the allocator used by the sparse histograms. raw_ptr allocator_; // Iterator within the allocator for finding sample records. PersistentMemoryAllocator::Iterator record_iterator_ GUARDED_BY(lock_); // Mapping of sample-map IDs to their sample records. std::map> sample_records_ GUARDED_BY(lock_); base::Lock lock_; }; // This class manages sample-records used by a PersistentSampleMap container // that underlies a persistent SparseHistogram object. It is broken out into a // top-level class so that it can be forward-declared in other header files // rather than include this entire file as would be necessary if it were // declared within the PersistentSparseHistogramDataManager class above. class BASE_EXPORT PersistentSampleMapRecords { public: // Constructs an instance of this class. The manager object must live longer // than all instances of this class that reference it, which is not usually // a problem since these objects are generally managed from within that // manager instance. PersistentSampleMapRecords(PersistentSparseHistogramDataManager* data_manager, uint64_t sample_map_id); PersistentSampleMapRecords(const PersistentSampleMapRecords&) = delete; PersistentSampleMapRecords& operator=(const PersistentSampleMapRecords&) = delete; ~PersistentSampleMapRecords(); // Resets the internal state for a new object using this data. The return // value is "this" as a convenience. PersistentSampleMapRecords* Acquire(const void* user); // Indicates that the using object is done with this data. void Release(const void* user); // Gets the next reference to a persistent sample-map record. The type and // layout of the data being referenced is defined entirely within the // PersistentSampleMap class. PersistentMemoryAllocator::Reference GetNext(); // Creates a new persistent sample-map record for sample |value| and returns // a reference to it. PersistentMemoryAllocator::Reference CreateNew(HistogramBase::Sample value); // Convenience method that gets the object for a given reference so callers // don't have to also keep their own pointer to the appropriate allocator. // This is expected to be used with the SampleRecord structure defined inside // the persistent_sample_map.cc file but since that isn't exported (for // cleanliness of the interface), a template is defined that will be // resolved when used inside that file. template T* GetAsObject(PersistentMemoryAllocator::Reference ref) { return data_manager_->GetAsObject(ref); } private: friend PersistentSparseHistogramDataManager; // Weak-pointer to the parent data-manager object. raw_ptr data_manager_; // ID of PersistentSampleMap to which these records apply. const uint64_t sample_map_id_; // The current user of this set of records. It is used to ensure that no // more than one object is using these records at a given time. raw_ptr user_ = nullptr; // This is the count of how many "records" have already been read by the // owning sample-map. size_t seen_ = 0; // This is the set of records previously found for a sample map. Because // there is ever only one object with a given ID (typically a hash of a // histogram name) and because the parent SparseHistogram has acquired // its own lock before accessing the PersistentSampleMap it controls, this // list can be accessed without acquiring any additional lock. std::vector records_; // This is the set of records found during iteration through memory. It // is appended in bulk to "records". Access to this vector can be done // only while holding the parent manager's lock. std::vector found_; }; // This class manages histograms created within a PersistentMemoryAllocator. class BASE_EXPORT PersistentHistogramAllocator { public: // A reference to a histogram. While this is implemented as PMA::Reference, // it is not conceptually the same thing. Outside callers should always use // a Reference matching the class it is for and not mix the two. using Reference = PersistentMemoryAllocator::Reference; // Iterator used for fetching persistent histograms from an allocator. // It is lock-free and thread-safe. // See PersistentMemoryAllocator::Iterator for more information. class BASE_EXPORT Iterator { public: // Constructs an iterator on a given |allocator|, starting at the beginning. // The allocator must live beyond the lifetime of the iterator. explicit Iterator(PersistentHistogramAllocator* allocator); Iterator(const Iterator&) = delete; Iterator& operator=(const Iterator&) = delete; // Gets the next histogram from persistent memory; returns null if there // are no more histograms to be found. This may still be called again // later to retrieve any new histograms added in the meantime. std::unique_ptr GetNext() { return GetNextWithIgnore(0); } // Gets the next histogram from persistent memory, ignoring one particular // reference in the process. Pass |ignore| of zero (0) to ignore nothing. std::unique_ptr GetNextWithIgnore(Reference ignore); private: // Weak-pointer to histogram allocator being iterated over. raw_ptr allocator_; // The iterator used for stepping through objects in persistent memory. // It is lock-free and thread-safe which is why this class is also such. PersistentMemoryAllocator::Iterator memory_iter_; }; // A PersistentHistogramAllocator is constructed from a PersistentMemory- // Allocator object of which it takes ownership. explicit PersistentHistogramAllocator( std::unique_ptr memory); PersistentHistogramAllocator(const PersistentHistogramAllocator&) = delete; PersistentHistogramAllocator& operator=(const PersistentHistogramAllocator&) = delete; virtual ~PersistentHistogramAllocator(); // Direct access to underlying memory allocator. If the segment is shared // across threads or processes, reading data through these values does // not guarantee consistency. Use with care. Do not write. PersistentMemoryAllocator* memory_allocator() { return memory_allocator_.get(); } // Implement the "metadata" API of a PersistentMemoryAllocator, forwarding // those requests to the real one. uint64_t Id() const { return memory_allocator_->Id(); } const char* Name() const { return memory_allocator_->Name(); } const void* data() const { return memory_allocator_->data(); } size_t length() const { return memory_allocator_->length(); } size_t size() const { return memory_allocator_->size(); } size_t used() const { return memory_allocator_->used(); } // Recreate a Histogram from data held in persistent memory. Though this // object will be local to the current process, the sample data will be // shared with all other threads referencing it. This method takes a |ref| // to where the top-level histogram data may be found in this allocator. // This method will return null if any problem is detected with the data. std::unique_ptr GetHistogram(Reference ref); // Allocate a new persistent histogram. The returned histogram will not // be able to be located by other allocators until it is "finalized". std::unique_ptr AllocateHistogram( HistogramType histogram_type, const std::string& name, int minimum, int maximum, const BucketRanges* bucket_ranges, int32_t flags, Reference* ref_ptr); // Finalize the creation of the histogram, making it available to other // processes if |registered| (as in: added to the StatisticsRecorder) is // True, forgetting it otherwise. void FinalizeHistogram(Reference ref, bool registered); // Merges the data in a persistent histogram with one held globally by the // StatisticsRecorder, updating the "logged" samples within the passed // object so that repeated merges are allowed. Don't call this on a "global" // allocator because histograms created there will already be in the SR. void MergeHistogramDeltaToStatisticsRecorder(HistogramBase* histogram); // As above but merge the "final" delta. No update of "logged" samples is // done which means it can operate on read-only objects. It's essential, // however, not to call this more than once or those final samples will // get recorded again. void MergeHistogramFinalDeltaToStatisticsRecorder( const HistogramBase* histogram); // Returns the object that manages the persistent-sample-map records for a // given |id|. Only one |user| of this data is allowed at a time. This does // an automatic Acquire() on the records. The user must call Release() on // the returned object when it is finished with it. Ownership stays with // this allocator. PersistentSampleMapRecords* UseSampleMapRecords(uint64_t id, const void* user); // Create internal histograms for tracking memory use and allocation sizes // for allocator of |name| (which can simply be the result of Name()). This // is done seperately from construction for situations such as when the // histograms will be backed by memory provided by this very allocator. // // IMPORTANT: tools/metrics/histograms/metadata/uma/histograms.xml must // be updated with the following histograms for each |name| param: // UMA.PersistentAllocator.name.Errors // UMA.PersistentAllocator.name.UsedPct void CreateTrackingHistograms(StringPiece name); void UpdateTrackingHistograms(); // Sets the internal |ranges_manager_|, which will be used by the allocator to // register BucketRanges. Takes ownership of the passed |ranges_manager|. void SetRangesManager(RangesManager* ranges_manager); // Clears the internal |last_created_| reference so testing can validate // operation without that optimization. void ClearLastCreatedReferenceForTesting(); protected: // The structure used to hold histogram data in persistent memory. It is // defined and used entirely within the .cc file. struct PersistentHistogramData; // Gets the reference of the last histogram created, used to avoid // trying to import what was just created. Reference last_created() { return last_created_.load(std::memory_order_relaxed); } // Gets the next histogram in persistent data based on iterator while // ignoring a particular reference if it is found. std::unique_ptr GetNextHistogramWithIgnore(Iterator* iter, Reference ignore); private: // Create a histogram based on saved (persistent) information about it. std::unique_ptr CreateHistogram( PersistentHistogramData* histogram_data_ptr); // Gets or creates an object in the global StatisticsRecorder matching // the |histogram| passed. Null is returned if one was not found and // one could not be created. HistogramBase* GetOrCreateStatisticsRecorderHistogram( const HistogramBase* histogram); // The memory allocator that provides the actual histogram storage. std::unique_ptr memory_allocator_; // The RangesManager that the allocator will register its BucketRanges with. // If this is null (default), the BucketRanges will be registered with the // global statistics recorder. Used when loading self-contained metrics coming // from a previous session. Registering the BucketRanges with the global // statistics recorder could create unnecessary contention, and a low amount // of extra memory. std::unique_ptr ranges_manager_; // The data-manager used to improve performance of sparse histograms. PersistentSparseHistogramDataManager sparse_histogram_data_manager_; // A reference to the last-created histogram in the allocator, used to avoid // trying to import what was just created. std::atomic last_created_ = 0; }; // A special case of the PersistentHistogramAllocator that operates on a // global scale, collecting histograms created through standard macros and // the FactoryGet() method. class BASE_EXPORT GlobalHistogramAllocator : public PersistentHistogramAllocator { public: GlobalHistogramAllocator(const GlobalHistogramAllocator&) = delete; GlobalHistogramAllocator& operator=(const GlobalHistogramAllocator&) = delete; ~GlobalHistogramAllocator() override; // Create a global allocator using the passed-in memory |base|, |size|, and // other parameters. Ownership of the memory segment remains with the caller. static void CreateWithPersistentMemory(void* base, size_t size, size_t page_size, uint64_t id, StringPiece name); // Create a global allocator using an internal block of memory of the // specified |size| taken from the heap. static void CreateWithLocalMemory(size_t size, uint64_t id, StringPiece name); #if !BUILDFLAG(IS_NACL) // Create a global allocator by memory-mapping a |file|. If the file does // not exist, it will be created with the specified |size|. If the file does // exist, the allocator will use and add to its contents, ignoring the passed // size in favor of the existing size. Returns whether the global allocator // was set. If |exclusive_write| is true, the file will be opened in a mode // that disallows multiple concurrent writers (no effect on non-Windows). static bool CreateWithFile(const FilePath& file_path, size_t size, uint64_t id, StringPiece name, bool exclusive_write = false); // Creates a new file at |active_path|. If it already exists, it will first be // moved to |base_path|. In all cases, any old file at |base_path| will be // removed. If |spare_path| is non-empty and exists, that will be renamed and // used as the active file. Otherwise, the file will be created using the // given size, id, and name. Returns whether the global allocator was set. static bool CreateWithActiveFile(const FilePath& base_path, const FilePath& active_path, const FilePath& spare_path, size_t size, uint64_t id, StringPiece name); // Uses ConstructBaseActivePairFilePaths() to build a pair of file names which // are then used for CreateWithActiveFile(). |name| is used for both the // internal name for the allocator and also for the name of the file inside // |dir|. static bool CreateWithActiveFileInDir(const FilePath& dir, size_t size, uint64_t id, StringPiece name); // Constructs a filename using a name. static FilePath ConstructFilePath(const FilePath& dir, StringPiece name); // Constructs a filename using a name for an "active" file. static FilePath ConstructFilePathForActiveFile(const FilePath& dir, StringPiece name); // Like above but with timestamp and pid for use in upload directories. static FilePath ConstructFilePathForUploadDir(const FilePath& dir, StringPiece name, base::Time stamp, ProcessId pid); // Override that uses the current time stamp and current process id. static FilePath ConstructFilePathForUploadDir(const FilePath& dir, StringPiece name); // Parses a filename to extract name, timestamp, and pid. static bool ParseFilePath(const FilePath& path, std::string* out_name, Time* out_stamp, ProcessId* out_pid); // Create a "spare" file that can later be made the "active" file. This // should be done on a background thread if possible. static bool CreateSpareFile(const FilePath& spare_path, size_t size); #endif // Create a global allocator using a block of shared memory accessed // through the given |region|. The allocator maps the shared memory into // current process's virtual address space and frees it upon destruction. // The memory will continue to live if other processes have access to it. static void CreateWithSharedMemoryRegion( const WritableSharedMemoryRegion& region); // Sets a GlobalHistogramAllocator for globally storing histograms in // a space that can be persisted or shared between processes. There is only // ever one allocator for all such histograms created by a single process. // This takes ownership of the object and should be called as soon as // possible during startup to capture as many histograms as possible and // while operating single-threaded so there are no race-conditions. static void Set(std::unique_ptr allocator); // Gets a pointer to the global histogram allocator. Returns null if none // exists. static GlobalHistogramAllocator* Get(); // This access to the persistent allocator is only for testing; it extracts // the current allocator completely. This allows easy creation of histograms // within persistent memory segments which can then be extracted and used in // other ways. static std::unique_ptr ReleaseForTesting(); // Stores a pathname to which the contents of this allocator should be saved // in order to persist the data for a later use. void SetPersistentLocation(const FilePath& location); // Retrieves a previously set pathname to which the contents of this allocator // are to be saved. const FilePath& GetPersistentLocation() const; // Writes the internal data to a previously set location. This is generally // called when a process is exiting from a section of code that may not know // the filesystem. The data is written in an atomic manner. The return value // indicates success. bool WriteToPersistentLocation(); // If there is a global metrics file being updated on disk, mark it to be // deleted when the process exits. void DeletePersistentLocation(); private: friend class StatisticsRecorder; // Creates a new global histogram allocator. explicit GlobalHistogramAllocator( std::unique_ptr memory); // Import new histograms from the global histogram allocator. It's possible // for other processes to create histograms in the active memory segment; // this adds those to the internal list of known histograms to avoid creating // duplicates that would have to be merged during reporting. Every call to // this method resumes from the last entry it saw; it costs nothing if // nothing new has been added. void ImportHistogramsToStatisticsRecorder(); // Builds a FilePath for a metrics file. static FilePath MakeMetricsFilePath(const FilePath& dir, StringPiece name); // Import always continues from where it left off, making use of a single // iterator to continue the work. Iterator import_iterator_; // The location to which the data should be persisted. FilePath persistent_location_; }; } // namespace base #endif // BASE_METRICS_PERSISTENT_HISTOGRAM_ALLOCATOR_H__