#!/bin/sh
# =================================================================
# Detect whether running in a container and set appropriate options
# for limiting Java VM resources
#
# Usage: JAVA_OPTIONS="$(java-default-options.sh)"

# Env Vars respected:

# JAVA_OPTIONS: Checked for already set options
# JAVA_INITIAL_MEM_RATIO: Ratio of maximum memory to use for initial heap size
#                         (i.e. -Xms).  Defaults to 100 (i.e -Xms=-Xmx).
# JAVA_MAX_INITIAL_MEM: The maximum value of the initial heap size, defaults to 4G.
# JAVA_MAX_MEM_RATIO: Ratio use to calculate a default maximum Memory, in percent.
#                     E.g. the default value "50" implies that 50% of the Memory
#                     given to the container is used as the maximum heap memory with
#                     '-Xmx'. It is a heuristic and should be better backed up with real
#                     experiments and measurements.
#                     For a good overviews what tuning options are available -->
#                             https://youtu.be/Vt4G-pHXfs4
#                             https://www.youtube.com/watch?v=w1rZOY5gbvk
#                             https://vimeo.com/album/4133413/video/181900266
# Also note that heap is only a small portion of the memory used by a JVM. There are lot
# of other memory areas (metadata, thread, code cache, ...) which addes to the overall
# size. There is no easy solution for this, 50% seems to be are reasonable compromise.
# However, when your container gets killed because of an OOM, then you should tune
# the absolute values
#

__DEFAULT_JAVA_MAX_MEM_RATIO=50
__DEFAULT_JAVA_INITIAL_MEM_RATIO=25
__DEFAULT_JAVA_MAX_INITIAL_MEM=4096

initialize_container_limits() {
  # we can't run without limits
  source /opt/run-java/container-limits
}

# Check for memory options and calculate a sane default if not given
max_memory() {
  # Check whether -Xmx is already given in JAVA_OPTIONS. Then we dont
  # do anything here
  if echo "${JAVA_OPTIONS}" | grep -q -- "-Xmx"; then
    return
  fi

  # Check if explicitely disabled
  if [ "x$JAVA_MAX_MEM_RATIO" = "x0" ]; then
    return
  fi

  # Check for the 'real memory size' and caluclate mx from a ratio
  # given (default is 50%)
  if [ "x$CONTAINER_MAX_MEMORY" != x ]; then
    local max_mem="${CONTAINER_MAX_MEMORY}"
    local ratio=${JAVA_MAX_MEM_RATIO:-${__DEFAULT_JAVA_MAX_MEM_RATIO}}
    local mx=$(echo "${max_mem} ${ratio} 1048576" | awk '{printf "%d\n" , ($1*$2)/(100*$3) + 0.5}')
    echo "-Xmx${mx}m"
  fi
}

# Check for memory options and calculate a sane default if not given
initial_memory() {
  # Check whether -Xms is already given in JAVA_OPTIONS. Then we dont
  # do anything here
  if echo "${JAVA_OPTIONS}" | grep -q -- "-Xms"; then
    return
  fi

  # Check if explicitely disabled
  if [ "x$JAVA_INITIAL_MEM_RATIO" = "x0" ]; then
    return
  fi

  # Check for the 'real memory size' and calculate ms from a ratio
  # given (default is 100%)
  if [ "x$CONTAINER_MAX_MEMORY" != x ]; then
    local max_mem="${CONTAINER_MAX_MEMORY}"
    local max_ratio=${JAVA_MAX_MEM_RATIO:-${__DEFAULT_JAVA_MAX_MEM_RATIO}}
    local initial_ratio=${JAVA_INITIAL_MEM_RATIO:-${__DEFAULT_JAVA_INITIAL_MEM_RATIO}}
    local ms=$(echo "${max_mem} ${max_ratio} ${initial_ratio} 1048576" | awk '{printf "%d\n" , ($1*(($2*$3)/10000))/$4 + 0.5}')
    local max_initial_memory=${JAVA_MAX_INITIAL_MEM:-${__DEFAULT_JAVA_MAX_INITIAL_MEM}}
    if [ "${ms}" -lt "${max_initial_memory}" ] ; then
      echo "-Xms${ms}m"
    else
      echo "-Xms${max_initial_memory}m"
    fi
  fi
}

# Switch on diagnostics except when switched off
diagnostics() {
  if [ "x$JAVA_DIAGNOSTICS" != "x" ]; then
    echo "-XX:NativeMemoryTracking=summary -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UnlockDiagnosticVMOptions"
  fi
}

cpu_core_tunning() {
  # If both are set rely on JAVA_CORE_LIMIT limited to CONTAINER_CORE_LIMIT
  # If none are set, just return.
  local core_limit=${JAVA_CORE_LIMIT:-${CONTAINER_CORE_LIMIT}}
  if [ "x$core_limit" = "x0" -o "x$core_limit" = "x" ]; then
    return
    else
        if [ "x$CONTAINER_CORE_LIMIT" != "x" ] && [ $core_limit -gt $CONTAINER_CORE_LIMIT ]; then
                core_limit=$CONTAINER_CORE_LIMIT
        fi
         echo "-XX:ParallelGCThreads=${core_limit} " \
              "-Djava.util.concurrent.ForkJoinPool.common.parallelism=${core_limit} "\
              "-XX:CICompilerCount=2"
  fi
}

gc_config() {
  local minHeapFreeRatio=${GC_MIN_HEAP_FREE_RATIO:-10}
  local maxHeapFreeRatio=${GC_MAX_HEAP_FREE_RATIO:-20}
  local timeRatio=${GC_TIME_RATIO:-4}
  local adaptiveSizePolicyWeight=${GC_ADAPTIVE_SIZE_POLICY_WEIGHT:-90}
  local maxMetaspaceSize=${GC_MAX_METASPACE_SIZE:-100}

  echo "-XX:+UnlockExperimentalVMOptions " \
       "-XX:+UseCGroupMemoryLimitForHeap " \
       "-XX:+UseParallelOldGC " \
       "-XX:MinHeapFreeRatio=${minHeapFreeRatio} "\
       "-XX:MaxHeapFreeRatio=${maxHeapFreeRatio} "\
       "-XX:GCTimeRatio=${timeRatio} "\
       "-XX:AdaptiveSizePolicyWeight=${adaptiveSizePolicyWeight} "\
       "-XX:MaxMetaspaceSize=${maxMetaspaceSize}m"
}

error_handling() {
  echo "-XX:+ExitOnOutOfMemoryError"
}

initialize_container_limits > /dev/null

## Echo options, trimming trailing and multiple spaces
echo "$(initial_memory) $(max_memory) $(gc_config) $(diagnostics) $(cpu_core_tunning) $(error_handling)" | awk '$1=$1'
