Skip to content

Commit

Permalink
Add a metric for diagram generations in the thread pool
Browse files Browse the repository at this point in the history
Signed-off-by: Slimane AMAR <amarsli@gm0winl104.bureau.si.interne>
  • Loading branch information
Slimane AMAR committed Dec 27, 2024
1 parent 53b0714 commit 6e3e4f3
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 52 deletions.
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>

<!-- Runtime dependencies -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <slimane.amar at rte-france.com>
*/
@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 <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return CompletableFuture.supplyAsync(supplier, executorService);
}
}
Original file line number Diff line number Diff line change
@@ -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 <slimane.amar at rte-france.com>
*/
@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())
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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<String> 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<String> voltageLevelsIds, int depth, boolean withGeoData) {
Network network = DiagramUtils.getNetwork(networkUuid, variantId, networkStoreService, PreloadingStrategy.COLLECTION);
List<String> existingVLIds = voltageLevelsIds.stream().filter(vl -> network.getVoltageLevel(vl) != null).toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand All @@ -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
//
Expand Down Expand Up @@ -280,27 +273,9 @@ public ResponseEntity<Collection<String>> 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<String> 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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -70,8 +70,8 @@
* @author Abdelsalem Hedhili <abdelsalem.hedhili at rte-france.com>
* @author Franck Lecuyer <franck.lecuyer at rte-france.com>
*/
@WebMvcTest(SingleLineDiagramController.class)
@ContextConfiguration(classes = {SingleLineDiagramApplication.class})
@SpringBootTest
@AutoConfigureMockMvc
class SingleLineDiagramTest {

@Autowired
Expand Down

0 comments on commit 6e3e4f3

Please sign in to comment.