blob: c6f21ec84c87ec1775b6f5f3e34132f3a27876e3 [file] [log] [blame]
// Copyright (c) 2021 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_ALLOCATOR_PARTITION_ALLOCATOR_STARSCAN_STATS_COLLECTOR_H_
#define BASE_ALLOCATOR_PARTITION_ALLOCATOR_STARSCAN_STATS_COLLECTOR_H_
#include <array>
#include <atomic>
#include <functional>
#include <mutex>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include "base/allocator/partition_allocator/partition_alloc_base/threading/platform_thread.h"
#include "base/allocator/partition_allocator/partition_alloc_base/time/time.h"
#include "base/allocator/partition_allocator/starscan/metadata_allocator.h"
#include "base/allocator/partition_allocator/starscan/starscan_fwd.h"
namespace partition_alloc {
class StatsReporter;
namespace internal {
#define FOR_ALL_PCSCAN_SCANNER_SCOPES(V) \
V(Clear) \
V(Scan) \
V(Sweep) \
V(Overall)
#define FOR_ALL_PCSCAN_MUTATOR_SCOPES(V) \
V(Clear) \
V(ScanStack) \
V(Scan) \
V(Overall)
class StatsCollector final {
public:
enum class ScannerId {
#define DECLARE_ENUM(name) k##name,
FOR_ALL_PCSCAN_SCANNER_SCOPES(DECLARE_ENUM)
#undef DECLARE_ENUM
kNumIds,
};
enum class MutatorId {
#define DECLARE_ENUM(name) k##name,
FOR_ALL_PCSCAN_MUTATOR_SCOPES(DECLARE_ENUM)
#undef DECLARE_ENUM
kNumIds,
};
template <Context context>
using IdType =
std::conditional_t<context == Context::kMutator, MutatorId, ScannerId>;
// We don't immediately trace events, but instead defer it until scanning is
// done. This is needed to avoid unpredictable work that can be done by traces
// (e.g. recursive mutex lock).
struct DeferredTraceEvent {
base::TimeTicks start_time;
base::TimeTicks end_time;
};
// Thread-safe hash-map that maps thread id to scanner events. Doesn't
// accumulate events, i.e. every event can only be registered once.
template <Context context>
class DeferredTraceEventMap final {
public:
using IdType = StatsCollector::IdType<context>;
using PerThreadEvents =
std::array<DeferredTraceEvent, static_cast<size_t>(IdType::kNumIds)>;
using UnderlyingMap = std::unordered_map<
internal::base::PlatformThreadId,
PerThreadEvents,
std::hash<internal::base::PlatformThreadId>,
std::equal_to<>,
MetadataAllocator<std::pair<const internal::base::PlatformThreadId,
PerThreadEvents>>>;
inline void RegisterBeginEventFromCurrentThread(IdType id);
inline void RegisterEndEventFromCurrentThread(IdType id);
const UnderlyingMap& get_underlying_map_unsafe() const { return events_; }
private:
std::mutex mutex_;
UnderlyingMap events_;
};
template <Context context>
class Scope final {
public:
Scope(StatsCollector& stats, IdType<context> type)
: stats_(stats), type_(type) {
stats_.RegisterBeginEventFromCurrentThread(type);
}
Scope(const Scope&) = delete;
Scope& operator=(const Scope&) = delete;
~Scope() { stats_.RegisterEndEventFromCurrentThread(type_); }
private:
StatsCollector& stats_;
IdType<context> type_;
};
using ScannerScope = Scope<Context::kScanner>;
using MutatorScope = Scope<Context::kMutator>;
StatsCollector(const char* process_name, size_t quarantine_last_size);
StatsCollector(const StatsCollector&) = delete;
StatsCollector& operator=(const StatsCollector&) = delete;
~StatsCollector();
void IncreaseSurvivedQuarantineSize(size_t size) {
survived_quarantine_size_.fetch_add(size, std::memory_order_relaxed);
}
size_t survived_quarantine_size() const {
return survived_quarantine_size_.load(std::memory_order_relaxed);
}
void IncreaseSweptSize(size_t size) { swept_size_ += size; }
size_t swept_size() const { return swept_size_; }
void IncreaseDiscardedQuarantineSize(size_t size) {
discarded_quarantine_size_ += size;
}
base::TimeDelta GetOverallTime() const;
void ReportTracesAndHists(partition_alloc::StatsReporter& reporter) const;
private:
using MetadataString =
std::basic_string<char, std::char_traits<char>, MetadataAllocator<char>>;
MetadataString ToUMAString(ScannerId id) const;
MetadataString ToUMAString(MutatorId id) const;
void RegisterBeginEventFromCurrentThread(MutatorId id) {
mutator_trace_events_.RegisterBeginEventFromCurrentThread(id);
}
void RegisterEndEventFromCurrentThread(MutatorId id) {
mutator_trace_events_.RegisterEndEventFromCurrentThread(id);
}
void RegisterBeginEventFromCurrentThread(ScannerId id) {
scanner_trace_events_.RegisterBeginEventFromCurrentThread(id);
}
void RegisterEndEventFromCurrentThread(ScannerId id) {
scanner_trace_events_.RegisterEndEventFromCurrentThread(id);
}
template <Context context>
base::TimeDelta GetTimeImpl(const DeferredTraceEventMap<context>& event_map,
IdType<context> id) const;
template <Context context>
void ReportTracesAndHistsImpl(
partition_alloc::StatsReporter& reporter,
const DeferredTraceEventMap<context>& event_map) const;
void ReportSurvivalRate(partition_alloc::StatsReporter& reporter) const;
DeferredTraceEventMap<Context::kMutator> mutator_trace_events_;
DeferredTraceEventMap<Context::kScanner> scanner_trace_events_;
std::atomic<size_t> survived_quarantine_size_{0u};
size_t swept_size_ = 0u;
size_t discarded_quarantine_size_ = 0u;
const char* process_name_ = nullptr;
const size_t quarantine_last_size_ = 0u;
};
template <Context context>
inline void StatsCollector::DeferredTraceEventMap<
context>::RegisterBeginEventFromCurrentThread(IdType id) {
std::lock_guard<std::mutex> lock(mutex_);
const auto tid = base::PlatformThread::CurrentId();
const auto now = base::TimeTicks::Now();
auto& event_array = events_[tid];
auto& event = event_array[static_cast<size_t>(id)];
PA_DCHECK(event.start_time.is_null());
PA_DCHECK(event.end_time.is_null());
event.start_time = now;
}
template <Context context>
inline void StatsCollector::DeferredTraceEventMap<
context>::RegisterEndEventFromCurrentThread(IdType id) {
std::lock_guard<std::mutex> lock(mutex_);
const auto tid = base::PlatformThread::CurrentId();
const auto now = base::TimeTicks::Now();
auto& event_array = events_[tid];
auto& event = event_array[static_cast<size_t>(id)];
PA_DCHECK(!event.start_time.is_null());
PA_DCHECK(event.end_time.is_null());
event.end_time = now;
}
inline StatsCollector::MetadataString StatsCollector::ToUMAString(
ScannerId id) const {
PA_DCHECK(process_name_);
const MetadataString process_name = process_name_;
switch (id) {
case ScannerId::kClear:
return "PA.PCScan." + process_name + ".Scanner.Clear";
case ScannerId::kScan:
return "PA.PCScan." + process_name + ".Scanner.Scan";
case ScannerId::kSweep:
return "PA.PCScan." + process_name + ".Scanner.Sweep";
case ScannerId::kOverall:
return "PA.PCScan." + process_name + ".Scanner";
case ScannerId::kNumIds:
__builtin_unreachable();
}
}
inline StatsCollector::MetadataString StatsCollector::ToUMAString(
MutatorId id) const {
PA_DCHECK(process_name_);
const MetadataString process_name = process_name_;
switch (id) {
case MutatorId::kClear:
return "PA.PCScan." + process_name + ".Mutator.Clear";
case MutatorId::kScanStack:
return "PA.PCScan." + process_name + ".Mutator.ScanStack";
case MutatorId::kScan:
return "PA.PCScan." + process_name + ".Mutator.Scan";
case MutatorId::kOverall:
return "PA.PCScan." + process_name + ".Mutator";
case MutatorId::kNumIds:
__builtin_unreachable();
}
}
#undef FOR_ALL_PCSCAN_MUTATOR_SCOPES
#undef FOR_ALL_PCSCAN_SCANNER_SCOPES
} // namespace internal
} // namespace partition_alloc
#endif // BASE_ALLOCATOR_PARTITION_ALLOCATOR_STARSCAN_STATS_COLLECTOR_H_