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