From e3b79a167faeeabe2685875b6945f69d6f41af24 Mon Sep 17 00:00:00 2001 From: Julien Message Date: Mon, 1 Dec 2025 16:02:12 +0100 Subject: [PATCH 1/2] refactor: separate responsibilities in CarbonEstimator --- .../domain/calculator/model/Component.java | 18 +++++++++++ .../calculator/model/ConfiguredSetting.java | 30 +++++++++++++++++++ .../calculator/model/emu/SettingName.java | 11 ++++++- .../domain/estimator/CarbonEstimator.java | 30 ++----------------- .../calculator/model/ComponentTest.java | 29 ++++++++++++++++++ 5 files changed, 89 insertions(+), 29 deletions(-) diff --git a/domain/src/main/java/fr/ippon/iroco2/domain/calculator/model/Component.java b/domain/src/main/java/fr/ippon/iroco2/domain/calculator/model/Component.java index 517e626..25482fb 100644 --- a/domain/src/main/java/fr/ippon/iroco2/domain/calculator/model/Component.java +++ b/domain/src/main/java/fr/ippon/iroco2/domain/calculator/model/Component.java @@ -23,11 +23,14 @@ import lombok.Getter; import lombok.Setter; +import java.time.Duration; import java.time.LocalDateTime; import java.util.List; +import java.util.OptionalDouble; import java.util.UUID; import static fr.ippon.iroco2.domain.calculator.model.emu.SettingName.MEMORY_IN_MEGA_BYTE; +import static fr.ippon.iroco2.domain.estimator.TimeConstant.MS_IN_ONE_MONTH; import static java.util.Optional.ofNullable; @Getter @@ -52,6 +55,21 @@ public static Component load(UUID id, UUID infrastructureID, String name, LocalD return new Component(id, infrastructureID, name, lastModificationDate, regionID, service, values); } + /** + * Calculates the estimated monthly uptime for a component based on its configuration values. + * If no valid configuration values are set, the default uptime ratio is considered as 1. + * + * @return the computed monthly uptime as a {@link Duration}, representing the duration of uptime for one month. + */ + public Duration computeEstimatedMonthlyUptime() { + double averageUptimeRatio = configurationValues.stream() + .filter(configurationValue -> configurationValue.configurationSettingName().isUptimeParameter()) + .map(ConfiguredSetting::computeAverageUptimeRatio) + .flatMapToDouble(OptionalDouble::stream) + .reduce(1., (a, b) -> a * b); // standard product. 1 if no valid configuration is set + return Duration.ofMillis((long) (averageUptimeRatio * MS_IN_ONE_MONTH)); + } + public double getMemoryInMegaByte() { return ofNullable(getValue(MEMORY_IN_MEGA_BYTE)) .map(Double::parseDouble) diff --git a/domain/src/main/java/fr/ippon/iroco2/domain/calculator/model/ConfiguredSetting.java b/domain/src/main/java/fr/ippon/iroco2/domain/calculator/model/ConfiguredSetting.java index 1002fbb..6afbe66 100644 --- a/domain/src/main/java/fr/ippon/iroco2/domain/calculator/model/ConfiguredSetting.java +++ b/domain/src/main/java/fr/ippon/iroco2/domain/calculator/model/ConfiguredSetting.java @@ -19,10 +19,40 @@ import fr.ippon.iroco2.domain.calculator.model.emu.SettingName; +import java.util.OptionalDouble; import java.util.UUID; +import static fr.ippon.iroco2.domain.estimator.TimeConstant.AVERAGE_DAYS_PER_MONTH; +import static fr.ippon.iroco2.domain.estimator.TimeConstant.MS_IN_ONE_DAY; +import static fr.ippon.iroco2.domain.estimator.TimeConstant.MS_IN_ONE_MONTH; +import static java.lang.Double.parseDouble; + public record ConfiguredSetting( UUID configurationSettingId, SettingName configurationSettingName, String value) { + + /** + * Computes the average uptime ratio for this setting according to its type and value. + * + * @return an optional containing the uptime ratio corresponding to this setting. + * Empty if the setting is irrelevant to the uptime ration + */ + public OptionalDouble computeAverageUptimeRatio() { + if (!configurationSettingName.isUptimeParameter()) { + return OptionalDouble.empty(); + } + + final double value = parseDouble(this.value); + final double averageUptimeRatio = switch (configurationSettingName) { + case INSTANCE_NUMBER, VOLUME_NUMBER, MONTHLY_INVOCATION_COUNT -> value; + case DAYS_ON_PER_MONTH -> value / AVERAGE_DAYS_PER_MONTH; + case DAILY_USAGE_COUNT -> value * AVERAGE_DAYS_PER_MONTH; + case AVERAGE_EXEC_TIME_IN_MS -> value / MS_IN_ONE_MONTH; + case DAILY_RUNNING_TIME_IN_MS -> value / MS_IN_ONE_DAY; + default -> + throw new IllegalStateException("Unexpected configuration setting: '%s'".formatted(configurationSettingName)); + }; + return OptionalDouble.of(averageUptimeRatio); + } } diff --git a/domain/src/main/java/fr/ippon/iroco2/domain/calculator/model/emu/SettingName.java b/domain/src/main/java/fr/ippon/iroco2/domain/calculator/model/emu/SettingName.java index 6e15f33..9e5df57 100644 --- a/domain/src/main/java/fr/ippon/iroco2/domain/calculator/model/emu/SettingName.java +++ b/domain/src/main/java/fr/ippon/iroco2/domain/calculator/model/emu/SettingName.java @@ -17,7 +17,10 @@ */ package fr.ippon.iroco2.domain.calculator.model.emu; +import java.util.Set; + public enum SettingName { + INSTANCE_NUMBER, INSTANCE_TYPE, STORAGE_IN_MEGA_BYTE, @@ -28,5 +31,11 @@ public enum SettingName { MEMORY_IN_MEGA_BYTE, DAILY_USAGE_COUNT, DAYS_ON_PER_MONTH, - VOLUME_NUMBER + VOLUME_NUMBER; + + private static final Set UPTIME_PARAMETERS = Set.of(INSTANCE_NUMBER, VOLUME_NUMBER, MONTHLY_INVOCATION_COUNT, DAYS_ON_PER_MONTH, DAILY_USAGE_COUNT, AVERAGE_EXEC_TIME_IN_MS, DAILY_RUNNING_TIME_IN_MS); + + public boolean isUptimeParameter() { + return UPTIME_PARAMETERS.contains(this); + } } diff --git a/domain/src/main/java/fr/ippon/iroco2/domain/estimator/CarbonEstimator.java b/domain/src/main/java/fr/ippon/iroco2/domain/estimator/CarbonEstimator.java index 66bcaec..05f59f3 100644 --- a/domain/src/main/java/fr/ippon/iroco2/domain/estimator/CarbonEstimator.java +++ b/domain/src/main/java/fr/ippon/iroco2/domain/estimator/CarbonEstimator.java @@ -18,7 +18,6 @@ package fr.ippon.iroco2.domain.estimator; import fr.ippon.iroco2.domain.calculator.model.Component; -import fr.ippon.iroco2.domain.calculator.model.ConfiguredSetting; import fr.ippon.iroco2.domain.calculator.model.emu.SettingName; import fr.ippon.iroco2.domain.commons.DomainService; import fr.ippon.iroco2.domain.commons.exception.FunctionalException; @@ -46,12 +45,8 @@ import static fr.ippon.iroco2.domain.calculator.model.emu.SettingName.STORAGE_IN_MEGA_BYTE; import static fr.ippon.iroco2.domain.commons.model.PayloadConfiguration.INSTANCE_TYPE; import static fr.ippon.iroco2.domain.commons.model.PayloadConfiguration.S3_STORAGE; -import static fr.ippon.iroco2.domain.estimator.TimeConstant.AVERAGE_DAYS_PER_MONTH; -import static fr.ippon.iroco2.domain.estimator.TimeConstant.MS_IN_ONE_DAY; -import static fr.ippon.iroco2.domain.estimator.TimeConstant.MS_IN_ONE_MONTH; import static fr.ippon.iroco2.domain.estimator.aws.EC2Instance.TDP_TO_POWER_CONSUMPTION_RATIO; import static fr.ippon.iroco2.domain.estimator.model.MemoryConfig.ZERO_MB; -import static java.lang.Integer.parseInt; import static java.math.RoundingMode.UP; @DomainService @@ -61,27 +56,6 @@ public class CarbonEstimator { private final GlobalEnergyMixStorage globalEnergyMixStorage; private final EC2InstanceStorage ec2InstanceStorage; - private static Duration computeDuration(Component component) { - double percentageUptime = 1; - for (ConfiguredSetting configurationValue : component.getConfigurationValues()) { - switch (configurationValue.configurationSettingName()) { - case INSTANCE_NUMBER, VOLUME_NUMBER, MONTHLY_INVOCATION_COUNT -> - percentageUptime *= parseInt(configurationValue.value()); - case DAYS_ON_PER_MONTH -> - percentageUptime *= parseInt(configurationValue.value()) / AVERAGE_DAYS_PER_MONTH; - case DAILY_USAGE_COUNT -> - percentageUptime *= parseInt(configurationValue.value()) * AVERAGE_DAYS_PER_MONTH; - case AVERAGE_EXEC_TIME_IN_MS -> - percentageUptime *= parseInt(configurationValue.value()) / MS_IN_ONE_MONTH; - case DAILY_RUNNING_TIME_IN_MS -> - percentageUptime *= (double) parseInt(configurationValue.value()) / MS_IN_ONE_DAY; - default -> { // not an UpTime parameter - } - } - } - return Duration.ofMillis((long) (percentageUptime * MS_IN_ONE_MONTH)); - } - private static MemoryConfig findMemoryConfig(Component component, SettingName settingName) { return Optional.ofNullable(component.getValue(settingName)) .map(Double::parseDouble) @@ -124,8 +98,8 @@ public int estimateComponent(String countryIsoCode, Component component) throws CPUConfig cpu = findCPU(component); MemoryConfig ram = findMemoryConfig(component, MEMORY_IN_MEGA_BYTE); MemoryConfig disk = findMemoryConfig(component, STORAGE_IN_MEGA_BYTE); - Duration duration = computeDuration(component); - var estimatableServer = getEstimatableServer(component.getValue(SettingName.INSTANCE_TYPE), disk, duration, cpu, ram); + Duration monthlyUptime = component.computeEstimatedMonthlyUptime(); + var estimatableServer = getEstimatableServer(component.getValue(SettingName.INSTANCE_TYPE), disk, monthlyUptime, cpu, ram); return estimateServer(countryIsoCode, estimatableServer); } diff --git a/domain/src/test/java/fr/ippon/iroco2/domain/calculator/model/ComponentTest.java b/domain/src/test/java/fr/ippon/iroco2/domain/calculator/model/ComponentTest.java index 21c3ffd..c4f53b6 100644 --- a/domain/src/test/java/fr/ippon/iroco2/domain/calculator/model/ComponentTest.java +++ b/domain/src/test/java/fr/ippon/iroco2/domain/calculator/model/ComponentTest.java @@ -17,11 +17,15 @@ */ package fr.ippon.iroco2.domain.calculator.model; +import fr.ippon.iroco2.domain.calculator.model.emu.SettingName; import org.junit.jupiter.api.Test; +import java.time.Duration; import java.util.List; import static fr.ippon.iroco2.domain.calculator.model.emu.SettingName.INSTANCE_NUMBER; +import static fr.ippon.iroco2.domain.estimator.TimeConstant.AVERAGE_DAYS_PER_MONTH; +import static fr.ippon.iroco2.domain.estimator.TimeConstant.MS_IN_ONE_MONTH; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -78,4 +82,29 @@ void getValue_should_return_corresponding_value() { //then assertThat(result).isEqualTo("666"); } + + @Test + void testComputeEstimatedMonthlyUptime_WithFilteredValues_ProductCalculation() { + // Given + + // 0 = ignored, 2, 3, 5, 7 = kept + List configurationValues = List.of( + new ConfiguredSetting(null, SettingName.PROCESSOR_ARCHITECTURE, "0"), + new ConfiguredSetting(null, SettingName.MEMORY_IN_MEGA_BYTE, "0"), + new ConfiguredSetting(null, SettingName.INSTANCE_NUMBER, "2"), + new ConfiguredSetting(null, SettingName.VOLUME_NUMBER, "3"), + new ConfiguredSetting(null, SettingName.MONTHLY_INVOCATION_COUNT, "5"), + new ConfiguredSetting(null, SettingName.DAYS_ON_PER_MONTH, "7") + ); + Component component = Component.load( + null, null, "Test", null, null, null, configurationValues + ); + + // When + Duration uptime = component.computeEstimatedMonthlyUptime(); + + // Then + Duration expectedUptime = Duration.ofMillis((long) (210 * MS_IN_ONE_MONTH / AVERAGE_DAYS_PER_MONTH)); // 2 * 3 * 5 * 7 = 210 + assertThat(uptime).isEqualTo(expectedUptime); + } } From f4f09ecd563eaf6800813014c3def71ee2c0c23b Mon Sep 17 00:00:00 2001 From: Julien Message Date: Tue, 2 Dec 2025 15:54:06 +0100 Subject: [PATCH 2/2] fix: repair Jacoco coverage (for domain only) --- .github/workflows/sonarcloud.yml | 7 +-- pom.xml | 79 ++++++++++++++++---------------- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index 5bb33c3..c947340 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -32,16 +32,13 @@ jobs: key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - - name: Build and test (generate JaCoCo) - run: mvn -B -DskipTests=false verify - - name: SonarCloud analyze (push) - non-blocking if: github.event_name != 'pull_request' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: >- - mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar + cd domain && mvn verify && mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=${{ vars.SONAR_ORG }} -Dsonar.projectKey=${{ vars.SONAR_PROJECT_KEY }} @@ -54,7 +51,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: >- - mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar + cd domain && mvn verify && mvn -B org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.host.url=https://sonarcloud.io -Dsonar.organization=${{ vars.SONAR_ORG }} -Dsonar.projectKey=${{ vars.SONAR_PROJECT_KEY }} diff --git a/pom.xml b/pom.xml index 49b732a..42a83ea 100644 --- a/pom.xml +++ b/pom.xml @@ -31,15 +31,6 @@ - - - - org.jacoco - jacoco-maven-plugin - 0.8.12 - - - org.sonarsource.scanner.maven @@ -49,40 +40,50 @@ org.apache.maven.plugins maven-surefire-plugin - 3.5.2 + 3.5.3 + + + org.jacoco + jacoco-maven-plugin + 0.8.13 + + + default-prepare-agent + + prepare-agent + + + + default-report + + report + + + + default-check + + check + + + + + BUNDLE + + + COMPLEXITY + COVEREDRATIO + 0.60 + + + + + + + - - - coverage - - - - org.jacoco - jacoco-maven-plugin - - - prepare-agent - - prepare-agent - - - - report - prepare-package - - report - - - - - - - - -