Add file temperature related counter and bytes stats to and io_stats (#8710)

Summary:
For tiered storage project, we need to know the block read count and read bytes of files with different temperature. Add FileIOByTemperature to IOStatsContext and collect the bytes read and read count from different temperature files through the RandomAccessFileReader.

Pull Request resolved: https://github.com/facebook/rocksdb/pull/8710

Test Plan: make check, add the testing cases

Reviewed By: siying

Differential Revision: D30582400

Pulled By: zhichao-cao

fbshipit-source-id: d83173de594374fc8404af5ce93a6a9be72c7141
This commit is contained in:
Zhichao Cao 2021-10-07 14:57:02 -07:00 committed by Facebook GitHub Bot
parent 699f45049d
commit b632ed0c67
9 changed files with 160 additions and 38 deletions

View File

@ -17,6 +17,7 @@
#include "options/options_helper.h" #include "options/options_helper.h"
#include "port/port.h" #include "port/port.h"
#include "port/stack_trace.h" #include "port/stack_trace.h"
#include "rocksdb/iostats_context.h"
#include "rocksdb/persistent_cache.h" #include "rocksdb/persistent_cache.h"
#include "rocksdb/trace_record.h" #include "rocksdb/trace_record.h"
#include "rocksdb/trace_record_result.h" #include "rocksdb/trace_record_result.h"
@ -6522,6 +6523,9 @@ TEST_F(DBTest2, BottommostTemperature) {
ASSERT_OK(Flush()); ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact()); ASSERT_OK(dbfull()->TEST_WaitForCompact());
get_iostats_context()->Reset();
IOStatsContext* iostats = get_iostats_context();
ColumnFamilyMetaData metadata; ColumnFamilyMetaData metadata;
db_->GetColumnFamilyMetaData(&metadata); db_->GetColumnFamilyMetaData(&metadata);
ASSERT_EQ(1, metadata.file_count); ASSERT_EQ(1, metadata.file_count);
@ -6530,11 +6534,31 @@ TEST_F(DBTest2, BottommostTemperature) {
ASSERT_EQ(size, 0); ASSERT_EQ(size, 0);
size = GetSstSizeHelper(Temperature::kWarm); size = GetSstSizeHelper(Temperature::kWarm);
ASSERT_GT(size, 0); ASSERT_GT(size, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.warm_file_read_count, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
ASSERT_EQ("bar", Get("foo"));
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.warm_file_read_count, 1);
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_bytes_read, 0);
ASSERT_GT(iostats->file_io_stats_by_temperature.warm_file_bytes_read, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.cold_file_bytes_read, 0);
// non-bottommost file still has unknown temperature // non-bottommost file still has unknown temperature
ASSERT_OK(Put("foo", "bar")); ASSERT_OK(Put("foo", "bar"));
ASSERT_OK(Put("bar", "bar")); ASSERT_OK(Put("bar", "bar"));
ASSERT_OK(Flush()); ASSERT_OK(Flush());
ASSERT_EQ("bar", Get("bar"));
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.warm_file_read_count, 1);
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_bytes_read, 0);
ASSERT_GT(iostats->file_io_stats_by_temperature.warm_file_bytes_read, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.cold_file_bytes_read, 0);
db_->GetColumnFamilyMetaData(&metadata); db_->GetColumnFamilyMetaData(&metadata);
ASSERT_EQ(2, metadata.file_count); ASSERT_EQ(2, metadata.file_count);
ASSERT_EQ(Temperature::kUnknown, metadata.levels[0].files[0].temperature); ASSERT_EQ(Temperature::kUnknown, metadata.levels[0].files[0].temperature);
@ -6583,6 +6607,8 @@ TEST_F(DBTest2, BottommostTemperatureUniversal) {
ASSERT_EQ(size, 0); ASSERT_EQ(size, 0);
size = GetSstSizeHelper(Temperature::kHot); size = GetSstSizeHelper(Temperature::kHot);
ASSERT_EQ(size, 0); ASSERT_EQ(size, 0);
get_iostats_context()->Reset();
IOStatsContext* iostats = get_iostats_context();
for (int i = 0; i < kTriggerNum; i++) { for (int i = 0; i < kTriggerNum; i++) {
ASSERT_OK(Put("foo", "bar")); ASSERT_OK(Put("foo", "bar"));
@ -6600,6 +6626,17 @@ TEST_F(DBTest2, BottommostTemperatureUniversal) {
ASSERT_GT(size, 0); ASSERT_GT(size, 0);
size = GetSstSizeHelper(Temperature::kWarm); size = GetSstSizeHelper(Temperature::kWarm);
ASSERT_EQ(size, 0); ASSERT_EQ(size, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.warm_file_read_count, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
ASSERT_EQ("bar", Get("foo"));
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.warm_file_read_count, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_read_count, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.hot_file_bytes_read, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.warm_file_bytes_read, 0);
ASSERT_EQ(iostats->file_io_stats_by_temperature.cold_file_bytes_read, 0);
ASSERT_OK(Put("foo", "bar")); ASSERT_OK(Put("foo", "bar"));
ASSERT_OK(Put("bar", "bar")); ASSERT_OK(Put("bar", "bar"));

View File

@ -103,7 +103,7 @@ Status TableCache::GetTableReader(
std::unique_ptr<TableReader>* table_reader, std::unique_ptr<TableReader>* table_reader,
const SliceTransform* prefix_extractor, bool skip_filters, int level, const SliceTransform* prefix_extractor, bool skip_filters, int level,
bool prefetch_index_and_filter_in_cache, bool prefetch_index_and_filter_in_cache,
size_t max_file_size_for_l0_meta_pin) { size_t max_file_size_for_l0_meta_pin, Temperature file_temperature) {
std::string fname = std::string fname =
TableFileName(ioptions_.cf_paths, fd.GetNumber(), fd.GetPathId()); TableFileName(ioptions_.cf_paths, fd.GetNumber(), fd.GetPathId());
std::unique_ptr<FSRandomAccessFile> file; std::unique_ptr<FSRandomAccessFile> file;
@ -132,7 +132,8 @@ Status TableCache::GetTableReader(
new RandomAccessFileReader( new RandomAccessFileReader(
std::move(file), fname, ioptions_.clock, io_tracer_, std::move(file), fname, ioptions_.clock, io_tracer_,
record_read_stats ? ioptions_.stats : nullptr, SST_READ_MICROS, record_read_stats ? ioptions_.stats : nullptr, SST_READ_MICROS,
file_read_hist, ioptions_.rate_limiter.get(), ioptions_.listeners)); file_read_hist, ioptions_.rate_limiter.get(), ioptions_.listeners,
file_temperature));
s = ioptions_.table_factory->NewTableReader( s = ioptions_.table_factory->NewTableReader(
ro, ro,
TableReaderOptions( TableReaderOptions(
@ -154,15 +155,13 @@ void TableCache::EraseHandle(const FileDescriptor& fd, Cache::Handle* handle) {
cache_->Erase(key); cache_->Erase(key);
} }
Status TableCache::FindTable(const ReadOptions& ro, Status TableCache::FindTable(
const FileOptions& file_options, const ReadOptions& ro, const FileOptions& file_options,
const InternalKeyComparator& internal_comparator, const InternalKeyComparator& internal_comparator, const FileDescriptor& fd,
const FileDescriptor& fd, Cache::Handle** handle, Cache::Handle** handle, const SliceTransform* prefix_extractor,
const SliceTransform* prefix_extractor, const bool no_io, bool record_read_stats, HistogramImpl* file_read_hist,
const bool no_io, bool record_read_stats, bool skip_filters, int level, bool prefetch_index_and_filter_in_cache,
HistogramImpl* file_read_hist, bool skip_filters, size_t max_file_size_for_l0_meta_pin, Temperature file_temperature) {
int level, bool prefetch_index_and_filter_in_cache,
size_t max_file_size_for_l0_meta_pin) {
PERF_TIMER_GUARD_WITH_CLOCK(find_table_nanos, ioptions_.clock); PERF_TIMER_GUARD_WITH_CLOCK(find_table_nanos, ioptions_.clock);
uint64_t number = fd.GetNumber(); uint64_t number = fd.GetNumber();
Slice key = GetSliceForFileNumber(&number); Slice key = GetSliceForFileNumber(&number);
@ -186,7 +185,7 @@ Status TableCache::FindTable(const ReadOptions& ro,
ro, file_options, internal_comparator, fd, false /* sequential mode */, ro, file_options, internal_comparator, fd, false /* sequential mode */,
record_read_stats, file_read_hist, &table_reader, prefix_extractor, record_read_stats, file_read_hist, &table_reader, prefix_extractor,
skip_filters, level, prefetch_index_and_filter_in_cache, skip_filters, level, prefetch_index_and_filter_in_cache,
max_file_size_for_l0_meta_pin); max_file_size_for_l0_meta_pin, file_temperature);
if (!s.ok()) { if (!s.ok()) {
assert(table_reader == nullptr); assert(table_reader == nullptr);
RecordTick(ioptions_.stats, NO_FILE_ERRORS); RecordTick(ioptions_.stats, NO_FILE_ERRORS);
@ -231,7 +230,7 @@ InternalIterator* TableCache::NewIterator(
options.read_tier == kBlockCacheTier /* no_io */, options.read_tier == kBlockCacheTier /* no_io */,
!for_compaction /* record_read_stats */, file_read_hist, skip_filters, !for_compaction /* record_read_stats */, file_read_hist, skip_filters,
level, true /* prefetch_index_and_filter_in_cache */, level, true /* prefetch_index_and_filter_in_cache */,
max_file_size_for_l0_meta_pin); max_file_size_for_l0_meta_pin, file_meta.temperature);
if (s.ok()) { if (s.ok()) {
table_reader = GetTableReaderFromHandle(handle); table_reader = GetTableReaderFromHandle(handle);
} }
@ -431,7 +430,7 @@ Status TableCache::Get(const ReadOptions& options,
options.read_tier == kBlockCacheTier /* no_io */, options.read_tier == kBlockCacheTier /* no_io */,
true /* record_read_stats */, file_read_hist, skip_filters, true /* record_read_stats */, file_read_hist, skip_filters,
level, true /* prefetch_index_and_filter_in_cache */, level, true /* prefetch_index_and_filter_in_cache */,
max_file_size_for_l0_meta_pin); max_file_size_for_l0_meta_pin, file_meta.temperature);
if (s.ok()) { if (s.ok()) {
t = GetTableReaderFromHandle(handle); t = GetTableReaderFromHandle(handle);
} }
@ -531,10 +530,12 @@ Status TableCache::MultiGet(const ReadOptions& options,
// found in the row cache and thus the range may now be empty // found in the row cache and thus the range may now be empty
if (s.ok() && !table_range.empty()) { if (s.ok() && !table_range.empty()) {
if (t == nullptr) { if (t == nullptr) {
s = FindTable( s = FindTable(options, file_options_, internal_comparator, fd, &handle,
options, file_options_, internal_comparator, fd, &handle, prefix_extractor,
prefix_extractor, options.read_tier == kBlockCacheTier /* no_io */, options.read_tier == kBlockCacheTier /* no_io */,
true /* record_read_stats */, file_read_hist, skip_filters, level); true /* record_read_stats */, file_read_hist, skip_filters,
level, true /* prefetch_index_and_filter_in_cache */,
0 /*max_file_size_for_l0_meta_pin*/, file_meta.temperature);
TEST_SYNC_POINT_CALLBACK("TableCache::MultiGet:FindTable", &s); TEST_SYNC_POINT_CALLBACK("TableCache::MultiGet:FindTable", &s);
if (s.ok()) { if (s.ok()) {
t = GetTableReaderFromHandle(handle); t = GetTableReaderFromHandle(handle);

View File

@ -140,7 +140,8 @@ class TableCache {
HistogramImpl* file_read_hist = nullptr, HistogramImpl* file_read_hist = nullptr,
bool skip_filters = false, int level = -1, bool skip_filters = false, int level = -1,
bool prefetch_index_and_filter_in_cache = true, bool prefetch_index_and_filter_in_cache = true,
size_t max_file_size_for_l0_meta_pin = 0); size_t max_file_size_for_l0_meta_pin = 0,
Temperature file_temperature = Temperature::kUnknown);
// Get TableReader from a cache handle. // Get TableReader from a cache handle.
TableReader* GetTableReaderFromHandle(Cache::Handle* handle); TableReader* GetTableReaderFromHandle(Cache::Handle* handle);
@ -206,7 +207,8 @@ class TableCache {
const SliceTransform* prefix_extractor = nullptr, const SliceTransform* prefix_extractor = nullptr,
bool skip_filters = false, int level = -1, bool skip_filters = false, int level = -1,
bool prefetch_index_and_filter_in_cache = true, bool prefetch_index_and_filter_in_cache = true,
size_t max_file_size_for_l0_meta_pin = 0); size_t max_file_size_for_l0_meta_pin = 0,
Temperature file_temperature = Temperature::kUnknown);
// Create a key prefix for looking up the row cache. The prefix is of the // Create a key prefix for looking up the row cache. The prefix is of the
// format row_cache_id + fd_number + seq_no. Later, the user key can be // format row_cache_id + fd_number + seq_no. Later, the user key can be

View File

@ -1017,7 +1017,8 @@ class VersionBuilder::Rep {
&file_meta->table_reader_handle, prefix_extractor, false /*no_io */, &file_meta->table_reader_handle, prefix_extractor, false /*no_io */,
true /* record_read_stats */, true /* record_read_stats */,
internal_stats->GetFileReadHist(level), false, level, internal_stats->GetFileReadHist(level), false, level,
prefetch_index_and_filter_in_cache, max_file_size_for_l0_meta_pin); prefetch_index_and_filter_in_cache, max_file_size_for_l0_meta_pin,
file_meta->temperature);
if (file_meta->table_reader_handle != nullptr) { if (file_meta->table_reader_handle != nullptr) {
// Load table_reader // Load table_reader
file_meta->fd.table_reader = table_cache_->GetTableReaderFromHandle( file_meta->fd.table_reader = table_cache_->GetTableReaderFromHandle(

View File

@ -22,6 +22,46 @@
#include "util/rate_limiter.h" #include "util/rate_limiter.h"
namespace ROCKSDB_NAMESPACE { namespace ROCKSDB_NAMESPACE {
inline void IOStatsAddBytesByTemperature(Temperature file_temperature,
size_t value) {
if (file_temperature == Temperature::kUnknown) {
return;
}
switch (file_temperature) {
case Temperature::kHot:
IOSTATS_ADD(file_io_stats_by_temperature.hot_file_bytes_read, value);
break;
case Temperature::kWarm:
IOSTATS_ADD(file_io_stats_by_temperature.warm_file_bytes_read, value);
break;
case Temperature::kCold:
IOSTATS_ADD(file_io_stats_by_temperature.cold_file_bytes_read, value);
break;
default:
break;
}
}
inline void IOStatsAddCountByTemperature(Temperature file_temperature,
size_t value) {
if (file_temperature == Temperature::kUnknown) {
return;
}
switch (file_temperature) {
case Temperature::kHot:
IOSTATS_ADD(file_io_stats_by_temperature.hot_file_read_count, value);
break;
case Temperature::kWarm:
IOSTATS_ADD(file_io_stats_by_temperature.warm_file_read_count, value);
break;
case Temperature::kCold:
IOSTATS_ADD(file_io_stats_by_temperature.cold_file_read_count, value);
break;
default:
break;
}
}
IOStatus RandomAccessFileReader::Create( IOStatus RandomAccessFileReader::Create(
const std::shared_ptr<FileSystem>& fs, const std::string& fname, const std::shared_ptr<FileSystem>& fs, const std::string& fname,
const FileOptions& file_opts, const FileOptions& file_opts,
@ -182,6 +222,8 @@ IOStatus RandomAccessFileReader::Read(const IOOptions& opts, uint64_t offset,
*result = Slice(res_scratch, io_s.ok() ? pos : 0); *result = Slice(res_scratch, io_s.ok() ? pos : 0);
} }
IOSTATS_ADD(bytes_read, result->size()); IOSTATS_ADD(bytes_read, result->size());
IOStatsAddBytesByTemperature(file_temperature_, result->size());
IOStatsAddCountByTemperature(file_temperature_, 1);
SetPerfLevel(prev_perf_level); SetPerfLevel(prev_perf_level);
} }
if (stats_ != nullptr && file_read_hist_ != nullptr) { if (stats_ != nullptr && file_read_hist_ != nullptr) {
@ -347,6 +389,9 @@ IOStatus RandomAccessFileReader::MultiRead(const IOOptions& opts,
} }
#endif // ROCKSDB_LITE #endif // ROCKSDB_LITE
IOSTATS_ADD(bytes_read, read_reqs[i].result.size()); IOSTATS_ADD(bytes_read, read_reqs[i].result.size());
IOStatsAddBytesByTemperature(file_temperature_,
read_reqs[i].result.size());
IOStatsAddCountByTemperature(file_temperature_, 1);
} }
SetPerfLevel(prev_perf_level); SetPerfLevel(prev_perf_level);
} }

View File

@ -73,6 +73,7 @@ class RandomAccessFileReader {
HistogramImpl* file_read_hist_; HistogramImpl* file_read_hist_;
RateLimiter* rate_limiter_; RateLimiter* rate_limiter_;
std::vector<std::shared_ptr<EventListener>> listeners_; std::vector<std::shared_ptr<EventListener>> listeners_;
Temperature file_temperature_;
public: public:
explicit RandomAccessFileReader( explicit RandomAccessFileReader(
@ -82,7 +83,8 @@ class RandomAccessFileReader {
Statistics* stats = nullptr, uint32_t hist_type = 0, Statistics* stats = nullptr, uint32_t hist_type = 0,
HistogramImpl* file_read_hist = nullptr, HistogramImpl* file_read_hist = nullptr,
RateLimiter* rate_limiter = nullptr, RateLimiter* rate_limiter = nullptr,
const std::vector<std::shared_ptr<EventListener>>& listeners = {}) const std::vector<std::shared_ptr<EventListener>>& listeners = {},
Temperature file_temperature = Temperature::kUnknown)
: file_(std::move(raf), io_tracer, _file_name), : file_(std::move(raf), io_tracer, _file_name),
file_name_(std::move(_file_name)), file_name_(std::move(_file_name)),
clock_(clock), clock_(clock),
@ -90,7 +92,8 @@ class RandomAccessFileReader {
hist_type_(hist_type), hist_type_(hist_type),
file_read_hist_(file_read_hist), file_read_hist_(file_read_hist),
rate_limiter_(rate_limiter), rate_limiter_(rate_limiter),
listeners_() { listeners_(),
file_temperature_(file_temperature) {
#ifndef ROCKSDB_LITE #ifndef ROCKSDB_LITE
std::for_each(listeners.begin(), listeners.end(), std::for_each(listeners.begin(), listeners.end(),
[this](const std::shared_ptr<EventListener>& e) { [this](const std::shared_ptr<EventListener>& e) {

View File

@ -14,6 +14,32 @@
namespace ROCKSDB_NAMESPACE { namespace ROCKSDB_NAMESPACE {
// EXPERIMENTAL: the IO statistics for tiered storage. It matches with each
// item in Temperature class.
struct FileIOByTemperature {
// the number of bytes read to Temperature::kHot file
uint64_t hot_file_bytes_read;
// the number of bytes read to Temperature::kWarm file
uint64_t warm_file_bytes_read;
// the number of bytes read to Temperature::kCold file
uint64_t cold_file_bytes_read;
// total number of reads to Temperature::kHot file
uint64_t hot_file_read_count;
// total number of reads to Temperature::kWarm file
uint64_t warm_file_read_count;
// total number of reads to Temperature::kCold file
uint64_t cold_file_read_count;
// reset all the statistics to 0.
void Reset() {
hot_file_bytes_read = 0;
warm_file_bytes_read = 0;
cold_file_bytes_read = 0;
hot_file_read_count = 0;
warm_file_read_count = 0;
cold_file_read_count = 0;
}
};
struct IOStatsContext { struct IOStatsContext {
// reset all io-stats counter to zero // reset all io-stats counter to zero
void Reset(); void Reset();
@ -48,6 +74,8 @@ struct IOStatsContext {
uint64_t cpu_write_nanos; uint64_t cpu_write_nanos;
// CPU time spent in read() and pread() // CPU time spent in read() and pread()
uint64_t cpu_read_nanos; uint64_t cpu_read_nanos;
FileIOByTemperature file_io_stats_by_temperature;
}; };
// If RocksDB is compiled with -DNIOSTATS_CONTEXT, then a pointer to a global, // If RocksDB is compiled with -DNIOSTATS_CONTEXT, then a pointer to a global,

View File

@ -39,6 +39,7 @@ void IOStatsContext::Reset() {
logger_nanos = 0; logger_nanos = 0;
cpu_write_nanos = 0; cpu_write_nanos = 0;
cpu_read_nanos = 0; cpu_read_nanos = 0;
file_io_stats_by_temperature.Reset();
#endif //! NIOSTATS_CONTEXT #endif //! NIOSTATS_CONTEXT
} }
@ -66,7 +67,12 @@ std::string IOStatsContext::ToString(bool exclude_zero_counters) const {
IOSTATS_CONTEXT_OUTPUT(logger_nanos); IOSTATS_CONTEXT_OUTPUT(logger_nanos);
IOSTATS_CONTEXT_OUTPUT(cpu_write_nanos); IOSTATS_CONTEXT_OUTPUT(cpu_write_nanos);
IOSTATS_CONTEXT_OUTPUT(cpu_read_nanos); IOSTATS_CONTEXT_OUTPUT(cpu_read_nanos);
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.hot_file_bytes_read);
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.warm_file_bytes_read);
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.cold_file_bytes_read);
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.hot_file_read_count);
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.warm_file_read_count);
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.cold_file_read_count);
std::string str = ss.str(); std::string str = ss.str();
str.erase(str.find_last_not_of(", ") + 1); str.erase(str.find_last_not_of(", ") + 1);
return str; return str;

View File

@ -84,13 +84,12 @@ extern thread_local PerfContext perf_context;
if ((*(perf_context.level_to_perf_context)).find(level) != \ if ((*(perf_context.level_to_perf_context)).find(level) != \
(*(perf_context.level_to_perf_context)).end()) { \ (*(perf_context.level_to_perf_context)).end()) { \
(*(perf_context.level_to_perf_context))[level].metric += value; \ (*(perf_context.level_to_perf_context))[level].metric += value; \
} \ } else { \
else { \
PerfContextByLevel empty_context; \ PerfContextByLevel empty_context; \
(*(perf_context.level_to_perf_context))[level] = empty_context; \ (*(perf_context.level_to_perf_context))[level] = empty_context; \
(*(perf_context.level_to_perf_context))[level].metric += value; \ (*(perf_context.level_to_perf_context))[level].metric += value; \
} \ } \
} \ }
#endif #endif