diff options
author | danielhanrahan <daniel.hanrahan@est.tech> | 2023-11-15 13:21:34 +0000 |
---|---|---|
committer | danielhanrahan <daniel.hanrahan@est.tech> | 2023-11-22 15:28:47 +0000 |
commit | c6dfb03f66eb4573c6fcea761e2414aadaaf7878 (patch) | |
tree | fbd947570136320e6908b13a3dcca46fe2388b30 | |
parent | e1f12d7e903c6bb3071f2848c939ccb4afb939ba (diff) |
Make performance tests measure PEAK memory usage
Presently, performance tests measure CURRENT memory usage instead of
PEAK memory usage, leading to under-reporting if garbage collector
runs during a test. This patch fixes it, so that memory reported will
now be at least the memory of live objects at that time.
- Add tests for ResourceMeter class
- ResourceMeter measures peak memory usage instead of current
Issue-ID: CPS-1967
Signed-off-by: danielhanrahan <daniel.hanrahan@est.tech>
Change-Id: I36e9ea2196420b84877ecabc1b7331c5d3e2e252
-rw-r--r-- | integration-test/src/test/groovy/org/onap/cps/integration/ResourceMeterPerfTest.groovy | 83 | ||||
-rw-r--r-- | integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java | 37 |
2 files changed, 115 insertions, 5 deletions
diff --git a/integration-test/src/test/groovy/org/onap/cps/integration/ResourceMeterPerfTest.groovy b/integration-test/src/test/groovy/org/onap/cps/integration/ResourceMeterPerfTest.groovy new file mode 100644 index 0000000000..c42bfd7be6 --- /dev/null +++ b/integration-test/src/test/groovy/org/onap/cps/integration/ResourceMeterPerfTest.groovy @@ -0,0 +1,83 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.cps.integration + +import java.util.concurrent.TimeUnit +import spock.lang.Specification + +class ResourceMeterPerfTest extends Specification { + + final int MEGABYTE = 1_000_000 + + def resourceMeter = new ResourceMeter() + + def 'ResourceMeter accurately measures duration'() { + when: 'we measure how long a known operation takes' + resourceMeter.start() + TimeUnit.SECONDS.sleep(2) + resourceMeter.stop() + then: 'ResourceMeter reports a duration within 10ms of the expected duration' + assert resourceMeter.getTotalTimeInSeconds() >= 2 + assert resourceMeter.getTotalTimeInSeconds() <= 2.01 + } + + def 'ResourceMeter reports memory usage when allocating a large byte array'() { + when: 'the resource meter is started' + resourceMeter.start() + and: 'some memory is allocated' + byte[] array = new byte[50 * MEGABYTE] + and: 'the resource meter is stopped' + resourceMeter.stop() + then: 'the reported memory usage is close to the amount of memory allocated' + assert resourceMeter.getTotalMemoryUsageInMB() >= 50 + assert resourceMeter.getTotalMemoryUsageInMB() <= 55 + } + + def 'ResourceMeter measures PEAK memory usage when garbage collector runs'() { + when: 'the resource meter is started' + resourceMeter.start() + and: 'some memory is allocated' + byte[] array = new byte[50 * MEGABYTE] + and: 'the memory is garbage collected' + array = null + ResourceMeter.performGcAndWait() + and: 'the resource meter is stopped' + resourceMeter.stop() + then: 'the reported memory usage is close to the peak amount of memory allocated' + assert resourceMeter.getTotalMemoryUsageInMB() >= 50 + assert resourceMeter.getTotalMemoryUsageInMB() <= 55 + } + + def 'ResourceMeter measures memory increase only during measurement'() { + given: '50 megabytes is allocated before measurement' + byte[] arrayBefore = new byte[50 * MEGABYTE] + when: 'memory is allocated during measurement' + resourceMeter.start() + byte[] arrayDuring = new byte[40 * MEGABYTE] + resourceMeter.stop() + and: '50 megabytes is allocated after measurement' + byte[] arrayAfter = new byte[50 * MEGABYTE] + then: 'the reported memory usage is close to the amount allocated DURING measurement' + assert resourceMeter.getTotalMemoryUsageInMB() >= 40 + assert resourceMeter.getTotalMemoryUsageInMB() <= 45 + } + +} diff --git a/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java b/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java index c7d96c4c2b..f8a2ecb4df 100644 --- a/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java +++ b/integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java @@ -20,6 +20,10 @@ package org.onap.cps.integration; +import java.lang.management.GarbageCollectorMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryType; import org.springframework.util.StopWatch; /** @@ -34,8 +38,9 @@ public class ResourceMeter { * Start measurement. */ public void start() { - System.gc(); - memoryUsedBefore = getCurrentMemoryUsage(); + performGcAndWait(); + resetPeakHeapUsage(); + memoryUsedBefore = getPeakHeapUsage(); stopWatch.start(); } @@ -44,7 +49,7 @@ public class ResourceMeter { */ public void stop() { stopWatch.stop(); - memoryUsedAfter = getCurrentMemoryUsage(); + memoryUsedAfter = getPeakHeapUsage(); } /** @@ -63,8 +68,30 @@ public class ResourceMeter { return (memoryUsedAfter - memoryUsedBefore) / 1_000_000.0; } - private static long getCurrentMemoryUsage() { - return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); + static void performGcAndWait() { + final long gcCountBefore = getGcCount(); + System.gc(); + while (getGcCount() == gcCountBefore) {} + } + + private static long getGcCount() { + return ManagementFactory.getGarbageCollectorMXBeans().stream() + .mapToLong(GarbageCollectorMXBean::getCollectionCount) + .filter(gcCount -> gcCount != -1) + .sum(); + } + + private static long getPeakHeapUsage() { + return ManagementFactory.getMemoryPoolMXBeans().stream() + .filter(pool -> pool.getType() == MemoryType.HEAP) + .mapToLong(pool -> pool.getPeakUsage().getUsed()) + .sum(); + } + + private static void resetPeakHeapUsage() { + ManagementFactory.getMemoryPoolMXBeans().stream() + .filter(pool -> pool.getType() == MemoryType.HEAP) + .forEach(MemoryPoolMXBean::resetPeakUsage); } } |