diff --git a/.github/workflows/update_documentation.yml b/.github/workflows/update_documentation.yml index b3fa2fe2..64739b71 100644 --- a/.github/workflows/update_documentation.yml +++ b/.github/workflows/update_documentation.yml @@ -30,13 +30,13 @@ jobs: GITHUB_TOKEN: ${{ secrets.READ_PACKAGES }} - name: Upload JavaDoc as HTML - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: JavaDoc path: ./target/site/apidocs - name: Upload JavaDoc as JAR - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: JavaDocJar path: ./target/misim-javadoc.jar @@ -50,12 +50,12 @@ jobs: steps: - name: Download JavaDoc Artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: JavaDoc path: ~/docs/ - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/src/main/java/cambio/simulator/entities/patterns/HorizontalPodAutoscalingPolicy.java b/src/main/java/cambio/simulator/entities/patterns/HorizontalPodAutoscalingPolicy.java new file mode 100644 index 00000000..d90a0102 --- /dev/null +++ b/src/main/java/cambio/simulator/entities/patterns/HorizontalPodAutoscalingPolicy.java @@ -0,0 +1,85 @@ +package cambio.simulator.entities.patterns; + +import cambio.simulator.entities.microservice.Microservice; +import cambio.simulator.export.MultiDataPointReporter; +import cambio.simulator.misc.TimeUtil; +import cambio.simulator.parsing.JsonTypeName; +import desmoj.core.simulator.TimeInstant; + +/** + * AN implementation based on Kubernetes + * Horizontal Pod Autoscaler. + * + *
+ * By default, scale-up can only happen if there was no rescaling within the last 3 minutes. Scale-down will wait for 5 + * minutes from the last rescaling. Moreover, any scaling will only be made if: avg(CurrentPodsConsumption) / Target + * drops below 0.9 or increases above 1.1 (10% tolerance) + * + *
+ * TODO Maybe also include via adapter, upscaling/downscaling behavior not 100% as in Kubernetes, e.g. see + * HorizontalPodAutoscalerBehavior Implementation + * In particular, Kubernetes mentions different holdTimes for up- and downscaling, as well as a 30s evaluation + * frequency (MiSim probably evaluates every time unit. Thus, reacts faster.) + */ +@JsonTypeName("hpa") +public class HorizontalPodAutoscalingPolicy implements IAutoscalingPolicy { + private transient MultiDataPointReporter reporter = null; + private double targetUtilization = 0.8; + private int minInstances = 1; + private int maxInstances = Integer.MAX_VALUE; + + /** + * Minimum time an instance has to run before it can be shutdown by down-scaling. + */ + private double holdTime = 120; + private transient TimeInstant lastScaleUp = new TimeInstant(0); + + @Override + public void apply(Microservice owner) { + if (reporter == null) { + reporter = new MultiDataPointReporter(String.format("AS[%s]_", owner.getPlainName()), owner.getModel()); + } + + TimeInstant presentTime = owner.presentTime(); + int currentInstanceCount = owner.getInstancesCount(); + double avg2Target = owner.getAverageRelativeUtilization() / targetUtilization; + // Tolerance area + + int newInstanceCount = (int) Math.ceil(avg2Target * currentInstanceCount); + + newInstanceCount = Math.min(newInstanceCount, maxInstances); + newInstanceCount = Math.max(minInstances, newInstanceCount); + + if (currentInstanceCount < minInstances) { //starts minimum instances + owner.setInstancesCount(minInstances); + reporter.addDatapoint("Decision", presentTime, "Spawn"); + reporter.addDatapoint("InstanceChange", presentTime, minInstances - currentInstanceCount); + } else if (currentInstanceCount > maxInstances) { + owner.setInstancesCount(maxInstances); + reporter.addDatapoint("Decision", presentTime, "Despawn"); + reporter.addDatapoint("InstanceChange", presentTime, maxInstances - currentInstanceCount); + } else if (avg2Target > 0.9 && avg2Target < 1.1) { + // Tolerance area + reporter.addDatapoint("Decision", presentTime, "Hold"); + reporter.addDatapoint("InstanceChange", presentTime, 0); + } else if (avg2Target > 1 && currentInstanceCount < maxInstances) { + owner.scaleToInstancesCount(newInstanceCount); + lastScaleUp = presentTime; + reporter.addDatapoint("Decision", presentTime, "Up"); + reporter.addDatapoint("InstanceChange", presentTime, newInstanceCount - currentInstanceCount); + } else if (avg2Target < 1 && currentInstanceCount > minInstances && TimeUtil.subtract(presentTime, + lastScaleUp).getTimeAsDouble() > holdTime) { + owner.scaleToInstancesCount(newInstanceCount); + lastScaleUp = presentTime; + reporter.addDatapoint("Decision", presentTime, "Down"); + reporter.addDatapoint("InstanceChange", presentTime, newInstanceCount - currentInstanceCount); + } else { + reporter.addDatapoint("Decision", presentTime, "Hold"); + reporter.addDatapoint("InstanceChange", presentTime, 0); + } + + if (owner.getInstancesCount() != currentInstanceCount) { + owner.sendTraceNote(String.format("Changed target instance count to %d", owner.getInstancesCount())); + } + } +} \ No newline at end of file diff --git a/src/main/java/cambio/simulator/entities/patterns/ReactiveAutoscalingPolicy.java b/src/main/java/cambio/simulator/entities/patterns/ReactiveAutoscalingPolicy.java index a57a70a1..d56efef8 100644 --- a/src/main/java/cambio/simulator/entities/patterns/ReactiveAutoscalingPolicy.java +++ b/src/main/java/cambio/simulator/entities/patterns/ReactiveAutoscalingPolicy.java @@ -39,26 +39,27 @@ public void apply(Microservice owner) { if (currentInstanceCount < minInstances) { //starts minimum instances owner.setInstancesCount(minInstances); reporter.addDatapoint("Decision", presentTime, "Spawn"); - reporter.addDatapoint("InstanceChange", presentTime, minInstances-currentInstanceCount); + reporter.addDatapoint("InstanceChange", presentTime, minInstances - currentInstanceCount); } else if (currentInstanceCount > maxInstances) { owner.setInstancesCount(maxInstances); reporter.addDatapoint("Decision", presentTime, "Despawn"); - reporter.addDatapoint("InstanceChange", presentTime, maxInstances-currentInstanceCount); + reporter.addDatapoint("InstanceChange", presentTime, maxInstances - currentInstanceCount); } else if (avg >= upperBound && currentInstanceCount < maxInstances) { double upScalingFactor = avg / (upperBound - 0.01); - int newInstanceCount = Math.min(maxInstances, (int) Math.max(1, Math.ceil(currentInstanceCount * upScalingFactor))); + int newInstanceCount = Math.min(maxInstances, (int) Math.max(1, + Math.ceil(currentInstanceCount * upScalingFactor))); owner.scaleToInstancesCount(newInstanceCount); lastScaleUp = presentTime; reporter.addDatapoint("Decision", presentTime, "Up"); reporter.addDatapoint("InstanceChange", presentTime, newInstanceCount - currentInstanceCount); - } else if (avg <= lowerBound - && currentInstanceCount > minInstances - && TimeUtil.subtract(presentTime, lastScaleUp).getTimeAsDouble() > holdTime) { + } else if (avg <= lowerBound && currentInstanceCount > minInstances && TimeUtil.subtract(presentTime, + lastScaleUp).getTimeAsDouble() > holdTime) { System.out.println(presentTime); System.out.println(lastScaleUp); System.out.println(holdTime); double downScaleFactor = Math.max(0.01, avg) / lowerBound; - int newInstanceCount = Math.max(minInstances, (int) Math.max(1, Math.ceil(currentInstanceCount * downScaleFactor))); + int newInstanceCount = Math.max(minInstances, (int) Math.max(1, + Math.ceil(currentInstanceCount * downScaleFactor))); owner.scaleToInstancesCount(newInstanceCount); lastScaleUp = presentTime; reporter.addDatapoint("Decision", presentTime, "Down");