mirror of
https://github.com/facebook/rocksdb.git
synced 2026-10-23 08:09:02 +03:00
Summary: Pull Request resolved: https://github.com/facebook/rocksdb/pull/15387 ## Summary RocksDB's `.clang-tidy` enables `cppcoreguidelines-special-member-functions`, `cppcoreguidelines-virtual-class-destructor` and the noexcept-move checks, and clang warns `-Wdeprecated-copy-with-user-provided-dtor`. A full scan reports 476 findings. In most of them, a class declares a destructor (or copy operations) but leaves the rest of copy/move implicit. That has two costs. Readers can't tell whether a class may be copied or moved. And resource-owning classes keep an implicit copy that would double-free if anyone ever used it. This diff resolves 453 of the 476. Approach, picked per class from least to most invasive: 1. An empty user-provided destructor becomes `= default`. This covers most interface classes, such as `virtual ~X() {}` -> `= default`. If the class is still copied, that would only rename the deprecated-copy warning, so the destructor is removed instead when nothing needs it (`Customizable`, `TableFactory` and `InternalKeyComparator` inherit a virtual one; `IteratorWrapperBase` isn't polymorphic), and `MemTableRep::KeyComparator` gets defaulted copy and move. 2. Classes whose copy is already deleted get explicitly deleted move operations too. Move was already unavailable, so this only documents it. 3. Resource-owning classes that are never copied get deleted copy and move operations. Examples: the C API wrapper structs, which run a `destructor_` callback (edited in `tools/c_api_gen/c_base.cc` and regenerated into `db/c.cc`); `ShardedCache`, which frees its shards; iterator wrappers; and RAII handles. 4. Value types that are copied or moved get the missing operations as `= default`, which keeps their current behavior. 5. Exception specifications of existing functions are unchanged, except for `WriteBatch`'s move assignment (below): no other `noexcept` is added or removed. Only the move operations this diff newly declares (`Comparator`, `FileSampledStats` and the thread-local `Entry`) are `noexcept`; they copy only scalars or atomics. `WriteBatch`'s copy and move assignment destroyed `*this` and placement-new'ed a new `WriteBatch` over it. That is undefined behavior when `*this` is a subclass, and if the copy constructor threw, `*this` was left destroyed and was later destroyed again. Move assignment now assigns member-wise and ends in the same state as the move constructor, which leaves `has_key_with_ts_`, the timestamp-size tracking and the other fields it doesn't copy at their defaults. `rep_` is swapped through a temporary, because a plain string move-assignment can keep the old buffer (e.g. a large batch reset with `batch = WriteBatch()` would keep its memory). Nothing in it can throw, so it is now `noexcept`. Copy assignment copies into a temporary and moves it in, so a throwing copy leaves `*this` unchanged. Bugs this exposed and fixed: - `RestoreAfterCopyOrCreateWorkItem` (backup engine): the hand-written move constructor and assignment moved only some fields, so `from_file` and `to_file` were empty after `push_back(std::move(...))`. A restore that hit a checksum mismatch then reported "While restoring -> : ..." without file names. It now uses the compiler-generated moves, which are still `noexcept`. - `ExternalSstFileIngestionJob` lived in a `std::vector`, and its destructor frees raw-owned pointers, so the implicit copy used on vector reallocation would double-free. Its file batches also point into its own inline file storage, so neither a copy nor a move is safe after `Prepare()`. Copy and move are now deleted, and `DBImpl` holds jobs through `std::unique_ptr`. Compatibility: no public class loses an operation users can call. Public interfaces mostly only get `virtual ~X() = default;`. `WriteBatch`'s move assignment becomes `noexcept`, which is not part of the mangled name, so the ABI doesn't change. `SubcompactionJobInfo` and `CompactionJobInfo` intentionally stay C++20 aggregates, so users' aggregate and designated initializers keep compiling. `Configurable`, `CompareInterface`, `WriteBatchBase`, `ObjectLibrary::Entry` and `MemTableRep::KeyComparator` declare defaulted copy and move, and `Customizable` no longer declares a destructor. As a result, `Configurable` and `Customizable` subclasses that declare no destructor or copy operations of their own now move their registered-options list instead of copying it. In-tree, these are the mutable-options adapters, the table factories, the memory allocators and test classes; all are held through smart pointers or used in place, and `Clone()` copies, so none is moved by value. Not changed (23): `IODebugContext` (custom deep copy plus a `std::shared_mutex`), `DBWithColumnFamilies` in db_bench (stored in a `std::vector` with non-movable members), `Snapshot`'s protected virtual destructor (intended), `ManagedSnapshot`, the `SubcompactionJobInfo` and `CompactionJobInfo` aggregates, and the 16 noexcept-move-constructor and noexcept-swap findings on existing functions, including `autovector`'s move assignment (adding `noexcept` would change their exception specifications). Copying `ManagedSnapshot` already double-releases the snapshot. Fixing that needs a real move implementation and is a behavior change, so it is left for a separate diff. Reviewed By: rban1 Differential Revision: D123186309 fbshipit-source-id: 6f8e57e233e12199a007407130bb9a1f6d52e8d1
336 lines
12 KiB
C++
336 lines
12 KiB
C++
// 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).
|
|
|
|
#pragma once
|
|
|
|
#include <list>
|
|
#include <map>
|
|
#include <queue>
|
|
#include <set>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "rocksdb/env.h"
|
|
#include "rocksdb/trace_reader_writer.h"
|
|
#include "rocksdb/trace_record.h"
|
|
#include "rocksdb/write_batch.h"
|
|
#include "trace_replay/trace_replay.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
// Value sizes may be used as denominators. Replacing 0 value sizes with this
|
|
// positive integer avoids division error.
|
|
extern const size_t kShadowValueSize /* = 10*/;
|
|
|
|
enum TraceOperationType : int {
|
|
kGet = 0,
|
|
kPut = 1,
|
|
kDelete = 2,
|
|
kSingleDelete = 3,
|
|
kRangeDelete = 4,
|
|
kMerge = 5,
|
|
kIteratorSeek = 6,
|
|
kIteratorSeekForPrev = 7,
|
|
kMultiGet = 8,
|
|
kPutEntity = 9,
|
|
kTaTypeNum = 10
|
|
};
|
|
|
|
struct TraceUnit {
|
|
uint64_t ts;
|
|
uint32_t type;
|
|
uint32_t cf_id;
|
|
size_t value_size;
|
|
std::string key;
|
|
};
|
|
|
|
struct TypeCorrelation {
|
|
uint64_t count;
|
|
uint64_t total_ts;
|
|
};
|
|
|
|
struct StatsUnit {
|
|
uint64_t key_id;
|
|
uint64_t access_count;
|
|
uint64_t latest_ts;
|
|
uint64_t succ_count; // current only used to count Get if key found
|
|
uint32_t cf_id;
|
|
size_t value_size;
|
|
std::vector<TypeCorrelation> v_correlation;
|
|
};
|
|
|
|
class AnalyzerOptions {
|
|
public:
|
|
std::vector<std::vector<int>> correlation_map;
|
|
std::vector<std::pair<int, int>> correlation_list;
|
|
|
|
AnalyzerOptions();
|
|
|
|
~AnalyzerOptions() = default;
|
|
|
|
void SparseCorrelationInput(const std::string& in_str);
|
|
};
|
|
|
|
// Note that, for the variable names in the trace_analyzer,
|
|
// Starting with 'a_' means the variable is used for 'accessed_keys'.
|
|
// Starting with 'w_' means it is used for 'the whole key space'.
|
|
// Ending with '_f' means a file write or reader pointer.
|
|
// For example, 'a_count' means 'accessed_keys_count',
|
|
// 'w_key_f' means 'whole_key_space_file'.
|
|
|
|
struct TraceStats {
|
|
uint32_t cf_id;
|
|
std::string cf_name;
|
|
uint64_t a_count;
|
|
uint64_t a_succ_count;
|
|
uint64_t a_key_id;
|
|
uint64_t a_key_size_sqsum;
|
|
uint64_t a_key_size_sum;
|
|
uint64_t a_key_mid;
|
|
uint64_t a_value_size_sqsum;
|
|
uint64_t a_value_size_sum;
|
|
uint64_t a_value_mid;
|
|
uint32_t a_peak_qps;
|
|
double a_ave_qps;
|
|
std::map<std::string, StatsUnit> a_key_stats;
|
|
std::map<uint64_t, uint64_t> a_count_stats;
|
|
std::map<uint64_t, uint64_t> a_key_size_stats;
|
|
std::map<uint64_t, uint64_t> a_value_size_stats;
|
|
std::map<uint32_t, uint32_t> a_qps_stats;
|
|
std::map<uint32_t, std::map<std::string, uint32_t>> a_qps_prefix_stats;
|
|
std::priority_queue<std::pair<uint64_t, std::string>,
|
|
std::vector<std::pair<uint64_t, std::string>>,
|
|
std::greater<std::pair<uint64_t, std::string>>>
|
|
top_k_queue;
|
|
std::priority_queue<std::pair<uint64_t, std::string>,
|
|
std::vector<std::pair<uint64_t, std::string>>,
|
|
std::greater<std::pair<uint64_t, std::string>>>
|
|
top_k_prefix_access;
|
|
std::priority_queue<std::pair<double, std::string>,
|
|
std::vector<std::pair<double, std::string>>,
|
|
std::greater<std::pair<double, std::string>>>
|
|
top_k_prefix_ave;
|
|
std::priority_queue<std::pair<uint32_t, uint32_t>,
|
|
std::vector<std::pair<uint32_t, uint32_t>>,
|
|
std::greater<std::pair<uint32_t, uint32_t>>>
|
|
top_k_qps_sec;
|
|
std::list<TraceUnit> time_series;
|
|
std::vector<std::pair<uint64_t, uint64_t>> correlation_output;
|
|
std::map<uint32_t, uint64_t> uni_key_num;
|
|
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> time_series_f;
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_key_f;
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_count_dist_f;
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_prefix_cut_f;
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_value_size_f;
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_key_size_f;
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_key_num_f;
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_qps_f;
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> a_top_qps_prefix_f;
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> w_key_f;
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> w_prefix_cut_f;
|
|
|
|
TraceStats();
|
|
~TraceStats();
|
|
TraceStats(const TraceStats&) = delete;
|
|
TraceStats& operator=(const TraceStats&) = delete;
|
|
TraceStats(TraceStats&&) = default;
|
|
TraceStats& operator=(TraceStats&&) = default;
|
|
};
|
|
|
|
struct TypeUnit {
|
|
std::string type_name;
|
|
bool enabled;
|
|
uint64_t total_keys;
|
|
uint64_t total_access;
|
|
uint64_t total_succ_access;
|
|
uint32_t sample_count;
|
|
std::map<uint32_t, TraceStats> stats;
|
|
TypeUnit() = default;
|
|
~TypeUnit() = default;
|
|
TypeUnit(const TypeUnit&) = delete;
|
|
TypeUnit& operator=(const TypeUnit&) = delete;
|
|
TypeUnit(TypeUnit&&) = default;
|
|
TypeUnit& operator=(TypeUnit&&) = default;
|
|
};
|
|
|
|
struct CfUnit {
|
|
uint32_t cf_id;
|
|
uint64_t w_count; // total keys in this cf if we use the whole key space
|
|
uint64_t a_count; // the total keys in this cf that are accessed
|
|
std::map<uint64_t, uint64_t> w_key_size_stats; // whole key space key size
|
|
// statistic this cf
|
|
std::map<uint32_t, uint32_t> cf_qps;
|
|
};
|
|
|
|
class TraceAnalyzer : private TraceRecord::Handler,
|
|
private WriteBatch::Handler {
|
|
public:
|
|
TraceAnalyzer(std::string& trace_path, std::string& output_path,
|
|
AnalyzerOptions _analyzer_opts);
|
|
~TraceAnalyzer() override;
|
|
|
|
Status PrepareProcessing();
|
|
|
|
Status StartProcessing();
|
|
|
|
Status MakeStatistics();
|
|
|
|
Status ReProcessing();
|
|
|
|
Status EndProcessing();
|
|
|
|
Status WriteTraceUnit(TraceUnit& unit);
|
|
|
|
std::vector<TypeUnit>& GetTaVector() { return ta_; }
|
|
|
|
private:
|
|
using TraceRecord::Handler::Handle;
|
|
Status Handle(const WriteQueryTraceRecord& record,
|
|
std::unique_ptr<TraceRecordResult>* result) override;
|
|
Status Handle(const GetQueryTraceRecord& record,
|
|
std::unique_ptr<TraceRecordResult>* result) override;
|
|
Status Handle(const IteratorSeekQueryTraceRecord& record,
|
|
std::unique_ptr<TraceRecordResult>* result) override;
|
|
Status Handle(const MultiGetQueryTraceRecord& record,
|
|
std::unique_ptr<TraceRecordResult>* result) override;
|
|
|
|
using WriteBatch::Handler::PutCF;
|
|
Status PutCF(uint32_t column_family_id, const Slice& key,
|
|
const Slice& value) override;
|
|
|
|
using WriteBatch::Handler::TimedPutCF;
|
|
Status TimedPutCF(uint32_t column_family_id, const Slice& key,
|
|
const Slice& value, uint64_t write_unix_time) override;
|
|
|
|
using WriteBatch::Handler::PutEntityCF;
|
|
Status PutEntityCF(uint32_t column_family_id, const Slice& key,
|
|
const Slice& value) override;
|
|
|
|
using WriteBatch::Handler::DeleteCF;
|
|
Status DeleteCF(uint32_t column_family_id, const Slice& key) override;
|
|
|
|
using WriteBatch::Handler::SingleDeleteCF;
|
|
Status SingleDeleteCF(uint32_t column_family_id, const Slice& key) override;
|
|
|
|
using WriteBatch::Handler::DeleteRangeCF;
|
|
Status DeleteRangeCF(uint32_t column_family_id, const Slice& begin_key,
|
|
const Slice& end_key) override;
|
|
|
|
using WriteBatch::Handler::MergeCF;
|
|
Status MergeCF(uint32_t column_family_id, const Slice& key,
|
|
const Slice& value) override;
|
|
|
|
// The following hanlders are not implemented, return Status::OK() to avoid
|
|
// the running time assertion and other irrelevant falures.
|
|
using WriteBatch::Handler::PutBlobIndexCF;
|
|
Status PutBlobIndexCF(uint32_t /*column_family_id*/, const Slice& /*key*/,
|
|
const Slice& /*value*/) override {
|
|
return Status::OK();
|
|
}
|
|
|
|
// The default implementation of LogData does nothing.
|
|
using WriteBatch::Handler::LogData;
|
|
void LogData(const Slice& /*blob*/) override {}
|
|
|
|
using WriteBatch::Handler::MarkBeginPrepare;
|
|
Status MarkBeginPrepare(bool = false) override { return Status::OK(); }
|
|
|
|
using WriteBatch::Handler::MarkEndPrepare;
|
|
Status MarkEndPrepare(const Slice& /*xid*/) override { return Status::OK(); }
|
|
|
|
using WriteBatch::Handler::MarkNoop;
|
|
Status MarkNoop(bool /*empty_batch*/) override { return Status::OK(); }
|
|
|
|
using WriteBatch::Handler::MarkRollback;
|
|
Status MarkRollback(const Slice& /*xid*/) override { return Status::OK(); }
|
|
|
|
using WriteBatch::Handler::MarkCommit;
|
|
Status MarkCommit(const Slice& /*xid*/) override { return Status::OK(); }
|
|
|
|
using WriteBatch::Handler::MarkCommitWithTimestamp;
|
|
Status MarkCommitWithTimestamp(const Slice& /*xid*/,
|
|
const Slice& /*commit_ts*/) override {
|
|
return Status::OK();
|
|
}
|
|
|
|
// Process each trace operation and output the analysis result to
|
|
// stdout/files.
|
|
Status OutputAnalysisResult(TraceOperationType op_type, uint64_t timestamp,
|
|
std::vector<uint32_t> cf_ids,
|
|
std::vector<Slice> keys,
|
|
std::vector<size_t> value_sizes);
|
|
|
|
Status OutputAnalysisResult(TraceOperationType op_type, uint64_t timestamp,
|
|
uint32_t cf_id, const Slice& key,
|
|
size_t value_size);
|
|
|
|
ROCKSDB_NAMESPACE::Env* env_;
|
|
EnvOptions env_options_;
|
|
std::unique_ptr<TraceReader> trace_reader_;
|
|
size_t offset_;
|
|
char buffer_[1024];
|
|
// Timestamp of a WriteBatch, used in its iteration.
|
|
uint64_t write_batch_ts_;
|
|
std::string trace_name_;
|
|
std::string output_path_;
|
|
AnalyzerOptions analyzer_opts_;
|
|
uint64_t total_requests_;
|
|
uint64_t total_access_keys_;
|
|
uint64_t total_gets_;
|
|
uint64_t total_writes_;
|
|
uint64_t total_seeks_;
|
|
uint64_t total_seek_prevs_;
|
|
uint64_t total_multigets_;
|
|
uint64_t trace_create_time_;
|
|
uint64_t begin_time_;
|
|
uint64_t end_time_;
|
|
uint64_t time_series_start_;
|
|
uint32_t sample_max_;
|
|
uint32_t cur_time_sec_;
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile>
|
|
trace_sequence_f_; // readable trace
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile> qps_f_; // overall qps
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile>
|
|
cf_qps_f_; // The qps of each CF>
|
|
std::vector<TypeUnit> ta_; // The main statistic collecting data structure
|
|
std::map<uint32_t, CfUnit> cfs_; // All the cf_id appears in this trace;
|
|
std::vector<uint32_t> qps_peak_;
|
|
std::vector<double> qps_ave_;
|
|
|
|
Status ReadTraceHeader(Trace* header);
|
|
Status ReadTraceFooter(Trace* footer);
|
|
Status ReadTraceRecord(Trace* trace);
|
|
Status KeyStatsInsertion(const uint32_t& type, const uint32_t& cf_id,
|
|
const std::string& key, const size_t value_size,
|
|
const uint64_t ts);
|
|
Status StatsUnitCorrelationUpdate(StatsUnit& unit, const uint32_t& type,
|
|
const uint64_t& ts, const std::string& key);
|
|
Status OpenStatsOutputFiles(const std::string& type, TraceStats& new_stats);
|
|
Status CreateOutputFile(
|
|
const std::string& type, const std::string& cf_name,
|
|
const std::string& ending,
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile>* f_ptr);
|
|
Status CloseOutputFiles();
|
|
|
|
void PrintStatistics();
|
|
Status TraceUnitWriter(
|
|
std::unique_ptr<ROCKSDB_NAMESPACE::WritableFile>& f_ptr, TraceUnit& unit);
|
|
Status WriteTraceSequence(const uint32_t& type, const uint32_t& cf_id,
|
|
const Slice& key, const size_t value_size,
|
|
const uint64_t ts);
|
|
Status WriteRangeDeleteTraceSequence(const uint32_t& cf_id,
|
|
const Slice& begin_key,
|
|
const Slice& end_key, const uint64_t ts);
|
|
Status MakeStatisticKeyStatsOrPrefix(TraceStats& stats);
|
|
Status MakeStatisticCorrelation(TraceStats& stats, StatsUnit& unit);
|
|
Status MakeStatisticQPS();
|
|
int db_version_;
|
|
};
|
|
|
|
int trace_analyzer_tool(int argc, char** argv);
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|