2023-05-17 23:58:15 +01:00
|
|
|
package stirling.software.SPDF.controller.web;
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2023-08-08 19:55:18 +01:00
|
|
|
import java.time.Duration;
|
|
|
|
import java.time.LocalDateTime;
|
2024-09-13 16:42:38 +01:00
|
|
|
import java.util.*;
|
2023-08-08 19:55:18 +01:00
|
|
|
import java.util.stream.Collectors;
|
2023-05-17 23:58:15 +01:00
|
|
|
|
2023-08-08 19:55:18 +01:00
|
|
|
import org.springframework.http.HttpStatus;
|
|
|
|
import org.springframework.http.ResponseEntity;
|
2023-05-17 23:58:15 +01:00
|
|
|
import org.springframework.web.bind.annotation.GetMapping;
|
|
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
|
import org.springframework.web.bind.annotation.RequestParam;
|
|
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
|
|
|
2023-05-21 17:12:18 +01:00
|
|
|
import io.micrometer.core.instrument.Counter;
|
|
|
|
import io.micrometer.core.instrument.MeterRegistry;
|
|
|
|
import io.swagger.v3.oas.annotations.Operation;
|
2023-06-29 21:51:08 +01:00
|
|
|
import io.swagger.v3.oas.annotations.Parameter;
|
2023-06-25 09:16:32 +01:00
|
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2023-08-08 19:55:18 +01:00
|
|
|
import jakarta.annotation.PostConstruct;
|
2024-09-13 16:42:38 +01:00
|
|
|
import lombok.extern.slf4j.Slf4j;
|
2023-08-08 19:55:18 +01:00
|
|
|
import stirling.software.SPDF.config.StartupApplicationListener;
|
2023-08-26 22:33:23 +01:00
|
|
|
import stirling.software.SPDF.model.ApplicationProperties;
|
2023-05-21 17:12:18 +01:00
|
|
|
|
2023-05-17 23:58:15 +01:00
|
|
|
@RestController
|
2023-12-29 21:05:32 +00:00
|
|
|
@RequestMapping("/api/v1/info")
|
|
|
|
@Tag(name = "Info", description = "Info APIs")
|
2024-09-13 16:42:38 +01:00
|
|
|
@Slf4j
|
2023-05-17 23:58:15 +01:00
|
|
|
public class MetricsController {
|
|
|
|
|
2024-12-24 09:52:53 +00:00
|
|
|
private final ApplicationProperties applicationProperties;
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2023-05-17 23:58:15 +01:00
|
|
|
private final MeterRegistry meterRegistry;
|
|
|
|
|
2023-08-26 22:33:23 +01:00
|
|
|
private boolean metricsEnabled;
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2024-12-24 09:52:53 +00:00
|
|
|
public MetricsController(
|
|
|
|
ApplicationProperties applicationProperties, MeterRegistry meterRegistry) {
|
|
|
|
this.applicationProperties = applicationProperties;
|
|
|
|
this.meterRegistry = meterRegistry;
|
|
|
|
}
|
|
|
|
|
2023-08-08 19:55:18 +01:00
|
|
|
@PostConstruct
|
|
|
|
public void init() {
|
2023-08-26 22:33:23 +01:00
|
|
|
Boolean metricsEnabled = applicationProperties.getMetrics().getEnabled();
|
|
|
|
if (metricsEnabled == null) metricsEnabled = true;
|
|
|
|
this.metricsEnabled = metricsEnabled;
|
2023-08-08 19:55:18 +01:00
|
|
|
}
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2023-05-17 23:58:15 +01:00
|
|
|
@GetMapping("/status")
|
|
|
|
@Operation(
|
|
|
|
summary = "Application status and version",
|
|
|
|
description =
|
|
|
|
"This endpoint returns the status of the application and its version number.")
|
2023-08-08 19:55:18 +01:00
|
|
|
public ResponseEntity<?> getStatus() {
|
2023-08-26 22:33:23 +01:00
|
|
|
if (!metricsEnabled) {
|
2023-08-08 19:55:18 +01:00
|
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
|
|
|
}
|
2023-05-17 23:58:15 +01:00
|
|
|
Map<String, String> status = new HashMap<>();
|
|
|
|
status.put("status", "UP");
|
|
|
|
status.put("version", getClass().getPackage().getImplementationVersion());
|
2023-08-08 19:55:18 +01:00
|
|
|
return ResponseEntity.ok(status);
|
2023-05-17 23:58:15 +01:00
|
|
|
}
|
2023-12-30 19:11:27 +00:00
|
|
|
|
2024-09-13 16:42:38 +01:00
|
|
|
@GetMapping("/load")
|
2023-05-17 23:58:15 +01:00
|
|
|
@Operation(
|
|
|
|
summary = "GET request count",
|
|
|
|
description =
|
2024-09-13 16:42:38 +01:00
|
|
|
"This endpoint returns the total count of GET requests for a specific endpoint or all endpoints.")
|
2023-08-08 19:55:18 +01:00
|
|
|
public ResponseEntity<?> getPageLoads(
|
|
|
|
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
|
|
|
|
Optional<String> endpoint) {
|
2023-08-26 22:33:23 +01:00
|
|
|
if (!metricsEnabled) {
|
2023-08-08 19:55:18 +01:00
|
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
|
|
|
}
|
|
|
|
try {
|
2024-09-13 16:42:38 +01:00
|
|
|
double count = getRequestCount("GET", endpoint);
|
|
|
|
return ResponseEntity.ok(count);
|
|
|
|
} catch (Exception e) {
|
|
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
|
|
|
}
|
|
|
|
}
|
2023-05-17 23:58:15 +01:00
|
|
|
|
2024-09-13 16:42:38 +01:00
|
|
|
@GetMapping("/load/unique")
|
|
|
|
@Operation(
|
|
|
|
summary = "Unique users count for GET requests",
|
|
|
|
description =
|
|
|
|
"This endpoint returns the count of unique users for GET requests for a specific endpoint or all endpoints.")
|
|
|
|
public ResponseEntity<?> getUniquePageLoads(
|
|
|
|
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
|
|
|
|
Optional<String> endpoint) {
|
|
|
|
if (!metricsEnabled) {
|
|
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
double count = getUniqueUserCount("GET", endpoint);
|
2023-08-08 19:55:18 +01:00
|
|
|
return ResponseEntity.ok(count);
|
|
|
|
} catch (Exception e) {
|
|
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-13 16:42:38 +01:00
|
|
|
@GetMapping("/load/all")
|
2023-08-08 19:55:18 +01:00
|
|
|
@Operation(
|
|
|
|
summary = "GET requests count for all endpoints",
|
|
|
|
description = "This endpoint returns the count of GET requests for each endpoint.")
|
|
|
|
public ResponseEntity<?> getAllEndpointLoads() {
|
2023-08-26 22:33:23 +01:00
|
|
|
if (!metricsEnabled) {
|
2023-08-08 19:55:18 +01:00
|
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
|
|
|
}
|
|
|
|
try {
|
2024-09-13 16:42:38 +01:00
|
|
|
List<EndpointCount> results = getEndpointCounts("GET");
|
2023-08-08 19:55:18 +01:00
|
|
|
return ResponseEntity.ok(results);
|
2023-05-17 23:58:15 +01:00
|
|
|
} catch (Exception e) {
|
2023-08-08 19:55:18 +01:00
|
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
2023-05-17 23:58:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-13 16:42:38 +01:00
|
|
|
@GetMapping("/load/all/unique")
|
|
|
|
@Operation(
|
|
|
|
summary = "Unique users count for GET requests for all endpoints",
|
|
|
|
description =
|
|
|
|
"This endpoint returns the count of unique users for GET requests for each endpoint.")
|
|
|
|
public ResponseEntity<?> getAllUniqueEndpointLoads() {
|
|
|
|
if (!metricsEnabled) {
|
|
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
2024-09-13 16:42:38 +01:00
|
|
|
try {
|
|
|
|
List<EndpointCount> results = getUniqueUserCounts("GET");
|
|
|
|
return ResponseEntity.ok(results);
|
|
|
|
} catch (Exception e) {
|
|
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
2023-12-30 19:11:27 +00:00
|
|
|
}
|
|
|
|
}
|
2023-08-08 19:55:18 +01:00
|
|
|
|
2023-05-17 23:58:15 +01:00
|
|
|
@GetMapping("/requests")
|
|
|
|
@Operation(
|
|
|
|
summary = "POST request count",
|
|
|
|
description =
|
2024-09-13 16:42:38 +01:00
|
|
|
"This endpoint returns the total count of POST requests for a specific endpoint or all endpoints.")
|
2023-08-08 19:55:18 +01:00
|
|
|
public ResponseEntity<?> getTotalRequests(
|
|
|
|
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
|
|
|
|
Optional<String> endpoint) {
|
2023-08-26 22:33:23 +01:00
|
|
|
if (!metricsEnabled) {
|
2023-08-08 19:55:18 +01:00
|
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
|
|
|
}
|
|
|
|
try {
|
2024-09-13 16:42:38 +01:00
|
|
|
double count = getRequestCount("POST", endpoint);
|
|
|
|
return ResponseEntity.ok(count);
|
|
|
|
} catch (Exception e) {
|
|
|
|
return ResponseEntity.ok(-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@GetMapping("/requests/unique")
|
|
|
|
@Operation(
|
|
|
|
summary = "Unique users count for POST requests",
|
|
|
|
description =
|
|
|
|
"This endpoint returns the count of unique users for POST requests for a specific endpoint or all endpoints.")
|
|
|
|
public ResponseEntity<?> getUniqueTotalRequests(
|
|
|
|
@RequestParam(required = false, name = "endpoint") @Parameter(description = "endpoint")
|
|
|
|
Optional<String> endpoint) {
|
|
|
|
if (!metricsEnabled) {
|
|
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
double count = getUniqueUserCount("POST", endpoint);
|
2023-08-08 19:55:18 +01:00
|
|
|
return ResponseEntity.ok(count);
|
|
|
|
} catch (Exception e) {
|
|
|
|
return ResponseEntity.ok(-1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@GetMapping("/requests/all")
|
|
|
|
@Operation(
|
|
|
|
summary = "POST requests count for all endpoints",
|
|
|
|
description = "This endpoint returns the count of POST requests for each endpoint.")
|
|
|
|
public ResponseEntity<?> getAllPostRequests() {
|
2023-08-26 22:33:23 +01:00
|
|
|
if (!metricsEnabled) {
|
2023-08-08 19:55:18 +01:00
|
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
|
|
|
}
|
2023-05-17 23:58:15 +01:00
|
|
|
try {
|
2024-09-13 16:42:38 +01:00
|
|
|
List<EndpointCount> results = getEndpointCounts("POST");
|
|
|
|
return ResponseEntity.ok(results);
|
|
|
|
} catch (Exception e) {
|
|
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
|
|
|
}
|
|
|
|
}
|
2023-08-08 19:55:18 +01:00
|
|
|
|
2024-09-13 16:42:38 +01:00
|
|
|
@GetMapping("/requests/all/unique")
|
|
|
|
@Operation(
|
|
|
|
summary = "Unique users count for POST requests for all endpoints",
|
|
|
|
description =
|
|
|
|
"This endpoint returns the count of unique users for POST requests for each endpoint.")
|
|
|
|
public ResponseEntity<?> getAllUniquePostRequests() {
|
|
|
|
if (!metricsEnabled) {
|
|
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
List<EndpointCount> results = getUniqueUserCounts("POST");
|
2023-08-08 19:55:18 +01:00
|
|
|
return ResponseEntity.ok(results);
|
2023-05-17 23:58:15 +01:00
|
|
|
} catch (Exception e) {
|
2023-08-08 19:55:18 +01:00
|
|
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
2023-05-17 23:58:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-13 16:42:38 +01:00
|
|
|
private double getRequestCount(String method, Optional<String> endpoint) {
|
|
|
|
log.info(
|
|
|
|
"Getting request count for method: {}, endpoint: {}",
|
|
|
|
method,
|
|
|
|
endpoint.orElse("all"));
|
|
|
|
double count =
|
|
|
|
meterRegistry.find("http.requests").tag("method", method).counters().stream()
|
|
|
|
.filter(
|
|
|
|
counter ->
|
|
|
|
!endpoint.isPresent()
|
|
|
|
|| endpoint.get()
|
|
|
|
.equals(counter.getId().getTag("uri")))
|
|
|
|
.mapToDouble(Counter::count)
|
|
|
|
.sum();
|
|
|
|
log.info("Request count: {}", count);
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
private List<EndpointCount> getEndpointCounts(String method) {
|
|
|
|
log.info("Getting endpoint counts for method: {}", method);
|
|
|
|
Map<String, Double> counts = new HashMap<>();
|
|
|
|
meterRegistry
|
|
|
|
.find("http.requests")
|
|
|
|
.tag("method", method)
|
|
|
|
.counters()
|
|
|
|
.forEach(
|
|
|
|
counter -> {
|
|
|
|
String uri = counter.getId().getTag("uri");
|
|
|
|
counts.merge(uri, counter.count(), Double::sum);
|
|
|
|
});
|
|
|
|
List<EndpointCount> result =
|
|
|
|
counts.entrySet().stream()
|
|
|
|
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue()))
|
|
|
|
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
log.info("Found {} endpoints with counts", result.size());
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
private double getUniqueUserCount(String method, Optional<String> endpoint) {
|
|
|
|
log.info(
|
|
|
|
"Getting unique user count for method: {}, endpoint: {}",
|
|
|
|
method,
|
|
|
|
endpoint.orElse("all"));
|
|
|
|
Set<String> uniqueUsers = new HashSet<>();
|
|
|
|
meterRegistry.find("http.requests").tag("method", method).counters().stream()
|
|
|
|
.filter(
|
|
|
|
counter ->
|
|
|
|
!endpoint.isPresent()
|
|
|
|
|| endpoint.get().equals(counter.getId().getTag("uri")))
|
|
|
|
.forEach(
|
|
|
|
counter -> {
|
|
|
|
String session = counter.getId().getTag("session");
|
|
|
|
if (session != null) {
|
|
|
|
uniqueUsers.add(session);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
log.info("Unique user count: {}", uniqueUsers.size());
|
|
|
|
return uniqueUsers.size();
|
|
|
|
}
|
|
|
|
|
|
|
|
private List<EndpointCount> getUniqueUserCounts(String method) {
|
|
|
|
log.info("Getting unique user counts for method: {}", method);
|
|
|
|
Map<String, Set<String>> uniqueUsers = new HashMap<>();
|
|
|
|
meterRegistry
|
|
|
|
.find("http.requests")
|
|
|
|
.tag("method", method)
|
|
|
|
.counters()
|
|
|
|
.forEach(
|
|
|
|
counter -> {
|
|
|
|
String uri = counter.getId().getTag("uri");
|
|
|
|
String session = counter.getId().getTag("session");
|
|
|
|
if (uri != null && session != null) {
|
|
|
|
uniqueUsers.computeIfAbsent(uri, k -> new HashSet<>()).add(session);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
List<EndpointCount> result =
|
|
|
|
uniqueUsers.entrySet().stream()
|
|
|
|
.map(entry -> new EndpointCount(entry.getKey(), entry.getValue().size()))
|
|
|
|
.sorted(Comparator.comparing(EndpointCount::getCount).reversed())
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
log.info("Found {} endpoints with unique user counts", result.size());
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-12-24 09:52:53 +00:00
|
|
|
@GetMapping("/uptime")
|
|
|
|
public ResponseEntity<?> getUptime() {
|
|
|
|
if (!metricsEnabled) {
|
|
|
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("This endpoint is disabled.");
|
|
|
|
}
|
|
|
|
LocalDateTime now = LocalDateTime.now();
|
|
|
|
Duration uptime = Duration.between(StartupApplicationListener.startTime, now);
|
|
|
|
return ResponseEntity.ok(formatDuration(uptime));
|
|
|
|
}
|
|
|
|
|
|
|
|
private String formatDuration(Duration duration) {
|
|
|
|
long days = duration.toDays();
|
|
|
|
long hours = duration.toHoursPart();
|
|
|
|
long minutes = duration.toMinutesPart();
|
|
|
|
long seconds = duration.toSecondsPart();
|
|
|
|
return String.format("%dd %dh %dm %ds", days, hours, minutes, seconds);
|
|
|
|
}
|
|
|
|
|
2024-09-13 16:42:38 +01:00
|
|
|
public static class EndpointCount {
|
2024-12-24 09:52:53 +00:00
|
|
|
|
2024-09-13 16:42:38 +01:00
|
|
|
private String endpoint;
|
2024-12-24 09:52:53 +00:00
|
|
|
|
2024-09-13 16:42:38 +01:00
|
|
|
private double count;
|
|
|
|
|
|
|
|
public EndpointCount(String endpoint, double count) {
|
|
|
|
this.endpoint = endpoint;
|
|
|
|
this.count = count;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getEndpoint() {
|
|
|
|
return endpoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setEndpoint(String endpoint) {
|
|
|
|
this.endpoint = endpoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
public double getCount() {
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void setCount(double count) {
|
|
|
|
this.count = count;
|
|
|
|
}
|
|
|
|
}
|
2023-05-17 23:58:15 +01:00
|
|
|
}
|