mirror of
https://github.com/valitydev/beholder.git
synced 2024-11-06 00:35:19 +00:00
parent
71512a4863
commit
c4ba522e43
@ -4,6 +4,7 @@ import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@ -11,7 +12,7 @@ public class FormDataResponse {
|
||||
|
||||
private FormDataRequest request;
|
||||
|
||||
private FormPerformance formPerformance;
|
||||
private Map<String, Object> performanceMetrics;
|
||||
|
||||
private Browser browser;
|
||||
|
||||
@ -21,18 +22,4 @@ public class FormDataResponse {
|
||||
|
||||
private boolean failed;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public static class FormPerformance {
|
||||
|
||||
private Double requestStartAt;
|
||||
|
||||
private Double responseStartAt;
|
||||
|
||||
private Double responseEndAt;
|
||||
|
||||
private Double domCompletedAt;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,21 +12,9 @@ public enum Metric {
|
||||
MetricUnit.MILLIS.getUnit()
|
||||
),
|
||||
|
||||
WAITING_RESPONSE_DURATION(
|
||||
"beholder_form_waiting_response_duration",
|
||||
"Time between sending request and first received byte of data",
|
||||
MetricUnit.MILLIS.getUnit()
|
||||
),
|
||||
|
||||
RECEIVING_RESPONSE_DURATION(
|
||||
"beholder_form_receiving_response_duration",
|
||||
"Time between receiving first and last byte of data",
|
||||
MetricUnit.MILLIS.getUnit()
|
||||
),
|
||||
|
||||
DOM_COMPLETE_DURATION(
|
||||
"beholder_form_dom_complete_duration",
|
||||
"Time between sending request and fully rendered DOM",
|
||||
PERFORMANCE_TIMING_TEMPLATE(
|
||||
"beholder_form_performance_timing_%s",
|
||||
"%s timing, received from JS navigation and resource timings",
|
||||
MetricUnit.MILLIS.getUnit()
|
||||
),
|
||||
|
||||
|
@ -2,16 +2,23 @@ package dev.vality.beholder.service;
|
||||
|
||||
import dev.vality.beholder.model.FormDataResponse;
|
||||
import dev.vality.beholder.model.Metric;
|
||||
import dev.vality.beholder.model.NetworkLog;
|
||||
import dev.vality.beholder.util.MetricUtil;
|
||||
import io.micrometer.core.instrument.*;
|
||||
import dev.vality.beholder.util.StringUtil;
|
||||
import io.micrometer.core.instrument.Counter;
|
||||
import io.micrometer.core.instrument.MeterRegistry;
|
||||
import io.micrometer.core.instrument.MultiGauge;
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static dev.vality.beholder.model.Metric.PERFORMANCE_TIMING_TEMPLATE;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
@Service
|
||||
@ -20,9 +27,7 @@ public class MetricsService {
|
||||
|
||||
private final MeterRegistry meterRegistry;
|
||||
private final MultiGauge resourcesLoadingTimings;
|
||||
private final MultiGauge formDataWaitingDurationGauges;
|
||||
private final MultiGauge formDataReceivingDuration;
|
||||
private final MultiGauge formDomCompleteDuration;
|
||||
private final Map<String, MultiGauge> performanceTimingGauges;
|
||||
|
||||
public MetricsService(MeterRegistry meterRegistry) {
|
||||
|
||||
@ -33,67 +38,60 @@ public class MetricsService {
|
||||
.baseUnit(Metric.RESOURCE_LOADING_DURATION.getUnit())
|
||||
.register(meterRegistry);
|
||||
|
||||
this.formDataWaitingDurationGauges = MultiGauge.builder(Metric.WAITING_RESPONSE_DURATION.getName())
|
||||
.description(Metric.WAITING_RESPONSE_DURATION.getDescription())
|
||||
.baseUnit(Metric.WAITING_RESPONSE_DURATION.getUnit())
|
||||
.register(meterRegistry);
|
||||
|
||||
this.formDataReceivingDuration = MultiGauge.builder(Metric.RECEIVING_RESPONSE_DURATION.getName())
|
||||
.description(Metric.RECEIVING_RESPONSE_DURATION.getDescription())
|
||||
.baseUnit(Metric.RECEIVING_RESPONSE_DURATION.getUnit())
|
||||
.register(meterRegistry);
|
||||
|
||||
this.formDomCompleteDuration = MultiGauge.builder(Metric.DOM_COMPLETE_DURATION.getName())
|
||||
.description(Metric.DOM_COMPLETE_DURATION.getDescription())
|
||||
.baseUnit(Metric.DOM_COMPLETE_DURATION.getUnit())
|
||||
.register(meterRegistry);
|
||||
|
||||
this.performanceTimingGauges = createPerformanceTimingGauges(meterRegistry);
|
||||
}
|
||||
|
||||
public void updateMetrics(List<FormDataResponse> formDataResponses) {
|
||||
log.debug("Updating beholder metrics started");
|
||||
updateWaitingResponseDuration(formDataResponses);
|
||||
updateFormDataReceivingDuration(formDataResponses);
|
||||
updateFormDomCompleteDuration(formDataResponses);
|
||||
updatePerformanceTimings(formDataResponses);
|
||||
updateResourceLoadingDuration(formDataResponses);
|
||||
updateFormLoadingRequestsTotal(formDataResponses);
|
||||
log.debug("Updating beholder metrics finished");
|
||||
}
|
||||
|
||||
private void updateWaitingResponseDuration(List<FormDataResponse> formDataResponses) {
|
||||
formDataWaitingDurationGauges.register(
|
||||
formDataResponses.stream()
|
||||
.filter(Predicate.not(FormDataResponse::isFailed))
|
||||
.map(formDataResponse -> MultiGauge.Row.of(
|
||||
MetricUtil.createCommonTags(formDataResponse),
|
||||
MetricUtil.calculateWaitingResponseDuration(formDataResponse.getFormPerformance())))
|
||||
.collect(toList()),
|
||||
true
|
||||
);
|
||||
private Map<String, MultiGauge> createPerformanceTimingGauges(MeterRegistry registry) {
|
||||
Map<String, MultiGauge> gauges = new HashMap<>();
|
||||
for (String jsMetricName : MetricUtil.PERFORMANCE_METRICS) {
|
||||
String prometheusMetricName =
|
||||
String.format(PERFORMANCE_TIMING_TEMPLATE.getName(), StringUtil.camelToSnake(jsMetricName));
|
||||
String prometheusMetricDescription =
|
||||
String.format(PERFORMANCE_TIMING_TEMPLATE.getDescription(), jsMetricName);
|
||||
|
||||
MultiGauge multiGauge = MultiGauge.builder(prometheusMetricName)
|
||||
.description(prometheusMetricDescription)
|
||||
.baseUnit(PERFORMANCE_TIMING_TEMPLATE.getUnit())
|
||||
.register(registry);
|
||||
gauges.put(jsMetricName, multiGauge);
|
||||
}
|
||||
return gauges;
|
||||
}
|
||||
|
||||
private void updateFormDataReceivingDuration(List<FormDataResponse> formDataResponses) {
|
||||
formDataReceivingDuration.register(
|
||||
formDataResponses.stream()
|
||||
.filter(Predicate.not(FormDataResponse::isFailed))
|
||||
.map(formDataResponse -> MultiGauge.Row.of(
|
||||
MetricUtil.createCommonTags(formDataResponse),
|
||||
MetricUtil.calculateDataReceivingDuration(formDataResponse.getFormPerformance())))
|
||||
.collect(toList()),
|
||||
true
|
||||
);
|
||||
private void updatePerformanceTimings(List<FormDataResponse> formDataResponses) {
|
||||
MetricUtil.PERFORMANCE_METRICS.forEach(metricName -> {
|
||||
Map<Tags, Double> perfTimingsStats = convertToPerformanceTimingsStats(metricName, formDataResponses);
|
||||
performanceTimingGauges.get(metricName)
|
||||
.register(
|
||||
perfTimingsStats.entrySet().stream()
|
||||
.map(networkLogEntry -> MultiGauge.Row.of(networkLogEntry.getKey(),
|
||||
networkLogEntry.getValue()))
|
||||
.collect(toList()), true);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateFormDomCompleteDuration(List<FormDataResponse> formDataResponses) {
|
||||
formDomCompleteDuration.register(
|
||||
formDataResponses.stream()
|
||||
.filter(Predicate.not(FormDataResponse::isFailed))
|
||||
.map(formDataResponse -> MultiGauge.Row.of(
|
||||
MetricUtil.createCommonTags(formDataResponse),
|
||||
MetricUtil.calculateDomCompleteDuration(formDataResponse.getFormPerformance())))
|
||||
.collect(toList()),
|
||||
true
|
||||
);
|
||||
private Map<Tags, Double> convertToPerformanceTimingsStats(String metricName,
|
||||
List<FormDataResponse> formDataResponses) {
|
||||
return formDataResponses.stream()
|
||||
.filter(Predicate.not(FormDataResponse::isFailed))
|
||||
.map(formDataResponse -> convertToPerformanceTimingEntry(metricName, formDataResponse))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
private Map.Entry<Tags, Double> convertToPerformanceTimingEntry(String metricName,
|
||||
FormDataResponse formDataResponse) {
|
||||
Double value = MetricUtil.castToDouble(
|
||||
formDataResponse.getPerformanceMetrics().getOrDefault(metricName, Double.NaN));
|
||||
Tags tags = MetricUtil.createCommonTags(formDataResponse);
|
||||
return Map.entry(tags, value);
|
||||
}
|
||||
|
||||
private void updateFormLoadingRequestsTotal(List<FormDataResponse> formDataResponses) {
|
||||
@ -115,7 +113,8 @@ public class MetricsService {
|
||||
private void updateResourceLoadingDuration(List<FormDataResponse> formDataResponses) {
|
||||
Map<Tags, Double> resourcesStats = convertToResourceLoadingStats(formDataResponses);
|
||||
resourcesLoadingTimings.register(
|
||||
resourcesStats.entrySet().stream().map(networkLogEntry -> MultiGauge.Row.of(
|
||||
resourcesStats.entrySet().stream()
|
||||
.map(networkLogEntry -> MultiGauge.Row.of(
|
||||
networkLogEntry.getKey(),
|
||||
networkLogEntry.getValue()))
|
||||
.collect(toList()),
|
||||
@ -128,16 +127,20 @@ public class MetricsService {
|
||||
.filter(Predicate.not(FormDataResponse::isFailed))
|
||||
.flatMap(formDataResponse ->
|
||||
formDataResponse.getNetworkLogs().stream()
|
||||
.map(networkLog -> Map.entry(MetricUtil.createCommonTags(formDataResponse)
|
||||
.and("resource",
|
||||
MetricUtil.getNormalisedPath(networkLog,
|
||||
formDataResponse.getRequest()
|
||||
.getInvoiceId(),
|
||||
formDataResponse.getRequest()
|
||||
.getInvoiceAccessToken()))
|
||||
.and("method", networkLog.getMethod()),
|
||||
MetricUtil.calculateRequestDuration(networkLog))))
|
||||
.map(log -> convertToResourceLoadingEntry(formDataResponse, log)))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
private Map.Entry<Tags, Double> convertToResourceLoadingEntry(FormDataResponse formDataResponse,
|
||||
NetworkLog networkLog) {
|
||||
return Map.entry(MetricUtil.createCommonTags(formDataResponse)
|
||||
.and("resource",
|
||||
MetricUtil.getNormalisedPath(networkLog,
|
||||
formDataResponse.getRequest()
|
||||
.getInvoiceId(),
|
||||
formDataResponse.getRequest()
|
||||
.getInvoiceAccessToken()))
|
||||
.and("method", networkLog.getMethod()), MetricUtil.calculateRequestDuration(networkLog));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -17,10 +17,8 @@ import org.openqa.selenium.support.ui.WebDriverWait;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static dev.vality.beholder.util.MetricUtil.castToDouble;
|
||||
import java.util.Map;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@ -44,8 +42,8 @@ public class SeleniumService {
|
||||
driver = new RemoteWebDriver(seleniumUrl, capabilities);
|
||||
driver.get(prepareParams(formDataRequest));
|
||||
|
||||
//noinspection rawtypes
|
||||
ArrayList performanceMetrics = (ArrayList) driver.executeScript(SeleniumUtil.PERFORMANCE_SCRIPT);
|
||||
Map<String, Object> performanceMetrics =
|
||||
(Map<String, Object>) driver.executeScript(SeleniumUtil.PERFORMANCE_SCRIPT);
|
||||
fillAndSendPaymentRequest(driver, formDataRequest.getCardInfo());
|
||||
|
||||
LogEntries les = driver.manage().logs().get(LogType.PERFORMANCE);
|
||||
@ -54,14 +52,7 @@ public class SeleniumService {
|
||||
return FormDataResponse.builder()
|
||||
.networkLogs(networkLogs)
|
||||
.request(formDataRequest)
|
||||
.formPerformance(
|
||||
FormDataResponse.FormPerformance.builder()
|
||||
.requestStartAt(castToDouble(performanceMetrics.get(0)))
|
||||
.responseStartAt(castToDouble(performanceMetrics.get(1)))
|
||||
.responseEndAt(castToDouble(performanceMetrics.get(2)))
|
||||
.domCompletedAt(castToDouble(performanceMetrics.get(3)))
|
||||
.build()
|
||||
)
|
||||
.performanceMetrics(performanceMetrics)
|
||||
.region(region)
|
||||
.browser(browser)
|
||||
.build();
|
||||
|
@ -6,10 +6,18 @@ import io.micrometer.core.instrument.Tags;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@UtilityClass
|
||||
public class MetricUtil {
|
||||
|
||||
public static final List<String> PERFORMANCE_METRICS =
|
||||
List.of("redirectStart", "redirectEnd", "fetchStart", "domainLookupStart", "domainLookupEnd",
|
||||
"connectStart", "secureConnectionStart", "connectEnd", "requestStart", "responseStart",
|
||||
"responseEnd", "domInteractive", "domContentLoadedEventStart", "domContentLoadedEventEnd",
|
||||
"domComplete", "loadEventStart");
|
||||
|
||||
public static String getNormalisedPath(NetworkLog networkLog, String invoiceId, String invoiceToken) {
|
||||
String path = networkLog.getResource();
|
||||
path = path.replaceAll(invoiceId, "{invoice_id}")
|
||||
@ -24,18 +32,6 @@ public class MetricUtil {
|
||||
return calculateDiff(networkLog.getStart(), networkLog.getEnd());
|
||||
}
|
||||
|
||||
public static double calculateWaitingResponseDuration(FormDataResponse.FormPerformance performance) {
|
||||
return calculateDiff(performance.getRequestStartAt(), performance.getResponseStartAt());
|
||||
}
|
||||
|
||||
public static double calculateDataReceivingDuration(FormDataResponse.FormPerformance performance) {
|
||||
return calculateDiff(performance.getResponseStartAt(), performance.getResponseEndAt());
|
||||
}
|
||||
|
||||
public static double calculateDomCompleteDuration(FormDataResponse.FormPerformance performance) {
|
||||
return calculateDiff(performance.getRequestStartAt(), performance.getDomCompletedAt());
|
||||
}
|
||||
|
||||
private static double calculateDiff(Double from, Double to) {
|
||||
if (isNumber(from) && isNumber(to)) {
|
||||
return Math.round(to - from);
|
||||
|
@ -12,14 +12,7 @@ import java.util.logging.Level;
|
||||
@UtilityClass
|
||||
public class SeleniumUtil {
|
||||
|
||||
public static final String PERFORMANCE_SCRIPT = """
|
||||
var data = new Array();
|
||||
var navigation = window.performance.getEntriesByType("navigation")[0];
|
||||
data[0] = navigation.requestStart;
|
||||
data[1] = navigation.responseStart;
|
||||
data[2] = navigation.responseEnd;
|
||||
data[3] = navigation.domComplete;
|
||||
return data;""";
|
||||
public static final String PERFORMANCE_SCRIPT = "return window.performance.getEntriesByType(\"navigation\")[0];";
|
||||
|
||||
public static DesiredCapabilities getCommonCapabilities() {
|
||||
var capabilities = new DesiredCapabilities();
|
||||
|
26
src/main/java/dev/vality/beholder/util/StringUtil.java
Normal file
26
src/main/java/dev/vality/beholder/util/StringUtil.java
Normal file
@ -0,0 +1,26 @@
|
||||
package dev.vality.beholder.util;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
@UtilityClass
|
||||
public class StringUtil {
|
||||
|
||||
public static String camelToSnake(String str) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
if (!ObjectUtils.isEmpty(str)) {
|
||||
char firstLetter = str.charAt(0);
|
||||
result.append(Character.toLowerCase(firstLetter));
|
||||
for (int i = 1; i < str.length(); i++) {
|
||||
char letter = str.charAt(i);
|
||||
if (Character.isUpperCase(letter)) {
|
||||
result.append('_');
|
||||
result.append(Character.toLowerCase(letter));
|
||||
} else {
|
||||
result.append(letter);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ import lombok.experimental.UtilityClass;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@UtilityClass
|
||||
@ -61,17 +62,16 @@ public class ResponseUtil {
|
||||
var responseStartAt = requestStartAt + 5;
|
||||
var responseEndAt = responseStartAt + 5;
|
||||
|
||||
FormDataResponse.FormPerformance performance = FormDataResponse.FormPerformance.builder()
|
||||
.requestStartAt(requestStartAt)
|
||||
.responseStartAt(responseStartAt)
|
||||
.responseEndAt(responseEndAt).build();
|
||||
Map<String, Object> performanceMetrics = Map.of("requestStart", requestStartAt,
|
||||
"responseStart", responseStartAt,
|
||||
"responseEnd", responseEndAt);
|
||||
|
||||
return FormDataResponse.builder()
|
||||
.region(region)
|
||||
.browser(Browser.CHROME)
|
||||
.request(request)
|
||||
.networkLogs(List.of())
|
||||
.formPerformance(performance)
|
||||
.performanceMetrics(performanceMetrics)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user