From 285a32ff71f10e97360816d9d141e80332084565 Mon Sep 17 00:00:00 2001 From: Jean Bisutti Date: Fri, 29 May 2020 17:37:49 +0200 Subject: [PATCH] Improvements (#1) * Initial Commit. Reformat, Rearrange, CheckStyle and Tests required. * Added AllocationRate test classes * Pretty formatting of Jvm Profiling output. * Fix failing tests * Minor test improvements * Add licence headers * Minor improvements * Fix build issue and simplify exception management * Simplify exception management * Time stamp should have no reason to be negative * Fix typo * Fix compilation warning * Separation of concerns and code simplification * Extract methods * Allocation duration has no reason to be negative * Remove local variable * Should return an empty string if allocation duration is zero * Simplify code by removing not possible test cases * Remove compilation warning * Item collection has no reason to be null * Thre is no reason to don't have JFR events * With this removal, a blank string is always return when no allocation events * Invert code lines * Remove local variables * Improve separation of concerns Co-authored-by: Edward Rose --- .../jvm/allocation/AllocationUnit.java | 18 ++ .../ByteAllocationMeasureFormatter.java | 47 +++-- .../MaxHeapAllocationPerfVerifier.java | 4 +- .../MeasureHeapAllocationPerfVerifier.java | 2 +- .../NoHeapAllocationPerfVerifier.java | 4 +- .../DisplayJvmProfilingValueVerifier.java | 66 +++--- .../jvm/jmc/value/ProfilingInfo.java | 26 ++- .../value/allocationrate/AllocationRate.java | 37 ++++ .../AllocationRateFormatter.java | 38 ++++ .../AllocationRateRetriever.java | 110 ++++++++++ .../jvm/rss/ExpectMaxRssPerfVerifier.java | 4 +- .../ByteAllocationMeasureFormatterTest.java | 16 +- .../jvm/jmc/value/AllocationRateTest.java | 198 ++++++++++++++++++ .../jvm/jmc/AllocationRateProfileJVMTest.java | 22 ++ 14 files changed, 529 insertions(+), 63 deletions(-) create mode 100644 jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/allocationrate/AllocationRate.java create mode 100644 jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/allocationrate/AllocationRateFormatter.java create mode 100644 jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/allocationrate/AllocationRateRetriever.java create mode 100644 jvm/jvm-annotations/src/test/java/org/quickperf/jvm/jmc/value/AllocationRateTest.java create mode 100644 testng/testng-jvm-test/src/test/java/org/quickperf/testng/jvm/jmc/AllocationRateProfileJVMTest.java diff --git a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/AllocationUnit.java b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/AllocationUnit.java index 2e68b428..8ec57f82 100644 --- a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/AllocationUnit.java +++ b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/AllocationUnit.java @@ -14,24 +14,40 @@ public enum AllocationUnit { BYTE(1) { + @Override + public String shortFormat() { + return "bytes"; + } @Override public String toString() { return "bytes"; } } , KILO_BYTE(1024) { + @Override + public String shortFormat() { + return "KiB"; + } @Override public String toString() { return "Kilo bytes"; } } , MEGA_BYTE(1024 * 1024) { + @Override + public String shortFormat() { + return "MiB"; + } @Override public String toString() { return "Mega bytes"; } } , GIGA_BYTE(1024 * 1024 * 1024) { + @Override + public String shortFormat() { + return "GiB"; + } @Override public String toString() { return "Giga bytes"; @@ -50,4 +66,6 @@ public int getValueInBytes() { return valueInBytes; } + public abstract String shortFormat(); + } diff --git a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/ByteAllocationMeasureFormatter.java b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/ByteAllocationMeasureFormatter.java index 740ca9a0..b592bb61 100644 --- a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/ByteAllocationMeasureFormatter.java +++ b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/ByteAllocationMeasureFormatter.java @@ -21,20 +21,44 @@ public class ByteAllocationMeasureFormatter { private ByteAllocationMeasureFormatter() {} - public String format(Allocation allocation) { + public String formatWithAllocationInBytes(Allocation allocation) { if(isByteOrderOfMagnitude(allocation)) { return formatAllocationInBytes(allocation); - } else if(isKiloByteOrderOfMagnitude(allocation)) { - String formattedAllocation = formatAllocationInKiloBytes(allocation); - return formatByteSuffixAllocationValue(formattedAllocation, allocation); - } else if(isMegaByteOrderOfMagnitude(allocation)) { - String formattedAllocation = formatAllocationInMegaBytes(allocation); - return formatByteSuffixAllocationValue(formattedAllocation, allocation); + } + if(isKiloByteOrderOfMagnitude(allocation)) { + return formatAllocationInKiloBytes(allocation) + + formatInByteAllocationBetweenParentheses(allocation); + } + if(isMegaByteOrderOfMagnitude(allocation)) { + return formatAllocationInMegaBytes(allocation) + + formatInByteAllocationBetweenParentheses(allocation); } - String formattedAllocation = formatAllocationInGigaBytes(allocation); - return formatByteSuffixAllocationValue(formattedAllocation, allocation); + return formatAllocationInGigaBytes(allocation) + + formatInByteAllocationBetweenParentheses(allocation); + + } + + public String shortFormat(double allocationInBytes) { + + if(allocationInBytes < 1024) { + return "" + allocationInBytes + " " + AllocationUnit.BYTE.shortFormat(); + } + if(allocationInBytes < 1024 * 1024) { + double kiloByteValue = allocationInBytes / 1024; + String formattedAllocationValue = formatAllocationValue(kiloByteValue); + return formattedAllocationValue + " " + AllocationUnit.KILO_BYTE.shortFormat(); + } + if(allocationInBytes < Math.pow(1024, 3)) { + double megaByteValue = allocationInBytes / Math.pow(1024, 2); + String formattedAllocationValue = formatAllocationValue(megaByteValue); + return formattedAllocationValue + " " + AllocationUnit.MEGA_BYTE.shortFormat(); + } + + double gigaByteValue = allocationInBytes / Math.pow(1024, 3); + String formattedAllocationValue = formatAllocationValue(gigaByteValue); + return formattedAllocationValue + " " + AllocationUnit.GIGA_BYTE.shortFormat(); } @@ -100,11 +124,6 @@ private String formatAllocationValue(double allocationValue) { } - private String formatByteSuffixAllocationValue(String prefix, Allocation allocationValue) - { - return prefix + formatInByteAllocationBetweenParentheses(allocationValue); - } - private String formatInByteAllocationBetweenParentheses(Allocation allocationValue) { DecimalFormat decimalFormat = buildBytePrefixFormatter(); return " (" + decimalFormat.format(allocationValue.getValue()) + " bytes)"; diff --git a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/MaxHeapAllocationPerfVerifier.java b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/MaxHeapAllocationPerfVerifier.java index b16bab40..fff4d16a 100644 --- a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/MaxHeapAllocationPerfVerifier.java +++ b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/MaxHeapAllocationPerfVerifier.java @@ -32,8 +32,8 @@ public PerfIssue verifyPerfIssue(ExpectMaxHeapAllocation annotation, Allocation String assertionMessage = "Expected allocation (test method thread) to be less than " - + byteAllocationMeasureFormatter.format(maxExpectedAllocation) - + " but is " + byteAllocationMeasureFormatter.format(measuredAllocation) + "."; + + byteAllocationMeasureFormatter.formatWithAllocationInBytes(maxExpectedAllocation) + + " but is " + byteAllocationMeasureFormatter.formatWithAllocationInBytes(measuredAllocation) + "."; String description = assertionMessage + System.lineSeparator() + measuredAllocation.getComment(); return new PerfIssue(description); diff --git a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/MeasureHeapAllocationPerfVerifier.java b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/MeasureHeapAllocationPerfVerifier.java index 72afa446..5c58dcaa 100644 --- a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/MeasureHeapAllocationPerfVerifier.java +++ b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/MeasureHeapAllocationPerfVerifier.java @@ -27,7 +27,7 @@ private MeasureHeapAllocationPerfVerifier() { } @Override public PerfIssue verifyPerfIssue(MeasureHeapAllocation annotation, Allocation measuredAllocation) { - String allocationAsString = byteAllocationMeasureFormatter.format(measuredAllocation); + String allocationAsString = byteAllocationMeasureFormatter.formatWithAllocationInBytes(measuredAllocation); PrintWriter pw = new PrintWriter(System.out); pw.printf(annotation.format(), allocationAsString); // do not call close on pw since it will call close on System.out diff --git a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/NoHeapAllocationPerfVerifier.java b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/NoHeapAllocationPerfVerifier.java index f99119d1..563c9a73 100644 --- a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/NoHeapAllocationPerfVerifier.java +++ b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/allocation/NoHeapAllocationPerfVerifier.java @@ -25,13 +25,12 @@ private NoHeapAllocationPerfVerifier() {} private final ByteAllocationMeasureFormatter byteAllocationMeasureFormatter = ByteAllocationMeasureFormatter.INSTANCE; - @Override public PerfIssue verifyPerfIssue(ExpectNoHeapAllocation annotation, Allocation measuredAllocation) { if(!ZERO_ALLOCATION.isEqualTo(measuredAllocation)) { String assertionMessage = - "Expected allocation (test method thread) to be 0 but is " + byteAllocationMeasureFormatter.format(measuredAllocation) + "."; + "Expected allocation (test method thread) to be 0 but is " + byteAllocationMeasureFormatter.formatWithAllocationInBytes(measuredAllocation) + "."; String description = assertionMessage + System.lineSeparator() + measuredAllocation.getComment(); return new PerfIssue(description); } @@ -39,4 +38,5 @@ public PerfIssue verifyPerfIssue(ExpectNoHeapAllocation annotation, Allocation m return PerfIssue.NONE; } + } diff --git a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/DisplayJvmProfilingValueVerifier.java b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/DisplayJvmProfilingValueVerifier.java index 38a380b3..97748213 100644 --- a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/DisplayJvmProfilingValueVerifier.java +++ b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/DisplayJvmProfilingValueVerifier.java @@ -18,16 +18,18 @@ import static org.quickperf.jvm.jmc.value.ProfilingInfo.*; -public class DisplayJvmProfilingValueVerifier implements VerifiablePerformanceIssue { +public class DisplayJvmProfilingValueVerifier implements + VerifiablePerformanceIssue { public static DisplayJvmProfilingValueVerifier INSTANCE = new DisplayJvmProfilingValueVerifier(); private static final String LINE_SEPARATOR = System.lineSeparator(); - private static final String LINE = "-----------------------------------------------------------------------------" + LINE_SEPARATOR; + private static final String LINE = "------------------------------------------------------------------------------" + LINE_SEPARATOR; - private DisplayJvmProfilingValueVerifier() { } + private DisplayJvmProfilingValueVerifier() { + } @Override public PerfIssue verifyPerfIssue(ProfileJvm annotation, JfrEventsMeasure jfrEventsMeasure) { @@ -39,6 +41,7 @@ public PerfIssue verifyPerfIssue(ProfileJvm annotation, JfrEventsMeasure jfrEven String allocationTotal = ALLOCATION_TOTAL.formatAsString(jfrEvents); String insideTlabSum = ALLOC_INSIDE_TLAB_SUM.formatAsString(jfrEvents); String outsideTlabSum = ALLOC_OUTSIDE_TLAB_SUM.formatAsString(jfrEvents); + String allocationRate = ALLOCATION_RATE.formatAsString(jfrEvents); String totalGcPause = TOTAL_GC_PAUSE.formatAsString(jfrEvents); String gcPause = LONGEST_GC_PAUSE.formatAsString(jfrEvents); @@ -64,38 +67,41 @@ public PerfIssue verifyPerfIssue(ProfileJvm annotation, JfrEventsMeasure jfrEven String osVersion = OS_VERSION.formatAsString(jfrEvents); - StringWidthAdapter thirteen = new StringWidthAdapter(13); + StringWidthAdapter twelveLength = new StringWidthAdapter(12); + + StringWidthAdapter fifteenLength = new StringWidthAdapter(15); StringWidthAdapter twentyNineLength = new StringWidthAdapter(29); - StringWidthAdapter twentyEightLength = new StringWidthAdapter(28); + StringWidthAdapter thirtyLength = new StringWidthAdapter(30); String text = - LINE - + " ALLOCATION (estimations)" + " | " + "GARBAGE COLLECTION " + "| THROWABLE" + LINE_SEPARATOR - + " Total : " + thirteen.adapt(allocationTotal) + "| " + twentyNineLength.adapt("Total pause: " + totalGcPause ) + "| Exception: " + exceptionsCount +LINE_SEPARATOR - + " Inside TLAB : " + thirteen.adapt(insideTlabSum) + "| " + twentyNineLength.adapt("Longest GC pause: " + gcPause) + "| Error: " + errorCount + LINE_SEPARATOR - + " Outside TLAB: " + thirteen.adapt(outsideTlabSum) + "| " + twentyNineLength.adapt("") + "| Throwable: " +throwablesCount + LINE_SEPARATOR - + LINE - + twentyEightLength.adapt(" COMPILATION") + "| " + "CODE CACHE" + LINE_SEPARATOR - + twentyEightLength.adapt(" Number: " + compilationsCount) + "| " + codeCacheFullCount + LINE_SEPARATOR - + twentyEightLength.adapt(" Longest: " + longestCompilation) + "| " + LINE_SEPARATOR - + LINE - + " " + "JVM" + LINE_SEPARATOR - + " Name: " + jvmName + LINE_SEPARATOR - + " Version: " + jvmVersion + LINE_SEPARATOR - + " Arguments: " + jvmArguments + LINE_SEPARATOR - + LINE - + " " + "HARDWARE" + LINE_SEPARATOR - + " Hardware threads: " + minHwThreads + LINE_SEPARATOR - + " Cores: " + minNumberOfCores + LINE_SEPARATOR - + " Sockets: " + minNumberOfSockets + LINE_SEPARATOR - + " CPU: " + LINE_SEPARATOR - + cpuDescription + LINE_SEPARATOR - + LINE - + " OS:" + LINE_SEPARATOR - + osVersion + LINE_SEPARATOR - + LINE; + LINE + + " ALLOCATION (estimations)" + " | " + "GARBAGE COLLECTION " + "| THROWABLE" + LINE_SEPARATOR + + " Total : " + fifteenLength.adapt(allocationTotal) + "| " + twentyNineLength.adapt("Total pause: " + totalGcPause) + "| Exception: " + exceptionsCount + LINE_SEPARATOR + + " Inside TLAB : " + fifteenLength.adapt(insideTlabSum) + "| " + twentyNineLength.adapt("Longest GC pause: " + gcPause) + "| Error: " + errorCount + LINE_SEPARATOR + + " Outside TLAB: " + fifteenLength.adapt(outsideTlabSum) + "| " + twentyNineLength.adapt("") + "| Throwable: " + throwablesCount + LINE_SEPARATOR + + " Allocation rate: " + twelveLength.adapt(allocationRate) + "| " + twentyNineLength.adapt("") + "|" + LINE_SEPARATOR + + LINE + + thirtyLength.adapt(" COMPILATION") + "| " + "CODE CACHE" + LINE_SEPARATOR + + thirtyLength.adapt(" Number: " + compilationsCount) + "| " + codeCacheFullCount + LINE_SEPARATOR + + thirtyLength.adapt(" Longest: " + longestCompilation) + "| " + LINE_SEPARATOR + + LINE + + " " + "JVM" + LINE_SEPARATOR + + " Name: " + jvmName + LINE_SEPARATOR + + " Version: " + jvmVersion + LINE_SEPARATOR + + " Arguments: " + jvmArguments + LINE_SEPARATOR + + LINE + + " " + "HARDWARE" + LINE_SEPARATOR + + " Hardware threads: " + minHwThreads + LINE_SEPARATOR + + " Cores: " + minNumberOfCores + LINE_SEPARATOR + + " Sockets: " + minNumberOfSockets + LINE_SEPARATOR + + " CPU: " + LINE_SEPARATOR + + cpuDescription + LINE_SEPARATOR + + LINE + + " OS:" + LINE_SEPARATOR + + osVersion + LINE_SEPARATOR + + LINE; System.out.println(text); diff --git a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/ProfilingInfo.java b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/ProfilingInfo.java index 899275b2..5258f4eb 100644 --- a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/ProfilingInfo.java +++ b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/ProfilingInfo.java @@ -16,6 +16,9 @@ import org.openjdk.jmc.common.item.IItemCollection; import org.openjdk.jmc.common.unit.IQuantity; import org.openjdk.jmc.flightrecorder.jdk.JdkAggregators; +import org.quickperf.jvm.jmc.value.allocationrate.AllocationRate; +import org.quickperf.jvm.jmc.value.allocationrate.AllocationRateFormatter; +import org.quickperf.jvm.jmc.value.allocationrate.AllocationRateRetriever; public enum ProfilingInfo { @@ -271,7 +274,6 @@ public String getLabel() { } , OS_VERSION { - @Override public String formatAsString(IItemCollection jfrEvents) { return formatAsString(jfrEvents, JdkAggregators.OS_VERSION, String.class); @@ -282,8 +284,24 @@ public String getLabel() { return getLabel(JdkAggregators.OS_VERSION, String.class); } - } - ; + }, + ALLOCATION_RATE { + @Override + public String formatAsString(IItemCollection jfrEvents) { + + + AllocationRate allocationRate = AllocationRateRetriever.INSTANCE + .retrieveAllocationRateFrom(jfrEvents); + + return AllocationRateFormatter.INSTANCE.format(allocationRate); + + } + + @Override + public String getLabel() { + return "Allocation Rate"; + } + }; public abstract String formatAsString(IItemCollection jfrEvents); @@ -292,7 +310,7 @@ public String getLabel() { @SuppressWarnings("unchecked") String getLabel(IAggregator aggregator, Class type) { - if(type.isAssignableFrom(IQuantity.class)) { + if (type.isAssignableFrom(IQuantity.class)) { return getLabelFrom(aggregator); } diff --git a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/allocationrate/AllocationRate.java b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/allocationrate/AllocationRate.java new file mode 100644 index 00000000..4c650131 --- /dev/null +++ b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/allocationrate/AllocationRate.java @@ -0,0 +1,37 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License 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. + * + * Copyright 2019-2020 the original author or authors. + */ + +package org.quickperf.jvm.jmc.value.allocationrate; + +public class AllocationRate { + + public static final AllocationRate NONE = new AllocationRate(); + + private AllocationRate() { } + + private long totalAllocationInBytes; + + private double allocationDurationInMs; + + public AllocationRate(long totalAllocationInBytes, double allocationDurationInMs) { + this.totalAllocationInBytes = totalAllocationInBytes; + this.allocationDurationInMs = allocationDurationInMs; + } + + public double getValueInBytesBySecond() { + return (totalAllocationInBytes / allocationDurationInMs) * 1000; + } + + public boolean isNone() { + return this == NONE; + } + +} diff --git a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/allocationrate/AllocationRateFormatter.java b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/allocationrate/AllocationRateFormatter.java new file mode 100644 index 00000000..3666ee50 --- /dev/null +++ b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/allocationrate/AllocationRateFormatter.java @@ -0,0 +1,38 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License 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. + * + * Copyright 2019-2020 the original author or authors. + */ + +package org.quickperf.jvm.jmc.value.allocationrate; + +import org.quickperf.jvm.allocation.ByteAllocationMeasureFormatter; + +public class AllocationRateFormatter { + + public static final AllocationRateFormatter INSTANCE = new AllocationRateFormatter(); + + private final ByteAllocationMeasureFormatter byteAllocationMeasureFormatter = ByteAllocationMeasureFormatter.INSTANCE; + + private AllocationRateFormatter() { } + + public String format(AllocationRate allocationRate) { + + if (allocationRate.isNone()) { + return " "; + } + + double allocationRateInBytesPerSecond = allocationRate.getValueInBytesBySecond(); + + return byteAllocationMeasureFormatter.shortFormat(allocationRateInBytesPerSecond) + "/s"; + + } + + + +} diff --git a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/allocationrate/AllocationRateRetriever.java b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/allocationrate/AllocationRateRetriever.java new file mode 100644 index 00000000..74562e7f --- /dev/null +++ b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/jmc/value/allocationrate/AllocationRateRetriever.java @@ -0,0 +1,110 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License 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. + * + * Copyright 2019-2020 the original author or authors. + */ + +package org.quickperf.jvm.jmc.value.allocationrate; + +import org.openjdk.jmc.common.item.*; +import org.openjdk.jmc.common.unit.IQuantity; +import org.openjdk.jmc.common.unit.QuantityConversionException; +import org.openjdk.jmc.common.unit.UnitLookup; +import org.openjdk.jmc.flightrecorder.JfrAttributes; +import org.openjdk.jmc.flightrecorder.jdk.JdkAggregators; +import org.openjdk.jmc.flightrecorder.jdk.JdkFilters; + +public class AllocationRateRetriever { + + public static final AllocationRateRetriever INSTANCE = new AllocationRateRetriever(); + + private AllocationRateRetriever() { } + + public AllocationRate retrieveAllocationRateFrom(IItemCollection jfrEvents) { + + long allocationDurationInMs; + try { + allocationDurationInMs = computeAllocationDurationInMs(jfrEvents); + } catch (QuantityConversionException e) { + return AllocationRate.NONE; + } + + if (allocationDurationInMs == 0) { + return AllocationRate.NONE; + } + + long totalAllocationInBytes = computeTotalAllocationInBytes(jfrEvents); + + return new AllocationRate(totalAllocationInBytes, allocationDurationInMs); + } + + + private long computeTotalAllocationInBytes(IItemCollection jfrEvents) { + IQuantity totalAlloc = jfrEvents.getAggregate(JdkAggregators.ALLOCATION_TOTAL); + return totalAlloc.longValue(); + } + + private long computeAllocationDurationInMs(IItemCollection jfrEvents) throws QuantityConversionException { + + IItemCollection insideTlab = jfrEvents.apply(JdkFilters.ALLOC_INSIDE_TLAB); + IItemCollection outsideTlab = jfrEvents.apply(JdkFilters.ALLOC_OUTSIDE_TLAB); + + return searchMaxTimeStampInMs(insideTlab, outsideTlab) + - searchMinTimeStampInMs(insideTlab, outsideTlab); + + } + + private long searchMaxTimeStampInMs(IItemCollection insideTlab, IItemCollection outsideTlab) throws QuantityConversionException { + long insideTlabMaxTimeStamp = computeMaxTimeStampInMs(insideTlab); + long outsideTlabMaxTimeStamp = computeMaxTimeStampInMs(outsideTlab); + return Math.max(insideTlabMaxTimeStamp, outsideTlabMaxTimeStamp); + } + + private long searchMinTimeStampInMs(IItemCollection insideTlab, IItemCollection outsideTlab) throws QuantityConversionException { + long insideTlabMinTimeStamp = computeMinTimeStampInMs(insideTlab); + long outsideTlabMinTimeStamp = computeMinTimeStampInMs(outsideTlab); + return Math.min(insideTlabMinTimeStamp, outsideTlabMinTimeStamp); + } + + private long computeMinTimeStampInMs(IItemCollection allocationEvents) + throws ArithmeticException, QuantityConversionException { + long minTimeStamp = Long.MAX_VALUE; + for (IItemIterable jfrEventCollection : allocationEvents) { + for (IItem item : jfrEventCollection) { + long currentTimeStamp = getTimeStampInMs(item); + minTimeStamp = Math.min(minTimeStamp, currentTimeStamp); + } + } + return minTimeStamp; + } + + private long computeMaxTimeStampInMs(IItemCollection allocationEvents) + throws ArithmeticException, QuantityConversionException { + long maxTimeStamp = 0; + for (IItemIterable jfrEventCollection : allocationEvents) { + for (IItem item : jfrEventCollection) { + long currentTimeStamp = getTimeStampInMs(item); + maxTimeStamp = Math.max(maxTimeStamp, currentTimeStamp); + } + } + return maxTimeStamp; + } + + @SuppressWarnings("unchecked") + private long getTimeStampInMs(IItem allocationEvent) throws ArithmeticException, QuantityConversionException { + + IType type = (IType) allocationEvent.getType(); + + IMemberAccessor endTimeAccessor = JfrAttributes.END_TIME.getAccessor(type); + IQuantity quantityEndTime = endTimeAccessor.getMember(allocationEvent); + + return quantityEndTime.longValueIn(UnitLookup.EPOCH_MS); + + } + +} diff --git a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/rss/ExpectMaxRssPerfVerifier.java b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/rss/ExpectMaxRssPerfVerifier.java index b47245d0..4e821861 100644 --- a/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/rss/ExpectMaxRssPerfVerifier.java +++ b/jvm/jvm-annotations/src/main/java/org/quickperf/jvm/rss/ExpectMaxRssPerfVerifier.java @@ -36,8 +36,8 @@ public PerfIssue verifyPerfIssue(ExpectMaxRSS annotation, ProcessStatus processS String assertionMessage = "Expected RSS to be less than " - + byteAllocationMeasureFormatter.format(maxExpectedRss) - + " but is " + byteAllocationMeasureFormatter.format(measuredRss) + "."; + + byteAllocationMeasureFormatter.formatWithAllocationInBytes(maxExpectedRss) + + " but is " + byteAllocationMeasureFormatter.formatWithAllocationInBytes(measuredRss) + "."; String description = assertionMessage + System.lineSeparator() + measuredRss.getComment(); return new PerfIssue(description); diff --git a/jvm/jvm-annotations/src/test/java/org/quickperf/jvm/ByteAllocationMeasureFormatterTest.java b/jvm/jvm-annotations/src/test/java/org/quickperf/jvm/ByteAllocationMeasureFormatterTest.java index e858b607..67f1f89c 100644 --- a/jvm/jvm-annotations/src/test/java/org/quickperf/jvm/ByteAllocationMeasureFormatterTest.java +++ b/jvm/jvm-annotations/src/test/java/org/quickperf/jvm/ByteAllocationMeasureFormatterTest.java @@ -30,7 +30,7 @@ public class ByteAllocationMeasureFormatterTest { Allocation allocation = new Allocation(oneHundredKiloBytes, AllocationUnit.BYTE, ""); // WHEN - String formattedAllocation = byteAllocationMeasureFormatter.format(allocation); + String formattedAllocation = byteAllocationMeasureFormatter.formatWithAllocationInBytes(allocation); // THEN assertThat(formattedAllocation).isEqualTo("100.0 Kilo bytes (102 400 bytes)"); @@ -45,7 +45,7 @@ public class ByteAllocationMeasureFormatterTest { Allocation allocation = new Allocation(oneMegaBytes, AllocationUnit.BYTE, ""); // WHEN - String formattedAllocation = byteAllocationMeasureFormatter.format(allocation); + String formattedAllocation = byteAllocationMeasureFormatter.formatWithAllocationInBytes(allocation); // THEN assertThat(formattedAllocation).isEqualTo("1.0 Mega bytes (1 048 576 bytes)"); @@ -60,7 +60,7 @@ public class ByteAllocationMeasureFormatterTest { Allocation allocation = new Allocation(oneHundredMegaBytes, AllocationUnit.BYTE, ""); // WHEN - String formattedAllocation = byteAllocationMeasureFormatter.format(allocation); + String formattedAllocation = byteAllocationMeasureFormatter.formatWithAllocationInBytes(allocation); // THEN assertThat(formattedAllocation).isEqualTo("100.0 Mega bytes (104 857 600 bytes)"); @@ -75,7 +75,7 @@ public class ByteAllocationMeasureFormatterTest { Allocation allocation = new Allocation(oneGigaBytes, AllocationUnit.BYTE, ""); // WHEN - String formattedAllocation = byteAllocationMeasureFormatter.format(allocation); + String formattedAllocation = byteAllocationMeasureFormatter.formatWithAllocationInBytes(allocation); // THEN assertThat(formattedAllocation).isEqualTo("1.0 Giga bytes (1 073 741 824 bytes)"); @@ -90,7 +90,7 @@ public class ByteAllocationMeasureFormatterTest { Allocation allocation = new Allocation(tenGigaBytes, AllocationUnit.BYTE, ""); // WHEN - String formattedAllocation = byteAllocationMeasureFormatter.format(allocation); + String formattedAllocation = byteAllocationMeasureFormatter.formatWithAllocationInBytes(allocation); // THEN assertThat(formattedAllocation).isEqualTo("10.0 Giga bytes (10 737 418 240 bytes)"); @@ -105,7 +105,7 @@ public class ByteAllocationMeasureFormatterTest { Allocation allocation = new Allocation(oneHundredGigaBytes, AllocationUnit.BYTE, ""); // WHEN - String formattedAllocation = byteAllocationMeasureFormatter.format(allocation); + String formattedAllocation = byteAllocationMeasureFormatter.formatWithAllocationInBytes(allocation); // THEN assertThat(formattedAllocation).isEqualTo("100.0 Giga bytes (107 374 182 400 bytes)"); @@ -120,7 +120,7 @@ public class ByteAllocationMeasureFormatterTest { Allocation allocation = new Allocation(lessThanAKilobyte, AllocationUnit.BYTE, ""); // WHEN - String formattedAllocation = byteAllocationMeasureFormatter.format(allocation); + String formattedAllocation = byteAllocationMeasureFormatter.formatWithAllocationInBytes(allocation); // THEN assertThat(formattedAllocation).isEqualTo("1023.0 bytes"); @@ -135,7 +135,7 @@ public class ByteAllocationMeasureFormatterTest { Allocation allocation = new Allocation(exactlyOneKilobyte, AllocationUnit.BYTE, ""); // WHEN - String formattedAllocation = byteAllocationMeasureFormatter.format(allocation); + String formattedAllocation = byteAllocationMeasureFormatter.formatWithAllocationInBytes(allocation); // THEN assertThat(formattedAllocation).isEqualTo("1.0 Kilo bytes (1 024 bytes)"); diff --git a/jvm/jvm-annotations/src/test/java/org/quickperf/jvm/jmc/value/AllocationRateTest.java b/jvm/jvm-annotations/src/test/java/org/quickperf/jvm/jmc/value/AllocationRateTest.java new file mode 100644 index 00000000..764d6ae7 --- /dev/null +++ b/jvm/jvm-annotations/src/test/java/org/quickperf/jvm/jmc/value/AllocationRateTest.java @@ -0,0 +1,198 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License 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. + * + * Copyright 2019-2020 the original author or authors. + */ + +package org.quickperf.jvm.jmc.value; + +import org.junit.Before; +import org.junit.Test; +import org.openjdk.jmc.common.item.*; +import org.openjdk.jmc.common.unit.IQuantity; +import org.openjdk.jmc.common.unit.QuantityConversionException; +import org.openjdk.jmc.common.unit.StructContentType; +import org.openjdk.jmc.common.unit.UnitLookup; +import org.openjdk.jmc.flightrecorder.jdk.JdkFilters; + +import java.util.Iterator; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class AllocationRateTest { + + private IItemCollection mockedJfrEvents; + private Iterator mockedJfrEventsIterator; + private IItemIterable mockedAllocationEvents; + private Iterator mockedAllocationEventsIterator; + private IQuantity mockedTotalAlloc; + private IItem mockedEvent; + private IItem mockedEvent2; + private IItem mockedEvent3; + private StructContentType mockedIType; + private IMemberAccessor mockedIMemberAccessor; + private IQuantity mockedIQuantity; + + /** + * Set up an IItemCollection of jfr events. + *

+ * Set up the jfrEvents with a total allocation value of 1 KiB. + *

+ * The first three values returned from mockedIQuantity.longValueIn() are used in minTimeStamp() + * of allocation events inside Tlab.The next three elements are used in minTimeStamp() of + * allocation events outside Tlab. + *

+ * The next three elements are used in maxTimeStamp() of allocation events inside Tlab. The next + * three elements are used in maxTimeStamp() of allocation events outside Tlab. + *

+ * Example for test 1: + *

+ * minInside = min(1000,2000,3000) + * minOutside = min(10_000, 10_000, 11_000) + * min of both = 1000 + *

+ * maxInside = max(1000,2000,3000) + * maxOutside = max(10_000, 10_000, 11_000) + * max of both = 11_000 + *

+ * duration = 11_000 - 1000 = 10_000 ms + *

+ * Allocation rate = 1024 bytes / 10 seconds + * 102.4 KiB/s + *

+ */ + + @Before + @SuppressWarnings("unchecked") + public void setUpIItemCollection() throws QuantityConversionException { + mockedJfrEvents = mock(IItemCollection.class); + mockedJfrEventsIterator = mock(Iterator.class); + mockedTotalAlloc = mock(IQuantity.class); + mockedAllocationEvents = mock(IItemIterable.class); + mockedAllocationEventsIterator = mock(Iterator.class); + mockedEvent = mock(IItem.class); + mockedEvent2 = mock(IItem.class); + mockedEvent3 = mock(IItem.class); + mockedIType = mock(StructContentType.class); + mockedIMemberAccessor = mock(IMemberAccessor.class); + mockedIQuantity = mock(IQuantity.class); + + when(mockedJfrEvents.hasItems()).thenReturn(true); + + when(mockedTotalAlloc.longValue()).thenReturn(1024L); + + when(mockedJfrEvents.getAggregate(any(IAggregator.class))).thenReturn(mockedTotalAlloc); + + when(mockedJfrEvents.apply(JdkFilters.ALLOC_INSIDE_TLAB)).thenReturn(mockedJfrEvents); + when(mockedJfrEvents.apply(JdkFilters.ALLOC_OUTSIDE_TLAB)).thenReturn(mockedJfrEvents); + + when(mockedJfrEvents.iterator()) + .thenReturn(mockedJfrEventsIterator, mockedJfrEventsIterator, mockedJfrEventsIterator, + mockedJfrEventsIterator); + when(mockedJfrEventsIterator.next()) + .thenReturn(mockedAllocationEvents, mockedAllocationEvents, mockedAllocationEvents, + mockedAllocationEvents); + when(mockedJfrEventsIterator.hasNext()) + .thenReturn(true, false, true, false, true, false, true, false); + + when(mockedAllocationEvents.iterator()) + .thenReturn(mockedAllocationEventsIterator, mockedAllocationEventsIterator, + mockedAllocationEventsIterator, + mockedAllocationEventsIterator); + when(mockedAllocationEventsIterator.next()) + .thenReturn(mockedEvent, mockedEvent2, mockedEvent3, mockedEvent, mockedEvent2, + mockedEvent3, mockedEvent, mockedEvent2, mockedEvent3); + when(mockedAllocationEventsIterator.hasNext()) + .thenReturn(true, true, true, false, true, true, true, false, true, true, true, false, true, + true, true, false); + + when(mockedEvent.getType()).thenReturn(mockedIType); + when(mockedEvent2.getType()).thenReturn(mockedIType); + when(mockedEvent3.getType()).thenReturn(mockedIType); + + when(mockedIType.getAccessor(any(IAccessorKey.class))).thenReturn(mockedIMemberAccessor); + + when(mockedIMemberAccessor.getMember(any(IItem.class))).thenReturn(mockedIQuantity); + + when(mockedIQuantity.longValueIn(UnitLookup.EPOCH_MS)) + .thenReturn(1000L, 2000L, 3000L, 10_000L, 10_000L, 11_000L, 1000L, 2000L, 3000L, 10000L, + 11000L, 11000L); + } + + /** + * 1 KiB over 10 seconds. + */ + @Test + public void should_format_100_bytes_per_second_as_string() { + + when(mockedTotalAlloc.longValue()).thenReturn(1024L); + + assertThat(ProfilingInfo.ALLOCATION_RATE.formatAsString(mockedJfrEvents)) + .isEqualTo("102.4 bytes/s"); + + } + + /** + * 1 MiB over 10 seconds. + */ + @Test + public void should_format_100_ki_b_per_second_as_string() { + + when(mockedTotalAlloc.longValue()).thenReturn(1024L * 1024L); + + assertThat(ProfilingInfo.ALLOCATION_RATE.formatAsString(mockedJfrEvents)) + .isEqualTo("102.4 KiB/s"); + + } + + /** + * 1 GiB over 10 seconds. + */ + @Test + public void should_format_100_mi_b_per_second_as_string() { + + when(mockedTotalAlloc.longValue()).thenReturn((long) Math.pow(1024L, 3)); + + assertThat(ProfilingInfo.ALLOCATION_RATE.formatAsString(mockedJfrEvents)) + .isEqualTo("102.4 MiB/s"); + + } + + /** + * 1 TiB over 10 seconds. + */ + @Test + public void should_format_100_giga_bytes_per_second_as_string() { + + when(mockedTotalAlloc.longValue()).thenReturn((long) Math.pow(1024, 4)); + + assertThat(ProfilingInfo.ALLOCATION_RATE.formatAsString(mockedJfrEvents)) + .isEqualTo("102.4 GiB/s"); + + } + + /** + * Difference between allocation time stamps is zero, therefore the rate should return " ". + */ + @Test + public void should_return_an_empty_string_if_all_zero_time_stamps() throws QuantityConversionException { + + when(mockedTotalAlloc.longValue()).thenReturn(1000L); + when(mockedIQuantity.longValueIn(UnitLookup.EPOCH_MS)) + .thenReturn(0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, + 0L); + + assertThat(ProfilingInfo.ALLOCATION_RATE.formatAsString(mockedJfrEvents)) + .isEqualTo(" "); + + } + +} \ No newline at end of file diff --git a/testng/testng-jvm-test/src/test/java/org/quickperf/testng/jvm/jmc/AllocationRateProfileJVMTest.java b/testng/testng-jvm-test/src/test/java/org/quickperf/testng/jvm/jmc/AllocationRateProfileJVMTest.java new file mode 100644 index 00000000..d4a661cc --- /dev/null +++ b/testng/testng-jvm-test/src/test/java/org/quickperf/testng/jvm/jmc/AllocationRateProfileJVMTest.java @@ -0,0 +1,22 @@ +package org.quickperf.testng.jvm.jmc; + +import org.quickperf.jvm.annotations.ProfileJvm; +import org.testng.annotations.Test; + +public class AllocationRateProfileJVMTest { + + @ProfileJvm + @Test + public void allocationRateOutputFormattedWithProfileJVMAnnotation() { + for (int i = 0; i < 1_000_000; i++) { + int[] arr = new int[256]; + } + } + + @ProfileJvm + @Test + public void allocationRateProfileJVMAnnotationNoAllocationInMethod() { + //nothing + } + +}