From c4ba522e43d9fe404dd93606a455811d19d01091 Mon Sep 17 00:00:00 2001 From: Egor Cherniak Date: Wed, 6 Jul 2022 09:31:57 +0300 Subject: [PATCH] Add more js metrics (#15) * Add more js metrics * Feedback edits --- .../beholder/model/FormDataResponse.java | 17 +-- .../dev/vality/beholder/model/Metric.java | 18 +-- .../beholder/service/MetricsService.java | 127 +++++++++--------- .../beholder/service/SeleniumService.java | 17 +-- .../dev/vality/beholder/util/MetricUtil.java | 20 ++- .../vality/beholder/util/SeleniumUtil.java | 9 +- .../dev/vality/beholder/util/StringUtil.java | 26 ++++ .../beholder/testutil/ResponseUtil.java | 10 +- 8 files changed, 114 insertions(+), 130 deletions(-) create mode 100644 src/main/java/dev/vality/beholder/util/StringUtil.java diff --git a/src/main/java/dev/vality/beholder/model/FormDataResponse.java b/src/main/java/dev/vality/beholder/model/FormDataResponse.java index 7b998aa..f0f9ba0 100644 --- a/src/main/java/dev/vality/beholder/model/FormDataResponse.java +++ b/src/main/java/dev/vality/beholder/model/FormDataResponse.java @@ -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 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; - - } - } diff --git a/src/main/java/dev/vality/beholder/model/Metric.java b/src/main/java/dev/vality/beholder/model/Metric.java index f58c8cc..81aea79 100644 --- a/src/main/java/dev/vality/beholder/model/Metric.java +++ b/src/main/java/dev/vality/beholder/model/Metric.java @@ -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() ), diff --git a/src/main/java/dev/vality/beholder/service/MetricsService.java b/src/main/java/dev/vality/beholder/service/MetricsService.java index 92afe13..0e1c522 100644 --- a/src/main/java/dev/vality/beholder/service/MetricsService.java +++ b/src/main/java/dev/vality/beholder/service/MetricsService.java @@ -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 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 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 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 createPerformanceTimingGauges(MeterRegistry registry) { + Map 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 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 formDataResponses) { + MetricUtil.PERFORMANCE_METRICS.forEach(metricName -> { + Map 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 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 convertToPerformanceTimingsStats(String metricName, + List 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 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 formDataResponses) { @@ -115,7 +113,8 @@ public class MetricsService { private void updateResourceLoadingDuration(List formDataResponses) { Map 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 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)); + } + } diff --git a/src/main/java/dev/vality/beholder/service/SeleniumService.java b/src/main/java/dev/vality/beholder/service/SeleniumService.java index e920ada..949df8e 100644 --- a/src/main/java/dev/vality/beholder/service/SeleniumService.java +++ b/src/main/java/dev/vality/beholder/service/SeleniumService.java @@ -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 performanceMetrics = + (Map) 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(); diff --git a/src/main/java/dev/vality/beholder/util/MetricUtil.java b/src/main/java/dev/vality/beholder/util/MetricUtil.java index 5970832..e2f524e 100644 --- a/src/main/java/dev/vality/beholder/util/MetricUtil.java +++ b/src/main/java/dev/vality/beholder/util/MetricUtil.java @@ -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 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); diff --git a/src/main/java/dev/vality/beholder/util/SeleniumUtil.java b/src/main/java/dev/vality/beholder/util/SeleniumUtil.java index 9bf4610..275b854 100644 --- a/src/main/java/dev/vality/beholder/util/SeleniumUtil.java +++ b/src/main/java/dev/vality/beholder/util/SeleniumUtil.java @@ -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(); diff --git a/src/main/java/dev/vality/beholder/util/StringUtil.java b/src/main/java/dev/vality/beholder/util/StringUtil.java new file mode 100644 index 0000000..3a47fbc --- /dev/null +++ b/src/main/java/dev/vality/beholder/util/StringUtil.java @@ -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(); + } +} diff --git a/src/test/java/dev/vality/beholder/testutil/ResponseUtil.java b/src/test/java/dev/vality/beholder/testutil/ResponseUtil.java index 78ba6d6..c1f6700 100644 --- a/src/test/java/dev/vality/beholder/testutil/ResponseUtil.java +++ b/src/test/java/dev/vality/beholder/testutil/ResponseUtil.java @@ -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 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(); }