Add more js metrics (#15)

* Add more js metrics

* Feedback edits
This commit is contained in:
Egor Cherniak 2022-07-06 09:31:57 +03:00 committed by GitHub
parent 71512a4863
commit c4ba522e43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 114 additions and 130 deletions

View File

@ -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;
}
}

View File

@ -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()
),

View File

@ -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));
}
}

View File

@ -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();

View File

@ -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);

View File

@ -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();

View 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();
}
}

View File

@ -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();
}