### Auto-Balloon ###############################################################

### Constants
# If the percentage of host free memory drops below this value
# then we will consider the host to be under memory pressure
(defvar pressure_threshold 0.20)

# If pressure threshold drops below this level, then the pressure
# is critical and more aggressive ballooning will be employed.
(defvar pressure_critical 0.05)

# This is the minimum percentage of free memory that an unconstrained
# guest would like to maintain
(defvar min_guest_free_percent 0.20)

# Don't change a guest's memory by more than this percent of total memory
(defvar max_balloon_change_percent 0.05)

# Only ballooning operations that change the balloon by this percentage
# of current guest memory should be undertaken to avoid overhead
(defvar min_balloon_change_percent 0.0025)

### Helper functions
# Check if the proposed new balloon value is a large-enough
# change to justify a balloon operation.  This prevents us from
# introducing overhead through lots of small ballooning operations
(def change_big_enough (guest new_val)
{
    (if (> (abs (- new_val guest.balloon_cur))
           (* min_balloon_change_percent guest.balloon_cur))
        1 0)
})

(def shrink_guest (guest)
{
    # Determine the degree of host memory pressure
    (if (<= host_free_percent pressure_critical)
        # Pressure is critical:
        #   Force guest to swap by making free memory negative
        (defvar guest_free_percent (+ -0.05 host_free_percent))
        # Normal pressure situation
        #   Scale the guest free memory back according to host pressure
        (defvar guest_free_percent (* min_guest_free_percent
                                    (/ host_free_percent pressure_threshold))))

    # Given current conditions, determine the ideal guest memory size
    (defvar guest_used_mem (- (guest.StatAvg "balloon_cur")
                              (guest.StatAvg "mem_unused")))
    (defvar balloon_min (max guest.balloon_min (+ guest_used_mem
                           (* guest_free_percent guest.balloon_cur))))
    # But do not change it too fast
    (defvar balloon_size (* guest.balloon_cur
                            (- 1 max_balloon_change_percent)))
    (if (< balloon_size balloon_min)
        (set balloon_size balloon_min)
        0)
    # Set the new target for the BalloonController.  Only set it if the
    # value makes sense and is a large enough change to be worth it.
    (if (and (<= balloon_size guest.balloon_cur)
            (change_big_enough guest balloon_size))
        (guest.Control "balloon_target" balloon_size)
        0)
})

(def grow_guest (guest)
{
    # There is only work to do if the guest is ballooned
    (if (< guest.balloon_cur guest.balloon_max) {
        # Minimally, increase so the guest has its desired free memory
        (defvar guest_used_mem (- (guest.StatAvg "balloon_cur")
                                  (guest.StatAvg "mem_unused")))
        (defvar balloon_min (max guest.balloon_min (+ guest_used_mem
                               (* min_guest_free_percent guest.balloon_cur))))
        # Otherwise, increase according to the max balloon change
        (defvar balloon_size (* guest.balloon_cur
                                (+ 1 max_balloon_change_percent)))

        # Determine the new target for the BalloonController.  Only set
        # if the value is a large enough for the change to be worth it.
        (if (< balloon_size balloon_min)
            (set balloon_size balloon_min) 0)
        (if (> balloon_size guest.balloon_max)
            (set balloon_size guest.balloon_max) 0)
        (if (change_big_enough guest balloon_size)
            (guest.Control "balloon_target" balloon_size) 0)
    } 0)
})


### Main script
# Methodology: The goal is to shrink all guests fairly and by an amount
# scaled to the level of host memory pressure.  If the host is under
# severe pressure, scale back more aggressively.  We don't yet handle
# symptoms of over-ballooning guests or try to balloon idle guests more
# aggressively.  When the host is not under memory pressure, slowly
# deflate the balloons.

(defvar host_free_percent (/ (Host.StatAvg "mem_free") Host.mem_available))
(if (< host_free_percent pressure_threshold)
    (with Guests guest (shrink_guest guest))
    (with Guests guest (grow_guest guest)))
