summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordanielhanrahan <daniel.hanrahan@est.tech>2023-11-15 13:21:34 +0000
committerdanielhanrahan <daniel.hanrahan@est.tech>2023-11-22 15:28:47 +0000
commitc6dfb03f66eb4573c6fcea761e2414aadaaf7878 (patch)
treefbd947570136320e6908b13a3dcca46fe2388b30
parente1f12d7e903c6bb3071f2848c939ccb4afb939ba (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.groovy83
-rw-r--r--integration-test/src/test/java/org/onap/cps/integration/ResourceMeter.java37
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);
}
}