291 lines
12 KiB
C++
291 lines
12 KiB
C++
// Licensed to the Apache Software Foundation (ASF) under one
|
|
// or more contributor license agreements. See the NOTICE file
|
|
// distributed with this work for additional information
|
|
// regarding copyright ownership. The ASF licenses this file
|
|
// to you under the Apache License, Version 2.0 (the
|
|
// "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
#pragma once
|
|
|
|
#include <gen_cpp/Metrics_types.h>
|
|
#include <gen_cpp/Types_types.h>
|
|
#include <glog/logging.h>
|
|
#include <stdint.h>
|
|
|
|
#include <atomic>
|
|
// IWYU pragma: no_include <bits/std_abs.h>
|
|
#include <cmath> // IWYU pragma: keep
|
|
#include <list>
|
|
#include <memory>
|
|
#include <ostream>
|
|
#include <queue>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include "common/config.h"
|
|
#include "common/status.h"
|
|
#include "runtime/memory/mem_tracker.h"
|
|
#include "util/runtime_profile.h"
|
|
#include "util/string_util.h"
|
|
#include "util/uid_util.h"
|
|
|
|
namespace doris {
|
|
|
|
constexpr auto MEM_TRACKER_GROUP_NUM = 1000;
|
|
|
|
namespace taskgroup {
|
|
struct TgTrackerLimiterGroup;
|
|
class TaskGroup;
|
|
using TaskGroupPtr = std::shared_ptr<TaskGroup>;
|
|
} // namespace taskgroup
|
|
|
|
class MemTrackerLimiter;
|
|
|
|
struct TrackerLimiterGroup {
|
|
std::list<MemTrackerLimiter*> trackers;
|
|
std::mutex group_lock;
|
|
};
|
|
|
|
// Track and limit the memory usage of process and query.
|
|
// Contains an limit, arranged into a tree structure.
|
|
//
|
|
// Automatically track every once malloc/free of the system memory allocator (Currently, based on TCMlloc hook).
|
|
// Put Query MemTrackerLimiter into SCOPED_ATTACH_TASK when the thread starts,all memory used by this thread
|
|
// will be recorded on this Query, otherwise it will be recorded in Orphan Tracker by default.
|
|
class MemTrackerLimiter final : public MemTracker {
|
|
public:
|
|
enum class Type {
|
|
GLOBAL = 0, // Life cycle is the same as the process, e.g. Cache and default Orphan
|
|
QUERY = 1, // Count the memory consumption of all Query tasks.
|
|
LOAD = 2, // Count the memory consumption of all Load tasks.
|
|
COMPACTION = 3, // Count the memory consumption of all Base and Cumulative tasks.
|
|
SCHEMA_CHANGE = 4, // Count the memory consumption of all SchemaChange tasks.
|
|
CLONE = 5, // Count the memory consumption of all EngineCloneTask. Note: Memory that does not contain make/release snapshots.
|
|
EXPERIMENTAL =
|
|
6 // Experimental memory statistics, usually inaccurate, used for debugging, and expect to add other types in the future.
|
|
};
|
|
|
|
inline static std::unordered_map<Type, std::shared_ptr<RuntimeProfile::HighWaterMarkCounter>>
|
|
TypeMemSum = {{Type::GLOBAL,
|
|
std::make_shared<RuntimeProfile::HighWaterMarkCounter>(TUnit::BYTES)},
|
|
{Type::QUERY,
|
|
std::make_shared<RuntimeProfile::HighWaterMarkCounter>(TUnit::BYTES)},
|
|
{Type::LOAD,
|
|
std::make_shared<RuntimeProfile::HighWaterMarkCounter>(TUnit::BYTES)},
|
|
{Type::COMPACTION,
|
|
std::make_shared<RuntimeProfile::HighWaterMarkCounter>(TUnit::BYTES)},
|
|
{Type::SCHEMA_CHANGE,
|
|
std::make_shared<RuntimeProfile::HighWaterMarkCounter>(TUnit::BYTES)},
|
|
{Type::CLONE,
|
|
std::make_shared<RuntimeProfile::HighWaterMarkCounter>(TUnit::BYTES)},
|
|
{Type::EXPERIMENTAL,
|
|
std::make_shared<RuntimeProfile::HighWaterMarkCounter>(TUnit::BYTES)}};
|
|
|
|
public:
|
|
// byte_limit equal to -1 means no consumption limit, only participate in process memory statistics.
|
|
MemTrackerLimiter(Type type, const std::string& label = std::string(), int64_t byte_limit = -1,
|
|
RuntimeProfile* profile = nullptr,
|
|
const std::string& profile_counter_name = "PeakMemoryUsage");
|
|
|
|
~MemTrackerLimiter();
|
|
|
|
static std::string type_string(Type type) {
|
|
switch (type) {
|
|
case Type::GLOBAL:
|
|
return "global";
|
|
case Type::QUERY:
|
|
return "query";
|
|
case Type::LOAD:
|
|
return "load";
|
|
case Type::COMPACTION:
|
|
return "compaction";
|
|
case Type::SCHEMA_CHANGE:
|
|
return "schema_change";
|
|
case Type::CLONE:
|
|
return "clone";
|
|
case Type::EXPERIMENTAL:
|
|
return "experimental";
|
|
default:
|
|
LOG(FATAL) << "not match type of mem tracker limiter :" << static_cast<int>(type);
|
|
}
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
static bool sys_mem_exceed_limit_check(int64_t bytes);
|
|
|
|
void set_consumption() { LOG(FATAL) << "MemTrackerLimiter set_consumption not supported"; }
|
|
Type type() const { return _type; }
|
|
int64_t group_num() const { return _group_num; }
|
|
bool has_limit() const { return _limit >= 0; }
|
|
int64_t limit() const { return _limit; }
|
|
bool limit_exceeded() const { return _limit >= 0 && _limit < consumption(); }
|
|
|
|
Status check_limit(int64_t bytes = 0);
|
|
bool is_overcommit_tracker() const { return type() == Type::QUERY || type() == Type::LOAD; }
|
|
|
|
// Returns the maximum consumption that can be made without exceeding the limit on
|
|
// this tracker limiter.
|
|
int64_t spare_capacity() const { return _limit - consumption(); }
|
|
|
|
static void disable_oom_avoidance() { _oom_avoidance = false; }
|
|
|
|
public:
|
|
// If need to consume the tracker frequently, use it
|
|
void cache_consume(int64_t bytes);
|
|
|
|
// Transfer 'bytes' of consumption from this tracker to 'dst'.
|
|
void transfer_to(int64_t size, MemTrackerLimiter* dst) {
|
|
cache_consume(-size);
|
|
dst->cache_consume(size);
|
|
}
|
|
|
|
static void refresh_global_counter();
|
|
static void refresh_all_tracker_profile();
|
|
|
|
Snapshot make_snapshot() const override;
|
|
// Returns a list of all the valid tracker snapshots.
|
|
static void make_process_snapshots(std::vector<MemTracker::Snapshot>* snapshots);
|
|
static void make_type_snapshots(std::vector<MemTracker::Snapshot>* snapshots, Type type);
|
|
|
|
static std::string log_usage(MemTracker::Snapshot snapshot);
|
|
std::string log_usage() { return log_usage(make_snapshot()); }
|
|
static std::string type_log_usage(MemTracker::Snapshot snapshot);
|
|
static std::string type_detail_usage(const std::string& msg, Type type);
|
|
void print_log_usage(const std::string& msg);
|
|
void enable_print_log_usage() { _enable_print_log_usage = true; }
|
|
static void enable_print_log_process_usage() { _enable_print_log_process_usage = true; }
|
|
static std::string log_process_usage_str(const std::string& msg, bool with_stacktrace = true);
|
|
static void print_log_process_usage(const std::string& msg, bool with_stacktrace = true);
|
|
|
|
// Start canceling from the query with the largest memory usage until the memory of min_free_mem size is freed.
|
|
// vm_rss_str and mem_available_str recorded when gc is triggered, for log printing.
|
|
static int64_t free_top_memory_query(int64_t min_free_mem, const std::string& vm_rss_str,
|
|
const std::string& mem_available_str,
|
|
Type type = Type::QUERY);
|
|
|
|
template <typename TrackerGroups>
|
|
static int64_t free_top_memory_query(
|
|
int64_t min_free_mem, Type type, std::vector<TrackerGroups>& tracker_groups,
|
|
const std::function<std::string(int64_t, const std::string&)>& cancel_msg);
|
|
|
|
static int64_t free_top_memory_load(int64_t min_free_mem, const std::string& vm_rss_str,
|
|
const std::string& mem_available_str) {
|
|
return free_top_memory_query(min_free_mem, vm_rss_str, mem_available_str, Type::LOAD);
|
|
}
|
|
// Start canceling from the query with the largest memory overcommit ratio until the memory
|
|
// of min_free_mem size is freed.
|
|
static int64_t free_top_overcommit_query(int64_t min_free_mem, const std::string& vm_rss_str,
|
|
const std::string& mem_available_str,
|
|
Type type = Type::QUERY);
|
|
|
|
template <typename TrackerGroups>
|
|
static int64_t free_top_overcommit_query(
|
|
int64_t min_free_mem, Type type, std::vector<TrackerGroups>& tracker_groups,
|
|
const std::function<std::string(int64_t, const std::string&)>& cancel_msg);
|
|
|
|
static int64_t free_top_overcommit_load(int64_t min_free_mem, const std::string& vm_rss_str,
|
|
const std::string& mem_available_str) {
|
|
return free_top_overcommit_query(min_free_mem, vm_rss_str, mem_available_str, Type::LOAD);
|
|
}
|
|
|
|
static int64_t tg_memory_limit_gc(
|
|
int64_t request_free_memory, int64_t used_memory, uint64_t id, const std::string& name,
|
|
int64_t memory_limit,
|
|
std::vector<taskgroup::TgTrackerLimiterGroup>& tracker_limiter_groups);
|
|
|
|
// only for Type::QUERY or Type::LOAD.
|
|
static TUniqueId label_to_queryid(const std::string& label) {
|
|
if (label.rfind("Query#Id=", 0) != 0 && label.rfind("Load#Id=", 0) != 0) {
|
|
return TUniqueId();
|
|
}
|
|
auto queryid = split(label, "#Id=")[1];
|
|
TUniqueId querytid;
|
|
parse_id(queryid, &querytid);
|
|
return querytid;
|
|
}
|
|
|
|
static std::string process_mem_log_str();
|
|
static std::string process_limit_exceeded_errmsg_str();
|
|
// Log the memory usage when memory limit is exceeded.
|
|
std::string query_tracker_limit_exceeded_str(const std::string& tracker_limit_exceeded,
|
|
const std::string& last_consumer_tracker,
|
|
const std::string& executing_msg);
|
|
std::string tracker_limit_exceeded_str();
|
|
std::string tracker_limit_exceeded_str(int64_t bytes);
|
|
|
|
std::string debug_string() override {
|
|
std::stringstream msg;
|
|
msg << "limit: " << _limit << "; "
|
|
<< "consumption: " << _consumption->current_value() << "; "
|
|
<< "label: " << _label << "; "
|
|
<< "type: " << type_string(_type) << "; ";
|
|
return msg.str();
|
|
}
|
|
|
|
private:
|
|
friend class ThreadMemTrackerMgr;
|
|
|
|
// When the accumulated untracked memory value exceeds the upper limit,
|
|
// the current value is returned and set to 0.
|
|
// Thread safety.
|
|
int64_t add_untracked_mem(int64_t bytes);
|
|
|
|
private:
|
|
Type _type;
|
|
|
|
// Limit on memory consumption, in bytes.
|
|
int64_t _limit;
|
|
|
|
// Group number in MemTracker::mem_tracker_limiter_pool and MemTracker::mem_tracker_pool, generated by the timestamp.
|
|
int64_t _group_num;
|
|
|
|
// Consume size smaller than mem_tracker_consume_min_size_bytes will continue to accumulate
|
|
// to avoid frequent calls to consume/release of MemTracker.
|
|
std::atomic<int64_t> _untracked_mem = 0;
|
|
|
|
// Avoid frequent printing.
|
|
bool _enable_print_log_usage = false;
|
|
static std::atomic<bool> _enable_print_log_process_usage;
|
|
static bool _oom_avoidance;
|
|
|
|
// Iterator into mem_tracker_limiter_pool for this object. Stored to have O(1) remove.
|
|
std::list<MemTrackerLimiter*>::iterator _tracker_limiter_group_it;
|
|
};
|
|
|
|
inline int64_t MemTrackerLimiter::add_untracked_mem(int64_t bytes) {
|
|
_untracked_mem += bytes;
|
|
if (std::abs(_untracked_mem) >= config::mem_tracker_consume_min_size_bytes) {
|
|
return _untracked_mem.exchange(0);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
inline void MemTrackerLimiter::cache_consume(int64_t bytes) {
|
|
if (bytes == 0) return;
|
|
int64_t consume_bytes = add_untracked_mem(bytes);
|
|
consume(consume_bytes);
|
|
}
|
|
|
|
inline Status MemTrackerLimiter::check_limit(int64_t bytes) {
|
|
if (bytes <= 0 || (is_overcommit_tracker() && config::enable_query_memory_overcommit)) {
|
|
return Status::OK();
|
|
}
|
|
if (_limit > 0 && _consumption->current_value() + bytes > _limit) {
|
|
return Status::MemoryLimitExceeded(tracker_limit_exceeded_str(bytes));
|
|
}
|
|
return Status::OK();
|
|
}
|
|
|
|
} // namespace doris
|