From 9e32782de3a2ab7e4d6bd37d29b624bab0ea2095 Mon Sep 17 00:00:00 2001 From: yibole Date: Tue, 31 Dec 2024 16:47:24 -0800 Subject: [PATCH] End to end test passed --- .../emf-metric-publisher/pom.xml | 5 + .../publishers/emf/EmfMetricPublisher.java | 10 +- .../emf/internal/MetricEmfConverter.java | 221 ++++++++++-------- .../src/main/resources/log4j2.properties | 38 +++ .../emf/EmfMetricPublisherTest.java | 3 - .../emf/internal/MetricEmfConverterTest.java | 52 +++-- 6 files changed, 209 insertions(+), 120 deletions(-) create mode 100644 metric-publishers/emf-metric-publisher/src/main/resources/log4j2.properties diff --git a/metric-publishers/emf-metric-publisher/pom.xml b/metric-publishers/emf-metric-publisher/pom.xml index 9a48033fc6d7..77a8160c48a4 100644 --- a/metric-publishers/emf-metric-publisher/pom.xml +++ b/metric-publishers/emf-metric-publisher/pom.xml @@ -65,6 +65,11 @@ ${awsjavasdk.version} compile + + org.apache.logging.log4j + log4j-slf4j-impl + 2.20.0 + software.amazon.awssdk sdk-core diff --git a/metric-publishers/emf-metric-publisher/src/main/java/software/amazon/awssdk/metrics/publishers/emf/EmfMetricPublisher.java b/metric-publishers/emf-metric-publisher/src/main/java/software/amazon/awssdk/metrics/publishers/emf/EmfMetricPublisher.java index 7cdb4c6a0d3c..d11668a5cdd5 100644 --- a/metric-publishers/emf-metric-publisher/src/main/java/software/amazon/awssdk/metrics/publishers/emf/EmfMetricPublisher.java +++ b/metric-publishers/emf-metric-publisher/src/main/java/software/amazon/awssdk/metrics/publishers/emf/EmfMetricPublisher.java @@ -23,6 +23,7 @@ import software.amazon.awssdk.annotations.Immutable; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.annotations.ThreadSafe; +import software.amazon.awssdk.core.metrics.CoreMetric; import software.amazon.awssdk.metrics.MetricCategory; import software.amazon.awssdk.metrics.MetricCollection; import software.amazon.awssdk.metrics.MetricLevel; @@ -50,6 +51,13 @@ * monitoring and alerting. *

* + *
+ * // Create a CloudWatchMetricPublisher using a custom namespace.
+ * MetricPublisher metricPublisher = EmfMetricPublisher.builder()
+ *                                                     .namespace("MyApplication")
+ *                                                     .build();
+ * 
+ * * * @see MetricPublisher The base interface for metric publishers * @see MetricCollection For the collection of metrics to be published @@ -135,7 +143,7 @@ public Builder namespace(String namespace) { * this * publisher. * - *

If this is not specified, [] will be used. + *

If this is not specified, {@link CoreMetric#SERVICE_ID} and {@link CoreMetric#OPERATION_NAME} will be used. */ public Builder dimensions(Collection> dimensions) { this.dimensions = new ArrayList<>(dimensions); diff --git a/metric-publishers/emf-metric-publisher/src/main/java/software/amazon/awssdk/metrics/publishers/emf/internal/MetricEmfConverter.java b/metric-publishers/emf-metric-publisher/src/main/java/software/amazon/awssdk/metrics/publishers/emf/internal/MetricEmfConverter.java index 6165708d61c1..7adf04916624 100644 --- a/metric-publishers/emf-metric-publisher/src/main/java/software/amazon/awssdk/metrics/publishers/emf/internal/MetricEmfConverter.java +++ b/metric-publishers/emf-metric-publisher/src/main/java/software/amazon/awssdk/metrics/publishers/emf/internal/MetricEmfConverter.java @@ -33,23 +33,15 @@ import software.amazon.awssdk.utils.Logger; /** - * # MetricEmfConverter - - * Converts metrics into Amazon CloudWatch Embedded Metric Format (EMF). - * This internal class handles the transformation of various metric types - * into EMF-compatible format. - - * ## Configuration - * The converter is initialized with an `EmfMetricConfiguration` that defines: - * - Metric categories to process - * - Other EMF-specific settings - - * ## Implementation Notes - * - Boolean values are converted to numeric values (1.0 for true, 0.0 for false) - * - Null values are converted to 0.0 - * - Very small numeric values (below 0.0001) are normalized to 0.0 - * - Duration values are converted to milliseconds + * Converts {@link MetricCollection} into List of Amazon CloudWatch Embedded Metric Format (EMF) Strings. + *

+ * This class is responsible for transforming {@link MetricCollection} into the EMF format required by CloudWatch. + * It handles the conversion of different metric types and ensures the output conforms to EMF specifications. + *

* + * + * @see + * CloudWatch Embedded Metric Format Specification * @see EmfMetricConfiguration */ @SdkInternalApi @@ -73,48 +65,14 @@ public MetricEmfConverter(EmfMetricConfiguration config) { } /** - * Processes and normalizes metric values for EMF formatting. - * The method handles various input types and normalizes them according to EMF requirements: - * - `null` : 0.0 - * - `Boolean` : 1.0 (true) or 0.0 (false) - * - `Duration` : milliseconds value - * - `Double`: normalized to 0.0 if below threshold - * - * @param mRecord The metric record to process - */ - private void processAndWriteValue(JsonWriter jsonWriter, MetricRecord mRecord) { - Object value = mRecord.value(); - Class valueClass = mRecord.metric().valueClass(); - - if (value == null) { - jsonWriter.writeValue(0.0); - } else if (Boolean.class.isAssignableFrom(valueClass)) { - jsonWriter.writeValue(value.equals(true) ? 1.0 : 0.0); - } else if (Duration.class.isAssignableFrom(valueClass)) { - Duration duration = (Duration) value; - double millisValue = duration.toMillis(); - jsonWriter.writeValue(millisValue); - } else if (Double.class.isAssignableFrom(valueClass)) { - double doubleValue = (Double) value; - if (Math.abs(doubleValue) < ZERO_THRESHOLD) { - jsonWriter.writeValue(0.0); - } else { - jsonWriter.writeValue(doubleValue); - } - } else if (Integer.class.isAssignableFrom(valueClass)) { - jsonWriter.writeValue((Integer) value); - } else if (Long.class.isAssignableFrom(valueClass)) { - jsonWriter.writeValue((Long) value); - } - } - - /** - * # Convert SDK Metrics to EMF Format + *

+ * Convert SDK Metrics to EMF Format. * Transforms a collection of SDK metrics into CloudWatch's Embedded Metric Format (EMF). * The method processes standard SDK measurements and structures them according to * CloudWatch's EMF specification. - * ## Example Output - * ```json + *

+ * Example Output + *
      * {
      *   "_aws": {
      *     "Timestamp": 1672963200,
@@ -126,10 +84,10 @@ private void processAndWriteValue(JsonWriter jsonWriter, MetricRecord mRecord
      *       }]
      *     }]
      *   },
-     *   "ServiceId": "XXXXXXXXXXXXX",
+     *   "ServiceId": "DynamoDB",
      *   "AvailableConcurrency": 5
      * }
-     * ```
+     * 
* * @param metricCollection Collection of SDK metrics to be converted * @return List of EMF-formatted metrics ready for CloudWatch @@ -168,6 +126,48 @@ public List convertMetricCollectionToEmf(MetricCollection metricCollecti return createEmfStrings(aggregatedMetrics); } + /** + * Processes and normalizes metric values for EMF formatting. + * The method handles various input types and normalizes them according to EMF requirements: + * Value Conversion Rules: + *
    + *
  • Numbers (Integer, Long, Double, etc.) are converted to their native numeric format
  • + *
  • Duration values are converted to milliseconds
  • + *
  • Date/Time values are converted to epoch milliseconds
  • + *
  • Null values are omitted from the output
  • + *
  • Boolean values are converted to 1 (true) or 0 (false)
  • + *
  • Non-Dimension metrics with non-numeric values are omitted from the output
  • + *
+ * + * @param mRecord The metric record to process + */ + private void processAndWriteValue(JsonWriter jsonWriter, MetricRecord mRecord) { + Object value = mRecord.value(); + Class valueClass = mRecord.metric().valueClass(); + + if (value == null) { + return; + } + if (Boolean.class.isAssignableFrom(valueClass)) { + jsonWriter.writeValue(value.equals(true) ? 1.0 : 0.0); + } else if (Duration.class.isAssignableFrom(valueClass)) { + Duration duration = (Duration) value; + double millisValue = duration.toMillis(); + jsonWriter.writeValue(millisValue); + } else if (Double.class.isAssignableFrom(valueClass)) { + double doubleValue = (Double) value; + if (Math.abs(doubleValue) < ZERO_THRESHOLD) { + jsonWriter.writeValue(0.0); + } else { + jsonWriter.writeValue(doubleValue); + } + } else if (Integer.class.isAssignableFrom(valueClass)) { + jsonWriter.writeValue((Integer) value); + } else if (Long.class.isAssignableFrom(valueClass)) { + jsonWriter.writeValue((Long) value); + } + } + private List createEmfStrings(Map>> aggregatedMetrics) { List emfStrings = new ArrayList<>(); Map>> currentMetricBatch = new HashMap<>(); @@ -179,8 +179,9 @@ private List createEmfStrings(Map>> aggrega if (records.size() > 100) { records = records.subList(0, 100); + int size = records.size(); logger.warn(() -> "Some AWS SDK client-side metric data have been dropped because it exceeds the cloudwatch " - + "requirements."); + + "requirements. There are " + size + " values for metric " + metricName); } if (currentMetricNames.size() >= 100) { @@ -190,7 +191,7 @@ private List createEmfStrings(Map>> aggrega } currentMetricBatch.put(metricName, records); - if (!String.class.isAssignableFrom(records.get(0).metric().valueClass())) { + if (!isStringMetric(records.get(0))) { currentMetricNames.put(metricName, records.get(0).metric().valueClass()); } @@ -222,7 +223,7 @@ private void writeAwsObject(JsonWriter jsonWriter, Map> metricN jsonWriter.writeFieldName("Timestamp"); - jsonWriter.writeValue(clock.instant()); + jsonWriter.writeValue(clock.instant().toEpochMilli()); jsonWriter.writeFieldName("LogGroupName"); @@ -266,54 +267,86 @@ private void writeMetricDefinitionArray(JsonWriter jsonWriter, Map writeMetricDefinition(jsonWriter, name, type)); - // Write metric definitions - metricNames.forEach((name, type) -> { - jsonWriter.writeStartObject(); - jsonWriter.writeFieldName("Name"); - jsonWriter.writeValue(name); + jsonWriter.writeEndArray(); + } - String unit = getMetricUnit(type); - if (unit != null) { - jsonWriter.writeFieldName("Unit"); - jsonWriter.writeValue(unit); - } + private void writeMetricDefinition(JsonWriter jsonWriter, String name, Class type) { + jsonWriter.writeStartObject(); + jsonWriter.writeFieldName("Name"); + jsonWriter.writeValue(name); - jsonWriter.writeEndObject(); - }); + String unit = getMetricUnit(type); + if (unit != null) { + jsonWriter.writeFieldName("Unit"); + jsonWriter.writeValue(unit); + } - jsonWriter.writeEndArray(); + jsonWriter.writeEndObject(); } - private void writeMetricValues(JsonWriter jsonWriter, Map>> metrics) { - for (Map.Entry>> entry : metrics.entrySet()) { - String metricName = entry.getKey(); - List> records = entry.getValue(); - + metrics.forEach((metricName, records) -> { + if (records.isEmpty()) { + return; + } if (isDimension(metricName)) { - jsonWriter.writeFieldName(metricName); - jsonWriter.writeValue((String) records.get(0).value()); + writeDimensionValue(jsonWriter, metricName, records); } else { - if (records.get(0).value() instanceof String) { - continue; - } - if (records.size() == 1) { - jsonWriter.writeFieldName(metricName); - processAndWriteValue(jsonWriter, records.get(0)); - } else { - jsonWriter.writeFieldName(metricName); - jsonWriter.writeStartArray(); - for (MetricRecord mRecord: records) { - processAndWriteValue(jsonWriter, mRecord); - } - jsonWriter.writeEndArray(); - } + writeMetricRecord(jsonWriter, metricName, records); } + }); + } + + private void writeDimensionValue(JsonWriter jsonWriter, String metricName, List> records) { + if (records.get(0).value() == null) { + return; + } + + jsonWriter.writeFieldName(metricName); + jsonWriter.writeValue((String) records.get(0).value()); + } + + private void writeMetricRecord(JsonWriter jsonWriter, String metricName, List> records) { + MetricRecord firstRecord = records.get(0); + + if (!isNumericMetric(firstRecord) || (records.size() == 1 && firstRecord.value() == null)) { + return; + } + + jsonWriter.writeFieldName(metricName); + + if (records.size() == 1) { + processAndWriteValue(jsonWriter, firstRecord); + } else { + writeMetricArray(jsonWriter, records); + } + } + + private boolean isStringMetric(MetricRecord mRecord) { + return String.class.isAssignableFrom(mRecord.metric().valueClass()); + } + + private boolean isNumericMetric(MetricRecord mRecord) { + return Integer.class.isAssignableFrom(mRecord.metric().valueClass()) + || Boolean.class.isAssignableFrom(mRecord.metric().valueClass()) + || Long.class.isAssignableFrom(mRecord.metric().valueClass()) + || Duration.class.isAssignableFrom(mRecord.metric().valueClass()) + || Double.class.isAssignableFrom(mRecord.metric().valueClass()); + } + + + private void writeMetricArray(JsonWriter jsonWriter, List> records) { + jsonWriter.writeStartArray(); + for (MetricRecord mRecord : records) { + processAndWriteValue(jsonWriter, mRecord); } + jsonWriter.writeEndArray(); } + private boolean isDimension(String metricName) { return metricName != null && config.getDimensionStrings().contains(metricName); } diff --git a/metric-publishers/emf-metric-publisher/src/main/resources/log4j2.properties b/metric-publishers/emf-metric-publisher/src/main/resources/log4j2.properties new file mode 100644 index 000000000000..ea24f17148e6 --- /dev/null +++ b/metric-publishers/emf-metric-publisher/src/main/resources/log4j2.properties @@ -0,0 +1,38 @@ +# +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). +# You may not use this file except in compliance with the License. +# A copy of the License is located at +# +# http://aws.amazon.com/apache2.0 +# +# or in the "license" file accompanying this file. This file is distributed +# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either +# express or implied. See the License for the specific language governing +# permissions and limitations under the License. +# + +status = warn + +appender.console.type = Console +appender.console.name = ConsoleAppender +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n%throwable + +rootLogger.level = info +rootLogger.appenderRef.stdout.ref = ConsoleAppender + +# Uncomment below to enable more specific logging +# +#logger.sdk.name = software.amazon.awssdk +#logger.sdk.level = debug +# +#logger.request.name = software.amazon.awssdk.request +#logger.request.level = debug +# +#logger.apache.name = org.apache.http.wire +#logger.apache.level = debug +# +#logger.netty.name = io.netty.handler.logging +#logger.netty.level = debug \ No newline at end of file diff --git a/metric-publishers/emf-metric-publisher/src/test/java/software/amazon/awssdk/metrics/publishers/emf/EmfMetricPublisherTest.java b/metric-publishers/emf-metric-publisher/src/test/java/software/amazon/awssdk/metrics/publishers/emf/EmfMetricPublisherTest.java index 09e29d805dc7..ed9e2a2b5142 100644 --- a/metric-publishers/emf-metric-publisher/src/test/java/software/amazon/awssdk/metrics/publishers/emf/EmfMetricPublisherTest.java +++ b/metric-publishers/emf-metric-publisher/src/test/java/software/amazon/awssdk/metrics/publishers/emf/EmfMetricPublisherTest.java @@ -61,7 +61,4 @@ void Publish_EmptyMetrics() { assertThat(loggedEvents()).hasSize(4); } - - - } diff --git a/metric-publishers/emf-metric-publisher/src/test/java/software/amazon/awssdk/metrics/publishers/emf/internal/MetricEmfConverterTest.java b/metric-publishers/emf-metric-publisher/src/test/java/software/amazon/awssdk/metrics/publishers/emf/internal/MetricEmfConverterTest.java index 42545f3fa2ca..f2b3f5cbe698 100644 --- a/metric-publishers/emf-metric-publisher/src/test/java/software/amazon/awssdk/metrics/publishers/emf/internal/MetricEmfConverterTest.java +++ b/metric-publishers/emf-metric-publisher/src/test/java/software/amazon/awssdk/metrics/publishers/emf/internal/MetricEmfConverterTest.java @@ -59,11 +59,10 @@ void setUp() { @Test void ConvertMetricCollectionToEMF_EmptyCollection() { List emfLogs = metricEmfConverterDefault.convertMetricCollectionToEmf(MetricCollector.create("test").collect()); - for (String emfLog : emfLogs) { - assertThat(emfLog).isEqualTo("{\"_aws\":{\"Timestamp\":12345.678,\"LogGroupName\":\"my_log_group_name\"," + + assertThat(emfLogs).containsOnly("{\"_aws\":{\"Timestamp\":12345678,\"LogGroupName\":\"my_log_group_name\"," + "\"CloudWatchMetrics\":[{\"Namespace\":\"AwsSdk/JavaSdk2\",\"Dimensions\":[[]]," + "\"Metrics\":[]}]}}"); - } } @Test @@ -74,10 +73,9 @@ void ConvertMetricCollectionToEMF_MultipleMetrics(){ metricCollector.reportMetric(HttpMetric.CONCURRENCY_ACQUIRE_DURATION, java.time.Duration.ofMillis(100)); List emfLogs = metricEmfConverterDefault.convertMetricCollectionToEmf(metricCollector.collect()); - for (String emfLog : emfLogs) { - assertThat(emfLog).isEqualTo("{\"_aws\":{\"Timestamp\":12345.678,\"LogGroupName\":\"my_log_group_name\",\"CloudWatchMetrics\":[{\"Namespace\":\"AwsSdk/JavaSdk2\",\"Dimensions\":[[]],\"Metrics\":[{\"Name\":\"AvailableConcurrency\"},{\"Name\":\"ConcurrencyAcquireDuration\",\"Unit\":\"Milliseconds\"}]}]},\"AvailableConcurrency\":5,\"ConcurrencyAcquireDuration\":100.0}"); - - } + assertThat(emfLogs).containsOnly("{\"_aws\":{\"Timestamp\":12345678,\"LogGroupName\":\"my_log_group_name\"," + + "\"CloudWatchMetrics\":[{\"Namespace\":\"AwsSdk/JavaSdk2\",\"Dimensions\":[[]],\"Metrics\":[{\"Name\":\"AvailableConcurrency\"}," + + "{\"Name\":\"ConcurrencyAcquireDuration\",\"Unit\":\"Milliseconds\"}]}]},\"AvailableConcurrency\":5,\"ConcurrencyAcquireDuration\":100.0}"); } @@ -90,15 +88,12 @@ void ConvertMetricCollectionToEMF_Dimensions(){ metricCollector.reportMetric(HttpMetric.HTTP_STATUS_CODE, 404); List emfLogs = metricEmfConverterDefault.convertMetricCollectionToEmf(metricCollector.collect()); - for (String emfLog : emfLogs) { - assertThat(emfLog).isEqualTo("{\"_aws\":{\"Timestamp\":12345.678,\"LogGroupName\":\"my_log_group_name\"," + assertThat(emfLogs).containsOnly("{\"_aws\":{\"Timestamp\":12345678,\"LogGroupName\":\"my_log_group_name\"," + "\"CloudWatchMetrics\":[{\"Namespace\":\"AwsSdk/JavaSdk2\"," + "\"Dimensions\":[[\"OperationName\"]]," + "\"Metrics\":[{\"Name\":\"AvailableConcurrency\"}]}]}," + "\"AvailableConcurrency\":5," + "\"OperationName\":\"operationName\"}"); - - } } @Test @@ -107,12 +102,9 @@ void ConvertMetricCollectionToEMF_metricCategory(){ metricCollector.reportMetric(HttpMetric.AVAILABLE_CONCURRENCY, 5); List emfLogs = metricEmfConverterCustom.convertMetricCollectionToEmf(metricCollector.collect()); - for (String emfLog : emfLogs) { - assertThat(emfLog).isEqualTo("{\"_aws\":{\"Timestamp\":12345.678,\"LogGroupName\":\"my_log_group_name\",\"CloudWatchMetrics\":[{\"Namespace\":" + assertThat(emfLogs).containsOnly("{\"_aws\":{\"Timestamp\":12345678,\"LogGroupName\":\"my_log_group_name\",\"CloudWatchMetrics\":[{\"Namespace\":" + "\"AwsSdk/Test/JavaSdk2\",\"Dimensions\":[[]]," + "\"Metrics\":[{\"Name\":\"AvailableConcurrency\"}]}]},\"AvailableConcurrency\":5}"); - - } } @Test @@ -122,12 +114,9 @@ void ConvertMetricCollectionToEMF_metricLevel(){ metricCollector.reportMetric(HttpMetric.HTTP_STATUS_CODE, 404); List emfLogs = metricEmfConverterCustom.convertMetricCollectionToEmf(metricCollector.collect()); - for (String emfLog : emfLogs) { - assertThat(emfLog).isEqualTo("{\"_aws\":{\"Timestamp\":12345.678,\"LogGroupName\":\"my_log_group_name\"," + assertThat(emfLogs).containsOnly("{\"_aws\":{\"Timestamp\":12345678,\"LogGroupName\":\"my_log_group_name\"," + "\"CloudWatchMetrics\":[{\"Namespace\":\"AwsSdk/Test/JavaSdk2\"" + ",\"Dimensions\":[[]],\"Metrics\":[{\"Name\":\"AvailableConcurrency\"},{\"Name\":\"HttpStatusCode\"}]}]},\"AvailableConcurrency\":5,\"HttpStatusCode\":404}"); - - } } @@ -145,16 +134,35 @@ void ConvertMetricCollectionToEMF_MultiChildCollections(){ childMetricCollector2.reportMetric(HttpMetric.CONCURRENCY_ACQUIRE_DURATION, java.time.Duration.ofMillis(200)); List emfLogs = metricEmfConverterDefault.convertMetricCollectionToEmf(metricCollector.collect()); - for (String emfLog : emfLogs) { - assertThat(emfLog).isEqualTo("{\"_aws\":{\"Timestamp\":12345.678,\"LogGroupName\":\"my_log_group_name\"," + assertThat(emfLogs).containsOnly("{\"_aws\":{\"Timestamp\":12345678,\"LogGroupName\":\"my_log_group_name\"," + "\"CloudWatchMetrics\"" + ":[{\"Namespace\":\"AwsSdk/JavaSdk2\",\"Dimensions\":[[]]," + "\"Metrics\":[{\"Name\":\"AvailableConcurrency\"}," + "{\"Name\":\"ConcurrencyAcquireDuration\",\"Unit\":\"Milliseconds\"}]}]},\"AvailableConcurrency\":5,\"ConcurrencyAcquireDuration\":" + "[100.0,200.0]}"); + } + @Test + void ConvertMetricCollectionToEMF_OverSizedRecords(){ + + MetricCollector metricCollector = MetricCollector.create("test"); + metricCollector.reportMetric(HttpMetric.HTTP_CLIENT_NAME, "apache-http-client"); + metricCollector.reportMetric(HttpMetric.AVAILABLE_CONCURRENCY, 5); + + for (int i = 0; i < 120; i++) { + MetricCollector childMetricCollector = metricCollector.createChild("child" + i); + childMetricCollector.reportMetric(HttpMetric.CONCURRENCY_ACQUIRE_DURATION, java.time.Duration.ofMillis(100+i)); } - } + List emfLogs = metricEmfConverterDefault.convertMetricCollectionToEmf(metricCollector.collect()); + + assertThat(emfLogs).containsOnly("{\"_aws\":{\"Timestamp\":12345678,\"LogGroupName\":\"my_log_group_name\",\"CloudWatchMetrics\":[{\"Namespace\":\"AwsSdk/JavaSdk2\"," + + "\"Dimensions\":[[]],\"Metrics\":[{\"Name\":\"AvailableConcurrency\"},{\"Name\":\"ConcurrencyAcquireDuration\",\"Unit\":\"Milliseconds\"}]}]},\"AvailableConcurrency\":5," + + "\"ConcurrencyAcquireDuration\":[100.0,101.0,102.0,103.0,104.0,105.0,106.0,107.0,108.0,109.0,110.0,111.0,112.0,113.0,114.0,115.0,116.0,117.0,118.0,119.0,120.0,121.0,122.0," + + "123.0,124.0,125.0,126.0,127.0,128.0,129.0,130.0,131.0,132.0,133.0,134.0,135.0,136.0,137.0,138.0,139.0,140.0,141.0,142.0,143.0,144.0,145.0,146.0,147.0,148.0,149.0,150.0,151.0," + + "152.0,153.0,154.0,155.0,156.0,157.0,158.0,159.0,160.0,161.0,162.0,163.0,164.0,165.0,166.0,167.0,168.0,169.0,170.0,171.0,172.0,173.0,174.0,175.0,176.0,177.0,178.0,179.0,180.0,181.0," + + "182.0,183.0,184.0,185.0,186.0,187.0,188.0,189.0,190.0,191.0,192.0,193.0,194.0,195.0,196.0,197.0,198.0,199.0]}"); + + } } \ No newline at end of file