blob: c786f80fdcb1e608359df11c4db97dcb4c62447b [file]
// 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 IMPALA_RUNTIME_RESERVATION_TRACKER_H
#define IMPALA_RUNTIME_RESERVATION_TRACKER_H
#include <stdint.h>
#include <boost/scoped_ptr.hpp>
#include <boost/thread/locks.hpp>
#include <string>
#include "runtime/bufferpool/reservation-tracker-counters.h"
#include "common/status.h"
#include "util/spinlock.h"
namespace impala {
class DummyProfile;
class MemTracker;
class RuntimeProfile;
/// A tracker for a hierarchy of buffer pool memory reservations, denominated in bytes.
/// A hierarchy of ReservationTrackers provides a mechanism for subdividing buffer pool
/// memory and enforcing upper and lower bounds on memory usage.
///
/// The root of the tracker tree enforces a global maximum, which is distributed among its
/// children. Each tracker in the tree has a 'reservation': the total bytes of buffer pool
/// memory it is entitled to use. The reservation is inclusive of any memory that is
/// already allocated from the reservation, i.e. using a reservation to allocate memory
/// does not subtract from the reservation.
///
/// A reservation can be used directly at the tracker by calling AllocateFrom(), or
/// distributed to children of the tracker for the childrens' reservations. Each tracker
/// in the tree can use up to its reservation without checking parent trackers. To
/// increase its reservation, a tracker must use some of its parent's reservation (and
/// perhaps increase reservations all the way to the root of the tree).
///
/// Each tracker also has a maximum reservation that is enforced. E.g. if the root of the
/// tracker hierarchy is the global tracker for the Impala daemon and the next level of
/// the hierarchy is made up of per-query trackers, then the maximum reservation
/// mechanism can enforce both process-level and query-level limits on reservations.
///
/// Invariants:
/// * A tracker's reservation is at most its reservation limit: reservation <= limit
/// * A tracker's reservation is at least the sum of its childrens' reservations plus
/// the amount of the reservation used directly at this tracker. The difference is
/// the unused reservation:
/// child_reservations + used_reservation + unused_reservation = reservation.
///
/// Thread-safety:
/// All public ReservationTracker methods are thread-safe. If multiple threads
/// concurrently invoke methods on a ReservationTracker, each operation is applied
/// atomically to leave the ReservationTracker in a consistent state. Calling threads
/// are responsible for coordinating to avoid violating any method preconditions,
/// e.g. ensuring that there is sufficient unused reservation before calling AllocateTo().
///
/// Integration with MemTracker hierarchy:
/// TODO: we will remove MemTracker and this integration once all memory is accounted via
/// reservations.
///
/// Each ReservationTracker can optionally have a linked MemTracker. E.g. an exec
/// node's ReservationTracker can be linked with the exec node's MemTracker, so that
/// reservations are included in query memory consumption for the purposes of enforcing
/// memory limits, reporting and logging. The reservation is accounted as consumption
/// against the linked MemTracker and its ancestors because reserved memory is committed.
/// Allocating from a reservation therefore does not change the consumption reflected in
/// the MemTracker hierarchy.
///
/// MemTracker limits are only checked via the topmost link (i.e. the query-level
/// trackers): we require that no MemTrackers below this level have limits.
///
/// We require that the MemTracker hierarchy is consistent with the ReservationTracker
/// hierarchy. I.e. if a ReservationTracker is linked to a MemTracker "A", and its parent
/// is linked to a MemTracker "B", then "B" must be the parent of "A"'.
class ReservationTracker {
public:
ReservationTracker();
virtual ~ReservationTracker();
/// Initializes the root tracker with the given reservation limit in bytes. The initial
/// reservation is 0.
/// if 'profile' is not NULL, the counters defined in ReservationTrackerCounters are
/// added to 'profile'.
void InitRootTracker(RuntimeProfile* profile, int64_t reservation_limit);
/// Initializes a new ReservationTracker with a parent.
/// If 'mem_tracker' is not NULL, reservations for this ReservationTracker and its
/// children will be counted as consumption against 'mem_tracker'.
/// 'reservation_limit' is the maximum reservation for this tracker in bytes.
/// if 'profile' is not NULL, the counters in 'counters_' are added to 'profile'.
void InitChildTracker(RuntimeProfile* profile, ReservationTracker* parent,
MemTracker* mem_tracker, int64_t reservation_limit);
/// If the tracker is initialized, deregister the ReservationTracker from its parent,
/// relinquishing all this tracker's reservation. All of the reservation must be unused
/// and all the tracker's children must be closed before calling this method.
void Close();
/// Request to increase reservation by 'bytes'. The request is either granted in
/// full or not at all. Uses any unused reservation on ancestors and increase
/// ancestors' reservations if needed to fit the increased reservation.
/// Returns true if the reservation increase is granted, or false if not granted.
/// If the reservation is not granted, no modifications are made to the state of
/// any ReservationTrackers.
bool IncreaseReservation(int64_t bytes);
/// Tries to ensure that 'bytes' of unused reservation is available. If not already
/// available, tries to increase the reservation such that the unused reservation is
/// exactly equal to 'bytes'. Uses any unused reservation on ancestors and increase
/// ancestors' reservations if needed to fit the increased reservation.
/// Returns true if the reservation increase was successful or not necessary.
bool IncreaseReservationToFit(int64_t bytes);
/// Decrease tracker's reservation by 'bytes'. This tracker's reservation must be at
/// least 'bytes' before calling this method.
/// TODO: decide on and implement policy for how far to release the reservation up
/// the tree. Currently the reservation is released all the way to the root.
void DecreaseReservation(int64_t bytes);
/// Allocate 'bytes' from the reservation. The tracker must have at least 'bytes'
/// unused reservation before calling this method.
void AllocateFrom(int64_t bytes);
/// Release 'bytes' of previously allocated memory. The used reservation is
/// decreased by 'bytes'. Before the call, the used reservation must be at least
/// 'bytes' before calling this method.
void ReleaseTo(int64_t bytes);
/// Returns the amount of the reservation in bytes.
int64_t GetReservation();
/// Returns the current amount of the reservation used at this tracker, not including
/// reservations of children in bytes.
int64_t GetUsedReservation();
/// Returns the amount of the reservation neither used nor given to childrens'
/// reservations at this tracker in bytes.
int64_t GetUnusedReservation();
/// Returns the total reservations of children in bytes.
int64_t GetChildReservations();
std::string DebugString();
private:
/// Returns the amount of 'reservation_' that is unused.
inline int64_t unused_reservation() const {
return reservation_ - used_reservation_ - child_reservations_;
}
/// Returns the parent's memtracker if 'parent_' is non-NULL, or NULL otherwise.
MemTracker* GetParentMemTracker() const {
return parent_ == NULL ? NULL : parent_->mem_tracker_;
}
/// Initializes 'counters_', storing the counters in 'profile'.
/// If 'profile' is NULL, creates a dummy profile to store the counters.
void InitCounters(RuntimeProfile* profile, int64_t max_reservation);
/// Internal helper for IncreaseReservation(). If 'use_existing_reservation' is true,
/// increase by the minimum amount so that 'bytes' fits in the reservation, otherwise
/// just increase by 'bytes'. If 'is_child_reservation' is true, also increase
/// 'child_reservations_' by 'bytes'.
/// 'lock_' must be held by caller.
bool IncreaseReservationInternalLocked(
int64_t bytes, bool use_existing_reservation, bool is_child_reservation);
/// Update consumption on linked MemTracker. For the topmost link, return false if
/// this failed because it would exceed a memory limit. If there is no linked
/// MemTracker, just returns true.
/// TODO: remove once we account all memory via ReservationTrackers.
bool TryUpdateMemTracker(int64_t reservation_increase);
/// Internal helper for DecreaseReservation(). This behaves the same as
/// DecreaseReservation(), except when 'is_child_reservation' is true it decreases
/// 'child_reservations_' by 'bytes'.
void DecreaseReservationInternal(int64_t bytes, bool is_child_reservation);
/// Same as DecreaseReservationInternal(), but 'lock_' must be held by caller.
void DecreaseReservationInternalLocked(int64_t bytes, bool is_child_reservation);
/// Check the internal consistency of the ReservationTracker and DCHECKs if in an
/// inconsistent state.
/// 'lock_' must be held by caller.
void CheckConsistency() const;
/// Increase or decrease 'used_reservation_' and update profile counters accordingly.
/// 'lock_' must be held by caller.
void UpdateUsedReservation(int64_t delta);
/// Increase or decrease 'reservation_' and update profile counters accordingly.
/// 'lock_' must be held by caller.
void UpdateReservation(int64_t delta);
/// lock_ protects all members. In a hierarchy of trackers, locks must be acquired
/// from the bottom-up.
SpinLock lock_;
/// True if the tracker is initialized.
bool initialized_;
/// A dummy profile to hold the counters in 'counters_' in the case that no profile
/// is provided.
boost::scoped_ptr<DummyProfile> dummy_profile_;
/// The RuntimeProfile counters for this tracker.
/// All non-NULL if 'initialized_' is true.
ReservationTrackerCounters counters_;
/// The parent of this tracker in the hierarchy. Does not change after initialization.
ReservationTracker* parent_;
/// If non-NULL, reservations are counted as memory consumption against this tracker.
/// Does not change after initialization. Not owned.
/// TODO: remove once all memory is accounted via ReservationTrackers.
MemTracker* mem_tracker_;
/// The maximum reservation in bytes that this tracker can have.
int64_t reservation_limit_;
/// This tracker's current reservation in bytes. 'reservation_' <= 'reservation_limit_'.
int64_t reservation_;
/// Total reservation of children in bytes. This is included in 'reservation_'.
/// 'used_reservation_' + 'child_reservations_' <= 'reservation_'.
int64_t child_reservations_;
/// The amount of the reservation currently used by this tracker in bytes.
/// 'used_reservation_' + 'child_reservations_' <= 'reservation_'.
int64_t used_reservation_;
};
}
#endif