From 12e7a36e8cf0a3b217a88b067b7d2b6aef727ba4 Mon Sep 17 00:00:00 2001 From: xmx8 Date: Wed, 11 Jun 2025 11:38:00 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9Ethread=5Fstatus=5Fupdater?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../monitoring/thread_status_updater.cc | 314 ++++++++++++++++++ .../monitoring/thread_status_updater.h | 233 +++++++++++++ 2 files changed, 547 insertions(+) create mode 100644 storage/rocksdb/rocksdb/monitoring/thread_status_updater.cc create mode 100644 storage/rocksdb/rocksdb/monitoring/thread_status_updater.h diff --git a/storage/rocksdb/rocksdb/monitoring/thread_status_updater.cc b/storage/rocksdb/rocksdb/monitoring/thread_status_updater.cc new file mode 100644 index 000000000..7e4b299a8 --- /dev/null +++ b/storage/rocksdb/rocksdb/monitoring/thread_status_updater.cc @@ -0,0 +1,314 @@ +// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. +// This source code is licensed under both the GPLv2 (found in the +// COPYING file in the root directory) and Apache 2.0 License +// (found in the LICENSE.Apache file in the root directory). + +#include "monitoring/thread_status_updater.h" +#include +#include "port/likely.h" +#include "rocksdb/env.h" +#include "util/mutexlock.h" + +namespace ROCKSDB_NAMESPACE { + +#ifdef ROCKSDB_USING_THREAD_STATUS + +__thread ThreadStatusData* ThreadStatusUpdater::thread_status_data_ = nullptr; + +void ThreadStatusUpdater::RegisterThread(ThreadStatus::ThreadType ttype, + uint64_t thread_id) { + if (UNLIKELY(thread_status_data_ == nullptr)) { + thread_status_data_ = new ThreadStatusData(); + thread_status_data_->thread_type = ttype; + thread_status_data_->thread_id = thread_id; + std::lock_guard lck(thread_list_mutex_); + thread_data_set_.insert(thread_status_data_); + } + + ClearThreadOperationProperties(); +} + +void ThreadStatusUpdater::UnregisterThread() { + if (thread_status_data_ != nullptr) { + std::lock_guard lck(thread_list_mutex_); + thread_data_set_.erase(thread_status_data_); + delete thread_status_data_; + thread_status_data_ = nullptr; + } +} + +void ThreadStatusUpdater::ResetThreadStatus() { + ClearThreadState(); + ClearThreadOperation(); + SetColumnFamilyInfoKey(nullptr); +} + +void ThreadStatusUpdater::SetColumnFamilyInfoKey(const void* cf_key) { + auto* data = Get(); + if (data == nullptr) { + return; + } + // set the tracking flag based on whether cf_key is non-null or not. + // If enable_thread_tracking is set to false, the input cf_key + // would be nullptr. + data->enable_tracking = (cf_key != nullptr); + data->cf_key.store(const_cast(cf_key), std::memory_order_relaxed); +} + +const void* ThreadStatusUpdater::GetColumnFamilyInfoKey() { + auto* data = GetLocalThreadStatus(); + if (data == nullptr) { + return nullptr; + } + return data->cf_key.load(std::memory_order_relaxed); +} + +void ThreadStatusUpdater::SetThreadOperation( + const ThreadStatus::OperationType type) { + auto* data = GetLocalThreadStatus(); + if (data == nullptr) { + return; + } + // NOTE: Our practice here is to set all the thread operation properties + // and stage before we set thread operation, and thread operation + // will be set in std::memory_order_release. This is to ensure + // whenever a thread operation is not OP_UNKNOWN, we will always + // have a consistent information on its properties. + data->operation_type.store(type, std::memory_order_release); + if (type == ThreadStatus::OP_UNKNOWN) { + data->operation_stage.store(ThreadStatus::STAGE_UNKNOWN, + std::memory_order_relaxed); + ClearThreadOperationProperties(); + } +} + +void ThreadStatusUpdater::SetThreadOperationProperty(int i, uint64_t value) { + auto* data = GetLocalThreadStatus(); + if (data == nullptr) { + return; + } + data->op_properties[i].store(value, std::memory_order_relaxed); +} + +void ThreadStatusUpdater::IncreaseThreadOperationProperty(int i, + uint64_t delta) { + auto* data = GetLocalThreadStatus(); + if (data == nullptr) { + return; + } + data->op_properties[i].fetch_add(delta, std::memory_order_relaxed); +} + +void ThreadStatusUpdater::SetOperationStartTime(const uint64_t start_time) { + auto* data = GetLocalThreadStatus(); + if (data == nullptr) { + return; + } + data->op_start_time.store(start_time, std::memory_order_relaxed); +} + +void ThreadStatusUpdater::ClearThreadOperation() { + auto* data = GetLocalThreadStatus(); + if (data == nullptr) { + return; + } + data->operation_stage.store(ThreadStatus::STAGE_UNKNOWN, + std::memory_order_relaxed); + data->operation_type.store(ThreadStatus::OP_UNKNOWN, + std::memory_order_relaxed); + ClearThreadOperationProperties(); +} + +void ThreadStatusUpdater::ClearThreadOperationProperties() { + auto* data = GetLocalThreadStatus(); + if (data == nullptr) { + return; + } + for (int i = 0; i < ThreadStatus::kNumOperationProperties; ++i) { + data->op_properties[i].store(0, std::memory_order_relaxed); + } +} + +ThreadStatus::OperationStage ThreadStatusUpdater::SetThreadOperationStage( + ThreadStatus::OperationStage stage) { + auto* data = GetLocalThreadStatus(); + if (data == nullptr) { + return ThreadStatus::STAGE_UNKNOWN; + } + return data->operation_stage.exchange(stage, std::memory_order_relaxed); +} + +void ThreadStatusUpdater::SetThreadState(const ThreadStatus::StateType type) { + auto* data = GetLocalThreadStatus(); + if (data == nullptr) { + return; + } + data->state_type.store(type, std::memory_order_relaxed); +} + +void ThreadStatusUpdater::ClearThreadState() { + auto* data = GetLocalThreadStatus(); + if (data == nullptr) { + return; + } + data->state_type.store(ThreadStatus::STATE_UNKNOWN, + std::memory_order_relaxed); +} + +Status ThreadStatusUpdater::GetThreadList( + std::vector* thread_list) { + thread_list->clear(); + std::vector> valid_list; + uint64_t now_micros = Env::Default()->NowMicros(); + + std::lock_guard lck(thread_list_mutex_); + for (auto* thread_data : thread_data_set_) { + assert(thread_data); + auto thread_id = thread_data->thread_id.load(std::memory_order_relaxed); + auto thread_type = thread_data->thread_type.load(std::memory_order_relaxed); + // Since any change to cf_info_map requires thread_list_mutex, + // which is currently held by GetThreadList(), here we can safely + // use "memory_order_relaxed" to load the cf_key. + auto cf_key = thread_data->cf_key.load(std::memory_order_relaxed); + + ThreadStatus::OperationType op_type = ThreadStatus::OP_UNKNOWN; + ThreadStatus::OperationStage op_stage = ThreadStatus::STAGE_UNKNOWN; + ThreadStatus::StateType state_type = ThreadStatus::STATE_UNKNOWN; + uint64_t op_elapsed_micros = 0; + uint64_t op_props[ThreadStatus::kNumOperationProperties] = {0}; + + auto iter = cf_info_map_.find(cf_key); + if (iter != cf_info_map_.end()) { + op_type = thread_data->operation_type.load(std::memory_order_acquire); + // display lower-level info only when higher-level info is available. + if (op_type != ThreadStatus::OP_UNKNOWN) { + op_elapsed_micros = now_micros - thread_data->op_start_time.load( + std::memory_order_relaxed); + op_stage = thread_data->operation_stage.load(std::memory_order_relaxed); + state_type = thread_data->state_type.load(std::memory_order_relaxed); + for (int i = 0; i < ThreadStatus::kNumOperationProperties; ++i) { + op_props[i] = + thread_data->op_properties[i].load(std::memory_order_relaxed); + } + } + } + + thread_list->emplace_back( + thread_id, thread_type, + iter != cf_info_map_.end() ? iter->second.db_name : "", + iter != cf_info_map_.end() ? iter->second.cf_name : "", op_type, + op_elapsed_micros, op_stage, op_props, state_type); + } + + return Status::OK(); +} + +ThreadStatusData* ThreadStatusUpdater::GetLocalThreadStatus() { + if (thread_status_data_ == nullptr) { + return nullptr; + } + if (!thread_status_data_->enable_tracking) { + assert(thread_status_data_->cf_key.load(std::memory_order_relaxed) == + nullptr); + return nullptr; + } + return thread_status_data_; +} + +void ThreadStatusUpdater::NewColumnFamilyInfo(const void* db_key, + const std::string& db_name, + const void* cf_key, + const std::string& cf_name) { + // Acquiring same lock as GetThreadList() to guarantee + // a consistent view of global column family table (cf_info_map). + std::lock_guard lck(thread_list_mutex_); + + cf_info_map_.emplace(std::piecewise_construct, std::make_tuple(cf_key), + std::make_tuple(db_key, db_name, cf_name)); + db_key_map_[db_key].insert(cf_key); +} + +void ThreadStatusUpdater::EraseColumnFamilyInfo(const void* cf_key) { + // Acquiring same lock as GetThreadList() to guarantee + // a consistent view of global column family table (cf_info_map). + std::lock_guard lck(thread_list_mutex_); + + auto cf_pair = cf_info_map_.find(cf_key); + if (cf_pair != cf_info_map_.end()) { + // Remove its entry from db_key_map_ by the following steps: + // 1. Obtain the entry in db_key_map_ whose set contains cf_key + // 2. Remove it from the set. + ConstantColumnFamilyInfo& cf_info = cf_pair->second; + auto db_pair = db_key_map_.find(cf_info.db_key); + assert(db_pair != db_key_map_.end()); + size_t result __attribute__((__unused__)); + result = db_pair->second.erase(cf_key); + assert(result); + cf_info_map_.erase(cf_pair); + } +} + +void ThreadStatusUpdater::EraseDatabaseInfo(const void* db_key) { + // Acquiring same lock as GetThreadList() to guarantee + // a consistent view of global column family table (cf_info_map). + std::lock_guard lck(thread_list_mutex_); + auto db_pair = db_key_map_.find(db_key); + if (UNLIKELY(db_pair == db_key_map_.end())) { + // In some occasional cases such as DB::Open fails, we won't + // register ColumnFamilyInfo for a db. + return; + } + + for (auto cf_key : db_pair->second) { + auto cf_pair = cf_info_map_.find(cf_key); + if (cf_pair != cf_info_map_.end()) { + cf_info_map_.erase(cf_pair); + } + } + db_key_map_.erase(db_key); +} + +#else + +void ThreadStatusUpdater::RegisterThread(ThreadStatus::ThreadType /*ttype*/, + uint64_t /*thread_id*/) {} + +void ThreadStatusUpdater::UnregisterThread() {} + +void ThreadStatusUpdater::ResetThreadStatus() {} + +void ThreadStatusUpdater::SetColumnFamilyInfoKey(const void* /*cf_key*/) {} + +void ThreadStatusUpdater::SetThreadOperation( + const ThreadStatus::OperationType /*type*/) {} + +void ThreadStatusUpdater::ClearThreadOperation() {} + +void ThreadStatusUpdater::SetThreadState( + const ThreadStatus::StateType /*type*/) {} + +void ThreadStatusUpdater::ClearThreadState() {} + +Status ThreadStatusUpdater::GetThreadList( + std::vector* /*thread_list*/) { + return Status::NotSupported( + "GetThreadList is not supported in the current running environment."); +} + +void ThreadStatusUpdater::NewColumnFamilyInfo(const void* /*db_key*/, + const std::string& /*db_name*/, + const void* /*cf_key*/, + const std::string& /*cf_name*/) {} + +void ThreadStatusUpdater::EraseColumnFamilyInfo(const void* /*cf_key*/) {} + +void ThreadStatusUpdater::EraseDatabaseInfo(const void* /*db_key*/) {} + +void ThreadStatusUpdater::SetThreadOperationProperty(int /*i*/, + uint64_t /*value*/) {} + +void ThreadStatusUpdater::IncreaseThreadOperationProperty(int /*i*/, + uint64_t /*delta*/) {} + +#endif // ROCKSDB_USING_THREAD_STATUS +} // namespace ROCKSDB_NAMESPACE diff --git a/storage/rocksdb/rocksdb/monitoring/thread_status_updater.h b/storage/rocksdb/rocksdb/monitoring/thread_status_updater.h new file mode 100644 index 000000000..ac8c0f35d --- /dev/null +++ b/storage/rocksdb/rocksdb/monitoring/thread_status_updater.h @@ -0,0 +1,233 @@ +// Copyright (c) 2011-present, Facebook, Inc. All rights reserved. +// This source code is licensed under both the GPLv2 (found in the +// COPYING file in the root directory) and Apache 2.0 License +// (found in the LICENSE.Apache file in the root directory). +// +// The implementation of ThreadStatus. +// +// Note that we make get and set access to ThreadStatusData lockless. +// As a result, ThreadStatusData as a whole is not atomic. However, +// we guarantee consistent ThreadStatusData all the time whenever +// user call GetThreadList(). This consistency guarantee is done +// by having the following constraint in the internal implementation +// of set and get order: +// +// 1. When reset any information in ThreadStatusData, always start from +// clearing up the lower-level information first. +// 2. When setting any information in ThreadStatusData, always start from +// setting the higher-level information. +// 3. When returning ThreadStatusData to the user, fields are fetched from +// higher-level to lower-level. In addition, where there's a nullptr +// in one field, then all fields that has lower-level than that field +// should be ignored. +// +// The high to low level information would be: +// thread_id > thread_type > db > cf > operation > state +// +// This means user might not always get full information, but whenever +// returned by the GetThreadList() is guaranteed to be consistent. +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rocksdb/status.h" +#include "rocksdb/thread_status.h" +#include "port/port.h" +#include "util/thread_operation.h" + +namespace ROCKSDB_NAMESPACE { + +class ColumnFamilyHandle; + +// The structure that keeps constant information about a column family. +struct ConstantColumnFamilyInfo { +#ifdef ROCKSDB_USING_THREAD_STATUS + public: + ConstantColumnFamilyInfo( + const void* _db_key, + const std::string& _db_name, + const std::string& _cf_name) : + db_key(_db_key), db_name(_db_name), cf_name(_cf_name) {} + const void* db_key; + const std::string db_name; + const std::string cf_name; +#endif // ROCKSDB_USING_THREAD_STATUS +}; + +// the internal data-structure that is used to reflect the current +// status of a thread using a set of atomic pointers. +struct ThreadStatusData { +#ifdef ROCKSDB_USING_THREAD_STATUS + explicit ThreadStatusData() : enable_tracking(false) { + thread_id.store(0); + thread_type.store(ThreadStatus::USER); + cf_key.store(nullptr); + operation_type.store(ThreadStatus::OP_UNKNOWN); + op_start_time.store(0); + state_type.store(ThreadStatus::STATE_UNKNOWN); + } + + // A flag to indicate whether the thread tracking is enabled + // in the current thread. This value will be updated based on whether + // the associated Options::enable_thread_tracking is set to true + // in ThreadStatusUtil::SetColumnFamily(). + // + // If set to false, then SetThreadOperation and SetThreadState + // will be no-op. + bool enable_tracking; + + std::atomic thread_id; + std::atomic thread_type; + std::atomic cf_key; + std::atomic operation_type; + std::atomic op_start_time; + std::atomic operation_stage; + std::atomic op_properties[ThreadStatus::kNumOperationProperties]; + std::atomic state_type; +#endif // ROCKSDB_USING_THREAD_STATUS +}; + +// The class that stores and updates the status of the current thread +// using a thread-local ThreadStatusData. +// +// In most of the case, you should use ThreadStatusUtil to update +// the status of the current thread instead of using ThreadSatusUpdater +// directly. +// +// @see ThreadStatusUtil +class ThreadStatusUpdater { + public: + ThreadStatusUpdater() {} + + // Releases all ThreadStatusData of all active threads. + virtual ~ThreadStatusUpdater() {} + + // Unregister the current thread. + void UnregisterThread(); + + // Reset the status of the current thread. This includes resetting + // ColumnFamilyInfoKey, ThreadOperation, and ThreadState. + void ResetThreadStatus(); + + // Set the id of the current thread. + void SetThreadID(uint64_t thread_id); + + // Register the current thread for tracking. + void RegisterThread(ThreadStatus::ThreadType ttype, uint64_t thread_id); + + // Update the column-family info of the current thread by setting + // its thread-local pointer of ThreadStateInfo to the correct entry. + void SetColumnFamilyInfoKey(const void* cf_key); + + // returns the column family info key. + const void* GetColumnFamilyInfoKey(); + + // Update the thread operation of the current thread. + void SetThreadOperation(const ThreadStatus::OperationType type); + + // The start time of the current thread operation. It is in the format + // of micro-seconds since some fixed point in time. + void SetOperationStartTime(const uint64_t start_time); + + // Set the "i"th property of the current operation. + // + // NOTE: Our practice here is to set all the thread operation properties + // and stage before we set thread operation, and thread operation + // will be set in std::memory_order_release. This is to ensure + // whenever a thread operation is not OP_UNKNOWN, we will always + // have a consistent information on its properties. + void SetThreadOperationProperty( + int i, uint64_t value); + + // Increase the "i"th property of the current operation with + // the specified delta. + void IncreaseThreadOperationProperty( + int i, uint64_t delta); + + // Update the thread operation stage of the current thread. + ThreadStatus::OperationStage SetThreadOperationStage( + const ThreadStatus::OperationStage stage); + + // Clear thread operation of the current thread. + void ClearThreadOperation(); + + // Reset all thread-operation-properties to 0. + void ClearThreadOperationProperties(); + + // Update the thread state of the current thread. + void SetThreadState(const ThreadStatus::StateType type); + + // Clear the thread state of the current thread. + void ClearThreadState(); + + // Obtain the status of all active registered threads. + Status GetThreadList( + std::vector* thread_list); + + // Create an entry in the global ColumnFamilyInfo table for the + // specified column family. This function should be called only + // when the current thread does not hold db_mutex. + void NewColumnFamilyInfo( + const void* db_key, const std::string& db_name, + const void* cf_key, const std::string& cf_name); + + // Erase all ConstantColumnFamilyInfo that is associated with the + // specified db instance. This function should be called only when + // the current thread does not hold db_mutex. + void EraseDatabaseInfo(const void* db_key); + + // Erase the ConstantColumnFamilyInfo that is associated with the + // specified ColumnFamilyData. This function should be called only + // when the current thread does not hold db_mutex. + void EraseColumnFamilyInfo(const void* cf_key); + + // Verifies whether the input ColumnFamilyHandles matches + // the information stored in the current cf_info_map. + void TEST_VerifyColumnFamilyInfoMap( + const std::vector& handles, + bool check_exist); + + protected: +#ifdef ROCKSDB_USING_THREAD_STATUS + // The thread-local variable for storing thread status. + static __thread ThreadStatusData* thread_status_data_; + + // Returns the pointer to the thread status data only when the + // thread status data is non-null and has enable_tracking == true. + ThreadStatusData* GetLocalThreadStatus(); + + // Directly returns the pointer to thread_status_data_ without + // checking whether enabling_tracking is true of not. + ThreadStatusData* Get() { + return thread_status_data_; + } + + // The mutex that protects cf_info_map and db_key_map. + std::mutex thread_list_mutex_; + + // The current status data of all active threads. + std::unordered_set thread_data_set_; + + // A global map that keeps the column family information. It is stored + // globally instead of inside DB is to avoid the situation where DB is + // closing while GetThreadList function already get the pointer to its + // CopnstantColumnFamilyInfo. + std::unordered_map cf_info_map_; + + // A db_key to cf_key map that allows erasing elements in cf_info_map + // associated to the same db_key faster. + std::unordered_map< + const void*, std::unordered_set> db_key_map_; + +#else + static ThreadStatusData* thread_status_data_; +#endif // ROCKSDB_USING_THREAD_STATUS +}; + +} // namespace ROCKSDB_NAMESPACE -- Gitee