From 6e3e4f3da4bd518c6027de625db902890a6ddf47 Mon Sep 17 00:00:00 2001 From: Slimane AMAR Date: Fri, 27 Dec 2024 15:11:43 +0100 Subject: [PATCH] Add a metric for diagram generations in the thread pool Signed-off-by: Slimane AMAR --- pom.xml | 4 ++ .../DiagramGenerationExecutionService.java | 40 +++++++++++++ .../sld/server/DiagramGenerationObserver.java | 42 ++++++++++++++ .../sld/server/NetworkAreaDiagramService.java | 41 ++++++++++--- .../server/SingleLineDiagramController.java | 57 ++++++------------- .../sld/server/SingleLineDiagramTest.java | 8 +-- 6 files changed, 140 insertions(+), 52 deletions(-) create mode 100644 src/main/java/com/powsybl/sld/server/DiagramGenerationExecutionService.java create mode 100644 src/main/java/com/powsybl/sld/server/DiagramGenerationObserver.java diff --git a/pom.xml b/pom.xml index 6e5477c..e5fd24d 100644 --- a/pom.xml +++ b/pom.xml @@ -130,6 +130,10 @@ org.springdoc springdoc-openapi-starter-webmvc-ui + + io.micrometer + micrometer-core + diff --git a/src/main/java/com/powsybl/sld/server/DiagramGenerationExecutionService.java b/src/main/java/com/powsybl/sld/server/DiagramGenerationExecutionService.java new file mode 100644 index 0000000..0aecaed --- /dev/null +++ b/src/main/java/com/powsybl/sld/server/DiagramGenerationExecutionService.java @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package com.powsybl.sld.server; + +import jakarta.annotation.PreDestroy; +import lombok.NonNull; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.concurrent.*; +import java.util.function.Supplier; + +/** + * @author Slimane Amar + */ +@Service +public class DiagramGenerationExecutionService { + + private ThreadPoolExecutor executorService; + + public DiagramGenerationExecutionService(@Value("${max-concurrent-nad-generations}") int maxConcurrentNadGenerations, + @NonNull DiagramGenerationObserver diagramGenerationObserver) { + executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(maxConcurrentNadGenerations); + diagramGenerationObserver.createThreadPoolMetric(executorService); + } + + @PreDestroy + private void preDestroy() { + executorService.shutdown(); + } + + public CompletableFuture supplyAsync(Supplier supplier) { + return CompletableFuture.supplyAsync(supplier, executorService); + } +} diff --git a/src/main/java/com/powsybl/sld/server/DiagramGenerationObserver.java b/src/main/java/com/powsybl/sld/server/DiagramGenerationObserver.java new file mode 100644 index 0000000..0e53675 --- /dev/null +++ b/src/main/java/com/powsybl/sld/server/DiagramGenerationObserver.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package com.powsybl.sld.server; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.MultiGauge; +import io.micrometer.core.instrument.Tags; +import lombok.NonNull; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * @author Slimane Amar + */ +@Service +public class DiagramGenerationObserver { + private static final String OBSERVATION_PREFIX = "app.diagram."; + private static final String TYPE_TAG_NAME = "type"; + private static final String TYPE_TAG_VALUE_CURRENT = "current"; + private static final String TYPE_TAG_VALUE_PENDING = "pending"; + private static final String POOL_METER_NAME = OBSERVATION_PREFIX + "tasks.pool"; + + private final MeterRegistry meterRegistry; + + public DiagramGenerationObserver(@NonNull MeterRegistry meterRegistry) { + this.meterRegistry = meterRegistry; + } + + public void createThreadPoolMetric(ThreadPoolExecutor threadPoolExecutor) { + MultiGauge multiGauge = MultiGauge.builder(POOL_METER_NAME).description("The number of diagram generations (tasks) in the thread pool").register(meterRegistry); + multiGauge.register(List.of( + MultiGauge.Row.of(Tags.of(TYPE_TAG_NAME, TYPE_TAG_VALUE_CURRENT), () -> threadPoolExecutor.getActiveCount()), + MultiGauge.Row.of(Tags.of(TYPE_TAG_NAME, TYPE_TAG_VALUE_PENDING), () -> threadPoolExecutor.getQueue().size()) + )); + } +} diff --git a/src/main/java/com/powsybl/sld/server/NetworkAreaDiagramService.java b/src/main/java/com/powsybl/sld/server/NetworkAreaDiagramService.java index eb9dc77..d64971e 100644 --- a/src/main/java/com/powsybl/sld/server/NetworkAreaDiagramService.java +++ b/src/main/java/com/powsybl/sld/server/NetworkAreaDiagramService.java @@ -6,7 +6,10 @@ */ package com.powsybl.sld.server; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.util.RawValue; +import com.powsybl.commons.PowsyblException; import com.powsybl.iidm.network.Network; import com.powsybl.iidm.network.Substation; import com.powsybl.iidm.network.VoltageLevel; @@ -28,7 +31,6 @@ import com.powsybl.sld.server.dto.VoltageLevelInfos; import com.powsybl.sld.server.utils.DiagramUtils; import com.powsybl.sld.server.utils.GeoDataUtils; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.ComponentScan; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -46,15 +48,40 @@ @ComponentScan(basePackageClasses = {NetworkStoreService.class}) @Service class NetworkAreaDiagramService { - @Autowired - private NetworkStoreService networkStoreService; - - @Autowired - private GeoDataService geoDataService; - private static final int SCALING_FACTOR = 450000; private static final double RADIUS_FACTOR = 300; + static final String SVG_TAG = "svg"; + static final String METADATA = "metadata"; + static final String ADDITIONAL_METADATA = "additionalMetadata"; + + private final NetworkStoreService networkStoreService; + private final GeoDataService geoDataService; + + private final ObjectMapper objectMapper; + + public NetworkAreaDiagramService(NetworkStoreService networkStoreService, GeoDataService geoDataService, ObjectMapper objectMapper) { + this.networkStoreService = networkStoreService; + this.geoDataService = geoDataService; + this.objectMapper = objectMapper; + } + + public String getNetworkAreaDiagramSvg(UUID networkUuid, String variantId, List voltageLevelsIds, int depth, boolean withGeoData) { + try { + SvgAndMetadata svgAndMetadata = generateNetworkAreaDiagramSvg(networkUuid, variantId, voltageLevelsIds, depth, withGeoData); + String svg = svgAndMetadata.getSvg(); + String metadata = svgAndMetadata.getMetadata(); + Object additionalMetadata = svgAndMetadata.getAdditionalMetadata(); + return objectMapper.writeValueAsString( + objectMapper.createObjectNode() + .put(SVG_TAG, svg) + .putRawValue(METADATA, new RawValue(metadata)) + .putPOJO(ADDITIONAL_METADATA, additionalMetadata)); + } catch (JsonProcessingException e) { + throw new PowsyblException("Failed to parse JSON response", e); + } + } + public SvgAndMetadata generateNetworkAreaDiagramSvg(UUID networkUuid, String variantId, List voltageLevelsIds, int depth, boolean withGeoData) { Network network = DiagramUtils.getNetwork(networkUuid, variantId, networkStoreService, PreloadingStrategy.COLLECTION); List existingVLIds = voltageLevelsIds.stream().filter(vl -> network.getVoltageLevel(vl) != null).toList(); diff --git a/src/main/java/com/powsybl/sld/server/SingleLineDiagramController.java b/src/main/java/com/powsybl/sld/server/SingleLineDiagramController.java index 6cf93ee..3cecb12 100644 --- a/src/main/java/com/powsybl/sld/server/SingleLineDiagramController.java +++ b/src/main/java/com/powsybl/sld/server/SingleLineDiagramController.java @@ -9,20 +9,16 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.util.RawValue; -import com.powsybl.commons.PowsyblException; import com.powsybl.sld.server.dto.SvgAndMetadata; -import com.powsybl.sld.server.utils.SldDisplayMode; import com.powsybl.sld.server.utils.SingleLineDiagramParameters; +import com.powsybl.sld.server.utils.SldDisplayMode; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.PreDestroy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.ComponentScan; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; @@ -31,11 +27,9 @@ import java.util.Collection; import java.util.List; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import static com.powsybl.sld.server.NetworkAreaDiagramService.*; import static com.powsybl.ws.commons.LogUtils.sanitizeParam; /** @@ -53,21 +47,20 @@ public class SingleLineDiagramController { static final String IMAGE_SVG_PLUS_XML = "image/svg+xml"; static final String HORIZONTAL = "horizontal"; - static final String SVG_TAG = "svg"; - static final String METADATA = "metadata"; - static final String ADDITIONAL_METADATA = "additionalMetadata"; - private final ExecutorService executorService; + private final SingleLineDiagramService singleLineDiagramService; - public SingleLineDiagramController(@Value("${max-concurrent-nad-generations}") int maxConcurrentNadGenerations) { - this.executorService = Executors.newFixedThreadPool(maxConcurrentNadGenerations); - } + private final NetworkAreaDiagramService networkAreaDiagramService; - @Autowired - private SingleLineDiagramService singleLineDiagramService; + private final DiagramGenerationExecutionService diagramExecutionService; - @Autowired - private NetworkAreaDiagramService networkAreaDiagramService; + public SingleLineDiagramController(SingleLineDiagramService singleLineDiagramService, + NetworkAreaDiagramService networkAreaDiagramService, + DiagramGenerationExecutionService diagramExecutionService) { + this.singleLineDiagramService = singleLineDiagramService; + this.networkAreaDiagramService = networkAreaDiagramService; + this.diagramExecutionService = diagramExecutionService; + } // voltage levels // @@ -280,27 +273,9 @@ public ResponseEntity> getAvailableSvgComponentLibraries() { @Parameter(description = "Variant Id") @RequestParam(name = "variantId", required = false) String variantId, @Parameter(description = "depth") @RequestParam(name = "depth", required = false) int depth, @Parameter(description = "Initialize NAD with Geographical Data") @RequestParam(name = "withGeoData", defaultValue = "true") boolean withGeoData) throws InterruptedException, ExecutionException { - CompletableFuture futureNAD = CompletableFuture.supplyAsync(() -> { - try { - LOGGER.debug("getNetworkAreaDiagramSvg request received with parameter networkUuid = {}, voltageLevelsIds = {}, depth = {}", networkUuid, sanitizeParam(voltageLevelsIds.toString()), depth); - SvgAndMetadata svgAndMetadata = networkAreaDiagramService.generateNetworkAreaDiagramSvg(networkUuid, variantId, voltageLevelsIds, depth, withGeoData); - String svg = svgAndMetadata.getSvg(); - String metadata = svgAndMetadata.getMetadata(); - Object additionalMetadata = svgAndMetadata.getAdditionalMetadata(); - return OBJECT_MAPPER.writeValueAsString( - OBJECT_MAPPER.createObjectNode() - .put(SVG_TAG, svg) - .putRawValue(METADATA, new RawValue(metadata)) - .putPOJO(ADDITIONAL_METADATA, additionalMetadata)); - } catch (JsonProcessingException e) { - throw new PowsyblException("Failed to parse JSON response", e); - } - }, executorService); - return futureNAD.get(); - } - - @PreDestroy - private void preDestroy() { - executorService.shutdown(); + LOGGER.debug("getNetworkAreaDiagramSvg request received with parameter networkUuid = {}, voltageLevelsIds = {}, depth = {}", networkUuid, sanitizeParam(voltageLevelsIds.toString()), depth); + return diagramExecutionService + .supplyAsync(() -> networkAreaDiagramService.getNetworkAreaDiagramSvg(networkUuid, variantId, voltageLevelsIds, depth, withGeoData)) + .get(); } } diff --git a/src/test/java/com/powsybl/sld/server/SingleLineDiagramTest.java b/src/test/java/com/powsybl/sld/server/SingleLineDiagramTest.java index 6e289e8..619c31c 100644 --- a/src/test/java/com/powsybl/sld/server/SingleLineDiagramTest.java +++ b/src/test/java/com/powsybl/sld/server/SingleLineDiagramTest.java @@ -38,10 +38,10 @@ import org.junit.jupiter.api.Test; import org.mockito.MockitoAnnotations; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; -import org.springframework.test.context.ContextConfiguration; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; @@ -70,8 +70,8 @@ * @author Abdelsalem Hedhili * @author Franck Lecuyer */ -@WebMvcTest(SingleLineDiagramController.class) -@ContextConfiguration(classes = {SingleLineDiagramApplication.class}) +@SpringBootTest +@AutoConfigureMockMvc class SingleLineDiagramTest { @Autowired