blob: fc83363c8e2efd4ddc353c0efa56826fd41df7f1 [file] [log] [blame]
// 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.
#ifndef KUDU_UTIL_MEM_TRACKER_H
#define KUDU_UTIL_MEM_TRACKER_H
#include <cstdint>
#include <list>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <glog/logging.h>
#include "kudu/util/high_water_mark.h"
#include "kudu/util/mutex.h"
namespace kudu {
class MemTrackerPB;
// A MemTracker tracks memory consumption; it contains an optional limit and is
// arranged into a tree structure such that the consumption tracked by a
// MemTracker is also tracked by its ancestors.
//
// The MemTracker hierarchy is rooted in a single static MemTracker.
// The root MemTracker always exists, and it is the common
// ancestor to all MemTrackers. All operations that discover MemTrackers begin
// at the root and work their way down the tree, while operations that deal
// with adjusting memory consumption begin at a particular MemTracker and work
// their way up the tree to the root. All MemTrackers (except the root) must
// have a parent. As a rule, all children belonging to a parent should have
// unique ids, but this is only enforced during a Find() operation to allow for
// transient duplicates (e.g. the web UI grabbing very short-lived references
// to all MemTrackers while rendering a web page). This also means id
// uniqueness only exists where it's actually needed.
//
// When a MemTracker begins its life, it has a strong reference to its parent
// and the parent has a weak reference to it. Both remain for the lifetime of
// the MemTracker.
//
// Memory consumption is tracked via calls to Consume()/Release(), either to
// the tracker itself or to one of its descendants.
//
// This class is thread-safe.
class MemTracker : public std::enable_shared_from_this<MemTracker> {
public:
~MemTracker();
// Creates and adds the tracker to the tree so that it can be retrieved with
// FindTracker/FindOrCreateTracker.
//
// byte_limit < 0 means no limit; 'id' is a used as a label to uniquely identify
// the MemTracker for the below Find...() calls as well as the web UI.
//
// Use the two-argument form if there is no parent.
static std::shared_ptr<MemTracker> CreateTracker(
int64_t byte_limit,
const std::string& id,
std::shared_ptr<MemTracker> parent = std::shared_ptr<MemTracker>());
// If a tracker with the specified 'id' and 'parent' exists in the tree, sets
// 'tracker' to reference that instance. Returns false if no such tracker
// exists.
//
// Use the two-argument form if there is no parent.
//
// Note: this function will enforce that 'id' is unique amongst the children
// of 'parent'.
static bool FindTracker(
const std::string& id,
std::shared_ptr<MemTracker>* tracker,
const std::shared_ptr<MemTracker>& parent = std::shared_ptr<MemTracker>());
// If a global tracker with the specified 'id' exists in the tree, returns a
// shared_ptr to that instance. Otherwise, creates a new MemTracker with the
// specified byte_limit and id, parented to the root MemTracker.
//
// Note: this function will enforce that 'id' is unique amongst the children
// of the root MemTracker.
static std::shared_ptr<MemTracker> FindOrCreateGlobalTracker(
int64_t byte_limit, const std::string& id);
// Returns a list of all the valid trackers.
static void ListTrackers(std::vector<std::shared_ptr<MemTracker>>* trackers);
// Marshals the tracker tree into 'pb'.
static void TrackersToPb(MemTrackerPB* pb);
// Gets a shared_ptr to the "root" tracker, creating it if necessary.
static std::shared_ptr<MemTracker> GetRootTracker();
// Increases consumption of this tracker and its ancestors by 'bytes'.
void Consume(int64_t bytes);
// Increases consumption of this tracker and its ancestors by 'bytes' only if
// they can all consume 'bytes'. If this brings any of them over, none of them
// are updated.
// Returns true if the try succeeded.
bool TryConsume(int64_t bytes);
// Returns true if this tracker could consume 'bytes' without exceeding its
// limit, false otherwise.
bool CanConsumeNoAncestors(int64_t bytes);
// Decreases consumption of this tracker and its ancestors by 'bytes'.
//
// This will also cause the process to periodically trigger tcmalloc "ReleaseMemory"
// to ensure that memory is released to the OS.
void Release(int64_t bytes);
// Returns true if a valid limit of this tracker or one of its ancestors is
// exceeded.
bool AnyLimitExceeded();
// If this tracker has a limit, checks the limit and attempts to free up some memory if
// the limit is exceeded by calling any added GC functions. Returns true if the limit is
// exceeded after calling the GC functions. Returns false if there is no limit.
bool LimitExceeded() {
return limit_ >= 0 && limit_ < consumption();
}
// Returns the maximum consumption that can be made without exceeding the limit on
// this tracker or any of its parents. Returns int64_t::max() if there are no
// limits and a negative value if any limit is already exceeded.
int64_t SpareCapacity() const;
int64_t limit() const { return limit_; }
bool has_limit() const { return limit_ >= 0; }
const std::string& id() const { return id_; }
// Returns the memory consumed in bytes.
int64_t consumption() const {
return consumption_.current_value();
}
int64_t peak_consumption() const { return consumption_.max_value(); }
// Retrieve the parent tracker, or NULL If one is not set.
std::shared_ptr<MemTracker> parent() const { return parent_; }
// Returns a textual representation of the tracker that is likely (but not
// guaranteed) to be globally unique.
std::string ToString() const;
private:
// byte_limit < 0 means no limit
// 'id' is the label for LogUsage() and web UI.
MemTracker(int64_t byte_limit, const std::string& id, std::shared_ptr<MemTracker> parent);
// Further initializes the tracker.
void Init();
// Adds tracker to child_trackers_.
void AddChildTracker(const std::shared_ptr<MemTracker>& tracker);
// Variant of FindTracker() that must be called with a non-NULL parent.
static bool FindTrackerInternal(
const std::string& id,
std::shared_ptr<MemTracker>* tracker,
const std::shared_ptr<MemTracker>& parent);
// Creates the root tracker.
static void CreateRootTracker();
int64_t limit_;
const std::string id_;
const std::string descr_;
std::shared_ptr<MemTracker> parent_;
HighWaterMark consumption_;
// this tracker plus all of its ancestors
std::vector<MemTracker*> all_trackers_;
// all_trackers_ with valid limits
std::vector<MemTracker*> limit_trackers_;
// All the child trackers of this tracker. Used for error reporting and
// listing only (i.e. updating the consumption of a parent tracker does not
// update that of its children).
mutable Mutex child_trackers_lock_;
std::list<std::weak_ptr<MemTracker>> child_trackers_;
// Iterator into parent_->child_trackers_ for this object. Stored to have O(1)
// remove.
std::list<std::weak_ptr<MemTracker>>::iterator child_tracker_it_;
};
// An std::allocator that manipulates a MemTracker during allocation
// and deallocation.
template<typename T, typename Alloc = std::allocator<T> >
class MemTrackerAllocator : public Alloc {
public:
typedef typename Alloc::pointer pointer;
typedef typename Alloc::const_pointer const_pointer;
typedef typename Alloc::size_type size_type;
explicit MemTrackerAllocator(std::shared_ptr<MemTracker> mem_tracker)
: mem_tracker_(std::move(mem_tracker)) {}
// This constructor is used for rebinding.
template <typename U>
MemTrackerAllocator(const MemTrackerAllocator<U>& allocator)
: Alloc(allocator),
mem_tracker_(allocator.mem_tracker()) {
}
~MemTrackerAllocator() {
}
pointer allocate(size_type n, const_pointer hint = 0) {
// Ideally we'd use TryConsume() here to enforce the tracker's limit.
// However, that means throwing bad_alloc if the limit is exceeded, and
// it's not clear that the rest of Kudu can handle that.
mem_tracker_->Consume(n * sizeof(T));
return Alloc::allocate(n, hint);
}
void deallocate(pointer p, size_type n) {
Alloc::deallocate(p, n);
mem_tracker_->Release(n * sizeof(T));
}
// This allows an allocator<T> to be used for a different type.
template <class U>
struct rebind {
typedef MemTrackerAllocator<U, typename Alloc::template rebind<U>::other> other;
};
const std::shared_ptr<MemTracker>& mem_tracker() const { return mem_tracker_; }
private:
std::shared_ptr<MemTracker> mem_tracker_;
};
// Convenience class that adds memory consumption to a tracker when declared,
// releasing it when the end of scope is reached.
class ScopedTrackedConsumption {
public:
ScopedTrackedConsumption(std::shared_ptr<MemTracker> tracker,
int64_t to_consume)
: tracker_(std::move(tracker)), consumption_(to_consume) {
DCHECK(tracker_);
tracker_->Consume(consumption_);
}
void Reset(int64_t new_consumption) {
// Consume(-x) is the same as Release(x).
tracker_->Consume(new_consumption - consumption_);
consumption_ = new_consumption;
}
~ScopedTrackedConsumption() {
tracker_->Release(consumption_);
}
int64_t consumption() const { return consumption_; }
private:
std::shared_ptr<MemTracker> tracker_;
int64_t consumption_;
};
} // namespace kudu
#endif // KUDU_UTIL_MEM_TRACKER_H