All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.codecentric.spring.boot.chaos.monkey.assaults.MemoryAssault Maven / Gradle / Ivy

/*
 * Copyright 2018 the original author or authors.
 *
 * Licensed 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.
 */

package de.codecentric.spring.boot.chaos.monkey.assaults;

import de.codecentric.spring.boot.chaos.monkey.component.MetricEventPublisher;
import de.codecentric.spring.boot.chaos.monkey.component.MetricType;
import de.codecentric.spring.boot.chaos.monkey.configuration.ChaosMonkeySettings;
import java.util.Vector;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async;

/** @author Benjamin Wilms */
public class MemoryAssault implements ChaosMonkeyRuntimeAssault {

  private static final Logger Logger = LoggerFactory.getLogger(MemoryAssault.class);

  private static final AtomicLong stolenMemory = new AtomicLong(0);

  private final Runtime runtime;

  private final AtomicBoolean inAttack = new AtomicBoolean(false);

  private final ChaosMonkeySettings settings;

  private final MetricEventPublisher metricEventPublisher;

  public MemoryAssault(
      Runtime runtime, ChaosMonkeySettings settings, MetricEventPublisher metricEventPublisher) {
    this.runtime = runtime;
    this.settings = settings;
    this.metricEventPublisher = metricEventPublisher;
  }

  @Override
  public boolean isActive() {
    return settings.getAssaultProperties().isMemoryActive();
  }

  @Override
  @Async
  public void attack() {
    Logger.info("Chaos Monkey - memory assault");

    // metrics
    if (metricEventPublisher != null) {
      metricEventPublisher.publishMetricEvent(MetricType.MEMORY_ASSAULT);
    }

    if (inAttack.compareAndSet(false, true)) {
      try {
        Logger.debug("Detected java version: " + System.getProperty("java.version"));
        eatFreeMemory();
      } finally {
        inAttack.set(false);
      }
    }

    Logger.info("Chaos Monkey - memory assault cleaned up");
  }

  private void eatFreeMemory() {
    @SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
    Vector memoryVector = new Vector<>();

    long stolenMemoryTotal = 0L;

    while (isActive()) {
      // overview of memory methods in java https://stackoverflow.com/a/18375641
      long freeMemory = runtime.freeMemory();
      long usedMemory = runtime.totalMemory() - freeMemory;

      if (cannotAllocateMoreMemory()) {
        Logger.debug("Cannot allocate more memory");
        break;
      }

      Logger.debug("Used memory in bytes: " + usedMemory);

      stolenMemoryTotal = stealMemory(memoryVector, stolenMemoryTotal, getBytesToSteal());
      waitUntil(settings.getAssaultProperties().getMemoryMillisecondsWaitNextIncrease());
    }

    // Hold memory level and cleanUp after, only if experiment is running
    if (isActive()) {
      Logger.info("Memory fill reached, now sleeping and holding memory");
      waitUntil(settings.getAssaultProperties().getMemoryMillisecondsHoldFilledMemory());
    }

    // clean Vector
    memoryVector.clear();
    // quickly run gc for reuse
    runtime.gc();

    long stolenAfterComplete = MemoryAssault.stolenMemory.addAndGet(-stolenMemoryTotal);
    metricEventPublisher.publishMetricEvent(
        MetricType.MEMORY_ASSAULT_MEMORY_STOLEN, stolenAfterComplete);
  }

  private boolean cannotAllocateMoreMemory() {
    double limit =
        runtime.maxMemory() * settings.getAssaultProperties().getMemoryFillTargetFraction();
    return runtime.totalMemory() > Math.floor(limit);
  }

  private int getBytesToSteal() {
    int amount =
        (int)
            (runtime.freeMemory()
                * settings.getAssaultProperties().getMemoryFillIncrementFraction());
    boolean isJava8 = System.getProperty("java.version").startsWith("1.8");

    // TODO: Check again when JAVA 8 can be dropped.
    // seems filling more than 256 MB per slice is bad on java 8
    // we keep running into heap errors and other OOMs.
    return isJava8 ? Math.min(SizeConverter.toBytes(256), amount) : amount;
  }

  private long stealMemory(Vector memoryVector, long stolenMemoryTotal, int bytesToSteal) {
    memoryVector.add(createDirtyMemorySlice(bytesToSteal));

    stolenMemoryTotal += bytesToSteal;
    long newStolenTotal = MemoryAssault.stolenMemory.addAndGet(bytesToSteal);
    metricEventPublisher.publishMetricEvent(
        MetricType.MEMORY_ASSAULT_MEMORY_STOLEN, newStolenTotal);
    Logger.debug(
        "Chaos Monkey - memory assault increase, free memory: "
            + SizeConverter.toMegabytes(runtime.freeMemory()));

    return stolenMemoryTotal;
  }

  private byte[] createDirtyMemorySlice(int size) {
    byte[] b = new byte[size];
    for (int idx = 0; idx < size; idx += 4096) { // 4096
      // is commonly the size of a memory page, forcing a commit
      b[idx] = 19;
    }

    return b;
  }

  private void waitUntil(int ms) {
    final long startNano = System.nanoTime();
    long now = startNano;
    while (startNano + TimeUnit.MILLISECONDS.toNanos(ms) > now && isActive()) {
      try {
        long elapsed = TimeUnit.NANOSECONDS.toMillis(startNano - now);
        Thread.sleep(Math.min(100, ms - elapsed));
        now = System.nanoTime();
      } catch (InterruptedException e) {
        break;
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy