mirror of
https://github.com/facebook/rocksdb.git
synced 2024-11-26 16:30:56 +00:00
5724348689
Summary: * Consolidates most metadata into a single word per slot so that more can be accomplished with a single atomic update. In the common case, Lookup was previously about 4 atomic updates, now just 1 atomic update. Common case Release was previously 1 atomic read + 1 atomic update, now just 1 atomic update. * Eliminate spins / waits / yields, which likely threaten some "lock free" benefits. Compare-exchange loops are only used in explicit Erase, and strict_capacity_limit=true Insert. Eviction uses opportunistic compare- exchange. * Relaxes some aggressiveness and guarantees. For example, * Duplicate Inserts will sometimes go undetected and the shadow duplicate will age out with eviction. * In many cases, the older Inserted value for a given cache key will be kept (i.e. Insert does not support overwrite). * Entries explicitly erased (rather than evicted) might not be freed immediately in some rare cases. * With strict_capacity_limit=false, capacity limit is not tracked/enforced as precisely as LRUCache, but is self-correcting and should only deviate by a very small number of extra or fewer entries. * Use smaller "computed default" number of cache shards in many cases, because benefits to larger usage tracking / eviction pools outweigh the small cost of more lock-free atomic contention. The improvement in CPU and I/O is dramatic in some limit-memory cases. * Even without the sharding change, the eviction algorithm is likely more effective than LRU overall because it's more stateful, even though the "hot path" state tracking for it is essentially free with ref counting. It is like a generalized CLOCK with aging (see code comments). I don't have performance numbers showing a specific improvement, but in theory, for a Poisson access pattern to each block, keeping some state allows better estimation of time to next access (Poisson interval) than strict LRU. The bounded randomness in CLOCK can also reduce "cliff" effect for repeated range scans approaching and exceeding cache size. ## Hot path algorithm comparison Rough descriptions, focusing on number and kind of atomic operations: * Old `Lookup()` (2-5 atomic updates per probe): ``` Loop: Increment internal ref count at slot If possible hit: Check flags atomic (and non-atomic fields) If cache hit: Three distinct updates to 'flags' atomic Increment refs for internal-to-external Return Decrement internal ref count while atomic read 'displacements' > 0 ``` * New `Lookup()` (1-2 atomic updates per probe): ``` Loop: Increment acquire counter in meta word (optimistic) If visible entry (already read meta word): If match (read non-atomic fields): Return Else: Decrement acquire counter in meta word Else if invisible entry (rare, already read meta word): Decrement acquire counter in meta word while atomic read 'displacements' > 0 ``` * Old `Release()` (1 atomic update, conditional on atomic read, rarely more): ``` Read atomic ref count If last reference and invisible (rare): Use CAS etc. to remove Return Else: Decrement ref count ``` * New `Release()` (1 unconditional atomic update, rarely more): ``` Increment release counter in meta word If last reference and invisible (rare): Use CAS etc. to remove Return ``` ## Performance test setup Build DB with ``` TEST_TMPDIR=/dev/shm ./db_bench -benchmarks=fillrandom -num=30000000 -disable_wal=1 -bloom_bits=16 ``` Test with ``` TEST_TMPDIR=/dev/shm ./db_bench -benchmarks=readrandom -readonly -num=30000000 -bloom_bits=16 -cache_index_and_filter_blocks=1 -cache_size=${CACHE_MB}000000 -duration 60 -threads=$THREADS -statistics ``` Numbers on a single socket Skylake Xeon system with 48 hardware threads, DEBUG_LEVEL=0 PORTABLE=0. Very similar story on a dual socket system with 80 hardware threads. Using (every 2nd) Fibonacci MB cache sizes to sample the territory between powers of two. Configurations: base: LRUCache before this change, but with db_bench change to default cache_numshardbits=-1 (instead of fixed at 6) folly: LRUCache before this change, with folly enabled (distributed mutex) but on an old compiler (sorry) gt_clock: experimental ClockCache before this change new_clock: experimental ClockCache with this change ## Performance test results First test "hot path" read performance, with block cache large enough for whole DB: 4181MB 1thread base -> kops/s: 47.761 4181MB 1thread folly -> kops/s: 45.877 4181MB 1thread gt_clock -> kops/s: 51.092 4181MB 1thread new_clock -> kops/s: 53.944 4181MB 16thread base -> kops/s: 284.567 4181MB 16thread folly -> kops/s: 249.015 4181MB 16thread gt_clock -> kops/s: 743.762 4181MB 16thread new_clock -> kops/s: 861.821 4181MB 24thread base -> kops/s: 303.415 4181MB 24thread folly -> kops/s: 266.548 4181MB 24thread gt_clock -> kops/s: 975.706 4181MB 24thread new_clock -> kops/s: 1205.64 (~= 24 * 53.944) 4181MB 32thread base -> kops/s: 311.251 4181MB 32thread folly -> kops/s: 274.952 4181MB 32thread gt_clock -> kops/s: 1045.98 4181MB 32thread new_clock -> kops/s: 1370.38 4181MB 48thread base -> kops/s: 310.504 4181MB 48thread folly -> kops/s: 268.322 4181MB 48thread gt_clock -> kops/s: 1195.65 4181MB 48thread new_clock -> kops/s: 1604.85 (~= 24 * 1.25 * 53.944) 4181MB 64thread base -> kops/s: 307.839 4181MB 64thread folly -> kops/s: 272.172 4181MB 64thread gt_clock -> kops/s: 1204.47 4181MB 64thread new_clock -> kops/s: 1615.37 4181MB 128thread base -> kops/s: 310.934 4181MB 128thread folly -> kops/s: 267.468 4181MB 128thread gt_clock -> kops/s: 1188.75 4181MB 128thread new_clock -> kops/s: 1595.46 Whether we have just one thread on a quiet system or an overload of threads, the new version wins every time in thousand-ops per second, sometimes dramatically so. Mutex-based implementation quickly becomes contention-limited. New clock cache shows essentially perfect scaling up to number of physical cores (24), and then each hyperthreaded core adding about 1/4 the throughput of an additional physical core (see 48 thread case). Block cache miss rates (omitted above) are negligible across the board. With partitioned instead of full filters, the maximum speed-up vs. base is more like 2.5x rather than 5x. Now test a large block cache with low miss ratio, but some eviction is required: 1597MB 1thread base -> kops/s: 46.603 io_bytes/op: 1584.63 miss_ratio: 0.0201066 max_rss_mb: 1589.23 1597MB 1thread folly -> kops/s: 45.079 io_bytes/op: 1530.03 miss_ratio: 0.019872 max_rss_mb: 1550.43 1597MB 1thread gt_clock -> kops/s: 48.711 io_bytes/op: 1566.63 miss_ratio: 0.0198923 max_rss_mb: 1691.4 1597MB 1thread new_clock -> kops/s: 51.531 io_bytes/op: 1589.07 miss_ratio: 0.0201969 max_rss_mb: 1583.56 1597MB 32thread base -> kops/s: 301.174 io_bytes/op: 1439.52 miss_ratio: 0.0184218 max_rss_mb: 1656.59 1597MB 32thread folly -> kops/s: 273.09 io_bytes/op: 1375.12 miss_ratio: 0.0180002 max_rss_mb: 1586.8 1597MB 32thread gt_clock -> kops/s: 904.497 io_bytes/op: 1411.29 miss_ratio: 0.0179934 max_rss_mb: 1775.89 1597MB 32thread new_clock -> kops/s: 1182.59 io_bytes/op: 1440.77 miss_ratio: 0.0185449 max_rss_mb: 1636.45 1597MB 128thread base -> kops/s: 309.91 io_bytes/op: 1438.25 miss_ratio: 0.018399 max_rss_mb: 1689.98 1597MB 128thread folly -> kops/s: 267.605 io_bytes/op: 1394.16 miss_ratio: 0.0180286 max_rss_mb: 1631.91 1597MB 128thread gt_clock -> kops/s: 691.518 io_bytes/op: 9056.73 miss_ratio: 0.0186572 max_rss_mb: 1982.26 1597MB 128thread new_clock -> kops/s: 1406.12 io_bytes/op: 1440.82 miss_ratio: 0.0185463 max_rss_mb: 1685.63 610MB 1thread base -> kops/s: 45.511 io_bytes/op: 2279.61 miss_ratio: 0.0290528 max_rss_mb: 615.137 610MB 1thread folly -> kops/s: 43.386 io_bytes/op: 2217.29 miss_ratio: 0.0289282 max_rss_mb: 600.996 610MB 1thread gt_clock -> kops/s: 46.207 io_bytes/op: 2275.51 miss_ratio: 0.0290057 max_rss_mb: 637.934 610MB 1thread new_clock -> kops/s: 48.879 io_bytes/op: 2283.1 miss_ratio: 0.0291253 max_rss_mb: 613.5 610MB 32thread base -> kops/s: 306.59 io_bytes/op: 2250 miss_ratio: 0.0288721 max_rss_mb: 683.402 610MB 32thread folly -> kops/s: 269.176 io_bytes/op: 2187.86 miss_ratio: 0.0286938 max_rss_mb: 628.742 610MB 32thread gt_clock -> kops/s: 855.097 io_bytes/op: 2279.26 miss_ratio: 0.0288009 max_rss_mb: 733.062 610MB 32thread new_clock -> kops/s: 1121.47 io_bytes/op: 2244.29 miss_ratio: 0.0289046 max_rss_mb: 666.453 610MB 128thread base -> kops/s: 305.079 io_bytes/op: 2252.43 miss_ratio: 0.0288884 max_rss_mb: 723.457 610MB 128thread folly -> kops/s: 269.583 io_bytes/op: 2204.58 miss_ratio: 0.0287001 max_rss_mb: 676.426 610MB 128thread gt_clock -> kops/s: 53.298 io_bytes/op: 8128.98 miss_ratio: 0.0292452 max_rss_mb: 956.273 610MB 128thread new_clock -> kops/s: 1301.09 io_bytes/op: 2246.04 miss_ratio: 0.0289171 max_rss_mb: 788.812 The new version is still winning every time, sometimes dramatically so, and we can tell from the maximum resident memory numbers (which contain some noise, by the way) that the new cache is not cheating on memory usage. IMPORTANT: The previous generation experimental clock cache appears to hit a serious bottleneck in the higher thread count configurations, presumably due to some of its waiting functionality. (The same bottleneck is not seen with partitioned index+filters.) Now we consider even smaller cache sizes, with higher miss ratios, eviction work, etc. 233MB 1thread base -> kops/s: 10.557 io_bytes/op: 227040 miss_ratio: 0.0403105 max_rss_mb: 247.371 233MB 1thread folly -> kops/s: 15.348 io_bytes/op: 112007 miss_ratio: 0.0372238 max_rss_mb: 245.293 233MB 1thread gt_clock -> kops/s: 6.365 io_bytes/op: 244854 miss_ratio: 0.0413873 max_rss_mb: 259.844 233MB 1thread new_clock -> kops/s: 47.501 io_bytes/op: 2591.93 miss_ratio: 0.0330989 max_rss_mb: 242.461 233MB 32thread base -> kops/s: 96.498 io_bytes/op: 363379 miss_ratio: 0.0459966 max_rss_mb: 479.227 233MB 32thread folly -> kops/s: 109.95 io_bytes/op: 314799 miss_ratio: 0.0450032 max_rss_mb: 400.738 233MB 32thread gt_clock -> kops/s: 2.353 io_bytes/op: 385397 miss_ratio: 0.048445 max_rss_mb: 500.688 233MB 32thread new_clock -> kops/s: 1088.95 io_bytes/op: 2567.02 miss_ratio: 0.0330593 max_rss_mb: 303.402 233MB 128thread base -> kops/s: 84.302 io_bytes/op: 378020 miss_ratio: 0.0466558 max_rss_mb: 1051.84 233MB 128thread folly -> kops/s: 89.921 io_bytes/op: 338242 miss_ratio: 0.0460309 max_rss_mb: 812.785 233MB 128thread gt_clock -> kops/s: 2.588 io_bytes/op: 462833 miss_ratio: 0.0509158 max_rss_mb: 1109.94 233MB 128thread new_clock -> kops/s: 1299.26 io_bytes/op: 2565.94 miss_ratio: 0.0330531 max_rss_mb: 361.016 89MB 1thread base -> kops/s: 0.574 io_bytes/op: 5.35977e+06 miss_ratio: 0.274427 max_rss_mb: 91.3086 89MB 1thread folly -> kops/s: 0.578 io_bytes/op: 5.16549e+06 miss_ratio: 0.27276 max_rss_mb: 96.8984 89MB 1thread gt_clock -> kops/s: 0.512 io_bytes/op: 4.13111e+06 miss_ratio: 0.242817 max_rss_mb: 119.441 89MB 1thread new_clock -> kops/s: 48.172 io_bytes/op: 2709.76 miss_ratio: 0.0346162 max_rss_mb: 100.754 89MB 32thread base -> kops/s: 5.779 io_bytes/op: 6.14192e+06 miss_ratio: 0.320399 max_rss_mb: 311.812 89MB 32thread folly -> kops/s: 5.601 io_bytes/op: 5.83838e+06 miss_ratio: 0.313123 max_rss_mb: 252.418 89MB 32thread gt_clock -> kops/s: 0.77 io_bytes/op: 3.99236e+06 miss_ratio: 0.236296 max_rss_mb: 396.422 89MB 32thread new_clock -> kops/s: 1064.97 io_bytes/op: 2687.23 miss_ratio: 0.0346134 max_rss_mb: 155.293 89MB 128thread base -> kops/s: 4.959 io_bytes/op: 6.20297e+06 miss_ratio: 0.323945 max_rss_mb: 823.43 89MB 128thread folly -> kops/s: 4.962 io_bytes/op: 5.9601e+06 miss_ratio: 0.319857 max_rss_mb: 626.824 89MB 128thread gt_clock -> kops/s: 1.009 io_bytes/op: 4.1083e+06 miss_ratio: 0.242512 max_rss_mb: 1095.32 89MB 128thread new_clock -> kops/s: 1224.39 io_bytes/op: 2688.2 miss_ratio: 0.0346207 max_rss_mb: 218.223 ^ Now something interesting has happened: the new clock cache has gained a dramatic lead in the single-threaded case, and this is because the cache is so small, and full filters are so big, that dividing the cache into 64 shards leads to significant (random) imbalances in cache shards and excessive churn in imbalanced shards. This new clock cache only uses two shards for this configuration, and that helps to ensure that entries are part of a sufficiently big pool that their eviction order resembles the single-shard order. (This effect is not seen with partitioned index+filters.) Even smaller cache size: 34MB 1thread base -> kops/s: 0.198 io_bytes/op: 1.65342e+07 miss_ratio: 0.939466 max_rss_mb: 48.6914 34MB 1thread folly -> kops/s: 0.201 io_bytes/op: 1.63416e+07 miss_ratio: 0.939081 max_rss_mb: 45.3281 34MB 1thread gt_clock -> kops/s: 0.448 io_bytes/op: 4.43957e+06 miss_ratio: 0.266749 max_rss_mb: 100.523 34MB 1thread new_clock -> kops/s: 1.055 io_bytes/op: 1.85439e+06 miss_ratio: 0.107512 max_rss_mb: 75.3125 34MB 32thread base -> kops/s: 3.346 io_bytes/op: 1.64852e+07 miss_ratio: 0.93596 max_rss_mb: 180.48 34MB 32thread folly -> kops/s: 3.431 io_bytes/op: 1.62857e+07 miss_ratio: 0.935693 max_rss_mb: 137.531 34MB 32thread gt_clock -> kops/s: 1.47 io_bytes/op: 4.89704e+06 miss_ratio: 0.295081 max_rss_mb: 392.465 34MB 32thread new_clock -> kops/s: 8.19 io_bytes/op: 3.70456e+06 miss_ratio: 0.20826 max_rss_mb: 519.793 34MB 128thread base -> kops/s: 2.293 io_bytes/op: 1.64351e+07 miss_ratio: 0.931866 max_rss_mb: 449.484 34MB 128thread folly -> kops/s: 2.34 io_bytes/op: 1.6219e+07 miss_ratio: 0.932023 max_rss_mb: 396.457 34MB 128thread gt_clock -> kops/s: 1.798 io_bytes/op: 5.4241e+06 miss_ratio: 0.324881 max_rss_mb: 1104.41 34MB 128thread new_clock -> kops/s: 10.519 io_bytes/op: 2.39354e+06 miss_ratio: 0.136147 max_rss_mb: 1050.52 As the miss ratio gets higher (say, above 10%), the CPU time spent in eviction starts to erode the advantage of using fewer shards (13% miss rate much lower than 94%). LRU's O(1) eviction time can eventually pay off when there's enough block cache churn: 13MB 1thread base -> kops/s: 0.195 io_bytes/op: 1.65732e+07 miss_ratio: 0.946604 max_rss_mb: 45.6328 13MB 1thread folly -> kops/s: 0.197 io_bytes/op: 1.63793e+07 miss_ratio: 0.94661 max_rss_mb: 33.8633 13MB 1thread gt_clock -> kops/s: 0.519 io_bytes/op: 4.43316e+06 miss_ratio: 0.269379 max_rss_mb: 100.684 13MB 1thread new_clock -> kops/s: 0.176 io_bytes/op: 1.54148e+07 miss_ratio: 0.91545 max_rss_mb: 66.2383 13MB 32thread base -> kops/s: 3.266 io_bytes/op: 1.65544e+07 miss_ratio: 0.943386 max_rss_mb: 132.492 13MB 32thread folly -> kops/s: 3.396 io_bytes/op: 1.63142e+07 miss_ratio: 0.943243 max_rss_mb: 101.863 13MB 32thread gt_clock -> kops/s: 2.758 io_bytes/op: 5.13714e+06 miss_ratio: 0.310652 max_rss_mb: 396.121 13MB 32thread new_clock -> kops/s: 3.11 io_bytes/op: 1.23419e+07 miss_ratio: 0.708425 max_rss_mb: 321.758 13MB 128thread base -> kops/s: 2.31 io_bytes/op: 1.64823e+07 miss_ratio: 0.939543 max_rss_mb: 425.539 13MB 128thread folly -> kops/s: 2.339 io_bytes/op: 1.6242e+07 miss_ratio: 0.939966 max_rss_mb: 346.098 13MB 128thread gt_clock -> kops/s: 3.223 io_bytes/op: 5.76928e+06 miss_ratio: 0.345899 max_rss_mb: 1087.77 13MB 128thread new_clock -> kops/s: 2.984 io_bytes/op: 1.05341e+07 miss_ratio: 0.606198 max_rss_mb: 898.27 gt_clock is clearly blowing way past its memory budget for lower miss rates and best throughput. new_clock also seems to be exceeding budgets, and this warrants more investigation but is not the use case we are targeting with the new cache. With partitioned index+filter, the miss ratio is much better, and although still high enough that the eviction CPU time is definitely offsetting mutex contention: 13MB 1thread base -> kops/s: 16.326 io_bytes/op: 23743.9 miss_ratio: 0.205362 max_rss_mb: 65.2852 13MB 1thread folly -> kops/s: 15.574 io_bytes/op: 19415 miss_ratio: 0.184157 max_rss_mb: 56.3516 13MB 1thread gt_clock -> kops/s: 14.459 io_bytes/op: 22873 miss_ratio: 0.198355 max_rss_mb: 63.9688 13MB 1thread new_clock -> kops/s: 16.34 io_bytes/op: 24386.5 miss_ratio: 0.210512 max_rss_mb: 61.707 13MB 128thread base -> kops/s: 289.786 io_bytes/op: 23710.9 miss_ratio: 0.205056 max_rss_mb: 103.57 13MB 128thread folly -> kops/s: 185.282 io_bytes/op: 19433.1 miss_ratio: 0.184275 max_rss_mb: 116.219 13MB 128thread gt_clock -> kops/s: 354.451 io_bytes/op: 23150.6 miss_ratio: 0.200495 max_rss_mb: 102.871 13MB 128thread new_clock -> kops/s: 295.359 io_bytes/op: 24626.4 miss_ratio: 0.212452 max_rss_mb: 121.109 Pull Request resolved: https://github.com/facebook/rocksdb/pull/10626 Test Plan: updated unit tests, stress/crash test runs including with TSAN, ASAN, UBSAN Reviewed By: anand1976 Differential Revision: D39368406 Pulled By: pdillinger fbshipit-source-id: 5afc44da4c656f8f751b44552bbf27bd3ca6fef9
1931 lines
78 KiB
C++
1931 lines
78 KiB
C++
// 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).
|
|
//
|
|
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
|
//
|
|
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
|
|
#include "db/internal_stats.h"
|
|
|
|
#include <algorithm>
|
|
#include <cinttypes>
|
|
#include <cstddef>
|
|
#include <limits>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "cache/cache_entry_roles.h"
|
|
#include "cache/cache_entry_stats.h"
|
|
#include "db/column_family.h"
|
|
#include "db/db_impl/db_impl.h"
|
|
#include "port/port.h"
|
|
#include "rocksdb/system_clock.h"
|
|
#include "rocksdb/table.h"
|
|
#include "table/block_based/cachable_entry.h"
|
|
#include "util/hash_containers.h"
|
|
#include "util/string_util.h"
|
|
|
|
namespace ROCKSDB_NAMESPACE {
|
|
|
|
#ifndef ROCKSDB_LITE
|
|
|
|
const std::map<LevelStatType, LevelStat> InternalStats::compaction_level_stats =
|
|
{
|
|
{LevelStatType::NUM_FILES, LevelStat{"NumFiles", "Files"}},
|
|
{LevelStatType::COMPACTED_FILES,
|
|
LevelStat{"CompactedFiles", "CompactedFiles"}},
|
|
{LevelStatType::SIZE_BYTES, LevelStat{"SizeBytes", "Size"}},
|
|
{LevelStatType::SCORE, LevelStat{"Score", "Score"}},
|
|
{LevelStatType::READ_GB, LevelStat{"ReadGB", "Read(GB)"}},
|
|
{LevelStatType::RN_GB, LevelStat{"RnGB", "Rn(GB)"}},
|
|
{LevelStatType::RNP1_GB, LevelStat{"Rnp1GB", "Rnp1(GB)"}},
|
|
{LevelStatType::WRITE_GB, LevelStat{"WriteGB", "Write(GB)"}},
|
|
{LevelStatType::W_NEW_GB, LevelStat{"WnewGB", "Wnew(GB)"}},
|
|
{LevelStatType::MOVED_GB, LevelStat{"MovedGB", "Moved(GB)"}},
|
|
{LevelStatType::WRITE_AMP, LevelStat{"WriteAmp", "W-Amp"}},
|
|
{LevelStatType::READ_MBPS, LevelStat{"ReadMBps", "Rd(MB/s)"}},
|
|
{LevelStatType::WRITE_MBPS, LevelStat{"WriteMBps", "Wr(MB/s)"}},
|
|
{LevelStatType::COMP_SEC, LevelStat{"CompSec", "Comp(sec)"}},
|
|
{LevelStatType::COMP_CPU_SEC,
|
|
LevelStat{"CompMergeCPU", "CompMergeCPU(sec)"}},
|
|
{LevelStatType::COMP_COUNT, LevelStat{"CompCount", "Comp(cnt)"}},
|
|
{LevelStatType::AVG_SEC, LevelStat{"AvgSec", "Avg(sec)"}},
|
|
{LevelStatType::KEY_IN, LevelStat{"KeyIn", "KeyIn"}},
|
|
{LevelStatType::KEY_DROP, LevelStat{"KeyDrop", "KeyDrop"}},
|
|
{LevelStatType::R_BLOB_GB, LevelStat{"RblobGB", "Rblob(GB)"}},
|
|
{LevelStatType::W_BLOB_GB, LevelStat{"WblobGB", "Wblob(GB)"}},
|
|
};
|
|
|
|
const std::map<InternalStats::InternalDBStatsType, DBStatInfo>
|
|
InternalStats::db_stats_type_to_info = {
|
|
{InternalStats::kIntStatsWalFileBytes,
|
|
DBStatInfo{"db.wal_bytes_written"}},
|
|
{InternalStats::kIntStatsWalFileSynced, DBStatInfo{"db.wal_syncs"}},
|
|
{InternalStats::kIntStatsBytesWritten,
|
|
DBStatInfo{"db.user_bytes_written"}},
|
|
{InternalStats::kIntStatsNumKeysWritten,
|
|
DBStatInfo{"db.user_keys_written"}},
|
|
{InternalStats::kIntStatsWriteDoneByOther,
|
|
DBStatInfo{"db.user_writes_by_other"}},
|
|
{InternalStats::kIntStatsWriteDoneBySelf,
|
|
DBStatInfo{"db.user_writes_by_self"}},
|
|
{InternalStats::kIntStatsWriteWithWal,
|
|
DBStatInfo{"db.user_writes_with_wal"}},
|
|
{InternalStats::kIntStatsWriteStallMicros,
|
|
DBStatInfo{"db.user_write_stall_micros"}},
|
|
};
|
|
|
|
namespace {
|
|
const double kMB = 1048576.0;
|
|
const double kGB = kMB * 1024;
|
|
const double kMicrosInSec = 1000000.0;
|
|
|
|
void PrintLevelStatsHeader(char* buf, size_t len, const std::string& cf_name,
|
|
const std::string& group_by) {
|
|
int written_size =
|
|
snprintf(buf, len, "\n** Compaction Stats [%s] **\n", cf_name.c_str());
|
|
written_size = std::min(written_size, static_cast<int>(len));
|
|
auto hdr = [](LevelStatType t) {
|
|
return InternalStats::compaction_level_stats.at(t).header_name.c_str();
|
|
};
|
|
int line_size = snprintf(
|
|
buf + written_size, len - written_size,
|
|
"%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s "
|
|
"%s\n",
|
|
// Note that we skip COMPACTED_FILES and merge it with Files column
|
|
group_by.c_str(), hdr(LevelStatType::NUM_FILES),
|
|
hdr(LevelStatType::SIZE_BYTES), hdr(LevelStatType::SCORE),
|
|
hdr(LevelStatType::READ_GB), hdr(LevelStatType::RN_GB),
|
|
hdr(LevelStatType::RNP1_GB), hdr(LevelStatType::WRITE_GB),
|
|
hdr(LevelStatType::W_NEW_GB), hdr(LevelStatType::MOVED_GB),
|
|
hdr(LevelStatType::WRITE_AMP), hdr(LevelStatType::READ_MBPS),
|
|
hdr(LevelStatType::WRITE_MBPS), hdr(LevelStatType::COMP_SEC),
|
|
hdr(LevelStatType::COMP_CPU_SEC), hdr(LevelStatType::COMP_COUNT),
|
|
hdr(LevelStatType::AVG_SEC), hdr(LevelStatType::KEY_IN),
|
|
hdr(LevelStatType::KEY_DROP), hdr(LevelStatType::R_BLOB_GB),
|
|
hdr(LevelStatType::W_BLOB_GB));
|
|
|
|
written_size += line_size;
|
|
written_size = std::min(written_size, static_cast<int>(len));
|
|
snprintf(buf + written_size, len - written_size, "%s\n",
|
|
std::string(line_size, '-').c_str());
|
|
}
|
|
|
|
void PrepareLevelStats(std::map<LevelStatType, double>* level_stats,
|
|
int num_files, int being_compacted,
|
|
double total_file_size, double score, double w_amp,
|
|
const InternalStats::CompactionStats& stats) {
|
|
const uint64_t bytes_read = stats.bytes_read_non_output_levels +
|
|
stats.bytes_read_output_level +
|
|
stats.bytes_read_blob;
|
|
const uint64_t bytes_written = stats.bytes_written + stats.bytes_written_blob;
|
|
const int64_t bytes_new = stats.bytes_written - stats.bytes_read_output_level;
|
|
const double elapsed = (stats.micros + 1) / kMicrosInSec;
|
|
|
|
(*level_stats)[LevelStatType::NUM_FILES] = num_files;
|
|
(*level_stats)[LevelStatType::COMPACTED_FILES] = being_compacted;
|
|
(*level_stats)[LevelStatType::SIZE_BYTES] = total_file_size;
|
|
(*level_stats)[LevelStatType::SCORE] = score;
|
|
(*level_stats)[LevelStatType::READ_GB] = bytes_read / kGB;
|
|
(*level_stats)[LevelStatType::RN_GB] =
|
|
stats.bytes_read_non_output_levels / kGB;
|
|
(*level_stats)[LevelStatType::RNP1_GB] = stats.bytes_read_output_level / kGB;
|
|
(*level_stats)[LevelStatType::WRITE_GB] = stats.bytes_written / kGB;
|
|
(*level_stats)[LevelStatType::W_NEW_GB] = bytes_new / kGB;
|
|
(*level_stats)[LevelStatType::MOVED_GB] = stats.bytes_moved / kGB;
|
|
(*level_stats)[LevelStatType::WRITE_AMP] = w_amp;
|
|
(*level_stats)[LevelStatType::READ_MBPS] = bytes_read / kMB / elapsed;
|
|
(*level_stats)[LevelStatType::WRITE_MBPS] = bytes_written / kMB / elapsed;
|
|
(*level_stats)[LevelStatType::COMP_SEC] = stats.micros / kMicrosInSec;
|
|
(*level_stats)[LevelStatType::COMP_CPU_SEC] = stats.cpu_micros / kMicrosInSec;
|
|
(*level_stats)[LevelStatType::COMP_COUNT] = stats.count;
|
|
(*level_stats)[LevelStatType::AVG_SEC] =
|
|
stats.count == 0 ? 0 : stats.micros / kMicrosInSec / stats.count;
|
|
(*level_stats)[LevelStatType::KEY_IN] =
|
|
static_cast<double>(stats.num_input_records);
|
|
(*level_stats)[LevelStatType::KEY_DROP] =
|
|
static_cast<double>(stats.num_dropped_records);
|
|
(*level_stats)[LevelStatType::R_BLOB_GB] = stats.bytes_read_blob / kGB;
|
|
(*level_stats)[LevelStatType::W_BLOB_GB] = stats.bytes_written_blob / kGB;
|
|
}
|
|
|
|
void PrintLevelStats(char* buf, size_t len, const std::string& name,
|
|
const std::map<LevelStatType, double>& stat_value) {
|
|
snprintf(
|
|
buf, len,
|
|
"%4s " /* Level */
|
|
"%6d/%-3d " /* Files */
|
|
"%8s " /* Size */
|
|
"%5.1f " /* Score */
|
|
"%8.1f " /* Read(GB) */
|
|
"%7.1f " /* Rn(GB) */
|
|
"%8.1f " /* Rnp1(GB) */
|
|
"%9.1f " /* Write(GB) */
|
|
"%8.1f " /* Wnew(GB) */
|
|
"%9.1f " /* Moved(GB) */
|
|
"%5.1f " /* W-Amp */
|
|
"%8.1f " /* Rd(MB/s) */
|
|
"%8.1f " /* Wr(MB/s) */
|
|
"%9.2f " /* Comp(sec) */
|
|
"%17.2f " /* CompMergeCPU(sec) */
|
|
"%9d " /* Comp(cnt) */
|
|
"%8.3f " /* Avg(sec) */
|
|
"%7s " /* KeyIn */
|
|
"%6s " /* KeyDrop */
|
|
"%9.1f " /* Rblob(GB) */
|
|
"%9.1f\n", /* Wblob(GB) */
|
|
name.c_str(), static_cast<int>(stat_value.at(LevelStatType::NUM_FILES)),
|
|
static_cast<int>(stat_value.at(LevelStatType::COMPACTED_FILES)),
|
|
BytesToHumanString(
|
|
static_cast<uint64_t>(stat_value.at(LevelStatType::SIZE_BYTES)))
|
|
.c_str(),
|
|
stat_value.at(LevelStatType::SCORE),
|
|
stat_value.at(LevelStatType::READ_GB),
|
|
stat_value.at(LevelStatType::RN_GB),
|
|
stat_value.at(LevelStatType::RNP1_GB),
|
|
stat_value.at(LevelStatType::WRITE_GB),
|
|
stat_value.at(LevelStatType::W_NEW_GB),
|
|
stat_value.at(LevelStatType::MOVED_GB),
|
|
stat_value.at(LevelStatType::WRITE_AMP),
|
|
stat_value.at(LevelStatType::READ_MBPS),
|
|
stat_value.at(LevelStatType::WRITE_MBPS),
|
|
stat_value.at(LevelStatType::COMP_SEC),
|
|
stat_value.at(LevelStatType::COMP_CPU_SEC),
|
|
static_cast<int>(stat_value.at(LevelStatType::COMP_COUNT)),
|
|
stat_value.at(LevelStatType::AVG_SEC),
|
|
NumberToHumanString(
|
|
static_cast<std::int64_t>(stat_value.at(LevelStatType::KEY_IN)))
|
|
.c_str(),
|
|
NumberToHumanString(
|
|
static_cast<std::int64_t>(stat_value.at(LevelStatType::KEY_DROP)))
|
|
.c_str(),
|
|
stat_value.at(LevelStatType::R_BLOB_GB),
|
|
stat_value.at(LevelStatType::W_BLOB_GB));
|
|
}
|
|
|
|
void PrintLevelStats(char* buf, size_t len, const std::string& name,
|
|
int num_files, int being_compacted, double total_file_size,
|
|
double score, double w_amp,
|
|
const InternalStats::CompactionStats& stats) {
|
|
std::map<LevelStatType, double> level_stats;
|
|
PrepareLevelStats(&level_stats, num_files, being_compacted, total_file_size,
|
|
score, w_amp, stats);
|
|
PrintLevelStats(buf, len, name, level_stats);
|
|
}
|
|
|
|
// Assumes that trailing numbers represent an optional argument. This requires
|
|
// property names to not end with numbers.
|
|
std::pair<Slice, Slice> GetPropertyNameAndArg(const Slice& property) {
|
|
Slice name = property, arg = property;
|
|
size_t sfx_len = 0;
|
|
while (sfx_len < property.size() &&
|
|
isdigit(property[property.size() - sfx_len - 1])) {
|
|
++sfx_len;
|
|
}
|
|
name.remove_suffix(sfx_len);
|
|
arg.remove_prefix(property.size() - sfx_len);
|
|
return {name, arg};
|
|
}
|
|
} // anonymous namespace
|
|
|
|
static const std::string rocksdb_prefix = "rocksdb.";
|
|
|
|
static const std::string num_files_at_level_prefix = "num-files-at-level";
|
|
static const std::string compression_ratio_at_level_prefix =
|
|
"compression-ratio-at-level";
|
|
static const std::string allstats = "stats";
|
|
static const std::string sstables = "sstables";
|
|
static const std::string cfstats = "cfstats";
|
|
static const std::string cfstats_no_file_histogram =
|
|
"cfstats-no-file-histogram";
|
|
static const std::string cf_file_histogram = "cf-file-histogram";
|
|
static const std::string dbstats = "dbstats";
|
|
static const std::string levelstats = "levelstats";
|
|
static const std::string block_cache_entry_stats = "block-cache-entry-stats";
|
|
static const std::string num_immutable_mem_table = "num-immutable-mem-table";
|
|
static const std::string num_immutable_mem_table_flushed =
|
|
"num-immutable-mem-table-flushed";
|
|
static const std::string mem_table_flush_pending = "mem-table-flush-pending";
|
|
static const std::string compaction_pending = "compaction-pending";
|
|
static const std::string background_errors = "background-errors";
|
|
static const std::string cur_size_active_mem_table =
|
|
"cur-size-active-mem-table";
|
|
static const std::string cur_size_all_mem_tables = "cur-size-all-mem-tables";
|
|
static const std::string size_all_mem_tables = "size-all-mem-tables";
|
|
static const std::string num_entries_active_mem_table =
|
|
"num-entries-active-mem-table";
|
|
static const std::string num_entries_imm_mem_tables =
|
|
"num-entries-imm-mem-tables";
|
|
static const std::string num_deletes_active_mem_table =
|
|
"num-deletes-active-mem-table";
|
|
static const std::string num_deletes_imm_mem_tables =
|
|
"num-deletes-imm-mem-tables";
|
|
static const std::string estimate_num_keys = "estimate-num-keys";
|
|
static const std::string estimate_table_readers_mem =
|
|
"estimate-table-readers-mem";
|
|
static const std::string is_file_deletions_enabled =
|
|
"is-file-deletions-enabled";
|
|
static const std::string num_snapshots = "num-snapshots";
|
|
static const std::string oldest_snapshot_time = "oldest-snapshot-time";
|
|
static const std::string oldest_snapshot_sequence = "oldest-snapshot-sequence";
|
|
static const std::string num_live_versions = "num-live-versions";
|
|
static const std::string current_version_number =
|
|
"current-super-version-number";
|
|
static const std::string estimate_live_data_size = "estimate-live-data-size";
|
|
static const std::string min_log_number_to_keep_str = "min-log-number-to-keep";
|
|
static const std::string min_obsolete_sst_number_to_keep_str =
|
|
"min-obsolete-sst-number-to-keep";
|
|
static const std::string base_level_str = "base-level";
|
|
static const std::string total_sst_files_size = "total-sst-files-size";
|
|
static const std::string live_sst_files_size = "live-sst-files-size";
|
|
static const std::string live_sst_files_size_at_temperature =
|
|
"live-sst-files-size-at-temperature";
|
|
static const std::string estimate_pending_comp_bytes =
|
|
"estimate-pending-compaction-bytes";
|
|
static const std::string aggregated_table_properties =
|
|
"aggregated-table-properties";
|
|
static const std::string aggregated_table_properties_at_level =
|
|
aggregated_table_properties + "-at-level";
|
|
static const std::string num_running_compactions = "num-running-compactions";
|
|
static const std::string num_running_flushes = "num-running-flushes";
|
|
static const std::string actual_delayed_write_rate =
|
|
"actual-delayed-write-rate";
|
|
static const std::string is_write_stopped = "is-write-stopped";
|
|
static const std::string estimate_oldest_key_time = "estimate-oldest-key-time";
|
|
static const std::string block_cache_capacity = "block-cache-capacity";
|
|
static const std::string block_cache_usage = "block-cache-usage";
|
|
static const std::string block_cache_pinned_usage = "block-cache-pinned-usage";
|
|
static const std::string options_statistics = "options-statistics";
|
|
static const std::string num_blob_files = "num-blob-files";
|
|
static const std::string blob_stats = "blob-stats";
|
|
static const std::string total_blob_file_size = "total-blob-file-size";
|
|
static const std::string live_blob_file_size = "live-blob-file-size";
|
|
static const std::string live_blob_file_garbage_size =
|
|
"live-blob-file-garbage-size";
|
|
static const std::string blob_cache_capacity = "blob-cache-capacity";
|
|
static const std::string blob_cache_usage = "blob-cache-usage";
|
|
static const std::string blob_cache_pinned_usage = "blob-cache-pinned-usage";
|
|
|
|
const std::string DB::Properties::kNumFilesAtLevelPrefix =
|
|
rocksdb_prefix + num_files_at_level_prefix;
|
|
const std::string DB::Properties::kCompressionRatioAtLevelPrefix =
|
|
rocksdb_prefix + compression_ratio_at_level_prefix;
|
|
const std::string DB::Properties::kStats = rocksdb_prefix + allstats;
|
|
const std::string DB::Properties::kSSTables = rocksdb_prefix + sstables;
|
|
const std::string DB::Properties::kCFStats = rocksdb_prefix + cfstats;
|
|
const std::string DB::Properties::kCFStatsNoFileHistogram =
|
|
rocksdb_prefix + cfstats_no_file_histogram;
|
|
const std::string DB::Properties::kCFFileHistogram =
|
|
rocksdb_prefix + cf_file_histogram;
|
|
const std::string DB::Properties::kDBStats = rocksdb_prefix + dbstats;
|
|
const std::string DB::Properties::kLevelStats = rocksdb_prefix + levelstats;
|
|
const std::string DB::Properties::kBlockCacheEntryStats =
|
|
rocksdb_prefix + block_cache_entry_stats;
|
|
const std::string DB::Properties::kNumImmutableMemTable =
|
|
rocksdb_prefix + num_immutable_mem_table;
|
|
const std::string DB::Properties::kNumImmutableMemTableFlushed =
|
|
rocksdb_prefix + num_immutable_mem_table_flushed;
|
|
const std::string DB::Properties::kMemTableFlushPending =
|
|
rocksdb_prefix + mem_table_flush_pending;
|
|
const std::string DB::Properties::kCompactionPending =
|
|
rocksdb_prefix + compaction_pending;
|
|
const std::string DB::Properties::kNumRunningCompactions =
|
|
rocksdb_prefix + num_running_compactions;
|
|
const std::string DB::Properties::kNumRunningFlushes =
|
|
rocksdb_prefix + num_running_flushes;
|
|
const std::string DB::Properties::kBackgroundErrors =
|
|
rocksdb_prefix + background_errors;
|
|
const std::string DB::Properties::kCurSizeActiveMemTable =
|
|
rocksdb_prefix + cur_size_active_mem_table;
|
|
const std::string DB::Properties::kCurSizeAllMemTables =
|
|
rocksdb_prefix + cur_size_all_mem_tables;
|
|
const std::string DB::Properties::kSizeAllMemTables =
|
|
rocksdb_prefix + size_all_mem_tables;
|
|
const std::string DB::Properties::kNumEntriesActiveMemTable =
|
|
rocksdb_prefix + num_entries_active_mem_table;
|
|
const std::string DB::Properties::kNumEntriesImmMemTables =
|
|
rocksdb_prefix + num_entries_imm_mem_tables;
|
|
const std::string DB::Properties::kNumDeletesActiveMemTable =
|
|
rocksdb_prefix + num_deletes_active_mem_table;
|
|
const std::string DB::Properties::kNumDeletesImmMemTables =
|
|
rocksdb_prefix + num_deletes_imm_mem_tables;
|
|
const std::string DB::Properties::kEstimateNumKeys =
|
|
rocksdb_prefix + estimate_num_keys;
|
|
const std::string DB::Properties::kEstimateTableReadersMem =
|
|
rocksdb_prefix + estimate_table_readers_mem;
|
|
const std::string DB::Properties::kIsFileDeletionsEnabled =
|
|
rocksdb_prefix + is_file_deletions_enabled;
|
|
const std::string DB::Properties::kNumSnapshots =
|
|
rocksdb_prefix + num_snapshots;
|
|
const std::string DB::Properties::kOldestSnapshotTime =
|
|
rocksdb_prefix + oldest_snapshot_time;
|
|
const std::string DB::Properties::kOldestSnapshotSequence =
|
|
rocksdb_prefix + oldest_snapshot_sequence;
|
|
const std::string DB::Properties::kNumLiveVersions =
|
|
rocksdb_prefix + num_live_versions;
|
|
const std::string DB::Properties::kCurrentSuperVersionNumber =
|
|
rocksdb_prefix + current_version_number;
|
|
const std::string DB::Properties::kEstimateLiveDataSize =
|
|
rocksdb_prefix + estimate_live_data_size;
|
|
const std::string DB::Properties::kMinLogNumberToKeep =
|
|
rocksdb_prefix + min_log_number_to_keep_str;
|
|
const std::string DB::Properties::kMinObsoleteSstNumberToKeep =
|
|
rocksdb_prefix + min_obsolete_sst_number_to_keep_str;
|
|
const std::string DB::Properties::kTotalSstFilesSize =
|
|
rocksdb_prefix + total_sst_files_size;
|
|
const std::string DB::Properties::kLiveSstFilesSize =
|
|
rocksdb_prefix + live_sst_files_size;
|
|
const std::string DB::Properties::kBaseLevel = rocksdb_prefix + base_level_str;
|
|
const std::string DB::Properties::kEstimatePendingCompactionBytes =
|
|
rocksdb_prefix + estimate_pending_comp_bytes;
|
|
const std::string DB::Properties::kAggregatedTableProperties =
|
|
rocksdb_prefix + aggregated_table_properties;
|
|
const std::string DB::Properties::kAggregatedTablePropertiesAtLevel =
|
|
rocksdb_prefix + aggregated_table_properties_at_level;
|
|
const std::string DB::Properties::kActualDelayedWriteRate =
|
|
rocksdb_prefix + actual_delayed_write_rate;
|
|
const std::string DB::Properties::kIsWriteStopped =
|
|
rocksdb_prefix + is_write_stopped;
|
|
const std::string DB::Properties::kEstimateOldestKeyTime =
|
|
rocksdb_prefix + estimate_oldest_key_time;
|
|
const std::string DB::Properties::kBlockCacheCapacity =
|
|
rocksdb_prefix + block_cache_capacity;
|
|
const std::string DB::Properties::kBlockCacheUsage =
|
|
rocksdb_prefix + block_cache_usage;
|
|
const std::string DB::Properties::kBlockCachePinnedUsage =
|
|
rocksdb_prefix + block_cache_pinned_usage;
|
|
const std::string DB::Properties::kOptionsStatistics =
|
|
rocksdb_prefix + options_statistics;
|
|
const std::string DB::Properties::kLiveSstFilesSizeAtTemperature =
|
|
rocksdb_prefix + live_sst_files_size_at_temperature;
|
|
const std::string DB::Properties::kNumBlobFiles =
|
|
rocksdb_prefix + num_blob_files;
|
|
const std::string DB::Properties::kBlobStats = rocksdb_prefix + blob_stats;
|
|
const std::string DB::Properties::kTotalBlobFileSize =
|
|
rocksdb_prefix + total_blob_file_size;
|
|
const std::string DB::Properties::kLiveBlobFileSize =
|
|
rocksdb_prefix + live_blob_file_size;
|
|
const std::string DB::Properties::kLiveBlobFileGarbageSize =
|
|
rocksdb_prefix + live_blob_file_garbage_size;
|
|
const std::string DB::Properties::kBlobCacheCapacity =
|
|
rocksdb_prefix + blob_cache_capacity;
|
|
const std::string DB::Properties::kBlobCacheUsage =
|
|
rocksdb_prefix + blob_cache_usage;
|
|
const std::string DB::Properties::kBlobCachePinnedUsage =
|
|
rocksdb_prefix + blob_cache_pinned_usage;
|
|
|
|
const UnorderedMap<std::string, DBPropertyInfo>
|
|
InternalStats::ppt_name_to_info = {
|
|
{DB::Properties::kNumFilesAtLevelPrefix,
|
|
{false, &InternalStats::HandleNumFilesAtLevel, nullptr, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kCompressionRatioAtLevelPrefix,
|
|
{false, &InternalStats::HandleCompressionRatioAtLevelPrefix, nullptr,
|
|
nullptr, nullptr}},
|
|
{DB::Properties::kLevelStats,
|
|
{false, &InternalStats::HandleLevelStats, nullptr, nullptr, nullptr}},
|
|
{DB::Properties::kStats,
|
|
{false, &InternalStats::HandleStats, nullptr, nullptr, nullptr}},
|
|
{DB::Properties::kCFStats,
|
|
{false, &InternalStats::HandleCFStats, nullptr,
|
|
&InternalStats::HandleCFMapStats, nullptr}},
|
|
{DB::Properties::kCFStatsNoFileHistogram,
|
|
{false, &InternalStats::HandleCFStatsNoFileHistogram, nullptr, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kCFFileHistogram,
|
|
{false, &InternalStats::HandleCFFileHistogram, nullptr, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kDBStats,
|
|
{false, &InternalStats::HandleDBStats, nullptr,
|
|
&InternalStats::HandleDBMapStats, nullptr}},
|
|
{DB::Properties::kBlockCacheEntryStats,
|
|
{true, &InternalStats::HandleBlockCacheEntryStats, nullptr,
|
|
&InternalStats::HandleBlockCacheEntryStatsMap, nullptr}},
|
|
{DB::Properties::kSSTables,
|
|
{false, &InternalStats::HandleSsTables, nullptr, nullptr, nullptr}},
|
|
{DB::Properties::kAggregatedTableProperties,
|
|
{false, &InternalStats::HandleAggregatedTableProperties, nullptr,
|
|
&InternalStats::HandleAggregatedTablePropertiesMap, nullptr}},
|
|
{DB::Properties::kAggregatedTablePropertiesAtLevel,
|
|
{false, &InternalStats::HandleAggregatedTablePropertiesAtLevel,
|
|
nullptr, &InternalStats::HandleAggregatedTablePropertiesAtLevelMap,
|
|
nullptr}},
|
|
{DB::Properties::kNumImmutableMemTable,
|
|
{false, nullptr, &InternalStats::HandleNumImmutableMemTable, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kNumImmutableMemTableFlushed,
|
|
{false, nullptr, &InternalStats::HandleNumImmutableMemTableFlushed,
|
|
nullptr, nullptr}},
|
|
{DB::Properties::kMemTableFlushPending,
|
|
{false, nullptr, &InternalStats::HandleMemTableFlushPending, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kCompactionPending,
|
|
{false, nullptr, &InternalStats::HandleCompactionPending, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kBackgroundErrors,
|
|
{false, nullptr, &InternalStats::HandleBackgroundErrors, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kCurSizeActiveMemTable,
|
|
{false, nullptr, &InternalStats::HandleCurSizeActiveMemTable, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kCurSizeAllMemTables,
|
|
{false, nullptr, &InternalStats::HandleCurSizeAllMemTables, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kSizeAllMemTables,
|
|
{false, nullptr, &InternalStats::HandleSizeAllMemTables, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kNumEntriesActiveMemTable,
|
|
{false, nullptr, &InternalStats::HandleNumEntriesActiveMemTable,
|
|
nullptr, nullptr}},
|
|
{DB::Properties::kNumEntriesImmMemTables,
|
|
{false, nullptr, &InternalStats::HandleNumEntriesImmMemTables, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kNumDeletesActiveMemTable,
|
|
{false, nullptr, &InternalStats::HandleNumDeletesActiveMemTable,
|
|
nullptr, nullptr}},
|
|
{DB::Properties::kNumDeletesImmMemTables,
|
|
{false, nullptr, &InternalStats::HandleNumDeletesImmMemTables, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kEstimateNumKeys,
|
|
{false, nullptr, &InternalStats::HandleEstimateNumKeys, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kEstimateTableReadersMem,
|
|
{true, nullptr, &InternalStats::HandleEstimateTableReadersMem, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kIsFileDeletionsEnabled,
|
|
{false, nullptr, &InternalStats::HandleIsFileDeletionsEnabled, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kNumSnapshots,
|
|
{false, nullptr, &InternalStats::HandleNumSnapshots, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kOldestSnapshotTime,
|
|
{false, nullptr, &InternalStats::HandleOldestSnapshotTime, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kOldestSnapshotSequence,
|
|
{false, nullptr, &InternalStats::HandleOldestSnapshotSequence, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kNumLiveVersions,
|
|
{false, nullptr, &InternalStats::HandleNumLiveVersions, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kCurrentSuperVersionNumber,
|
|
{false, nullptr, &InternalStats::HandleCurrentSuperVersionNumber,
|
|
nullptr, nullptr}},
|
|
{DB::Properties::kEstimateLiveDataSize,
|
|
{true, nullptr, &InternalStats::HandleEstimateLiveDataSize, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kMinLogNumberToKeep,
|
|
{false, nullptr, &InternalStats::HandleMinLogNumberToKeep, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kMinObsoleteSstNumberToKeep,
|
|
{false, nullptr, &InternalStats::HandleMinObsoleteSstNumberToKeep,
|
|
nullptr, nullptr}},
|
|
{DB::Properties::kBaseLevel,
|
|
{false, nullptr, &InternalStats::HandleBaseLevel, nullptr, nullptr}},
|
|
{DB::Properties::kTotalSstFilesSize,
|
|
{false, nullptr, &InternalStats::HandleTotalSstFilesSize, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kLiveSstFilesSize,
|
|
{false, nullptr, &InternalStats::HandleLiveSstFilesSize, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kLiveSstFilesSizeAtTemperature,
|
|
{false, &InternalStats::HandleLiveSstFilesSizeAtTemperature, nullptr,
|
|
nullptr, nullptr}},
|
|
{DB::Properties::kEstimatePendingCompactionBytes,
|
|
{false, nullptr, &InternalStats::HandleEstimatePendingCompactionBytes,
|
|
nullptr, nullptr}},
|
|
{DB::Properties::kNumRunningFlushes,
|
|
{false, nullptr, &InternalStats::HandleNumRunningFlushes, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kNumRunningCompactions,
|
|
{false, nullptr, &InternalStats::HandleNumRunningCompactions, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kActualDelayedWriteRate,
|
|
{false, nullptr, &InternalStats::HandleActualDelayedWriteRate, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kIsWriteStopped,
|
|
{false, nullptr, &InternalStats::HandleIsWriteStopped, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kEstimateOldestKeyTime,
|
|
{false, nullptr, &InternalStats::HandleEstimateOldestKeyTime, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kBlockCacheCapacity,
|
|
{false, nullptr, &InternalStats::HandleBlockCacheCapacity, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kBlockCacheUsage,
|
|
{false, nullptr, &InternalStats::HandleBlockCacheUsage, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kBlockCachePinnedUsage,
|
|
{false, nullptr, &InternalStats::HandleBlockCachePinnedUsage, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kOptionsStatistics,
|
|
{true, nullptr, nullptr, nullptr,
|
|
&DBImpl::GetPropertyHandleOptionsStatistics}},
|
|
{DB::Properties::kNumBlobFiles,
|
|
{false, nullptr, &InternalStats::HandleNumBlobFiles, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kBlobStats,
|
|
{false, &InternalStats::HandleBlobStats, nullptr, nullptr, nullptr}},
|
|
{DB::Properties::kTotalBlobFileSize,
|
|
{false, nullptr, &InternalStats::HandleTotalBlobFileSize, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kLiveBlobFileSize,
|
|
{false, nullptr, &InternalStats::HandleLiveBlobFileSize, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kLiveBlobFileGarbageSize,
|
|
{false, nullptr, &InternalStats::HandleLiveBlobFileGarbageSize,
|
|
nullptr, nullptr}},
|
|
{DB::Properties::kBlobCacheCapacity,
|
|
{false, nullptr, &InternalStats::HandleBlobCacheCapacity, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kBlobCacheUsage,
|
|
{false, nullptr, &InternalStats::HandleBlobCacheUsage, nullptr,
|
|
nullptr}},
|
|
{DB::Properties::kBlobCachePinnedUsage,
|
|
{false, nullptr, &InternalStats::HandleBlobCachePinnedUsage, nullptr,
|
|
nullptr}},
|
|
};
|
|
|
|
InternalStats::InternalStats(int num_levels, SystemClock* clock,
|
|
ColumnFamilyData* cfd)
|
|
: db_stats_{},
|
|
cf_stats_value_{},
|
|
cf_stats_count_{},
|
|
comp_stats_(num_levels),
|
|
comp_stats_by_pri_(Env::Priority::TOTAL),
|
|
file_read_latency_(num_levels),
|
|
bg_error_count_(0),
|
|
number_levels_(num_levels),
|
|
clock_(clock),
|
|
cfd_(cfd),
|
|
started_at_(clock->NowMicros()) {
|
|
Cache* block_cache = GetBlockCacheForStats();
|
|
if (block_cache) {
|
|
// Extract or create stats collector. Could fail in rare cases.
|
|
Status s = CacheEntryStatsCollector<CacheEntryRoleStats>::GetShared(
|
|
block_cache, clock_, &cache_entry_stats_collector_);
|
|
if (s.ok()) {
|
|
assert(cache_entry_stats_collector_);
|
|
} else {
|
|
assert(!cache_entry_stats_collector_);
|
|
}
|
|
}
|
|
}
|
|
|
|
void InternalStats::TEST_GetCacheEntryRoleStats(CacheEntryRoleStats* stats,
|
|
bool foreground) {
|
|
CollectCacheEntryStats(foreground);
|
|
if (cache_entry_stats_collector_) {
|
|
cache_entry_stats_collector_->GetStats(stats);
|
|
}
|
|
}
|
|
|
|
void InternalStats::CollectCacheEntryStats(bool foreground) {
|
|
// This function is safe to call from any thread because
|
|
// cache_entry_stats_collector_ field is const after constructor
|
|
// and ->GetStats does its own synchronization, which also suffices for
|
|
// cache_entry_stats_.
|
|
|
|
if (!cache_entry_stats_collector_) {
|
|
return; // nothing to do (e.g. no block cache)
|
|
}
|
|
|
|
// For "background" collections, strictly cap the collection time by
|
|
// expanding effective cache TTL. For foreground, be more aggressive about
|
|
// getting latest data.
|
|
int min_interval_seconds = foreground ? 10 : 180;
|
|
// 1/500 = max of 0.2% of one CPU thread
|
|
int min_interval_factor = foreground ? 10 : 500;
|
|
cache_entry_stats_collector_->CollectStats(min_interval_seconds,
|
|
min_interval_factor);
|
|
}
|
|
|
|
std::function<void(const Slice&, void*, size_t, Cache::DeleterFn)>
|
|
InternalStats::CacheEntryRoleStats::GetEntryCallback() {
|
|
return [&](const Slice& /*key*/, void* /*value*/, size_t charge,
|
|
Cache::DeleterFn deleter) {
|
|
auto e = role_map_.find(deleter);
|
|
size_t role_idx;
|
|
if (e == role_map_.end()) {
|
|
role_idx = static_cast<size_t>(CacheEntryRole::kMisc);
|
|
} else {
|
|
role_idx = static_cast<size_t>(e->second);
|
|
}
|
|
entry_counts[role_idx]++;
|
|
total_charges[role_idx] += charge;
|
|
};
|
|
}
|
|
|
|
void InternalStats::CacheEntryRoleStats::BeginCollection(
|
|
Cache* cache, SystemClock*, uint64_t start_time_micros) {
|
|
Clear();
|
|
last_start_time_micros_ = start_time_micros;
|
|
++collection_count;
|
|
role_map_ = CopyCacheDeleterRoleMap();
|
|
std::ostringstream str;
|
|
str << cache->Name() << "@" << static_cast<void*>(cache) << "#"
|
|
<< port::GetProcessID();
|
|
cache_id = str.str();
|
|
cache_capacity = cache->GetCapacity();
|
|
cache_usage = cache->GetUsage();
|
|
table_size = cache->GetTableAddressCount();
|
|
occupancy = cache->GetOccupancyCount();
|
|
}
|
|
|
|
void InternalStats::CacheEntryRoleStats::EndCollection(
|
|
Cache*, SystemClock*, uint64_t end_time_micros) {
|
|
last_end_time_micros_ = end_time_micros;
|
|
}
|
|
|
|
void InternalStats::CacheEntryRoleStats::SkippedCollection() {
|
|
++copies_of_last_collection;
|
|
}
|
|
|
|
uint64_t InternalStats::CacheEntryRoleStats::GetLastDurationMicros() const {
|
|
if (last_end_time_micros_ > last_start_time_micros_) {
|
|
return last_end_time_micros_ - last_start_time_micros_;
|
|
} else {
|
|
return 0U;
|
|
}
|
|
}
|
|
|
|
std::string InternalStats::CacheEntryRoleStats::ToString(
|
|
SystemClock* clock) const {
|
|
std::ostringstream str;
|
|
str << "Block cache " << cache_id
|
|
<< " capacity: " << BytesToHumanString(cache_capacity)
|
|
<< " usage: " << BytesToHumanString(cache_usage)
|
|
<< " table_size: " << table_size << " occupancy: " << occupancy
|
|
<< " collections: " << collection_count
|
|
<< " last_copies: " << copies_of_last_collection
|
|
<< " last_secs: " << (GetLastDurationMicros() / 1000000.0)
|
|
<< " secs_since: "
|
|
<< ((clock->NowMicros() - last_end_time_micros_) / 1000000U) << "\n";
|
|
str << "Block cache entry stats(count,size,portion):";
|
|
for (size_t i = 0; i < kNumCacheEntryRoles; ++i) {
|
|
if (entry_counts[i] > 0) {
|
|
str << " " << kCacheEntryRoleToCamelString[i] << "(" << entry_counts[i]
|
|
<< "," << BytesToHumanString(total_charges[i]) << ","
|
|
<< (100.0 * total_charges[i] / cache_capacity) << "%)";
|
|
}
|
|
}
|
|
str << "\n";
|
|
return str.str();
|
|
}
|
|
|
|
void InternalStats::CacheEntryRoleStats::ToMap(
|
|
std::map<std::string, std::string>* values, SystemClock* clock) const {
|
|
values->clear();
|
|
auto& v = *values;
|
|
v[BlockCacheEntryStatsMapKeys::CacheId()] = cache_id;
|
|
v[BlockCacheEntryStatsMapKeys::CacheCapacityBytes()] =
|
|
std::to_string(cache_capacity);
|
|
v[BlockCacheEntryStatsMapKeys::LastCollectionDurationSeconds()] =
|
|
std::to_string(GetLastDurationMicros() / 1000000.0);
|
|
v[BlockCacheEntryStatsMapKeys::LastCollectionAgeSeconds()] =
|
|
std::to_string((clock->NowMicros() - last_end_time_micros_) / 1000000U);
|
|
for (size_t i = 0; i < kNumCacheEntryRoles; ++i) {
|
|
auto role = static_cast<CacheEntryRole>(i);
|
|
v[BlockCacheEntryStatsMapKeys::EntryCount(role)] =
|
|
std::to_string(entry_counts[i]);
|
|
v[BlockCacheEntryStatsMapKeys::UsedBytes(role)] =
|
|
std::to_string(total_charges[i]);
|
|
v[BlockCacheEntryStatsMapKeys::UsedPercent(role)] =
|
|
std::to_string(100.0 * total_charges[i] / cache_capacity);
|
|
}
|
|
}
|
|
|
|
bool InternalStats::HandleBlockCacheEntryStats(std::string* value,
|
|
Slice /*suffix*/) {
|
|
if (!cache_entry_stats_collector_) {
|
|
return false;
|
|
}
|
|
CollectCacheEntryStats(/*foreground*/ true);
|
|
CacheEntryRoleStats stats;
|
|
cache_entry_stats_collector_->GetStats(&stats);
|
|
*value = stats.ToString(clock_);
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleBlockCacheEntryStatsMap(
|
|
std::map<std::string, std::string>* values, Slice /*suffix*/) {
|
|
if (!cache_entry_stats_collector_) {
|
|
return false;
|
|
}
|
|
CollectCacheEntryStats(/*foreground*/ true);
|
|
CacheEntryRoleStats stats;
|
|
cache_entry_stats_collector_->GetStats(&stats);
|
|
stats.ToMap(values, clock_);
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleLiveSstFilesSizeAtTemperature(std::string* value,
|
|
Slice suffix) {
|
|
uint64_t temperature;
|
|
bool ok = ConsumeDecimalNumber(&suffix, &temperature) && suffix.empty();
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
|
|
uint64_t size = 0;
|
|
const auto* vstorage = cfd_->current()->storage_info();
|
|
for (int level = 0; level < vstorage->num_levels(); level++) {
|
|
for (const auto& file_meta : vstorage->LevelFiles(level)) {
|
|
if (static_cast<uint8_t>(file_meta->temperature) == temperature) {
|
|
size += file_meta->fd.GetFileSize();
|
|
}
|
|
}
|
|
}
|
|
|
|
*value = std::to_string(size);
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleNumBlobFiles(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
assert(value);
|
|
assert(cfd_);
|
|
|
|
const auto* current = cfd_->current();
|
|
assert(current);
|
|
|
|
const auto* vstorage = current->storage_info();
|
|
assert(vstorage);
|
|
|
|
const auto& blob_files = vstorage->GetBlobFiles();
|
|
|
|
*value = blob_files.size();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleBlobStats(std::string* value, Slice /*suffix*/) {
|
|
assert(value);
|
|
assert(cfd_);
|
|
|
|
const auto* current = cfd_->current();
|
|
assert(current);
|
|
|
|
const auto* vstorage = current->storage_info();
|
|
assert(vstorage);
|
|
|
|
const auto blob_st = vstorage->GetBlobStats();
|
|
|
|
std::ostringstream oss;
|
|
|
|
oss << "Number of blob files: " << vstorage->GetBlobFiles().size()
|
|
<< "\nTotal size of blob files: " << blob_st.total_file_size
|
|
<< "\nTotal size of garbage in blob files: " << blob_st.total_garbage_size
|
|
<< "\nBlob file space amplification: " << blob_st.space_amp << '\n';
|
|
|
|
value->append(oss.str());
|
|
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleTotalBlobFileSize(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
assert(value);
|
|
assert(cfd_);
|
|
|
|
*value = cfd_->GetTotalBlobFileSize();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleLiveBlobFileSize(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
assert(value);
|
|
assert(cfd_);
|
|
|
|
const auto* current = cfd_->current();
|
|
assert(current);
|
|
|
|
const auto* vstorage = current->storage_info();
|
|
assert(vstorage);
|
|
|
|
*value = vstorage->GetBlobStats().total_file_size;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleLiveBlobFileGarbageSize(uint64_t* value,
|
|
DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
assert(value);
|
|
assert(cfd_);
|
|
|
|
const auto* current = cfd_->current();
|
|
assert(current);
|
|
|
|
const auto* vstorage = current->storage_info();
|
|
assert(vstorage);
|
|
|
|
*value = vstorage->GetBlobStats().total_garbage_size;
|
|
|
|
return true;
|
|
}
|
|
|
|
Cache* InternalStats::GetBlobCacheForStats() {
|
|
return cfd_->ioptions()->blob_cache.get();
|
|
}
|
|
|
|
bool InternalStats::HandleBlobCacheCapacity(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
Cache* blob_cache = GetBlobCacheForStats();
|
|
if (blob_cache) {
|
|
*value = static_cast<uint64_t>(blob_cache->GetCapacity());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool InternalStats::HandleBlobCacheUsage(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
Cache* blob_cache = GetBlobCacheForStats();
|
|
if (blob_cache) {
|
|
*value = static_cast<uint64_t>(blob_cache->GetUsage());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool InternalStats::HandleBlobCachePinnedUsage(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
Cache* blob_cache = GetBlobCacheForStats();
|
|
if (blob_cache) {
|
|
*value = static_cast<uint64_t>(blob_cache->GetPinnedUsage());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const DBPropertyInfo* GetPropertyInfo(const Slice& property) {
|
|
std::string ppt_name = GetPropertyNameAndArg(property).first.ToString();
|
|
auto ppt_info_iter = InternalStats::ppt_name_to_info.find(ppt_name);
|
|
if (ppt_info_iter == InternalStats::ppt_name_to_info.end()) {
|
|
return nullptr;
|
|
}
|
|
return &ppt_info_iter->second;
|
|
}
|
|
|
|
bool InternalStats::GetStringProperty(const DBPropertyInfo& property_info,
|
|
const Slice& property,
|
|
std::string* value) {
|
|
assert(value != nullptr);
|
|
assert(property_info.handle_string != nullptr);
|
|
Slice arg = GetPropertyNameAndArg(property).second;
|
|
return (this->*(property_info.handle_string))(value, arg);
|
|
}
|
|
|
|
bool InternalStats::GetMapProperty(const DBPropertyInfo& property_info,
|
|
const Slice& property,
|
|
std::map<std::string, std::string>* value) {
|
|
assert(value != nullptr);
|
|
assert(property_info.handle_map != nullptr);
|
|
Slice arg = GetPropertyNameAndArg(property).second;
|
|
return (this->*(property_info.handle_map))(value, arg);
|
|
}
|
|
|
|
bool InternalStats::GetIntProperty(const DBPropertyInfo& property_info,
|
|
uint64_t* value, DBImpl* db) {
|
|
assert(value != nullptr);
|
|
assert(property_info.handle_int != nullptr &&
|
|
!property_info.need_out_of_mutex);
|
|
db->mutex_.AssertHeld();
|
|
return (this->*(property_info.handle_int))(value, db, nullptr /* version */);
|
|
}
|
|
|
|
bool InternalStats::GetIntPropertyOutOfMutex(
|
|
const DBPropertyInfo& property_info, Version* version, uint64_t* value) {
|
|
assert(value != nullptr);
|
|
assert(property_info.handle_int != nullptr &&
|
|
property_info.need_out_of_mutex);
|
|
return (this->*(property_info.handle_int))(value, nullptr /* db */, version);
|
|
}
|
|
|
|
bool InternalStats::HandleNumFilesAtLevel(std::string* value, Slice suffix) {
|
|
uint64_t level;
|
|
const auto* vstorage = cfd_->current()->storage_info();
|
|
bool ok = ConsumeDecimalNumber(&suffix, &level) && suffix.empty();
|
|
if (!ok || static_cast<int>(level) >= number_levels_) {
|
|
return false;
|
|
} else {
|
|
char buf[100];
|
|
snprintf(buf, sizeof(buf), "%d",
|
|
vstorage->NumLevelFiles(static_cast<int>(level)));
|
|
*value = buf;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool InternalStats::HandleCompressionRatioAtLevelPrefix(std::string* value,
|
|
Slice suffix) {
|
|
uint64_t level;
|
|
const auto* vstorage = cfd_->current()->storage_info();
|
|
bool ok = ConsumeDecimalNumber(&suffix, &level) && suffix.empty();
|
|
if (!ok || level >= static_cast<uint64_t>(number_levels_)) {
|
|
return false;
|
|
}
|
|
*value = std::to_string(
|
|
vstorage->GetEstimatedCompressionRatioAtLevel(static_cast<int>(level)));
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleLevelStats(std::string* value, Slice /*suffix*/) {
|
|
char buf[1000];
|
|
const auto* vstorage = cfd_->current()->storage_info();
|
|
snprintf(buf, sizeof(buf),
|
|
"Level Files Size(MB)\n"
|
|
"--------------------\n");
|
|
value->append(buf);
|
|
|
|
for (int level = 0; level < number_levels_; level++) {
|
|
snprintf(buf, sizeof(buf), "%3d %8d %8.0f\n", level,
|
|
vstorage->NumLevelFiles(level),
|
|
vstorage->NumLevelBytes(level) / kMB);
|
|
value->append(buf);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleStats(std::string* value, Slice suffix) {
|
|
if (!HandleCFStats(value, suffix)) {
|
|
return false;
|
|
}
|
|
if (!HandleDBStats(value, suffix)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleCFMapStats(
|
|
std::map<std::string, std::string>* cf_stats, Slice /*suffix*/) {
|
|
DumpCFMapStats(cf_stats);
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleCFStats(std::string* value, Slice /*suffix*/) {
|
|
DumpCFStats(value);
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleCFStatsNoFileHistogram(std::string* value,
|
|
Slice /*suffix*/) {
|
|
DumpCFStatsNoFileHistogram(value);
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleCFFileHistogram(std::string* value,
|
|
Slice /*suffix*/) {
|
|
DumpCFFileHistogram(value);
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleDBMapStats(
|
|
std::map<std::string, std::string>* db_stats, Slice /*suffix*/) {
|
|
DumpDBMapStats(db_stats);
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleDBStats(std::string* value, Slice /*suffix*/) {
|
|
DumpDBStats(value);
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleSsTables(std::string* value, Slice /*suffix*/) {
|
|
auto* current = cfd_->current();
|
|
*value = current->DebugString(true, true);
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleAggregatedTableProperties(std::string* value,
|
|
Slice /*suffix*/) {
|
|
std::shared_ptr<const TableProperties> tp;
|
|
auto s = cfd_->current()->GetAggregatedTableProperties(&tp);
|
|
if (!s.ok()) {
|
|
return false;
|
|
}
|
|
*value = tp->ToString();
|
|
return true;
|
|
}
|
|
|
|
static std::map<std::string, std::string> MapUint64ValuesToString(
|
|
const std::map<std::string, uint64_t>& from) {
|
|
std::map<std::string, std::string> to;
|
|
for (const auto& e : from) {
|
|
to[e.first] = std::to_string(e.second);
|
|
}
|
|
return to;
|
|
}
|
|
|
|
bool InternalStats::HandleAggregatedTablePropertiesMap(
|
|
std::map<std::string, std::string>* values, Slice /*suffix*/) {
|
|
std::shared_ptr<const TableProperties> tp;
|
|
auto s = cfd_->current()->GetAggregatedTableProperties(&tp);
|
|
if (!s.ok()) {
|
|
return false;
|
|
}
|
|
*values = MapUint64ValuesToString(tp->GetAggregatablePropertiesAsMap());
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleAggregatedTablePropertiesAtLevel(std::string* values,
|
|
Slice suffix) {
|
|
uint64_t level;
|
|
bool ok = ConsumeDecimalNumber(&suffix, &level) && suffix.empty();
|
|
if (!ok || static_cast<int>(level) >= number_levels_) {
|
|
return false;
|
|
}
|
|
std::shared_ptr<const TableProperties> tp;
|
|
auto s = cfd_->current()->GetAggregatedTableProperties(
|
|
&tp, static_cast<int>(level));
|
|
if (!s.ok()) {
|
|
return false;
|
|
}
|
|
*values = tp->ToString();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleAggregatedTablePropertiesAtLevelMap(
|
|
std::map<std::string, std::string>* values, Slice suffix) {
|
|
uint64_t level;
|
|
bool ok = ConsumeDecimalNumber(&suffix, &level) && suffix.empty();
|
|
if (!ok || static_cast<int>(level) >= number_levels_) {
|
|
return false;
|
|
}
|
|
std::shared_ptr<const TableProperties> tp;
|
|
auto s = cfd_->current()->GetAggregatedTableProperties(
|
|
&tp, static_cast<int>(level));
|
|
if (!s.ok()) {
|
|
return false;
|
|
}
|
|
*values = MapUint64ValuesToString(tp->GetAggregatablePropertiesAsMap());
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleNumImmutableMemTable(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
*value = cfd_->imm()->NumNotFlushed();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleNumImmutableMemTableFlushed(uint64_t* value,
|
|
DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
*value = cfd_->imm()->NumFlushed();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleMemTableFlushPending(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
*value = (cfd_->imm()->IsFlushPending() ? 1 : 0);
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleNumRunningFlushes(uint64_t* value, DBImpl* db,
|
|
Version* /*version*/) {
|
|
*value = db->num_running_flushes();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleCompactionPending(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
// 1 if the system already determines at least one compaction is needed.
|
|
// 0 otherwise,
|
|
const auto* vstorage = cfd_->current()->storage_info();
|
|
*value = (cfd_->compaction_picker()->NeedsCompaction(vstorage) ? 1 : 0);
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleNumRunningCompactions(uint64_t* value, DBImpl* db,
|
|
Version* /*version*/) {
|
|
*value = db->num_running_compactions_;
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleBackgroundErrors(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
// Accumulated number of errors in background flushes or compactions.
|
|
*value = GetBackgroundErrorCount();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleCurSizeActiveMemTable(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
// Current size of the active memtable
|
|
// Using ApproximateMemoryUsageFast to avoid the need for synchronization
|
|
*value = cfd_->mem()->ApproximateMemoryUsageFast();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleCurSizeAllMemTables(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
// Current size of the active memtable + immutable memtables
|
|
// Using ApproximateMemoryUsageFast to avoid the need for synchronization
|
|
*value = cfd_->mem()->ApproximateMemoryUsageFast() +
|
|
cfd_->imm()->ApproximateUnflushedMemTablesMemoryUsage();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleSizeAllMemTables(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
// Using ApproximateMemoryUsageFast to avoid the need for synchronization
|
|
*value = cfd_->mem()->ApproximateMemoryUsageFast() +
|
|
cfd_->imm()->ApproximateMemoryUsage();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleNumEntriesActiveMemTable(uint64_t* value,
|
|
DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
// Current number of entires in the active memtable
|
|
*value = cfd_->mem()->num_entries();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleNumEntriesImmMemTables(uint64_t* value,
|
|
DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
// Current number of entries in the immutable memtables
|
|
*value = cfd_->imm()->current()->GetTotalNumEntries();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleNumDeletesActiveMemTable(uint64_t* value,
|
|
DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
// Current number of entires in the active memtable
|
|
*value = cfd_->mem()->num_deletes();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleNumDeletesImmMemTables(uint64_t* value,
|
|
DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
// Current number of entries in the immutable memtables
|
|
*value = cfd_->imm()->current()->GetTotalNumDeletes();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleEstimateNumKeys(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
// Estimate number of entries in the column family:
|
|
// Use estimated entries in tables + total entries in memtables.
|
|
const auto* vstorage = cfd_->current()->storage_info();
|
|
uint64_t estimate_keys = cfd_->mem()->num_entries() +
|
|
cfd_->imm()->current()->GetTotalNumEntries() +
|
|
vstorage->GetEstimatedActiveKeys();
|
|
uint64_t estimate_deletes =
|
|
cfd_->mem()->num_deletes() + cfd_->imm()->current()->GetTotalNumDeletes();
|
|
*value = estimate_keys > estimate_deletes * 2
|
|
? estimate_keys - (estimate_deletes * 2)
|
|
: 0;
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleNumSnapshots(uint64_t* value, DBImpl* db,
|
|
Version* /*version*/) {
|
|
*value = db->snapshots().count();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleOldestSnapshotTime(uint64_t* value, DBImpl* db,
|
|
Version* /*version*/) {
|
|
*value = static_cast<uint64_t>(db->snapshots().GetOldestSnapshotTime());
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleOldestSnapshotSequence(uint64_t* value, DBImpl* db,
|
|
Version* /*version*/) {
|
|
*value = static_cast<uint64_t>(db->snapshots().GetOldestSnapshotSequence());
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleNumLiveVersions(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
*value = cfd_->GetNumLiveVersions();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleCurrentSuperVersionNumber(uint64_t* value,
|
|
DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
*value = cfd_->GetSuperVersionNumber();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleIsFileDeletionsEnabled(uint64_t* value, DBImpl* db,
|
|
Version* /*version*/) {
|
|
*value = db->IsFileDeletionsEnabled() ? 1 : 0;
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleBaseLevel(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
const auto* vstorage = cfd_->current()->storage_info();
|
|
*value = vstorage->base_level();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleTotalSstFilesSize(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
*value = cfd_->GetTotalSstFilesSize();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleLiveSstFilesSize(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
*value = cfd_->GetLiveSstFilesSize();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleEstimatePendingCompactionBytes(uint64_t* value,
|
|
DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
const auto* vstorage = cfd_->current()->storage_info();
|
|
*value = vstorage->estimated_compaction_needed_bytes();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleEstimateTableReadersMem(uint64_t* value,
|
|
DBImpl* /*db*/,
|
|
Version* version) {
|
|
*value = (version == nullptr) ? 0 : version->GetMemoryUsageByTableReaders();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleEstimateLiveDataSize(uint64_t* value, DBImpl* /*db*/,
|
|
Version* version) {
|
|
const auto* vstorage = version->storage_info();
|
|
*value = vstorage->EstimateLiveDataSize();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleMinLogNumberToKeep(uint64_t* value, DBImpl* db,
|
|
Version* /*version*/) {
|
|
*value = db->MinLogNumberToKeep();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleMinObsoleteSstNumberToKeep(uint64_t* value,
|
|
DBImpl* db,
|
|
Version* /*version*/) {
|
|
*value = db->MinObsoleteSstNumberToKeep();
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleActualDelayedWriteRate(uint64_t* value, DBImpl* db,
|
|
Version* /*version*/) {
|
|
const WriteController& wc = db->write_controller();
|
|
if (!wc.NeedsDelay()) {
|
|
*value = 0;
|
|
} else {
|
|
*value = wc.delayed_write_rate();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleIsWriteStopped(uint64_t* value, DBImpl* db,
|
|
Version* /*version*/) {
|
|
*value = db->write_controller().IsStopped() ? 1 : 0;
|
|
return true;
|
|
}
|
|
|
|
bool InternalStats::HandleEstimateOldestKeyTime(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
// TODO(yiwu): The property is currently available for fifo compaction
|
|
// with allow_compaction = false. This is because we don't propagate
|
|
// oldest_key_time on compaction.
|
|
if (cfd_->ioptions()->compaction_style != kCompactionStyleFIFO ||
|
|
cfd_->GetCurrentMutableCFOptions()
|
|
->compaction_options_fifo.allow_compaction) {
|
|
return false;
|
|
}
|
|
|
|
TablePropertiesCollection collection;
|
|
auto s = cfd_->current()->GetPropertiesOfAllTables(&collection);
|
|
if (!s.ok()) {
|
|
return false;
|
|
}
|
|
*value = std::numeric_limits<uint64_t>::max();
|
|
for (auto& p : collection) {
|
|
*value = std::min(*value, p.second->oldest_key_time);
|
|
if (*value == 0) {
|
|
break;
|
|
}
|
|
}
|
|
if (*value > 0) {
|
|
*value = std::min({cfd_->mem()->ApproximateOldestKeyTime(),
|
|
cfd_->imm()->ApproximateOldestKeyTime(), *value});
|
|
}
|
|
return *value > 0 && *value < std::numeric_limits<uint64_t>::max();
|
|
}
|
|
|
|
Cache* InternalStats::GetBlockCacheForStats() {
|
|
auto* table_factory = cfd_->ioptions()->table_factory.get();
|
|
assert(table_factory != nullptr);
|
|
return table_factory->GetOptions<Cache>(TableFactory::kBlockCacheOpts());
|
|
}
|
|
|
|
bool InternalStats::HandleBlockCacheCapacity(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
Cache* block_cache = GetBlockCacheForStats();
|
|
if (block_cache) {
|
|
*value = static_cast<uint64_t>(block_cache->GetCapacity());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool InternalStats::HandleBlockCacheUsage(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
Cache* block_cache = GetBlockCacheForStats();
|
|
if (block_cache) {
|
|
*value = static_cast<uint64_t>(block_cache->GetUsage());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool InternalStats::HandleBlockCachePinnedUsage(uint64_t* value, DBImpl* /*db*/,
|
|
Version* /*version*/) {
|
|
Cache* block_cache = GetBlockCacheForStats();
|
|
if (block_cache) {
|
|
*value = static_cast<uint64_t>(block_cache->GetPinnedUsage());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void InternalStats::DumpDBMapStats(
|
|
std::map<std::string, std::string>* db_stats) {
|
|
for (int i = 0; i < static_cast<int>(kIntStatsNumMax); ++i) {
|
|
InternalDBStatsType type = static_cast<InternalDBStatsType>(i);
|
|
(*db_stats)[db_stats_type_to_info.at(type).property_name] =
|
|
std::to_string(GetDBStats(type));
|
|
}
|
|
double seconds_up = (clock_->NowMicros() - started_at_) / kMicrosInSec;
|
|
(*db_stats)["db.uptime"] = std::to_string(seconds_up);
|
|
}
|
|
|
|
void InternalStats::DumpDBStats(std::string* value) {
|
|
char buf[1000];
|
|
// DB-level stats, only available from default column family
|
|
double seconds_up = (clock_->NowMicros() - started_at_) / kMicrosInSec;
|
|
double interval_seconds_up = seconds_up - db_stats_snapshot_.seconds_up;
|
|
snprintf(buf, sizeof(buf),
|
|
"\n** DB Stats **\nUptime(secs): %.1f total, %.1f interval\n",
|
|
seconds_up, interval_seconds_up);
|
|
value->append(buf);
|
|
// Cumulative
|
|
uint64_t user_bytes_written =
|
|
GetDBStats(InternalStats::kIntStatsBytesWritten);
|
|
uint64_t num_keys_written =
|
|
GetDBStats(InternalStats::kIntStatsNumKeysWritten);
|
|
uint64_t write_other = GetDBStats(InternalStats::kIntStatsWriteDoneByOther);
|
|
uint64_t write_self = GetDBStats(InternalStats::kIntStatsWriteDoneBySelf);
|
|
uint64_t wal_bytes = GetDBStats(InternalStats::kIntStatsWalFileBytes);
|
|
uint64_t wal_synced = GetDBStats(InternalStats::kIntStatsWalFileSynced);
|
|
uint64_t write_with_wal = GetDBStats(InternalStats::kIntStatsWriteWithWal);
|
|
uint64_t write_stall_micros =
|
|
GetDBStats(InternalStats::kIntStatsWriteStallMicros);
|
|
|
|
const int kHumanMicrosLen = 32;
|
|
char human_micros[kHumanMicrosLen];
|
|
|
|
// Data
|
|
// writes: total number of write requests.
|
|
// keys: total number of key updates issued by all the write requests
|
|
// commit groups: number of group commits issued to the DB. Each group can
|
|
// contain one or more writes.
|
|
// so writes/keys is the average number of put in multi-put or put
|
|
// writes/groups is the average group commit size.
|
|
//
|
|
// The format is the same for interval stats.
|
|
snprintf(buf, sizeof(buf),
|
|
"Cumulative writes: %s writes, %s keys, %s commit groups, "
|
|
"%.1f writes per commit group, ingest: %.2f GB, %.2f MB/s\n",
|
|
NumberToHumanString(write_other + write_self).c_str(),
|
|
NumberToHumanString(num_keys_written).c_str(),
|
|
NumberToHumanString(write_self).c_str(),
|
|
(write_other + write_self) /
|
|
std::max(1.0, static_cast<double>(write_self)),
|
|
user_bytes_written / kGB,
|
|
user_bytes_written / kMB / std::max(seconds_up, 0.001));
|
|
value->append(buf);
|
|
// WAL
|
|
snprintf(buf, sizeof(buf),
|
|
"Cumulative WAL: %s writes, %s syncs, "
|
|
"%.2f writes per sync, written: %.2f GB, %.2f MB/s\n",
|
|
NumberToHumanString(write_with_wal).c_str(),
|
|
NumberToHumanString(wal_synced).c_str(),
|
|
write_with_wal / std::max(1.0, static_cast<double>(wal_synced)),
|
|
wal_bytes / kGB, wal_bytes / kMB / std::max(seconds_up, 0.001));
|
|
value->append(buf);
|
|
// Stall
|
|
AppendHumanMicros(write_stall_micros, human_micros, kHumanMicrosLen, true);
|
|
snprintf(buf, sizeof(buf), "Cumulative stall: %s, %.1f percent\n",
|
|
human_micros,
|
|
// 10000 = divide by 1M to get secs, then multiply by 100 for pct
|
|
write_stall_micros / 10000.0 / std::max(seconds_up, 0.001));
|
|
value->append(buf);
|
|
|
|
// Interval
|
|
uint64_t interval_write_other = write_other - db_stats_snapshot_.write_other;
|
|
uint64_t interval_write_self = write_self - db_stats_snapshot_.write_self;
|
|
uint64_t interval_num_keys_written =
|
|
num_keys_written - db_stats_snapshot_.num_keys_written;
|
|
snprintf(
|
|
buf, sizeof(buf),
|
|
"Interval writes: %s writes, %s keys, %s commit groups, "
|
|
"%.1f writes per commit group, ingest: %.2f MB, %.2f MB/s\n",
|
|
NumberToHumanString(interval_write_other + interval_write_self).c_str(),
|
|
NumberToHumanString(interval_num_keys_written).c_str(),
|
|
NumberToHumanString(interval_write_self).c_str(),
|
|
static_cast<double>(interval_write_other + interval_write_self) /
|
|
std::max(1.0, static_cast<double>(interval_write_self)),
|
|
(user_bytes_written - db_stats_snapshot_.ingest_bytes) / kMB,
|
|
(user_bytes_written - db_stats_snapshot_.ingest_bytes) / kMB /
|
|
std::max(interval_seconds_up, 0.001)),
|
|
value->append(buf);
|
|
|
|
uint64_t interval_write_with_wal =
|
|
write_with_wal - db_stats_snapshot_.write_with_wal;
|
|
uint64_t interval_wal_synced = wal_synced - db_stats_snapshot_.wal_synced;
|
|
uint64_t interval_wal_bytes = wal_bytes - db_stats_snapshot_.wal_bytes;
|
|
|
|
snprintf(buf, sizeof(buf),
|
|
"Interval WAL: %s writes, %s syncs, "
|
|
"%.2f writes per sync, written: %.2f GB, %.2f MB/s\n",
|
|
NumberToHumanString(interval_write_with_wal).c_str(),
|
|
NumberToHumanString(interval_wal_synced).c_str(),
|
|
interval_write_with_wal /
|
|
std::max(1.0, static_cast<double>(interval_wal_synced)),
|
|
interval_wal_bytes / kGB,
|
|
interval_wal_bytes / kMB / std::max(interval_seconds_up, 0.001));
|
|
value->append(buf);
|
|
|
|
// Stall
|
|
AppendHumanMicros(write_stall_micros - db_stats_snapshot_.write_stall_micros,
|
|
human_micros, kHumanMicrosLen, true);
|
|
snprintf(buf, sizeof(buf), "Interval stall: %s, %.1f percent\n", human_micros,
|
|
// 10000 = divide by 1M to get secs, then multiply by 100 for pct
|
|
(write_stall_micros - db_stats_snapshot_.write_stall_micros) /
|
|
10000.0 / std::max(interval_seconds_up, 0.001));
|
|
value->append(buf);
|
|
|
|
db_stats_snapshot_.seconds_up = seconds_up;
|
|
db_stats_snapshot_.ingest_bytes = user_bytes_written;
|
|
db_stats_snapshot_.write_other = write_other;
|
|
db_stats_snapshot_.write_self = write_self;
|
|
db_stats_snapshot_.num_keys_written = num_keys_written;
|
|
db_stats_snapshot_.wal_bytes = wal_bytes;
|
|
db_stats_snapshot_.wal_synced = wal_synced;
|
|
db_stats_snapshot_.write_with_wal = write_with_wal;
|
|
db_stats_snapshot_.write_stall_micros = write_stall_micros;
|
|
}
|
|
|
|
/**
|
|
* Dump Compaction Level stats to a map of stat name with "compaction." prefix
|
|
* to value in double as string. The level in stat name is represented with
|
|
* a prefix "Lx" where "x" is the level number. A special level "Sum"
|
|
* represents the sum of a stat for all levels.
|
|
* The result also contains IO stall counters which keys start with "io_stalls."
|
|
* and values represent uint64 encoded as strings.
|
|
*/
|
|
void InternalStats::DumpCFMapStats(
|
|
std::map<std::string, std::string>* cf_stats) {
|
|
const VersionStorageInfo* vstorage = cfd_->current()->storage_info();
|
|
CompactionStats compaction_stats_sum;
|
|
std::map<int, std::map<LevelStatType, double>> levels_stats;
|
|
DumpCFMapStats(vstorage, &levels_stats, &compaction_stats_sum);
|
|
for (auto const& level_ent : levels_stats) {
|
|
auto level_str =
|
|
level_ent.first == -1 ? "Sum" : "L" + std::to_string(level_ent.first);
|
|
for (auto const& stat_ent : level_ent.second) {
|
|
auto stat_type = stat_ent.first;
|
|
auto key_str =
|
|
"compaction." + level_str + "." +
|
|
InternalStats::compaction_level_stats.at(stat_type).property_name;
|
|
(*cf_stats)[key_str] = std::to_string(stat_ent.second);
|
|
}
|
|
}
|
|
|
|
DumpCFMapStatsIOStalls(cf_stats);
|
|
}
|
|
|
|
void InternalStats::DumpCFMapStats(
|
|
const VersionStorageInfo* vstorage,
|
|
std::map<int, std::map<LevelStatType, double>>* levels_stats,
|
|
CompactionStats* compaction_stats_sum) {
|
|
assert(vstorage);
|
|
|
|
int num_levels_to_check =
|
|
(cfd_->ioptions()->compaction_style != kCompactionStyleFIFO)
|
|
? vstorage->num_levels() - 1
|
|
: 1;
|
|
|
|
// Compaction scores are sorted based on its value. Restore them to the
|
|
// level order
|
|
std::vector<double> compaction_score(number_levels_, 0);
|
|
for (int i = 0; i < num_levels_to_check; ++i) {
|
|
compaction_score[vstorage->CompactionScoreLevel(i)] =
|
|
vstorage->CompactionScore(i);
|
|
}
|
|
// Count # of files being compacted for each level
|
|
std::vector<int> files_being_compacted(number_levels_, 0);
|
|
for (int level = 0; level < number_levels_; ++level) {
|
|
for (auto* f : vstorage->LevelFiles(level)) {
|
|
if (f->being_compacted) {
|
|
++files_being_compacted[level];
|
|
}
|
|
}
|
|
}
|
|
|
|
int total_files = 0;
|
|
int total_files_being_compacted = 0;
|
|
double total_file_size = 0;
|
|
uint64_t flush_ingest = cf_stats_value_[BYTES_FLUSHED];
|
|
uint64_t add_file_ingest = cf_stats_value_[BYTES_INGESTED_ADD_FILE];
|
|
uint64_t curr_ingest = flush_ingest + add_file_ingest;
|
|
for (int level = 0; level < number_levels_; level++) {
|
|
int files = vstorage->NumLevelFiles(level);
|
|
total_files += files;
|
|
total_files_being_compacted += files_being_compacted[level];
|
|
if (comp_stats_[level].micros > 0 || comp_stats_[level].cpu_micros > 0 ||
|
|
files > 0) {
|
|
compaction_stats_sum->Add(comp_stats_[level]);
|
|
total_file_size += vstorage->NumLevelBytes(level);
|
|
uint64_t input_bytes;
|
|
if (level == 0) {
|
|
input_bytes = curr_ingest;
|
|
} else {
|
|
input_bytes = comp_stats_[level].bytes_read_non_output_levels +
|
|
comp_stats_[level].bytes_read_blob;
|
|
}
|
|
double w_amp =
|
|
(input_bytes == 0)
|
|
? 0.0
|
|
: static_cast<double>(comp_stats_[level].bytes_written +
|
|
comp_stats_[level].bytes_written_blob) /
|
|
input_bytes;
|
|
std::map<LevelStatType, double> level_stats;
|
|
PrepareLevelStats(&level_stats, files, files_being_compacted[level],
|
|
static_cast<double>(vstorage->NumLevelBytes(level)),
|
|
compaction_score[level], w_amp, comp_stats_[level]);
|
|
(*levels_stats)[level] = level_stats;
|
|
}
|
|
}
|
|
// Cumulative summary
|
|
double w_amp = (0 == curr_ingest)
|
|
? 0.0
|
|
: (compaction_stats_sum->bytes_written +
|
|
compaction_stats_sum->bytes_written_blob) /
|
|
static_cast<double>(curr_ingest);
|
|
// Stats summary across levels
|
|
std::map<LevelStatType, double> sum_stats;
|
|
PrepareLevelStats(&sum_stats, total_files, total_files_being_compacted,
|
|
total_file_size, 0, w_amp, *compaction_stats_sum);
|
|
(*levels_stats)[-1] = sum_stats; // -1 is for the Sum level
|
|
}
|
|
|
|
void InternalStats::DumpCFMapStatsByPriority(
|
|
std::map<int, std::map<LevelStatType, double>>* priorities_stats) {
|
|
for (size_t priority = 0; priority < comp_stats_by_pri_.size(); priority++) {
|
|
if (comp_stats_by_pri_[priority].micros > 0) {
|
|
std::map<LevelStatType, double> priority_stats;
|
|
PrepareLevelStats(&priority_stats, 0 /* num_files */,
|
|
0 /* being_compacted */, 0 /* total_file_size */,
|
|
0 /* compaction_score */, 0 /* w_amp */,
|
|
comp_stats_by_pri_[priority]);
|
|
(*priorities_stats)[static_cast<int>(priority)] = priority_stats;
|
|
}
|
|
}
|
|
}
|
|
|
|
void InternalStats::DumpCFMapStatsIOStalls(
|
|
std::map<std::string, std::string>* cf_stats) {
|
|
(*cf_stats)["io_stalls.level0_slowdown"] =
|
|
std::to_string(cf_stats_count_[L0_FILE_COUNT_LIMIT_SLOWDOWNS]);
|
|
(*cf_stats)["io_stalls.level0_slowdown_with_compaction"] =
|
|
std::to_string(cf_stats_count_[LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS]);
|
|
(*cf_stats)["io_stalls.level0_numfiles"] =
|
|
std::to_string(cf_stats_count_[L0_FILE_COUNT_LIMIT_STOPS]);
|
|
(*cf_stats)["io_stalls.level0_numfiles_with_compaction"] =
|
|
std::to_string(cf_stats_count_[LOCKED_L0_FILE_COUNT_LIMIT_STOPS]);
|
|
(*cf_stats)["io_stalls.stop_for_pending_compaction_bytes"] =
|
|
std::to_string(cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_STOPS]);
|
|
(*cf_stats)["io_stalls.slowdown_for_pending_compaction_bytes"] =
|
|
std::to_string(cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS]);
|
|
(*cf_stats)["io_stalls.memtable_compaction"] =
|
|
std::to_string(cf_stats_count_[MEMTABLE_LIMIT_STOPS]);
|
|
(*cf_stats)["io_stalls.memtable_slowdown"] =
|
|
std::to_string(cf_stats_count_[MEMTABLE_LIMIT_SLOWDOWNS]);
|
|
|
|
uint64_t total_stop = cf_stats_count_[L0_FILE_COUNT_LIMIT_STOPS] +
|
|
cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_STOPS] +
|
|
cf_stats_count_[MEMTABLE_LIMIT_STOPS];
|
|
|
|
uint64_t total_slowdown =
|
|
cf_stats_count_[L0_FILE_COUNT_LIMIT_SLOWDOWNS] +
|
|
cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS] +
|
|
cf_stats_count_[MEMTABLE_LIMIT_SLOWDOWNS];
|
|
|
|
(*cf_stats)["io_stalls.total_stop"] = std::to_string(total_stop);
|
|
(*cf_stats)["io_stalls.total_slowdown"] = std::to_string(total_slowdown);
|
|
}
|
|
|
|
void InternalStats::DumpCFStats(std::string* value) {
|
|
DumpCFStatsNoFileHistogram(value);
|
|
DumpCFFileHistogram(value);
|
|
}
|
|
|
|
void InternalStats::DumpCFStatsNoFileHistogram(std::string* value) {
|
|
char buf[2000];
|
|
// Per-ColumnFamily stats
|
|
PrintLevelStatsHeader(buf, sizeof(buf), cfd_->GetName(), "Level");
|
|
value->append(buf);
|
|
|
|
// Print stats for each level
|
|
const VersionStorageInfo* vstorage = cfd_->current()->storage_info();
|
|
std::map<int, std::map<LevelStatType, double>> levels_stats;
|
|
CompactionStats compaction_stats_sum;
|
|
DumpCFMapStats(vstorage, &levels_stats, &compaction_stats_sum);
|
|
for (int l = 0; l < number_levels_; ++l) {
|
|
if (levels_stats.find(l) != levels_stats.end()) {
|
|
PrintLevelStats(buf, sizeof(buf), "L" + std::to_string(l),
|
|
levels_stats[l]);
|
|
value->append(buf);
|
|
}
|
|
}
|
|
|
|
// Print sum of level stats
|
|
PrintLevelStats(buf, sizeof(buf), "Sum", levels_stats[-1]);
|
|
value->append(buf);
|
|
|
|
uint64_t flush_ingest = cf_stats_value_[BYTES_FLUSHED];
|
|
uint64_t add_file_ingest = cf_stats_value_[BYTES_INGESTED_ADD_FILE];
|
|
uint64_t ingest_files_addfile = cf_stats_value_[INGESTED_NUM_FILES_TOTAL];
|
|
uint64_t ingest_l0_files_addfile =
|
|
cf_stats_value_[INGESTED_LEVEL0_NUM_FILES_TOTAL];
|
|
uint64_t ingest_keys_addfile = cf_stats_value_[INGESTED_NUM_KEYS_TOTAL];
|
|
// Cumulative summary
|
|
uint64_t total_stall_count =
|
|
cf_stats_count_[L0_FILE_COUNT_LIMIT_SLOWDOWNS] +
|
|
cf_stats_count_[L0_FILE_COUNT_LIMIT_STOPS] +
|
|
cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS] +
|
|
cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_STOPS] +
|
|
cf_stats_count_[MEMTABLE_LIMIT_STOPS] +
|
|
cf_stats_count_[MEMTABLE_LIMIT_SLOWDOWNS];
|
|
// Interval summary
|
|
uint64_t interval_flush_ingest =
|
|
flush_ingest - cf_stats_snapshot_.ingest_bytes_flush;
|
|
uint64_t interval_add_file_inget =
|
|
add_file_ingest - cf_stats_snapshot_.ingest_bytes_addfile;
|
|
uint64_t interval_ingest =
|
|
interval_flush_ingest + interval_add_file_inget + 1;
|
|
CompactionStats interval_stats(compaction_stats_sum);
|
|
interval_stats.Subtract(cf_stats_snapshot_.comp_stats);
|
|
double w_amp =
|
|
(interval_stats.bytes_written + interval_stats.bytes_written_blob) /
|
|
static_cast<double>(interval_ingest);
|
|
PrintLevelStats(buf, sizeof(buf), "Int", 0, 0, 0, 0, w_amp, interval_stats);
|
|
value->append(buf);
|
|
|
|
PrintLevelStatsHeader(buf, sizeof(buf), cfd_->GetName(), "Priority");
|
|
value->append(buf);
|
|
std::map<int, std::map<LevelStatType, double>> priorities_stats;
|
|
DumpCFMapStatsByPriority(&priorities_stats);
|
|
for (size_t priority = 0; priority < comp_stats_by_pri_.size(); ++priority) {
|
|
if (priorities_stats.find(static_cast<int>(priority)) !=
|
|
priorities_stats.end()) {
|
|
PrintLevelStats(
|
|
buf, sizeof(buf),
|
|
Env::PriorityToString(static_cast<Env::Priority>(priority)),
|
|
priorities_stats[static_cast<int>(priority)]);
|
|
value->append(buf);
|
|
}
|
|
}
|
|
|
|
const auto blob_st = vstorage->GetBlobStats();
|
|
|
|
snprintf(buf, sizeof(buf),
|
|
"\nBlob file count: %" ROCKSDB_PRIszt
|
|
", total size: %.1f GB, garbage size: %.1f GB, space amp: %.1f\n\n",
|
|
vstorage->GetBlobFiles().size(), blob_st.total_file_size / kGB,
|
|
blob_st.total_garbage_size / kGB, blob_st.space_amp);
|
|
value->append(buf);
|
|
|
|
uint64_t now_micros = clock_->NowMicros();
|
|
double seconds_up = (now_micros - started_at_) / kMicrosInSec;
|
|
double interval_seconds_up = seconds_up - cf_stats_snapshot_.seconds_up;
|
|
snprintf(buf, sizeof(buf), "Uptime(secs): %.1f total, %.1f interval\n",
|
|
seconds_up, interval_seconds_up);
|
|
value->append(buf);
|
|
snprintf(buf, sizeof(buf), "Flush(GB): cumulative %.3f, interval %.3f\n",
|
|
flush_ingest / kGB, interval_flush_ingest / kGB);
|
|
value->append(buf);
|
|
snprintf(buf, sizeof(buf), "AddFile(GB): cumulative %.3f, interval %.3f\n",
|
|
add_file_ingest / kGB, interval_add_file_inget / kGB);
|
|
value->append(buf);
|
|
|
|
uint64_t interval_ingest_files_addfile =
|
|
ingest_files_addfile - cf_stats_snapshot_.ingest_files_addfile;
|
|
snprintf(buf, sizeof(buf),
|
|
"AddFile(Total Files): cumulative %" PRIu64 ", interval %" PRIu64
|
|
"\n",
|
|
ingest_files_addfile, interval_ingest_files_addfile);
|
|
value->append(buf);
|
|
|
|
uint64_t interval_ingest_l0_files_addfile =
|
|
ingest_l0_files_addfile - cf_stats_snapshot_.ingest_l0_files_addfile;
|
|
snprintf(buf, sizeof(buf),
|
|
"AddFile(L0 Files): cumulative %" PRIu64 ", interval %" PRIu64 "\n",
|
|
ingest_l0_files_addfile, interval_ingest_l0_files_addfile);
|
|
value->append(buf);
|
|
|
|
uint64_t interval_ingest_keys_addfile =
|
|
ingest_keys_addfile - cf_stats_snapshot_.ingest_keys_addfile;
|
|
snprintf(buf, sizeof(buf),
|
|
"AddFile(Keys): cumulative %" PRIu64 ", interval %" PRIu64 "\n",
|
|
ingest_keys_addfile, interval_ingest_keys_addfile);
|
|
value->append(buf);
|
|
|
|
// Compact
|
|
uint64_t compact_bytes_read = 0;
|
|
uint64_t compact_bytes_write = 0;
|
|
uint64_t compact_micros = 0;
|
|
for (int level = 0; level < number_levels_; level++) {
|
|
compact_bytes_read += comp_stats_[level].bytes_read_output_level +
|
|
comp_stats_[level].bytes_read_non_output_levels +
|
|
comp_stats_[level].bytes_read_blob;
|
|
compact_bytes_write += comp_stats_[level].bytes_written +
|
|
comp_stats_[level].bytes_written_blob;
|
|
compact_micros += comp_stats_[level].micros;
|
|
}
|
|
|
|
snprintf(buf, sizeof(buf),
|
|
"Cumulative compaction: %.2f GB write, %.2f MB/s write, "
|
|
"%.2f GB read, %.2f MB/s read, %.1f seconds\n",
|
|
compact_bytes_write / kGB,
|
|
compact_bytes_write / kMB / std::max(seconds_up, 0.001),
|
|
compact_bytes_read / kGB,
|
|
compact_bytes_read / kMB / std::max(seconds_up, 0.001),
|
|
compact_micros / kMicrosInSec);
|
|
value->append(buf);
|
|
|
|
// Compaction interval
|
|
uint64_t interval_compact_bytes_write =
|
|
compact_bytes_write - cf_stats_snapshot_.compact_bytes_write;
|
|
uint64_t interval_compact_bytes_read =
|
|
compact_bytes_read - cf_stats_snapshot_.compact_bytes_read;
|
|
uint64_t interval_compact_micros =
|
|
compact_micros - cf_stats_snapshot_.compact_micros;
|
|
|
|
snprintf(
|
|
buf, sizeof(buf),
|
|
"Interval compaction: %.2f GB write, %.2f MB/s write, "
|
|
"%.2f GB read, %.2f MB/s read, %.1f seconds\n",
|
|
interval_compact_bytes_write / kGB,
|
|
interval_compact_bytes_write / kMB / std::max(interval_seconds_up, 0.001),
|
|
interval_compact_bytes_read / kGB,
|
|
interval_compact_bytes_read / kMB / std::max(interval_seconds_up, 0.001),
|
|
interval_compact_micros / kMicrosInSec);
|
|
value->append(buf);
|
|
cf_stats_snapshot_.compact_bytes_write = compact_bytes_write;
|
|
cf_stats_snapshot_.compact_bytes_read = compact_bytes_read;
|
|
cf_stats_snapshot_.compact_micros = compact_micros;
|
|
|
|
snprintf(buf, sizeof(buf),
|
|
"Stalls(count): %" PRIu64
|
|
" level0_slowdown, "
|
|
"%" PRIu64
|
|
" level0_slowdown_with_compaction, "
|
|
"%" PRIu64
|
|
" level0_numfiles, "
|
|
"%" PRIu64
|
|
" level0_numfiles_with_compaction, "
|
|
"%" PRIu64
|
|
" stop for pending_compaction_bytes, "
|
|
"%" PRIu64
|
|
" slowdown for pending_compaction_bytes, "
|
|
"%" PRIu64
|
|
" memtable_compaction, "
|
|
"%" PRIu64
|
|
" memtable_slowdown, "
|
|
"interval %" PRIu64 " total count\n",
|
|
cf_stats_count_[L0_FILE_COUNT_LIMIT_SLOWDOWNS],
|
|
cf_stats_count_[LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS],
|
|
cf_stats_count_[L0_FILE_COUNT_LIMIT_STOPS],
|
|
cf_stats_count_[LOCKED_L0_FILE_COUNT_LIMIT_STOPS],
|
|
cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_STOPS],
|
|
cf_stats_count_[PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS],
|
|
cf_stats_count_[MEMTABLE_LIMIT_STOPS],
|
|
cf_stats_count_[MEMTABLE_LIMIT_SLOWDOWNS],
|
|
total_stall_count - cf_stats_snapshot_.stall_count);
|
|
value->append(buf);
|
|
|
|
cf_stats_snapshot_.seconds_up = seconds_up;
|
|
cf_stats_snapshot_.ingest_bytes_flush = flush_ingest;
|
|
cf_stats_snapshot_.ingest_bytes_addfile = add_file_ingest;
|
|
cf_stats_snapshot_.ingest_files_addfile = ingest_files_addfile;
|
|
cf_stats_snapshot_.ingest_l0_files_addfile = ingest_l0_files_addfile;
|
|
cf_stats_snapshot_.ingest_keys_addfile = ingest_keys_addfile;
|
|
cf_stats_snapshot_.comp_stats = compaction_stats_sum;
|
|
cf_stats_snapshot_.stall_count = total_stall_count;
|
|
|
|
// Do not gather cache entry stats during CFStats because DB
|
|
// mutex is held. Only dump last cached collection (rely on DB
|
|
// periodic stats dump to update)
|
|
if (cache_entry_stats_collector_) {
|
|
CacheEntryRoleStats stats;
|
|
// thread safe
|
|
cache_entry_stats_collector_->GetStats(&stats);
|
|
|
|
constexpr uint64_t kDayInMicros = uint64_t{86400} * 1000000U;
|
|
|
|
// Skip if stats are extremely old (> 1 day, incl not yet populated)
|
|
if (now_micros - stats.last_end_time_micros_ < kDayInMicros) {
|
|
value->append(stats.ToString(clock_));
|
|
}
|
|
}
|
|
}
|
|
|
|
void InternalStats::DumpCFFileHistogram(std::string* value) {
|
|
assert(value);
|
|
assert(cfd_);
|
|
|
|
std::ostringstream oss;
|
|
oss << "\n** File Read Latency Histogram By Level [" << cfd_->GetName()
|
|
<< "] **\n";
|
|
|
|
for (int level = 0; level < number_levels_; level++) {
|
|
if (!file_read_latency_[level].Empty()) {
|
|
oss << "** Level " << level << " read latency histogram (micros):\n"
|
|
<< file_read_latency_[level].ToString() << '\n';
|
|
}
|
|
}
|
|
|
|
if (!blob_file_read_latency_.Empty()) {
|
|
oss << "** Blob file read latency histogram (micros):\n"
|
|
<< blob_file_read_latency_.ToString() << '\n';
|
|
}
|
|
|
|
value->append(oss.str());
|
|
}
|
|
|
|
#else
|
|
|
|
const DBPropertyInfo* GetPropertyInfo(const Slice& /*property*/) {
|
|
return nullptr;
|
|
}
|
|
|
|
#endif // !ROCKSDB_LITE
|
|
|
|
} // namespace ROCKSDB_NAMESPACE
|