From 5e9f11d3df3c1fdd4beea1014ca5fae990588d23 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Wed, 26 Jul 2023 13:50:11 +0200 Subject: [PATCH 01/45] Added new dependencies for storage measurements (#23). --- jamopp.tests/pom.xml | 8 ++++++++ pom.xml | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/jamopp.tests/pom.xml b/jamopp.tests/pom.xml index 5610d278..67676e8f 100644 --- a/jamopp.tests/pom.xml +++ b/jamopp.tests/pom.xml @@ -123,5 +123,13 @@ org.apache.commons commons-compress + + commons-io + commons-io + + + org.eclipse.emfcloud + emfjson-jackson + diff --git a/pom.xml b/pom.xml index 011e4ef9..0358f1ef 100644 --- a/pom.xml +++ b/pom.xml @@ -148,6 +148,16 @@ commons-compress 1.23.0 + + commons-io + commons-io + 2.13.0 + + + org.eclipse.emfcloud + emfjson-jackson + 2.2.0 + From 3c86d0d2f732729d806dc33e2c5176f3b07cc0a5 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Wed, 26 Jul 2023 14:35:10 +0200 Subject: [PATCH 02/45] Extended the performance data with storage data (#23). --- .../test/performance/PerformanceData.java | 24 +++++-- .../test/performance/StoragePerformance.java | 62 +++++++++++++++++++ 2 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/StoragePerformance.java diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceData.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceData.java index 361b382f..af78d51d 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceData.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceData.java @@ -19,6 +19,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.List; import com.google.gson.Gson; @@ -27,13 +28,28 @@ */ public class PerformanceData { private ArrayList points = new ArrayList<>(); + private ArrayList storage = new ArrayList<>(); - public ArrayList getPoints() { - return points; + public List getPoints() { + return (List) points.clone(); + } + + public void addPoint(PerformanceDataPoint newPoint) { + this.points.add(newPoint); } - public void setPoints(ArrayList points) { - this.points = points; + public void setPoints(List points) { + this.points.clear(); + this.points.addAll(points); + } + + public List getStorage() { + return (List) storage.clone(); + } + + public void setStorage(List storage) { + this.storage.clear(); + this.storage.addAll(storage); } public double getAverageParseTime() { diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/StoragePerformance.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/StoragePerformance.java new file mode 100644 index 00000000..8098e63c --- /dev/null +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/StoragePerformance.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2023, Martin Armbruster + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Martin Armbruster + * - Initial implementation + ******************************************************************************/ + +package tools.mdsd.jamopp.test.performance; + +public class StoragePerformance { + private String id; + private long takenStorageByCodeFiles; + private long takenStorage; + private long overallFiles; + private long codeFiles; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public long getTakenStorageByCodeFiles() { + return takenStorageByCodeFiles; + } + + public void setTakenStorageByCodeFiles(long takenStorageByCodeFiles) { + this.takenStorageByCodeFiles = takenStorageByCodeFiles; + } + + public long getTakenStorage() { + return takenStorage; + } + + public void setTakenStorage(long takenStorage) { + this.takenStorage = takenStorage; + } + + public long getOverallFiles() { + return overallFiles; + } + + public void setOverallFiles(long overallFiles) { + this.overallFiles = overallFiles; + } + + public long getCodeFiles() { + return codeFiles; + } + + public void setCodeFiles(long codeFiles) { + this.codeFiles = codeFiles; + } +} From 2ed8d2bce227658489b9ce12100c7fb09c059a91 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Thu, 27 Jul 2023 21:47:16 +0200 Subject: [PATCH 03/45] Extracted and generalized the output of Java models in the XMI format (#23). --- .../tools/mdsd/jamopp/test/OutputUtility.java | 83 +++++++++++++++++++ .../test/xmi/JavaXMISerializationTest.java | 59 +------------ 2 files changed, 85 insertions(+), 57 deletions(-) create mode 100644 jamopp.tests/src/test/java/tools/mdsd/jamopp/test/OutputUtility.java diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/OutputUtility.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/OutputUtility.java new file mode 100644 index 00000000..6719a0e4 --- /dev/null +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/OutputUtility.java @@ -0,0 +1,83 @@ +package tools.mdsd.jamopp.test; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.emf.common.util.URI; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; +import org.eclipse.emf.ecore.xmi.XMIResource; + +import tools.mdsd.jamopp.model.java.containers.CompilationUnit; +import tools.mdsd.jamopp.model.java.containers.JavaRoot; +import tools.mdsd.jamopp.model.java.containers.Package; + +public class OutputUtility { + public record TransferResult(ResourceSet targetSet, Map sourceTargetMapping) {}; + + public static TransferResult transferToOutput(ResourceSet sourceSet, String outputFolder, String fileExtension, boolean includeAllResources) { + int emptyFileName = 0; + + ResourceSet targetSet = new ResourceSetImpl(); + HashMap srcTrgMap = new HashMap<>(); + + for (Resource javaResource : new ArrayList<>(sourceSet.getResources())) { + if (javaResource.getContents().isEmpty()) { + System.out.println("WARNING: Emtpy Resource: " + javaResource.getURI()); + continue; + } + if (!includeAllResources && !javaResource.getURI().isFile()) { + continue; + } + + JavaRoot root = (JavaRoot) javaResource.getContents().get(0); + String outputFileName = "ERROR"; + if (root instanceof CompilationUnit cu) { + outputFileName = cu.getNamespacesAsString().replace(".", File.separator) + File.separator; + if (cu.getClassifiers().size() > 0) { + outputFileName += cu.getClassifiers().get(0).getName(); + } else { + outputFileName += emptyFileName++; + } + } else if (root instanceof Package) { + outputFileName = root.getNamespacesAsString() + .replace(".", File.separator) + File.separator + "package-info"; + if (outputFileName.startsWith(File.separator)) { + outputFileName = outputFileName.substring(1); + } + } else if (root instanceof tools.mdsd.jamopp.model.java.containers.Module) { + outputFileName = root.getNamespacesAsString() + .replace(".", File.separator) + File.separator + "module-info"; + } else { + fail(); + } + + File outputFile = new File("." + File.separator + outputFolder + + File.separator + outputFileName); + URI fileURI = URI.createFileURI(outputFile.getAbsolutePath()).appendFileExtension(fileExtension); + + Resource targetResource = targetSet.createResource(fileURI); + if (targetResource instanceof XMIResource xmiResource) { + xmiResource.setEncoding(StandardCharsets.UTF_8.toString()); + } + targetResource.getContents().addAll(javaResource.getContents()); + srcTrgMap.put(javaResource, targetResource); + } + + for (Resource targetResource : targetSet.getResources()) { + try { + targetResource.save(targetSet.getLoadOptions()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + return new TransferResult(targetSet, srcTrgMap); + } +} diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/xmi/JavaXMISerializationTest.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/xmi/JavaXMISerializationTest.java index 324687d4..ba44bb19 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/xmi/JavaXMISerializationTest.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/xmi/JavaXMISerializationTest.java @@ -21,30 +21,24 @@ import static org.junit.jupiter.api.Assertions.fail; import java.io.File; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.Resource.Diagnostic; import org.eclipse.emf.ecore.resource.ResourceSet; -import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.ecore.util.EcoreUtil.EqualityHelper; -import org.eclipse.emf.ecore.xmi.XMIResource; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import tools.mdsd.jamopp.model.java.containers.CompilationUnit; -import tools.mdsd.jamopp.model.java.containers.JavaRoot; -import tools.mdsd.jamopp.model.java.containers.Package; import tools.mdsd.jamopp.options.ParserOptions; import tools.mdsd.jamopp.parser.jdt.singlefile.JaMoPPJDTSingleFileParser; import tools.mdsd.jamopp.test.AbstractJaMoPPTests; +import tools.mdsd.jamopp.test.OutputUtility; public class JavaXMISerializationTest extends AbstractJaMoPPTests { @@ -88,56 +82,7 @@ public void testXMISerialization() throws Exception { } protected ResourceSet transferToXMI(ResourceSet sourceSet, boolean includeAllResources) throws Exception { - int emptyFileName = 0; - - ResourceSet targetSet = new ResourceSetImpl(); - - for (Resource javaResource : new ArrayList<>(sourceSet.getResources())) { - if (javaResource.getContents().isEmpty()) { - System.out.println("WARNING: Emtpy Resource: " + javaResource.getURI()); - continue; - } - if (!includeAllResources && !javaResource.getURI().isFile()) { - continue; - } - JavaRoot root = (JavaRoot) javaResource.getContents().get(0); - String outputFileName = "ERROR"; - if (root instanceof CompilationUnit) { - outputFileName = root.getNamespacesAsString().replace(".", File.separator) + File.separator; - CompilationUnit cu = (CompilationUnit) root; - if (cu.getClassifiers().size() > 0) { - outputFileName += cu.getClassifiers().get(0).getName(); - } else { - outputFileName += emptyFileName++; - } - - } else if (root instanceof Package) { - outputFileName = root.getNamespacesAsString() - .replace(".", File.separator) + File.separator + "package-info"; - if (outputFileName.startsWith(File.separator)) { - outputFileName = outputFileName.substring(1); - } - } else if (root instanceof tools.mdsd.jamopp.model.java.containers.Module) { - outputFileName = root.getNamespacesAsString() - .replace(".", File.separator) + File.separator + "module-info"; - } else { - fail(); - } - File outputFile = new File("." + File.separator + TEST_OUTPUT_FOLDER_NAME - + File.separator + outputFileName); - URI xmiFileURI = URI.createFileURI(outputFile.getAbsolutePath()).appendFileExtension("xmi"); - XMIResource xmiResource = (XMIResource) targetSet.createResource(xmiFileURI); - xmiResource.setEncoding(StandardCharsets.UTF_8.toString()); - xmiResource.getContents().addAll(javaResource.getContents()); - } - for (Resource xmiResource : targetSet.getResources()) { - try { - xmiResource.save(targetSet.getLoadOptions()); - } catch (Exception e) { - e.printStackTrace(); - } - } - return targetSet; + return OutputUtility.transferToOutput(sourceSet, TEST_OUTPUT_FOLDER_NAME, "xmi", includeAllResources).targetSet(); } protected void compare(ResourceSet rs) throws Exception { From f477804a15197edb7c658dbc6d9fa0cd3bc98f5a Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Sun, 30 Jul 2023 14:14:46 +0200 Subject: [PATCH 04/45] Extended the performance test to also calculate the taken storage for different output formats (#23). --- .../tools/mdsd/jamopp/test/OutputUtility.java | 2 +- .../test/performance/PerformanceTest.java | 122 ++++++++++++++++-- 2 files changed, 109 insertions(+), 15 deletions(-) diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/OutputUtility.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/OutputUtility.java index 6719a0e4..e13569df 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/OutputUtility.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/OutputUtility.java @@ -70,7 +70,7 @@ public static TransferResult transferToOutput(ResourceSet sourceSet, String outp srcTrgMap.put(javaResource, targetResource); } - for (Resource targetResource : targetSet.getResources()) { + for (Resource targetResource : new ArrayList<>(targetSet.getResources())) { try { targetResource.save(targetSet.getLoadOptions()); } catch (Exception e) { diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java index 62806485..aa91f6c9 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java @@ -21,12 +21,19 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashSet; +import java.util.List; import java.util.Set; + import org.apache.logging.log4j.Logger; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.file.PathUtils; import org.apache.logging.log4j.LogManager; +import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.emfcloud.jackson.resource.JsonResourceFactory; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -34,6 +41,8 @@ import tools.mdsd.jamopp.parser.jdt.singlefile.JaMoPPJDTSingleFileParser; import tools.mdsd.jamopp.resource.JavaResource2; import tools.mdsd.jamopp.test.AbstractJaMoPPTests; +import tools.mdsd.jamopp.test.OutputUtility; +import tools.mdsd.jamopp.test.OutputUtility.TransferResult; import tools.mdsd.jamopp.test.bulk.SingleFileParserBulkTests; /** @@ -44,7 +53,27 @@ public class PerformanceTest extends AbstractJaMoPPTests { private static final Logger LOGGER = LogManager.getLogger("jamopp." + SingleFileParserBulkTests.class.getSimpleName()); private final String inputFolder = "target" + File.separator + "src-bulk" + File.separator + "TeaStore"; - private final Path parentOutput = Paths.get("output_performance"); + private final Path parentOutput = Paths.get("target", "tests", "output_performance"); + private final Path javaOutput = parentOutput.resolve("java"); + private final Path xmiOutput = parentOutput.resolve("xmi"); + private final Path jsonOutput = parentOutput.resolve("json"); + + @BeforeEach + public void setup() throws IOException { + super.initResourceFactory(); + Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("json", new JsonResourceFactory()); + if (Files.exists(javaOutput)) { + PathUtils.deleteDirectory(javaOutput); + PathUtils.deleteDirectory(xmiOutput); + PathUtils.deleteDirectory(jsonOutput); + } + try { + Files.createDirectories(javaOutput); + Files.createDirectories(xmiOutput); + Files.createDirectories(jsonOutput); + } catch (IOException e1) { + } + } @Test public void measureTeaStoreFullResolution() { @@ -67,7 +96,7 @@ public void measureTeaStoreWithoutResolvingEverything() { ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.TRUE); ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.FALSE); ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.TRUE); - measurePerformance("teastore-without-resolving-everything", 20, true); + measurePerformance("teastore-without-resolving-everything", 100, true); } @Test @@ -79,7 +108,7 @@ public void measureTeaStoreWithOneLevelResolution() { ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.TRUE); ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.FALSE); ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.FALSE); - measurePerformance("teastore-one-level-resolution", 20, false); + measurePerformance("teastore-one-level-resolution", 100, false); } @Test @@ -91,7 +120,7 @@ public void measureTeaStoreSecondVariant() { ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.FALSE); ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.FALSE); ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.FALSE); - measurePerformance("teastore-second-variant", 20, false); + measurePerformance("teastore-second-variant", 100, false); } @Test @@ -100,8 +129,21 @@ public void printAllAverageTimes() { Files.walk(parentOutput).forEach(path -> { var data = PerformanceData.load(path); System.out.println(path.getFileName().toString()); - System.out.println("Average parsing time: " + data.getAverageParseTime()); - System.out.println("Average resolution time: " + data.getAverageResolutionTime()); + System.out.println("Average parsing time (ms): " + data.getAverageParseTime()); + System.out.println("Average resolution time (ms): " + data.getAverageResolutionTime()); + for (var storage : data.getStorage()) { + System.out.println("Storage (" + + storage.getId() + + "): " + + storage.getCodeFiles() + + " code files of overall " + + storage.getOverallFiles() + + " files taking " + + storage.getTakenStorageByCodeFiles() + + " Bytes for code files of overall " + + storage.getTakenStorage() + + " Bytes."); + } }); } catch (IOException e) { } @@ -123,10 +165,7 @@ private void measurePerformance(String name, int max, boolean fullResolution) { Path target = Paths.get(testInput); JaMoPPJDTSingleFileParser parser = new JaMoPPJDTSingleFileParser(); parser.setExclusionPatterns(".*?src/test/.*?"); - try { - Files.createDirectories(parentOutput); - } catch (IOException e1) { - } + Path outputMeasurement = parentOutput.resolve(name + ".json"); PerformanceData result; if (Files.exists(outputMeasurement)) { @@ -157,28 +196,83 @@ private void measurePerformance(String name, int max, boolean fullResolution) { Set parsedFiles = new HashSet<>(set.getResources()); LOGGER.debug("Asserting the resolution of all proxy objects."); for (Resource res : parsedFiles) { - if (res.getContents().size() == 0 || !(fullResolution && res.getURI().isFile())) { + if (res.getContents().size() == 0 || (!fullResolution && !res.getURI().isFile())) { continue; } this.assertResolveAllProxies(res); } + LOGGER.debug("Reprinting."); for (Resource res : parsedFiles) { - if (res.getContents().size() == 0 || !(fullResolution && res.getURI().isFile())) { + if (res.getContents().size() == 0 || !res.getURI().isFile()) { continue; } + String oldUri = res.getURI().toString(); try { this.testReprint((JavaResource2) res); } catch (Exception e) { - fail(e.getMessage()); + fail(e); } + res.setURI(URI.createURI(oldUri)); } - result.getPoints().add(point); + + result.addPoint(point); PerformanceData.save(result, outputMeasurement); + + if (i == 0 && fullResolution) { + try { + result.setStorage(measureStorage(set)); + } catch (IOException e) { + fail(e); + } + PerformanceData.save(result, outputMeasurement); + } + for (Resource res : parsedFiles) { res.unload(); } } LOGGER.debug("Finished meausring " + name); } + + private List measureStorage(ResourceSet resourceSet) throws IOException { + StoragePerformance javaStorage = new StoragePerformance(); + javaStorage.setId("java"); + var result = OutputUtility.transferToOutput(resourceSet, javaOutput.toString(), "java", true); + fillStorageInformationFromTransfer(javaStorage, javaOutput, result); + + result.sourceTargetMapping().forEach((key, value) -> { + key.getContents().addAll(value.getContents()); + }); + + StoragePerformance xmiStorage = new StoragePerformance(); + xmiStorage.setId("xmi"); + result = OutputUtility.transferToOutput(resourceSet, xmiOutput.toString(), "xmi", true); + fillStorageInformationFromTransfer(xmiStorage, xmiOutput, result); + + result.sourceTargetMapping().forEach((key, value) -> { + key.getContents().addAll(value.getContents()); + }); + + StoragePerformance jsonStorage = new StoragePerformance(); + jsonStorage.setId("json"); + fillStorageInformationFromTransfer(jsonStorage, jsonOutput, OutputUtility.transferToOutput(resourceSet, jsonOutput.toString(), "json", true)); + + return List.of(javaStorage, xmiStorage, jsonStorage); + } + + private void fillStorageInformationFromTransfer(StoragePerformance storage, Path outputDir, TransferResult outputResult) throws IOException { + storage.setTakenStorage(PathUtils.sizeOfDirectory(outputDir)); + long codeFiles = 0; + long codeSize = 0; + for (var entry : outputResult.sourceTargetMapping().entrySet()) { + if (entry.getKey().getURI().isFile()) { + codeFiles++; + codeSize += FileUtils.sizeOf(new File(entry.getValue().getURI().toFileString())); + } + } + storage.setCodeFiles(codeFiles); + storage.setTakenStorageByCodeFiles(codeSize); + storage.setOverallFiles(outputResult.sourceTargetMapping().entrySet().size()); + } } From e1b27f77125f172a5b721b61c295fae94e4f758c Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Sun, 30 Jul 2023 18:19:49 +0200 Subject: [PATCH 05/45] The JDT parser only resolves bindings if the JaMoPP parser option is enabled (#23). --- .../jamopp/parser/jdt/singlefile/JaMoPPJDTSingleFileParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jamopp.parser.jdt.singlefile/src/main/java/tools/mdsd/jamopp/parser/jdt/singlefile/JaMoPPJDTSingleFileParser.java b/jamopp.parser.jdt.singlefile/src/main/java/tools/mdsd/jamopp/parser/jdt/singlefile/JaMoPPJDTSingleFileParser.java index c9f890d1..97d1564d 100644 --- a/jamopp.parser.jdt.singlefile/src/main/java/tools/mdsd/jamopp/parser/jdt/singlefile/JaMoPPJDTSingleFileParser.java +++ b/jamopp.parser.jdt.singlefile/src/main/java/tools/mdsd/jamopp/parser/jdt/singlefile/JaMoPPJDTSingleFileParser.java @@ -207,7 +207,7 @@ private void setUpResourceSet() { private ASTParser setUpParser() { ASTParser parser = ASTParser.newParser(AST.JLS15); - parser.setResolveBindings(true); + parser.setResolveBindings(ParserOptions.RESOLVE_BINDINGS.isTrue()); parser.setStatementsRecovery(true); Map compilerOptions = new HashMap<>(); compilerOptions.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_15); From 5242d4861b4b8bdc6510cb17b8ef92f9204db26c Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Sun, 30 Jul 2023 18:20:48 +0200 Subject: [PATCH 06/45] The trivial recovery creates an artificial Object class if it cannot find the model for the Object class (#23). --- .../mdsd/jamopp/recovery/trivial/TrivialRecovery.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/jamopp.resolution/src/main/java/tools/mdsd/jamopp/recovery/trivial/TrivialRecovery.java b/jamopp.resolution/src/main/java/tools/mdsd/jamopp/recovery/trivial/TrivialRecovery.java index 1ac6459e..2e25ae72 100644 --- a/jamopp.resolution/src/main/java/tools/mdsd/jamopp/recovery/trivial/TrivialRecovery.java +++ b/jamopp.resolution/src/main/java/tools/mdsd/jamopp/recovery/trivial/TrivialRecovery.java @@ -174,11 +174,18 @@ private void initArtificialResource() { } private tools.mdsd.jamopp.model.java.classifiers.Class findObjectClass() { - return this.set.getResources().stream().filter(resource -> !resource.getContents().isEmpty() + var optionalResult = this.set.getResources().stream().filter(resource -> !resource.getContents().isEmpty() && resource.getContents().get(0) instanceof CompilationUnit) .map(resource -> (CompilationUnit) resource.getContents().get(0)) .filter(cu -> cu.getNamespaces().size() == 2 && cu.getNamespaces().get(0).equals("java") && cu.getNamespaces().get(1).equals("lang") && cu.getName().equals("Object")) - .map(cu -> (tools.mdsd.jamopp.model.java.classifiers.Class) cu.getClassifiers().get(0)).findFirst().get(); + .map(cu -> (tools.mdsd.jamopp.model.java.classifiers.Class) cu.getClassifiers().get(0)).findFirst(); + if (optionalResult.isPresent()) { + return optionalResult.get(); + } + tools.mdsd.jamopp.model.java.classifiers.Class ownObjectClass = ClassifiersFactory.eINSTANCE.createClass(); + ownObjectClass.setName("Object"); + this.artificialCU.getClassifiers().add(ownObjectClass); + return ownObjectClass; } } From 4ef98d518b3d74ad95db3b386995ea7d89e2b232 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 31 Jul 2023 15:27:16 +0200 Subject: [PATCH 07/45] The class file parser creates blocks for methods and constructors (#23). --- .../tools/mdsd/jamopp/parser/bcel/ClassFileModelLoader.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jamopp.parser.bcel/src/main/java/tools/mdsd/jamopp/parser/bcel/ClassFileModelLoader.java b/jamopp.parser.bcel/src/main/java/tools/mdsd/jamopp/parser/bcel/ClassFileModelLoader.java index bd450dcd..0576fc43 100644 --- a/jamopp.parser.bcel/src/main/java/tools/mdsd/jamopp/parser/bcel/ClassFileModelLoader.java +++ b/jamopp.parser.bcel/src/main/java/tools/mdsd/jamopp/parser/bcel/ClassFileModelLoader.java @@ -57,6 +57,7 @@ import tools.mdsd.jamopp.model.java.modifiers.ModifiersFactory; import tools.mdsd.jamopp.model.java.parameters.Parameter; import tools.mdsd.jamopp.model.java.parameters.ParametersFactory; +import tools.mdsd.jamopp.model.java.statements.StatementsFactory; import tools.mdsd.jamopp.model.java.types.ClassifierReference; import tools.mdsd.jamopp.model.java.types.TypeReference; import tools.mdsd.jamopp.model.java.types.TypedElement; @@ -215,6 +216,10 @@ private Member constructMethod(org.apache.bcel.classfile.Method method, emfMethod = membersFactory.createClassMethod(); } emfMethod.setName(method.getName()); + + var block = StatementsFactory.eINSTANCE.createBlock(); + block.setName(""); + emfMethod.setStatement(block); String signature = method.getReturnType().getSignature(); String plainSignature = ""; @@ -302,6 +307,7 @@ private Member constructMethod(org.apache.bcel.classfile.Method method, constructor.getTypeParameters().addAll(emfMethod.getTypeParameters()); constructor.getParameters().addAll(emfMethod.getParameters()); constructor.setName(emfClassifier.getName()); + constructor.setBlock(block); return constructor; } From e269a38fcd7064a1bcfcc9bcd0ac43affa5d75e6 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 31 Jul 2023 15:27:36 +0200 Subject: [PATCH 08/45] The trivial recovery also recovers EnumConstants (#23). --- .../recovery/trivial/TrivialRecovery.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/jamopp.resolution/src/main/java/tools/mdsd/jamopp/recovery/trivial/TrivialRecovery.java b/jamopp.resolution/src/main/java/tools/mdsd/jamopp/recovery/trivial/TrivialRecovery.java index 2e25ae72..fdd1d0f7 100644 --- a/jamopp.resolution/src/main/java/tools/mdsd/jamopp/recovery/trivial/TrivialRecovery.java +++ b/jamopp.resolution/src/main/java/tools/mdsd/jamopp/recovery/trivial/TrivialRecovery.java @@ -24,9 +24,11 @@ import tools.mdsd.jamopp.model.java.classifiers.Annotation; import tools.mdsd.jamopp.model.java.classifiers.ClassifiersFactory; +import tools.mdsd.jamopp.model.java.classifiers.Enumeration; import tools.mdsd.jamopp.model.java.containers.CompilationUnit; import tools.mdsd.jamopp.model.java.containers.ContainersFactory; import tools.mdsd.jamopp.model.java.members.ClassMethod; +import tools.mdsd.jamopp.model.java.members.EnumConstant; import tools.mdsd.jamopp.model.java.members.Field; import tools.mdsd.jamopp.model.java.members.InterfaceMethod; import tools.mdsd.jamopp.model.java.members.MembersFactory; @@ -42,6 +44,7 @@ public class TrivialRecovery { private ResourceSet set; private Resource artificialResource; private CompilationUnit artificialCU; + private Enumeration artificialEnum; private tools.mdsd.jamopp.model.java.classifiers.Class artificialClass; private tools.mdsd.jamopp.model.java.classifiers.Class objectClass; private HashMap artClasses = new HashMap<>(); @@ -49,6 +52,7 @@ public class TrivialRecovery { private HashMap artFields = new HashMap<>(); private HashMap artClassMethods = new HashMap<>(); private HashMap artInterfaceMethods = new HashMap<>(); + private HashMap artEnumConstants = new HashMap<>(); private HashMap artPackages = new HashMap<>(); private HashMap artModules = new HashMap<>(); @@ -66,6 +70,9 @@ public void recover() { EList list = (EList) setting.getEObject() .eGet(setting.getEStructuralFeature()); var idx = list.indexOf(proxy); + if (idx == -1) { + continue; + } list.set(idx, actualElement); } else { setting.getEObject().eSet(setting.getEStructuralFeature(), actualElement); @@ -152,6 +159,15 @@ private EObject recoverActualElement(EObject obj) { this.artificialResource.getContents().add(result); this.artModules.put(name, result); return result; + } else if (obj instanceof EnumConstant) { + if (this.artEnumConstants.containsKey(name)) { + return this.artEnumConstants.get(name); + } + var result = MembersFactory.eINSTANCE.createEnumConstant(); + result.setName(name); + this.artificialEnum.getConstants().add(result); + this.artEnumConstants.put(name, result); + return result; } return null; } @@ -168,6 +184,10 @@ private void initArtificialResource() { this.artificialClass.setName("SyntheticClass"); this.artificialCU.getClassifiers().add(this.artificialClass); + this.artificialEnum = ClassifiersFactory.eINSTANCE.createEnumeration(); + this.artificialEnum.setName("SyntheticEnum"); + this.artificialCU.getClassifiers().add(this.artificialEnum); + this.objectClass = findObjectClass(); this.artClasses.put("Object", objectClass); } From 1e06e871f90085c09f8fa82b0c83d64c0cf4b7a0 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Wed, 2 Aug 2023 10:12:05 +0200 Subject: [PATCH 09/45] Added the trivial recovery to the performance test (#23). --- .../performance/PerformanceDataPoint.java | 9 ++++ .../test/performance/PerformanceTest.java | 50 +++++++++++++++---- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceDataPoint.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceDataPoint.java index be056cef..02c85f40 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceDataPoint.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceDataPoint.java @@ -19,6 +19,7 @@ public class PerformanceDataPoint { private long parseTime; private long resolutionTime; + private long recoverTime; public long getParseTime() { return parseTime; @@ -35,4 +36,12 @@ public long getResolutionTime() { public void setResolutionTime(long resolutionTime) { this.resolutionTime = resolutionTime; } + + public long getRecoverTime() { + return recoverTime; + } + + public void setRecoverTime(long recoverTime) { + this.recoverTime = recoverTime; + } } diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java index aa91f6c9..6756571f 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java @@ -39,6 +39,7 @@ import tools.mdsd.jamopp.options.ParserOptions; import tools.mdsd.jamopp.parser.jdt.singlefile.JaMoPPJDTSingleFileParser; +import tools.mdsd.jamopp.recovery.trivial.TrivialRecovery; import tools.mdsd.jamopp.resource.JavaResource2; import tools.mdsd.jamopp.test.AbstractJaMoPPTests; import tools.mdsd.jamopp.test.OutputUtility; @@ -84,7 +85,7 @@ public void measureTeaStoreFullResolution() { ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.TRUE); ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.TRUE); ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.TRUE); - measurePerformance("teastore-full-resolution", 100, true); + measurePerformance("teastore-full-resolution", 100, true, false); } @Test @@ -96,11 +97,10 @@ public void measureTeaStoreWithoutResolvingEverything() { ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.TRUE); ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.FALSE); ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.TRUE); - measurePerformance("teastore-without-resolving-everything", 100, true); + measurePerformance("teastore-without-resolving-everything", 100, true, false); } - @Test - public void measureTeaStoreWithOneLevelResolution() { + private void prepareParserOptionsForOneLevelResolution() { ParserOptions.CREATE_LAYOUT_INFORMATION.setValue(Boolean.TRUE); ParserOptions.REGISTER_LOCAL.setValue(Boolean.TRUE); ParserOptions.PREFER_BINDING_CONVERSION.setValue(Boolean.TRUE); @@ -108,11 +108,22 @@ public void measureTeaStoreWithOneLevelResolution() { ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.TRUE); ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.FALSE); ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.FALSE); - measurePerformance("teastore-one-level-resolution", 100, false); } @Test - public void measureTeaStoreSecondVariant() { + public void measureTeaStoreWithOneLevelResolution() { + prepareParserOptionsForOneLevelResolution(); + measurePerformance("teastore-one-level-resolution", 100, false, true); + } + + @Disabled("Takes several hours.") + @Test + public void measureTeaStoreWithOneLevelResolutionAndFullResolution() { + prepareParserOptionsForOneLevelResolution(); + measurePerformance("teastore-one-level-resolution-full", 1, true, false); + } + + private void prepareParserOptionsForSecondVariant() { ParserOptions.CREATE_LAYOUT_INFORMATION.setValue(Boolean.TRUE); ParserOptions.REGISTER_LOCAL.setValue(Boolean.TRUE); ParserOptions.PREFER_BINDING_CONVERSION.setValue(Boolean.TRUE); @@ -120,7 +131,19 @@ public void measureTeaStoreSecondVariant() { ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.FALSE); ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.FALSE); ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.FALSE); - measurePerformance("teastore-second-variant", 100, false); + } + + @Test + public void measureTeaStoreSecondVariant() { + prepareParserOptionsForSecondVariant(); + measurePerformance("teastore-second-variant", 1, false, true); + } + + @Disabled("Takes several hours.") + @Test + public void measureTeaStoreSecondVariantAndFullResolution() { + prepareParserOptionsForSecondVariant(); + measurePerformance("teastore-second-variant-resolution", 1, true, false); } @Test @@ -159,7 +182,7 @@ protected String getTestInputFolder() { return inputFolder; } - private void measurePerformance(String name, int max, boolean fullResolution) { + private void measurePerformance(String name, int max, boolean fullResolution, boolean recover) { String testInput = getTestInputFolder(); LOGGER.debug("Executing performance measurements for " + name); Path target = Paths.get(testInput); @@ -175,6 +198,7 @@ private void measurePerformance(String name, int max, boolean fullResolution) { } int actualMax = Math.min(max, max - result.getPoints().size()); for (int i = 0; i < actualMax; i++) { + System.out.println("Measurement " + i + " for " + name); PerformanceDataPoint point = new PerformanceDataPoint(); long millis = System.currentTimeMillis(); ResourceSet set = parser.parseDirectory(target); @@ -193,6 +217,14 @@ private void measurePerformance(String name, int max, boolean fullResolution) { millis = System.currentTimeMillis() - millis; } point.setResolutionTime(millis); + + if (recover) { + millis = System.currentTimeMillis(); + new TrivialRecovery(set).recover(); + millis = System.currentTimeMillis() - millis; + point.setRecoverTime(millis); + } + Set parsedFiles = new HashSet<>(set.getResources()); LOGGER.debug("Asserting the resolution of all proxy objects."); for (Resource res : parsedFiles) { @@ -219,7 +251,7 @@ private void measurePerformance(String name, int max, boolean fullResolution) { result.addPoint(point); PerformanceData.save(result, outputMeasurement); - if (i == 0 && fullResolution) { + if (i == 0 && (fullResolution || recover)) { try { result.setStorage(measureStorage(set)); } catch (IOException e) { From f077fbfd1206cd3245901e8bd44bc62ea15bda45 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Wed, 2 Aug 2023 10:38:55 +0200 Subject: [PATCH 10/45] Updated the CHANEGLOG (#23). --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe93a77a..dee84b23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), - Trivial recovery strategy to generate model elements for unresolved proxy objects - Parser: `TextBlock`s are converted to `TextBockReference`s so that model elements are generated for text blocks +- Performance Test: + - Performs trivial recovery + - Measures model storage ### Changed @@ -34,6 +37,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ### Fixed - First variant: always returns an empty model (temporary fix to not end in an endless loop) +- Class file parser: creates bodies for methods and constructors ### Security From 57a7eb25c9a89c5bbbed12cd429e168e66c70e7a Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Fri, 4 Aug 2023 10:53:37 +0200 Subject: [PATCH 11/45] Added some null checks (#23). --- .../expressions/MethodReferenceExpressionExtension.java | 5 ++++- .../java/extensions/generics/TypeParameterExtension.java | 3 +++ .../model/java/extensions/references/ReferenceExtension.java | 3 +++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/jamopp.model/src/main/java/tools/mdsd/jamopp/model/java/extensions/expressions/MethodReferenceExpressionExtension.java b/jamopp.model/src/main/java/tools/mdsd/jamopp/model/java/extensions/expressions/MethodReferenceExpressionExtension.java index 27782a09..1d3f4185 100644 --- a/jamopp.model/src/main/java/tools/mdsd/jamopp/model/java/extensions/expressions/MethodReferenceExpressionExtension.java +++ b/jamopp.model/src/main/java/tools/mdsd/jamopp/model/java/extensions/expressions/MethodReferenceExpressionExtension.java @@ -42,7 +42,7 @@ public static Type getTargetType(MethodReferenceExpression me) { public static TypeReference getTargetTypeReference(MethodReferenceExpression me) { TypeReference targetType = null; EObject parentContainer = me; - while (!(parentContainer.eContainer() instanceof MethodCall + while (parentContainer != null && !(parentContainer.eContainer() instanceof MethodCall || parentContainer.eContainer() instanceof LocalVariable || parentContainer.eContainer() instanceof AdditionalLocalVariable || parentContainer.eContainer() instanceof AssignmentExpression @@ -51,6 +51,9 @@ public static TypeReference getTargetTypeReference(MethodReferenceExpression me) || parentContainer.eContainer() instanceof AdditionalField)) { parentContainer = parentContainer.eContainer(); } + if (parentContainer == null) { + return null; + } if (parentContainer.eContainer() instanceof MethodCall) { MethodCall call = (MethodCall) parentContainer.eContainer(); Method m = (Method) call.getTarget(); diff --git a/jamopp.model/src/main/java/tools/mdsd/jamopp/model/java/extensions/generics/TypeParameterExtension.java b/jamopp.model/src/main/java/tools/mdsd/jamopp/model/java/extensions/generics/TypeParameterExtension.java index 179a99d4..9d7de310 100644 --- a/jamopp.model/src/main/java/tools/mdsd/jamopp/model/java/extensions/generics/TypeParameterExtension.java +++ b/jamopp.model/src/main/java/tools/mdsd/jamopp/model/java/extensions/generics/TypeParameterExtension.java @@ -738,6 +738,9 @@ private static TypeReference searchForTypeParameter(TypeParameter me, TypeRefere && targetReference instanceof TypeArgumentable) { TypeArgumentable typeArg = (TypeArgumentable) searchReference; TypeArgumentable targetArg = (TypeArgumentable) targetReference; + if (targetArg.getTypeArguments().size() != typeArg.getTypeArguments().size()) { + return null; + } for (int i = 0; i < typeArg.getTypeArguments().size(); i++) { TypeArgument arg = typeArg.getTypeArguments().get(i); TypeReference refOfArg = TypeReferenceExtension.getTypeReferenceOfTypeArgument(arg); diff --git a/jamopp.model/src/main/java/tools/mdsd/jamopp/model/java/extensions/references/ReferenceExtension.java b/jamopp.model/src/main/java/tools/mdsd/jamopp/model/java/extensions/references/ReferenceExtension.java index f66559a7..09e7a265 100644 --- a/jamopp.model/src/main/java/tools/mdsd/jamopp/model/java/extensions/references/ReferenceExtension.java +++ b/jamopp.model/src/main/java/tools/mdsd/jamopp/model/java/extensions/references/ReferenceExtension.java @@ -106,6 +106,9 @@ public static TypeReference getReferencedTypeReference(Reference me) { Type thisClass = null; if (me.getPrevious() != null) { thisClassRef = me.getPrevious().getReferencedTypeReference(); + if (thisClassRef == null) { + return null; + } thisClass = thisClassRef.getTarget(); } else { AnonymousClass anonymousContainer = me.getContainingAnonymousClass(); From 7c31de2bd9f542c8fa7c0276c6efb0188866ad96 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Sat, 12 Aug 2023 21:05:52 +0200 Subject: [PATCH 12/45] Added some length checks when printing the name of a module or package (#23). --- .../mdsd/jamopp/printer/ContainersPrinterSwitch.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/jamopp.printer/src/main/java/tools/mdsd/jamopp/printer/ContainersPrinterSwitch.java b/jamopp.printer/src/main/java/tools/mdsd/jamopp/printer/ContainersPrinterSwitch.java index 9972c202..16443228 100644 --- a/jamopp.printer/src/main/java/tools/mdsd/jamopp/printer/ContainersPrinterSwitch.java +++ b/jamopp.printer/src/main/java/tools/mdsd/jamopp/printer/ContainersPrinterSwitch.java @@ -44,7 +44,9 @@ public Boolean caseJavaRoot(JavaRoot root) { if (root.getNamespaces().size() > 0) { parent.doSwitch(AnnotationsPackage.Literals.ANNOTABLE, root); String p = root.getNamespacesAsString(); - p = p.substring(0, p.length() - 1); + if (p.length() > 0) { + p = p.substring(0, p.length() - 1); + } writer.append("package " + p + ";\n\n"); } parent.doSwitch(ImportsPackage.Literals.IMPORTING_ELEMENT, root); @@ -65,7 +67,9 @@ public Boolean caseModule(tools.mdsd.jamopp.model.java.containers.Module element writer.append("open "); } String n = LogicalJavaURIGenerator.packageName(element); - n = n.substring(0, n.length() - 1); + if (n.length() > 0) { + n = n.substring(0, n.length() - 1); + } writer.append(n); writer.append(" {\n"); for (ModuleDirective dir : element.getTarget()) { From e393762fe64b5a3131e51e3ffb4215b1cd71aa3f Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Sat, 12 Aug 2023 21:13:12 +0200 Subject: [PATCH 13/45] Preventing OutOfMemoryErrors by deleting all proxy objects after every resolution (#23). --- .../tools/mdsd/jamopp/test/performance/PerformanceData.java | 4 ++++ .../tools/mdsd/jamopp/test/performance/PerformanceTest.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceData.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceData.java index af78d51d..1ff00133 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceData.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceData.java @@ -60,6 +60,10 @@ public double getAverageResolutionTime() { return (double) points.stream().mapToLong(p -> p.getResolutionTime()).sum() / points.size(); } + public double getAverageRecoveryTime() { + return (double) points.stream().mapToLong(p -> p.getRecoverTime()).sum() / points.size(); + } + public static PerformanceData load(Path file) { try (BufferedReader reader = Files.newBufferedReader(file)) { Gson gson = new Gson(); diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java index 6756571f..b5938331 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java @@ -39,6 +39,7 @@ import tools.mdsd.jamopp.options.ParserOptions; import tools.mdsd.jamopp.parser.jdt.singlefile.JaMoPPJDTSingleFileParser; +import tools.mdsd.jamopp.proxy.IJavaContextDependentURIFragmentCollector; import tools.mdsd.jamopp.recovery.trivial.TrivialRecovery; import tools.mdsd.jamopp.resource.JavaResource2; import tools.mdsd.jamopp.test.AbstractJaMoPPTests; @@ -154,6 +155,7 @@ public void printAllAverageTimes() { System.out.println(path.getFileName().toString()); System.out.println("Average parsing time (ms): " + data.getAverageParseTime()); System.out.println("Average resolution time (ms): " + data.getAverageResolutionTime()); + System.out.println("Average recovery time (ms): " + data.getAverageRecoveryTime()); for (var storage : data.getStorage()) { System.out.println("Storage (" + storage.getId() @@ -263,6 +265,8 @@ private void measurePerformance(String name, int max, boolean fullResolution, bo for (Resource res : parsedFiles) { res.unload(); } + IJavaContextDependentURIFragmentCollector.GLOBAL_INSTANCE + .getContextDependentURIFragmentMap().clear(); } LOGGER.debug("Finished meausring " + name); } From c44f0c74c1339fcd234c491d2f3f2a0f50b2e696 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Thu, 5 Oct 2023 22:36:43 +0200 Subject: [PATCH 14/45] Added Apache Commons Math for statistical calculations (#23). --- jamopp.tests/pom.xml | 5 ++++- pom.xml | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/jamopp.tests/pom.xml b/jamopp.tests/pom.xml index 67676e8f..db87cfb4 100644 --- a/jamopp.tests/pom.xml +++ b/jamopp.tests/pom.xml @@ -130,6 +130,9 @@ org.eclipse.emfcloud emfjson-jackson - + + org.apache.commons + commons-math4-legacy + diff --git a/pom.xml b/pom.xml index 0358f1ef..b745134b 100644 --- a/pom.xml +++ b/pom.xml @@ -158,6 +158,11 @@ emfjson-jackson 2.2.0 + + org.apache.commons + commons-math4-legacy + 4.0-beta1 + From 758b7ee312c70aaac733290d499adec8160ea14e Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Thu, 5 Oct 2023 22:58:19 +0200 Subject: [PATCH 15/45] The overall average and standard deviation are calculated (#23). --- .../test/performance/PerformanceData.java | 18 +++++++++++++++++- .../test/performance/PerformanceTest.java | 4 ++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceData.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceData.java index 1ff00133..a020dad8 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceData.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceData.java @@ -20,7 +20,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; - +import org.apache.commons.math4.legacy.stat.descriptive.SummaryStatistics; import com.google.gson.Gson; /** @@ -64,6 +64,22 @@ public double getAverageRecoveryTime() { return (double) points.stream().mapToLong(p -> p.getRecoverTime()).sum() / points.size(); } + public SummaryStatistics getStatistics() { + SummaryStatistics stats = new SummaryStatistics(); + points.forEach(p -> { + stats.addValue(p.getParseTime() + p.getResolutionTime() + p.getRecoverTime()); + }); + return stats; + } + + public SummaryStatistics getStatisticsWithoutResolution() { + SummaryStatistics stats = new SummaryStatistics(); + points.forEach(p -> { + stats.addValue(p.getParseTime() + p.getRecoverTime()); + }); + return stats; + } + public static PerformanceData load(Path file) { try (BufferedReader reader = Files.newBufferedReader(file)) { Gson gson = new Gson(); diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java index b5938331..9095c1ec 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java @@ -153,6 +153,10 @@ public void printAllAverageTimes() { Files.walk(parentOutput).forEach(path -> { var data = PerformanceData.load(path); System.out.println(path.getFileName().toString()); + var stat = data.getStatistics(); + System.out.println("Average time (ms): " + stat.getMean() + " (with std. " + + stat.getStandardDeviation() + " ms)"); + stat = data.getStatistics(); + System.out.println("Average time without resolution (ms): " + stat.getMean() + " (with std. " + + stat.getStandardDeviation() + " ms)"); System.out.println("Average parsing time (ms): " + data.getAverageParseTime()); System.out.println("Average resolution time (ms): " + data.getAverageResolutionTime()); System.out.println("Average recovery time (ms): " + data.getAverageRecoveryTime()); From d35da40005e4c1f8d96a3968914a13b8e52fec6e Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Thu, 5 Oct 2023 23:01:28 +0200 Subject: [PATCH 16/45] Added JGit as dependency for step-wise executions (#23). --- jamopp.tests/pom.xml | 4 ++++ pom.xml | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/jamopp.tests/pom.xml b/jamopp.tests/pom.xml index db87cfb4..af2f6db3 100644 --- a/jamopp.tests/pom.xml +++ b/jamopp.tests/pom.xml @@ -134,5 +134,9 @@ org.apache.commons commons-math4-legacy + + org.eclipse.jgit + org.eclipse.jgit + diff --git a/pom.xml b/pom.xml index b745134b..09204f24 100644 --- a/pom.xml +++ b/pom.xml @@ -163,6 +163,11 @@ commons-math4-legacy 4.0-beta1 + + org.eclipse.jgit + org.eclipse.jgit + 6.7.0.202309050840-r + From fb760776793e9f09b43edf77a7974a8117b9fa07 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Sun, 31 Mar 2024 21:57:03 +0200 Subject: [PATCH 17/45] During the trivial recovery, an empty name for packages and the artificial compilation unit is set. --- .../src/jamopp/recovery/trivial/TrivialRecovery.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bundles/jamopp.resolution/src/jamopp/recovery/trivial/TrivialRecovery.java b/bundles/jamopp.resolution/src/jamopp/recovery/trivial/TrivialRecovery.java index 494de37d..c85d61a8 100644 --- a/bundles/jamopp.resolution/src/jamopp/recovery/trivial/TrivialRecovery.java +++ b/bundles/jamopp.resolution/src/jamopp/recovery/trivial/TrivialRecovery.java @@ -138,6 +138,7 @@ private EObject recoverActualElement(EObject obj) { return this.artPackages.get(name); } var result = ContainersFactory.eINSTANCE.createPackage(); + result.setName(""); p.getNamespaces().forEach(ns -> result.getNamespaces().add(ns)); this.artificialResource.getContents().add(result); this.artPackages.put(name, result); @@ -161,6 +162,7 @@ private void initArtificialResource() { URI.createURI("pathmap:/javaclass/ArtificialResource.java")); this.artificialCU = ContainersFactory.eINSTANCE.createCompilationUnit(); + this.artificialCU.setName(""); this.artificialResource.getContents().add(this.artificialCU); this.artificialClass = ClassifiersFactory.eINSTANCE.createClass(); From c2b7f6ef7721124acc0c5e8603bc1e4cefd67a9b Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Wed, 3 Apr 2024 17:24:49 +0200 Subject: [PATCH 18/45] Enum constants are also recovered by the trivial recovery. --- .../recovery/trivial/TrivialRecovery.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bundles/jamopp.resolution/src/jamopp/recovery/trivial/TrivialRecovery.java b/bundles/jamopp.resolution/src/jamopp/recovery/trivial/TrivialRecovery.java index c85d61a8..590a83ed 100644 --- a/bundles/jamopp.resolution/src/jamopp/recovery/trivial/TrivialRecovery.java +++ b/bundles/jamopp.resolution/src/jamopp/recovery/trivial/TrivialRecovery.java @@ -23,9 +23,11 @@ import org.eclipse.emf.ecore.util.EcoreUtil; import org.emftext.language.java.classifiers.Annotation; import org.emftext.language.java.classifiers.ClassifiersFactory; +import org.emftext.language.java.classifiers.Enumeration; import org.emftext.language.java.containers.CompilationUnit; import org.emftext.language.java.containers.ContainersFactory; import org.emftext.language.java.members.ClassMethod; +import org.emftext.language.java.members.EnumConstant; import org.emftext.language.java.members.Field; import org.emftext.language.java.members.InterfaceMethod; import org.emftext.language.java.members.MembersFactory; @@ -42,10 +44,12 @@ public class TrivialRecovery { private Resource artificialResource; private CompilationUnit artificialCU; private org.emftext.language.java.classifiers.Class artificialClass; + private Enumeration artificialEnum; private org.emftext.language.java.classifiers.Class objectClass; private HashMap artClasses = new HashMap<>(); private HashMap artAnnotations = new HashMap<>(); private HashMap artFields = new HashMap<>(); + private HashMap artConstants = new HashMap<>(); private HashMap artClassMethods = new HashMap<>(); private HashMap artInterfaceMethods = new HashMap<>(); private HashMap artPackages = new HashMap<>(); @@ -107,6 +111,15 @@ private EObject recoverActualElement(EObject obj) { this.artificialClass.getMembers().add(result); this.artFields.put(name, result); return result; + } else if (obj instanceof EnumConstant) { + if (this.artConstants.containsKey(obj)) { + return this.artConstants.get(obj); + } + var result = MembersFactory.eINSTANCE.createEnumConstant(); + result.setName(name); + this.artificialEnum.getConstants().add(result); + this.artConstants.put(name, result); + return result; } else if (obj instanceof ClassMethod) { if (this.artClassMethods.containsKey(name)) { return this.artClassMethods.get(name); @@ -171,6 +184,10 @@ private void initArtificialResource() { this.objectClass = findObjectClass(); this.artClasses.put("Object", objectClass); + + this.artificialEnum = ClassifiersFactory.eINSTANCE.createEnumeration(); + this.artificialEnum.setName("SyntheticEnum"); + this.artificialCU.getClassifiers().add(this.artificialEnum); } } From e405e262516a461589ff43a5d50b46dd55aa1987 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 2 Feb 2026 11:14:33 +0100 Subject: [PATCH 19/45] Added a data structure for storing results of a stepwise performance evaluation (#23). --- .../stepwise/EvaluationStepFileChange.java | 38 ++++++++++ .../stepwise/EvaluationStepResult.java | 76 +++++++++++++++++++ .../stepwise/StepwiseEvaluationResult.java | 41 ++++++++++ .../performance/stepwise/package-info.java | 20 +++++ 4 files changed, 175 insertions(+) create mode 100644 jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepFileChange.java create mode 100644 jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java create mode 100644 jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwiseEvaluationResult.java create mode 100644 jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/package-info.java diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepFileChange.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepFileChange.java new file mode 100644 index 00000000..8311aab2 --- /dev/null +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepFileChange.java @@ -0,0 +1,38 @@ +/******************************************************************************* + * Copyright (c) 2023-2026 + * Modelling for Continuous Software Engineering (MCSE) group, + * Institute of Information Security and Dependability (KASTEL), + * Karlsruhe Institute of Technology (KIT). + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Martin Armbruster (MCSE) + * - Initial implementation + ******************************************************************************/ + +package tools.mdsd.jamopp.test.performance.stepwise; + +public class EvaluationStepFileChange { + private String path; + private int newSize; + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public int getNewSize() { + return newSize; + } + + public void setNewSize(int newSize) { + this.newSize = newSize; + } +} diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java new file mode 100644 index 00000000..08048098 --- /dev/null +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright (c) 2023-2026 + * Modelling for Continuous Software Engineering (MCSE) group, + * Institute of Information Security and Dependability (KASTEL), + * Karlsruhe Institute of Technology (KIT). + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Martin Armbruster (MCSE) + * - Initial implementation + ******************************************************************************/ + +package tools.mdsd.jamopp.test.performance.stepwise; + +import java.util.List; + +public class EvaluationStepResult { + private int step; + private int totalDuration; + private int totalFiles; + private int totalSize; + private int totalProxies; + private List changedFiles; + + public int getStep() { + return step; + } + + public void setStep(int step) { + this.step = step; + } + + public int getTotalDuration() { + return totalDuration; + } + + public void setTotalDuration(int totalDuration) { + this.totalDuration = totalDuration; + } + + public int getTotalFiles() { + return totalFiles; + } + + public void setTotalFiles(int totalFiles) { + this.totalFiles = totalFiles; + } + + public int getTotalSize() { + return totalSize; + } + + public void setTotalSize(int totalSize) { + this.totalSize = totalSize; + } + + public int getTotalProxies() { + return totalProxies; + } + + public void setTotalProxies(int totalProxies) { + this.totalProxies = totalProxies; + } + + public List getChangedFiles() { + return changedFiles; + } + + public void addChangedFiles(EvaluationStepFileChange changedFile) { + this.changedFiles.add(changedFile); + } +} diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwiseEvaluationResult.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwiseEvaluationResult.java new file mode 100644 index 00000000..d6f0611d --- /dev/null +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwiseEvaluationResult.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2023-2026 + * Modelling for Continuous Software Engineering (MCSE) group, + * Institute of Information Security and Dependability (KASTEL), + * Karlsruhe Institute of Technology (KIT). + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Martin Armbruster (MCSE) + * - Initial implementation + ******************************************************************************/ + +package tools.mdsd.jamopp.test.performance.stepwise; + +import java.util.ArrayList; +import java.util.List; + +public class StepwiseEvaluationResult { + private String name; + private List steps = new ArrayList<>(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getSteps() { + return steps; + } + + public void addStep(EvaluationStepResult step) { + this.steps.add(step); + } +} diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/package-info.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/package-info.java new file mode 100644 index 00000000..f83a1aab --- /dev/null +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/package-info.java @@ -0,0 +1,20 @@ +/******************************************************************************* + * Copyright (c) 2023-2026 + * Modelling for Continuous Software Engineering (MCSE) group, + * Institute of Information Security and Dependability (KASTEL), + * Karlsruhe Institute of Technology (KIT). + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Martin Armbruster (MCSE) + * - Initial implementation + ******************************************************************************/ + +/** + * A package which enables fine-grained step-wise performance tests. + */ +package tools.mdsd.jamopp.test.performance.stepwise; From 67e436bcdfd4c7c780367405d705a5492993bba4 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 2 Mar 2026 14:00:25 +0100 Subject: [PATCH 20/45] Moved the number of repetitions to a separate method so that it can be overriden in subclasses (#23). --- .../jamopp/test/performance/PerformanceTest.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java index 9095c1ec..4fa014b0 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java @@ -50,7 +50,6 @@ /** * Class to perform performance tests and measurements. */ -@Disabled public class PerformanceTest extends AbstractJaMoPPTests { private static final Logger LOGGER = LogManager.getLogger("jamopp." + SingleFileParserBulkTests.class.getSimpleName()); @@ -86,7 +85,7 @@ public void measureTeaStoreFullResolution() { ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.TRUE); ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.TRUE); ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.TRUE); - measurePerformance("teastore-full-resolution", 100, true, false); + measurePerformance("teastore-full-resolution", getNumberOfRepetitions(), true, false); } @Test @@ -98,7 +97,7 @@ public void measureTeaStoreWithoutResolvingEverything() { ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.TRUE); ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.FALSE); ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.TRUE); - measurePerformance("teastore-without-resolving-everything", 100, true, false); + measurePerformance("teastore-without-resolving-everything", getNumberOfRepetitions(), true, false); } private void prepareParserOptionsForOneLevelResolution() { @@ -114,7 +113,7 @@ private void prepareParserOptionsForOneLevelResolution() { @Test public void measureTeaStoreWithOneLevelResolution() { prepareParserOptionsForOneLevelResolution(); - measurePerformance("teastore-one-level-resolution", 100, false, true); + measurePerformance("teastore-one-level-resolution", getNumberOfRepetitions(), false, true); } @Disabled("Takes several hours.") @@ -137,7 +136,7 @@ private void prepareParserOptionsForSecondVariant() { @Test public void measureTeaStoreSecondVariant() { prepareParserOptionsForSecondVariant(); - measurePerformance("teastore-second-variant", 1, false, true); + measurePerformance("teastore-second-variant", getNumberOfRepetitions(), false, true); } @Disabled("Takes several hours.") @@ -187,6 +186,10 @@ protected boolean isExcludedFromReprintTest(String filename) { protected String getTestInputFolder() { return inputFolder; } + + protected int getNumberOfRepetitions() { + return 100; + } private void measurePerformance(String name, int max, boolean fullResolution, boolean recover) { String testInput = getTestInputFolder(); From 621c968a0a176dad8be1d7747f4acb4c3576ef31 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Tue, 3 Mar 2026 14:01:07 +0100 Subject: [PATCH 21/45] Extended the OutputUtility, which could be also used outside of tests (#23). --- .../tools/mdsd/jamopp/test/OutputUtility.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/OutputUtility.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/OutputUtility.java index e13569df..f6d40a7e 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/OutputUtility.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/OutputUtility.java @@ -1,11 +1,11 @@ package tools.mdsd.jamopp.test; -import static org.junit.jupiter.api.Assertions.fail; - import java.io.File; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.eclipse.emf.common.util.URI; @@ -19,17 +19,24 @@ import tools.mdsd.jamopp.model.java.containers.Package; public class OutputUtility { + public final static String SUPPORTED_FILE_EXTENSION_JAVA = "java"; + public final static String SUPPORTED_FILE_EXTENSION_XMI = "xmi"; + public final static String SUPPORTED_FILE_EXTENSION_JSON = "json"; + public record TransferResult(ResourceSet targetSet, Map sourceTargetMapping) {}; - public static TransferResult transferToOutput(ResourceSet sourceSet, String outputFolder, String fileExtension, boolean includeAllResources) { + public static TransferResult transferToOutput(ResourceSet sourceSet, String outputFolder, String fileExtension, boolean includeAllResources) throws IOException { + return transferToOutput(new ArrayList<>(sourceSet.getResources()), outputFolder, fileExtension, includeAllResources); + } + + public static TransferResult transferToOutput(List sources, String outputFolder, String fileExtension, boolean includeAllResources) throws IOException { int emptyFileName = 0; ResourceSet targetSet = new ResourceSetImpl(); HashMap srcTrgMap = new HashMap<>(); - for (Resource javaResource : new ArrayList<>(sourceSet.getResources())) { + for (Resource javaResource : sources) { if (javaResource.getContents().isEmpty()) { - System.out.println("WARNING: Emtpy Resource: " + javaResource.getURI()); continue; } if (!includeAllResources && !javaResource.getURI().isFile()) { @@ -55,7 +62,7 @@ public static TransferResult transferToOutput(ResourceSet sourceSet, String outp outputFileName = root.getNamespacesAsString() .replace(".", File.separator) + File.separator + "module-info"; } else { - fail(); + continue; } File outputFile = new File("." + File.separator + outputFolder @@ -71,11 +78,7 @@ public static TransferResult transferToOutput(ResourceSet sourceSet, String outp } for (Resource targetResource : new ArrayList<>(targetSet.getResources())) { - try { - targetResource.save(targetSet.getLoadOptions()); - } catch (Exception e) { - e.printStackTrace(); - } + targetResource.save(targetSet.getLoadOptions()); } return new TransferResult(targetSet, srcTrgMap); From 2a9f4513427cba45217b8a41eeee103b55071e42 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Tue, 3 Mar 2026 14:02:03 +0100 Subject: [PATCH 22/45] Added an executor for a step-wise performance test, and adjusted the result data structure for its needs (#23). --- .../stepwise/EvaluationStepFileChange.java | 6 +- .../stepwise/EvaluationStepResult.java | 39 ++-- .../stepwise/StepwiseEvaluationResult.java | 9 + .../stepwise/StepwisePerformanceExecutor.java | 185 ++++++++++++++++++ 4 files changed, 212 insertions(+), 27 deletions(-) create mode 100644 jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepFileChange.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepFileChange.java index 8311aab2..9f566d64 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepFileChange.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepFileChange.java @@ -18,7 +18,7 @@ public class EvaluationStepFileChange { private String path; - private int newSize; + private long newSize; public String getPath() { return path; @@ -28,11 +28,11 @@ public void setPath(String path) { this.path = path; } - public int getNewSize() { + public long getNewSize() { return newSize; } - public void setNewSize(int newSize) { + public void setNewSize(long newSize) { this.newSize = newSize; } } diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java index 08048098..c696d67d 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java @@ -20,10 +20,9 @@ public class EvaluationStepResult { private int step; - private int totalDuration; - private int totalFiles; - private int totalSize; - private int totalProxies; + private long timeResolution; + private long timeModelSaving; + private long totalProxies; private List changedFiles; public int getStep() { @@ -34,35 +33,27 @@ public void setStep(int step) { this.step = step; } - public int getTotalDuration() { - return totalDuration; + public long getTimeResolution() { + return timeResolution; } - public void setTotalDuration(int totalDuration) { - this.totalDuration = totalDuration; + public void setTimeResolution(long totalDuration) { + this.timeResolution = totalDuration; } - - public int getTotalFiles() { - return totalFiles; - } - - public void setTotalFiles(int totalFiles) { - this.totalFiles = totalFiles; - } - - public int getTotalSize() { - return totalSize; + + public long getTimeModelSaving() { + return timeModelSaving; } - - public void setTotalSize(int totalSize) { - this.totalSize = totalSize; + + public void setTimeModelSaving(long timeModelSaving) { + this.timeModelSaving = timeModelSaving; } - public int getTotalProxies() { + public long getTotalProxies() { return totalProxies; } - public void setTotalProxies(int totalProxies) { + public void setTotalProxies(long totalProxies) { this.totalProxies = totalProxies; } diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwiseEvaluationResult.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwiseEvaluationResult.java index d6f0611d..b06e7d17 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwiseEvaluationResult.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwiseEvaluationResult.java @@ -22,6 +22,7 @@ public class StepwiseEvaluationResult { private String name; private List steps = new ArrayList<>(); + private long parsingTime; public String getName() { return name; @@ -38,4 +39,12 @@ public List getSteps() { public void addStep(EvaluationStepResult step) { this.steps.add(step); } + + public long getParsingTime() { + return parsingTime; + } + + public void setParsingTime(long parsingTime) { + this.parsingTime = parsingTime; + } } diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java new file mode 100644 index 00000000..b03804af --- /dev/null +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java @@ -0,0 +1,185 @@ +/******************************************************************************* + * Copyright (c) 2026 + * Modelling for Continuous Software Engineering (MCSE) group, + * Institute of Information Security and Dependability (KASTEL), + * Karlsruhe Institute of Technology (KIT). + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Martin Armbruster (MCSE) + * - Initial implementation + ******************************************************************************/ + +package tools.mdsd.jamopp.test.performance.stepwise; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.logging.log4j.Logger; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.LogManager; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; + +import com.google.gson.Gson; + +import tools.mdsd.jamopp.parser.jdt.singlefile.JaMoPPJDTSingleFileParser; +import tools.mdsd.jamopp.proxy.IJavaContextDependentURIFragmentCollector; +import tools.mdsd.jamopp.test.OutputUtility; + +public class StepwisePerformanceExecutor { + private static final Logger LOGGER = LogManager.getLogger("jamopp." + StepwisePerformanceExecutor.class.getSimpleName()); + private static final String GIT_AUTHOR_NAME = "Extended JaMoPP - Stepwise Performance Test"; + private static final String GIT_AUTHOR_MAIL = "noreply@null.localhost"; + private static final String GIT_DIRECTORY_NAME = "models"; + private static final String RESULTS_FILE_NAME = "results.json"; + private Git git; + private RevCommit lastCommit; + + public void measurePerformance(String name, Path srcDirectory, Path outputDirectory) throws IOException, GitAPIException { + if (Files.notExists(srcDirectory) || !Files.isDirectory(srcDirectory)) { + throw new IllegalStateException("Given input directory '" + srcDirectory.toString() + "' does not exist or is not a directory."); + } + + if (Files.exists(outputDirectory)) { + if (!Files.isDirectory(outputDirectory)) { + throw new IllegalStateException("The given output directory '" + outputDirectory.toString() + "' exists and is not a directory."); + } + } else { + Files.createDirectories(outputDirectory); + } + + LOGGER.info("Executing performance measurements for: " + name); + StepwiseEvaluationResult result = new StepwiseEvaluationResult(); + result.setName(name); + + JaMoPPJDTSingleFileParser parser = new JaMoPPJDTSingleFileParser(); + parser.setExclusionPatterns(".*?src/test/.*?"); + + long millis = System.currentTimeMillis(); + ResourceSet set = parser.parseDirectory(srcDirectory); + result.setParsingTime(System.currentTimeMillis() - millis); + + EvaluationStepResult stepResult = new EvaluationStepResult(); + stepResult.setStep(0); + stepResult.setTimeResolution(0); + stepResult.setTotalProxies(IJavaContextDependentURIFragmentCollector.GLOBAL_INSTANCE.getContextDependentURIFragmentMap().size()); + result.addStep(stepResult); + + var resultFile = outputDirectory.resolve(RESULTS_FILE_NAME); + var gitDir = outputDirectory.resolve(GIT_DIRECTORY_NAME); + this.git = Git.init().setDirectory(gitDir.toFile()).call(); + + var outputResult = this.storeModelsAndCalculateSizeChanges(set, gitDir); + stepResult.setTimeModelSaving(outputResult.getRight()); + outputResult.getLeft().forEach(stepResult::addChangedFiles); + this.saveResults(result, resultFile); + + List oldResources = List.of(); + int iteration = 1; + do { + oldResources = new ArrayList<>(set.getResources()); + + for (Resource resource : oldResources) { + if (EcoreUtil.ProxyCrossReferencer.find(resource).size() == 0) { + continue; + } + + System.out.println(resource.getURI().toString()); + + millis = System.currentTimeMillis(); + EcoreUtil.resolveAll(resource); + millis = System.currentTimeMillis() - millis; + + stepResult = new EvaluationStepResult(); + stepResult.setStep(iteration); + stepResult.setTimeResolution(millis); + stepResult.setTotalProxies(IJavaContextDependentURIFragmentCollector.GLOBAL_INSTANCE.getContextDependentURIFragmentMap().size()); + + outputResult = this.storeModelsAndCalculateSizeChanges(set, gitDir); + stepResult.setTimeModelSaving(outputResult.getRight()); + outputResult.getLeft().forEach(stepResult::addChangedFiles); + + result.addStep(stepResult); + this.saveResults(result, resultFile); + iteration++; + } + } while (oldResources.size() != set.getResources().size()); + + this.lastCommit = null; + + this.git.getRepository().close(); + this.git.close(); + this.git = null; + for (Resource res : set.getResources()) { + res.unload(); + } + IJavaContextDependentURIFragmentCollector.GLOBAL_INSTANCE.getContextDependentURIFragmentMap().clear(); + + LOGGER.info("Finished measuring: " + name); + // Planned graphs: iteration number (x axis) vs. following numbers on y axis + // Number of proxy objects, change (netto) in proxy objects, file size, change (netto) in file size, + // number of files, change (netto) in number of files, duration of resolution, duration of saving files, memory consumption + } + + private Pair, Long> storeModelsAndCalculateSizeChanges(ResourceSet resourceSet, Path output) throws GitAPIException, IOException { + // Store all model, and reset the source models. + long storingTime = System.currentTimeMillis(); + var result = OutputUtility.transferToOutput(resourceSet, output.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_XMI, true); + storingTime = System.currentTimeMillis() - storingTime; + result.sourceTargetMapping().forEach((key, value) -> { + key.getContents().addAll(value.getContents()); + }); + + // Add all generated files to the Git repository to save them. + this.git.add().addFilepattern(".").call(); + var recentCommit = this.git.commit().setAuthor(GIT_AUTHOR_NAME, GIT_AUTHOR_MAIL).setMessage("").call(); + + // Calculate the size of every changed file. + List fileChanges = new ArrayList<>(); + AtomicLong totalChangeSize = new AtomicLong(); + + // Get all changed files. + var gitObjReader = this.git.getRepository().newObjectReader(); + CanonicalTreeParser oldTree = null; + if (this.lastCommit != null) { + oldTree = new CanonicalTreeParser(null, gitObjReader, this.lastCommit); + } + CanonicalTreeParser newTree = new CanonicalTreeParser(null, gitObjReader, recentCommit); + var diffEntries = this.git.diff().setShowNameOnly(true).setOldTree(oldTree).setNewTree(newTree).call(); + + // Calculate the size for every changed file. + diffEntries.forEach(entry -> { + var affectedFile = output.resolve(entry.getNewPath()); + var size = FileUtils.sizeOf(affectedFile.toFile()); + totalChangeSize.addAndGet(size); + + var stepFileChange = new EvaluationStepFileChange(); + stepFileChange.setNewSize(size); + stepFileChange.setPath(entry.getNewPath()); + fileChanges.add(stepFileChange); + }); + + this.lastCommit = recentCommit; + return Pair.of(fileChanges, storingTime); + } + + private void saveResults(StepwiseEvaluationResult results, Path file) throws IOException { + Gson gson = new Gson(); + Files.writeString(file, gson.toJson(results)); + } +} From 3bbe8acb4162091f959af6bce70943d096751dae Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Thu, 5 Mar 2026 15:41:07 +0100 Subject: [PATCH 23/45] The performance tests are ignored for the test execution. --- jamopp.tests/pom.xml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/jamopp.tests/pom.xml b/jamopp.tests/pom.xml index af2f6db3..4e8faee8 100644 --- a/jamopp.tests/pom.xml +++ b/jamopp.tests/pom.xml @@ -65,6 +65,27 @@ + + org.apache.maven.plugins + maven-jar-plugin + + + package + + test-jar + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/tools/mdsd/jamopp/test/performance/**/* + + + @@ -130,7 +151,8 @@ org.eclipse.emfcloud emfjson-jackson - + + org.apache.commons commons-math4-legacy From 7622c17e03b5accffabc1e60da9be42a7b1b7a5e Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Thu, 5 Mar 2026 15:43:05 +0100 Subject: [PATCH 24/45] After all performance tests are executed, statistics are calculated and stored in a file (#23). --- .../test/performance/PerformanceTest.java | 82 +++++++++++-------- 1 file changed, 48 insertions(+), 34 deletions(-) diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java index 4fa014b0..425e4053 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java @@ -33,6 +33,7 @@ import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emfcloud.jackson.resource.JsonResourceFactory; +import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -54,24 +55,25 @@ public class PerformanceTest extends AbstractJaMoPPTests { private static final Logger LOGGER = LogManager.getLogger("jamopp." + SingleFileParserBulkTests.class.getSimpleName()); private final String inputFolder = "target" + File.separator + "src-bulk" + File.separator + "TeaStore"; - private final Path parentOutput = Paths.get("target", "tests", "output_performance"); - private final Path javaOutput = parentOutput.resolve("java"); - private final Path xmiOutput = parentOutput.resolve("xmi"); - private final Path jsonOutput = parentOutput.resolve("json"); + private static final Path PARENT_OUTPUT = Paths.get("target", "tests", "output_performance"); + private static final Path JAVA_OUTPUT = PARENT_OUTPUT.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_JAVA); + private static final Path XMI_OUTPUT = PARENT_OUTPUT.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_XMI); + private static final Path JSON_OUTPUT = PARENT_OUTPUT.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_JSON); + private static final Path SUMMARY_FILE_PATH = PARENT_OUTPUT.resolve("summary.md"); @BeforeEach public void setup() throws IOException { super.initResourceFactory(); Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("json", new JsonResourceFactory()); - if (Files.exists(javaOutput)) { - PathUtils.deleteDirectory(javaOutput); - PathUtils.deleteDirectory(xmiOutput); - PathUtils.deleteDirectory(jsonOutput); + if (Files.exists(JAVA_OUTPUT)) { + PathUtils.deleteDirectory(JAVA_OUTPUT); + PathUtils.deleteDirectory(XMI_OUTPUT); + PathUtils.deleteDirectory(JSON_OUTPUT); } try { - Files.createDirectories(javaOutput); - Files.createDirectories(xmiOutput); - Files.createDirectories(jsonOutput); + Files.createDirectories(JAVA_OUTPUT); + Files.createDirectories(XMI_OUTPUT); + Files.createDirectories(JSON_OUTPUT); } catch (IOException e1) { } } @@ -145,22 +147,33 @@ public void measureTeaStoreSecondVariantAndFullResolution() { prepareParserOptionsForSecondVariant(); measurePerformance("teastore-second-variant-resolution", 1, true, false); } + + @AfterAll + public static void clean() throws IOException { + calculateAndSaveAllStatistics(); + } - @Test - public void printAllAverageTimes() { - try { - Files.walk(parentOutput).forEach(path -> { + private static void calculateAndSaveAllStatistics() throws IOException { + StringBuilder builder = new StringBuilder(); + + Files + .walk(PARENT_OUTPUT, 1) + .filter(path -> Files.isRegularFile(path)) + .forEach(path -> { + builder.append("# Results for " + path.getFileName().toString()); + var data = PerformanceData.load(path); - System.out.println(path.getFileName().toString()); var stat = data.getStatistics(); - System.out.println("Average time (ms): " + stat.getMean() + " (with std. " + + stat.getStandardDeviation() + " ms)"); - stat = data.getStatistics(); - System.out.println("Average time without resolution (ms): " + stat.getMean() + " (with std. " + + stat.getStandardDeviation() + " ms)"); - System.out.println("Average parsing time (ms): " + data.getAverageParseTime()); - System.out.println("Average resolution time (ms): " + data.getAverageResolutionTime()); - System.out.println("Average recovery time (ms): " + data.getAverageRecoveryTime()); + builder.append("\n\nAverage time (ms): " + stat.getMean() + " (with std. " + + stat.getStandardDeviation() + " ms)\n"); + + stat = data.getStatisticsWithoutResolution(); + builder.append("Average time without resolution (ms): " + stat.getMean() + " (with std. " + + stat.getStandardDeviation() + " ms)\n"); + builder.append("Average parsing time (ms): " + data.getAverageParseTime()); + builder.append("\nAverage resolution time (ms): " + data.getAverageResolutionTime()); + builder.append("\nAverage recovery time (ms): " + data.getAverageRecoveryTime()); + for (var storage : data.getStorage()) { - System.out.println("Storage (" + builder.append("\n\nStorage (" + storage.getId() + "): " + storage.getCodeFiles() @@ -172,9 +185,10 @@ public void printAllAverageTimes() { + storage.getTakenStorage() + " Bytes."); } + builder.append("\n\n"); }); - } catch (IOException e) { - } + + Files.writeString(SUMMARY_FILE_PATH, builder.toString()); } @Override @@ -198,7 +212,7 @@ private void measurePerformance(String name, int max, boolean fullResolution, bo JaMoPPJDTSingleFileParser parser = new JaMoPPJDTSingleFileParser(); parser.setExclusionPatterns(".*?src/test/.*?"); - Path outputMeasurement = parentOutput.resolve(name + ".json"); + Path outputMeasurement = PARENT_OUTPUT.resolve(name + ".json"); PerformanceData result; if (Files.exists(outputMeasurement)) { result = PerformanceData.load(outputMeasurement); @@ -280,26 +294,26 @@ private void measurePerformance(String name, int max, boolean fullResolution, bo private List measureStorage(ResourceSet resourceSet) throws IOException { StoragePerformance javaStorage = new StoragePerformance(); - javaStorage.setId("java"); - var result = OutputUtility.transferToOutput(resourceSet, javaOutput.toString(), "java", true); - fillStorageInformationFromTransfer(javaStorage, javaOutput, result); + javaStorage.setId(OutputUtility.SUPPORTED_FILE_EXTENSION_JAVA); + var result = OutputUtility.transferToOutput(resourceSet, JAVA_OUTPUT.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_JAVA, true); + fillStorageInformationFromTransfer(javaStorage, JAVA_OUTPUT, result); result.sourceTargetMapping().forEach((key, value) -> { key.getContents().addAll(value.getContents()); }); StoragePerformance xmiStorage = new StoragePerformance(); - xmiStorage.setId("xmi"); - result = OutputUtility.transferToOutput(resourceSet, xmiOutput.toString(), "xmi", true); - fillStorageInformationFromTransfer(xmiStorage, xmiOutput, result); + xmiStorage.setId(OutputUtility.SUPPORTED_FILE_EXTENSION_XMI); + result = OutputUtility.transferToOutput(resourceSet, XMI_OUTPUT.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_XMI, true); + fillStorageInformationFromTransfer(xmiStorage, XMI_OUTPUT, result); result.sourceTargetMapping().forEach((key, value) -> { key.getContents().addAll(value.getContents()); }); StoragePerformance jsonStorage = new StoragePerformance(); - jsonStorage.setId("json"); - fillStorageInformationFromTransfer(jsonStorage, jsonOutput, OutputUtility.transferToOutput(resourceSet, jsonOutput.toString(), "json", true)); + jsonStorage.setId(OutputUtility.SUPPORTED_FILE_EXTENSION_JSON); + fillStorageInformationFromTransfer(jsonStorage, JSON_OUTPUT, OutputUtility.transferToOutput(resourceSet, JSON_OUTPUT.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_JSON, true)); return List.of(javaStorage, xmiStorage, jsonStorage); } From b05508d4ed2fbdf291477250cb501615ada1e94a Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Thu, 5 Mar 2026 15:43:37 +0100 Subject: [PATCH 25/45] Added a performance test, which uses a reduced number of repetitions (#23). --- .../performance/ReducedPerformanceTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/ReducedPerformanceTest.java diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/ReducedPerformanceTest.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/ReducedPerformanceTest.java new file mode 100644 index 00000000..fdf0a182 --- /dev/null +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/ReducedPerformanceTest.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2026 + * Modelling for Continuous Software Engineering (MCSE) group, + * Institute of Information Security and Dependability (KASTEL), + * Karlsruhe Institute of Technology (KIT). + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Martin Armbruster (MCSE) + * - Initial implementation + ******************************************************************************/ + +package tools.mdsd.jamopp.test.performance; + +/** + * This class provides a reduced extent of the performance tests to save time and resources. + * It acts more as a demonstration for the performance test execution. + */ +public class ReducedPerformanceTest extends PerformanceTest { + @Override + protected int getNumberOfRepetitions() { + return 1; + } +} From df45387f2a1d7199946c1b38b05405d62eadf9c7 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 9 Mar 2026 14:18:06 +0100 Subject: [PATCH 26/45] Moved the performance test-related classes to the main folder (#23). --- .../tools/mdsd/jamopp/test/OutputUtility.java | 0 .../test/performance/PerformanceData.java | 0 .../performance/PerformanceDataPoint.java | 0 .../performance/PerformanceTestExecutor.java | 297 ++++++++++++++++++ .../test/performance/StoragePerformance.java | 0 .../jamopp/test/performance/package-info.java | 0 .../stepwise/EvaluationStepFileChange.java | 0 .../stepwise/EvaluationStepResult.java | 0 .../stepwise/StepwiseEvaluationResult.java | 0 .../stepwise/StepwisePerformanceExecutor.java | 0 .../performance/stepwise/package-info.java | 0 11 files changed, 297 insertions(+) rename jamopp.tests/src/{test => main}/java/tools/mdsd/jamopp/test/OutputUtility.java (100%) rename jamopp.tests/src/{test => main}/java/tools/mdsd/jamopp/test/performance/PerformanceData.java (100%) rename jamopp.tests/src/{test => main}/java/tools/mdsd/jamopp/test/performance/PerformanceDataPoint.java (100%) create mode 100644 jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java rename jamopp.tests/src/{test => main}/java/tools/mdsd/jamopp/test/performance/StoragePerformance.java (100%) rename jamopp.tests/src/{test => main}/java/tools/mdsd/jamopp/test/performance/package-info.java (100%) rename jamopp.tests/src/{test => main}/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepFileChange.java (100%) rename jamopp.tests/src/{test => main}/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java (100%) rename jamopp.tests/src/{test => main}/java/tools/mdsd/jamopp/test/performance/stepwise/StepwiseEvaluationResult.java (100%) rename jamopp.tests/src/{test => main}/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java (100%) rename jamopp.tests/src/{test => main}/java/tools/mdsd/jamopp/test/performance/stepwise/package-info.java (100%) diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/OutputUtility.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/OutputUtility.java similarity index 100% rename from jamopp.tests/src/test/java/tools/mdsd/jamopp/test/OutputUtility.java rename to jamopp.tests/src/main/java/tools/mdsd/jamopp/test/OutputUtility.java diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceData.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceData.java similarity index 100% rename from jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceData.java rename to jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceData.java diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceDataPoint.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceDataPoint.java similarity index 100% rename from jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceDataPoint.java rename to jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceDataPoint.java diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java new file mode 100644 index 00000000..30bde696 --- /dev/null +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java @@ -0,0 +1,297 @@ +/******************************************************************************* + * Copyright (c) 2021-2026, Martin Armbruster + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Martin Armbruster + * - Initial implementation + ******************************************************************************/ + +package tools.mdsd.jamopp.test.performance; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.List; + +import org.apache.logging.log4j.Logger; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.file.PathUtils; +import org.apache.logging.log4j.LogManager; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.emf.ecore.util.EcoreUtil; +import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; +import org.eclipse.emfcloud.jackson.resource.JsonResourceFactory; + +import tools.mdsd.jamopp.model.java.JavaClasspath; +import tools.mdsd.jamopp.options.ParserOptions; +import tools.mdsd.jamopp.parser.jdt.singlefile.JaMoPPJDTSingleFileParser; +import tools.mdsd.jamopp.proxy.IJavaContextDependentURIFragmentCollector; +import tools.mdsd.jamopp.recovery.trivial.TrivialRecovery; +import tools.mdsd.jamopp.resource.JavaResource2Factory; +import tools.mdsd.jamopp.test.OutputUtility; +import tools.mdsd.jamopp.test.OutputUtility.TransferResult; + +/** + * Class to perform performance tests and measurements. + */ +public class PerformanceTestExecutor { + private static final Logger LOGGER = LogManager.getLogger("jamopp." + + PerformanceTestExecutor.class.getSimpleName()); + private final String inputFolder = "target" + File.separator + "src-bulk" + File.separator + "TeaStore"; + private static final Path PARENT_OUTPUT = Paths.get("target", "tests", "output_performance"); + private static final Path JAVA_OUTPUT = PARENT_OUTPUT.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_JAVA); + private static final Path XMI_OUTPUT = PARENT_OUTPUT.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_XMI); + private static final Path JSON_OUTPUT = PARENT_OUTPUT.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_JSON); + private static final Path SUMMARY_FILE_PATH = PARENT_OUTPUT.resolve("summary.md"); + + public void setupTestEnvironment() throws IOException { + Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("java", new JavaResource2Factory()); + Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl()); + JavaClasspath.get().clear(); + JavaClasspath.get().registerStdLib(); + Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("json", new JsonResourceFactory()); + if (Files.exists(JAVA_OUTPUT)) { + PathUtils.deleteDirectory(JAVA_OUTPUT); + PathUtils.deleteDirectory(XMI_OUTPUT); + PathUtils.deleteDirectory(JSON_OUTPUT); + } + try { + Files.createDirectories(JAVA_OUTPUT); + Files.createDirectories(XMI_OUTPUT); + Files.createDirectories(JSON_OUTPUT); + } catch (IOException e1) { + } + } + + public void measureTeaStoreFullResolution() { + ParserOptions.CREATE_LAYOUT_INFORMATION.setValue(Boolean.TRUE); + ParserOptions.REGISTER_LOCAL.setValue(Boolean.TRUE); + ParserOptions.PREFER_BINDING_CONVERSION.setValue(Boolean.TRUE); + ParserOptions.RESOLVE_BINDINGS.setValue(Boolean.TRUE); + ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.TRUE); + ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.TRUE); + ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.TRUE); + measurePerformance("teastore-full-resolution", getNumberOfRepetitions(), true, false); + } + + /** + * Currently, this method is protected since it probably does not provide further valuable insights in addition to the other tests. + */ + protected void measureTeaStoreWithoutResolvingEverything() { + ParserOptions.CREATE_LAYOUT_INFORMATION.setValue(Boolean.TRUE); + ParserOptions.REGISTER_LOCAL.setValue(Boolean.TRUE); + ParserOptions.PREFER_BINDING_CONVERSION.setValue(Boolean.TRUE); + ParserOptions.RESOLVE_BINDINGS.setValue(Boolean.TRUE); + ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.TRUE); + ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.FALSE); + ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.TRUE); + measurePerformance("teastore-without-resolving-everything", getNumberOfRepetitions(), true, false); + } + + private void prepareParserOptionsForOneLevelResolution() { + ParserOptions.CREATE_LAYOUT_INFORMATION.setValue(Boolean.TRUE); + ParserOptions.REGISTER_LOCAL.setValue(Boolean.TRUE); + ParserOptions.PREFER_BINDING_CONVERSION.setValue(Boolean.TRUE); + ParserOptions.RESOLVE_BINDINGS.setValue(Boolean.TRUE); + ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.TRUE); + ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.FALSE); + ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.FALSE); + } + + public void measureTeaStoreWithOneLevelResolution() { + prepareParserOptionsForOneLevelResolution(); + measurePerformance("teastore-one-level-resolution", getNumberOfRepetitions(), false, true); + } + + /** + * Currently, this method is protected since it takes several hours to complete. + */ + protected void measureTeaStoreWithOneLevelResolutionAndFullResolution() { + prepareParserOptionsForOneLevelResolution(); + measurePerformance("teastore-one-level-resolution-full", 1, true, false); + } + + private void prepareParserOptionsForSecondVariant() { + ParserOptions.CREATE_LAYOUT_INFORMATION.setValue(Boolean.TRUE); + ParserOptions.REGISTER_LOCAL.setValue(Boolean.TRUE); + ParserOptions.PREFER_BINDING_CONVERSION.setValue(Boolean.TRUE); + ParserOptions.RESOLVE_BINDINGS.setValue(Boolean.FALSE); + ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.FALSE); + ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.FALSE); + ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.FALSE); + } + + public void measureTeaStoreSecondVariant() { + prepareParserOptionsForSecondVariant(); + measurePerformance("teastore-second-variant", getNumberOfRepetitions(), false, true); + } + + /** + * Currently, this method is protected since it takes several hours to complete. + */ + protected void measureTeaStoreSecondVariantAndFullResolution() { + prepareParserOptionsForSecondVariant(); + measurePerformance("teastore-second-variant-resolution", 1, true, false); + } + + public static void cleanEverything() throws IOException { + calculateAndSaveAllStatistics(); + } + + private static void calculateAndSaveAllStatistics() throws IOException { + StringBuilder builder = new StringBuilder(); + + Files + .walk(PARENT_OUTPUT, 1) + .filter(path -> Files.isRegularFile(path)) + .forEach(path -> { + builder.append("# Results for " + path.getFileName().toString()); + + var data = PerformanceData.load(path); + var stat = data.getStatistics(); + builder.append("\n\nAverage time (ms): " + stat.getMean() + " (with std. " + + stat.getStandardDeviation() + " ms)\n"); + + stat = data.getStatisticsWithoutResolution(); + builder.append("Average time without resolution (ms): " + stat.getMean() + " (with std. " + + stat.getStandardDeviation() + " ms)\n"); + builder.append("Average parsing time (ms): " + data.getAverageParseTime()); + builder.append("\nAverage resolution time (ms): " + data.getAverageResolutionTime()); + builder.append("\nAverage recovery time (ms): " + data.getAverageRecoveryTime()); + + for (var storage : data.getStorage()) { + builder.append("\n\nStorage (" + + storage.getId() + + "): " + + storage.getCodeFiles() + + " code files of overall " + + storage.getOverallFiles() + + " files taking " + + storage.getTakenStorageByCodeFiles() + + " Bytes for code files of overall " + + storage.getTakenStorage() + + " Bytes."); + } + builder.append("\n\n"); + }); + + Files.writeString(SUMMARY_FILE_PATH, builder.toString()); + } + + protected int getNumberOfRepetitions() { + return 100; + } + + private void measurePerformance(String name, int max, boolean fullResolution, boolean recover) { + LOGGER.debug("Executing performance measurements for " + name); + Path target = Paths.get(this.inputFolder); + JaMoPPJDTSingleFileParser parser = new JaMoPPJDTSingleFileParser(); + parser.setExclusionPatterns(".*?src/test/.*?"); + + Path outputMeasurement = PARENT_OUTPUT.resolve(name + ".json"); + PerformanceData result; + if (Files.exists(outputMeasurement)) { + result = PerformanceData.load(outputMeasurement); + } else { + result = new PerformanceData(); + } + int actualMax = Math.min(max, max - result.getPoints().size()); + for (int i = 0; i < actualMax; i++) { + System.out.println("Measurement " + i + " for " + name); + PerformanceDataPoint point = new PerformanceDataPoint(); + long millis = System.currentTimeMillis(); + ResourceSet set = parser.parseDirectory(target); + millis = System.currentTimeMillis() - millis; + point.setParseTime(millis); + if (fullResolution) { + millis = System.currentTimeMillis(); + EcoreUtil.resolveAll(set); + millis = System.currentTimeMillis() - millis; + } else { + var ress = new HashSet<>(set.getResources()); + millis = System.currentTimeMillis(); + for (Resource r : ress) { + EcoreUtil.resolveAll(r); + } + millis = System.currentTimeMillis() - millis; + } + point.setResolutionTime(millis); + + if (recover) { + millis = System.currentTimeMillis(); + new TrivialRecovery(set).recover(); + millis = System.currentTimeMillis() - millis; + point.setRecoverTime(millis); + } + + result.addPoint(point); + PerformanceData.save(result, outputMeasurement); + + if (i == 0 && (fullResolution || recover)) { + try { + result.setStorage(measureStorage(set)); + } catch (IOException e) { + fail(e); + } + PerformanceData.save(result, outputMeasurement); + } + + for (Resource res : set.getResources()) { + res.unload(); + } + IJavaContextDependentURIFragmentCollector.GLOBAL_INSTANCE + .getContextDependentURIFragmentMap().clear(); + } + LOGGER.debug("Finished meausring " + name); + } + + private List measureStorage(ResourceSet resourceSet) throws IOException { + StoragePerformance javaStorage = new StoragePerformance(); + javaStorage.setId(OutputUtility.SUPPORTED_FILE_EXTENSION_JAVA); + var result = OutputUtility.transferToOutput(resourceSet, JAVA_OUTPUT.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_JAVA, true); + fillStorageInformationFromTransfer(javaStorage, JAVA_OUTPUT, result); + + result.sourceTargetMapping().forEach((key, value) -> { + key.getContents().addAll(value.getContents()); + }); + + StoragePerformance xmiStorage = new StoragePerformance(); + xmiStorage.setId(OutputUtility.SUPPORTED_FILE_EXTENSION_XMI); + result = OutputUtility.transferToOutput(resourceSet, XMI_OUTPUT.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_XMI, true); + fillStorageInformationFromTransfer(xmiStorage, XMI_OUTPUT, result); + + result.sourceTargetMapping().forEach((key, value) -> { + key.getContents().addAll(value.getContents()); + }); + + StoragePerformance jsonStorage = new StoragePerformance(); + jsonStorage.setId(OutputUtility.SUPPORTED_FILE_EXTENSION_JSON); + fillStorageInformationFromTransfer(jsonStorage, JSON_OUTPUT, OutputUtility.transferToOutput(resourceSet, JSON_OUTPUT.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_JSON, true)); + + return List.of(javaStorage, xmiStorage, jsonStorage); + } + + private void fillStorageInformationFromTransfer(StoragePerformance storage, Path outputDir, TransferResult outputResult) throws IOException { + storage.setTakenStorage(PathUtils.sizeOfDirectory(outputDir)); + long codeFiles = 0; + long codeSize = 0; + for (var entry : outputResult.sourceTargetMapping().entrySet()) { + if (entry.getKey().getURI().isFile()) { + codeFiles++; + codeSize += FileUtils.sizeOf(new File(entry.getValue().getURI().toFileString())); + } + } + storage.setCodeFiles(codeFiles); + storage.setTakenStorageByCodeFiles(codeSize); + storage.setOverallFiles(outputResult.sourceTargetMapping().entrySet().size()); + } +} diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/StoragePerformance.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/StoragePerformance.java similarity index 100% rename from jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/StoragePerformance.java rename to jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/StoragePerformance.java diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/package-info.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/package-info.java similarity index 100% rename from jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/package-info.java rename to jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/package-info.java diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepFileChange.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepFileChange.java similarity index 100% rename from jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepFileChange.java rename to jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepFileChange.java diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java similarity index 100% rename from jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java rename to jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwiseEvaluationResult.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwiseEvaluationResult.java similarity index 100% rename from jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwiseEvaluationResult.java rename to jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwiseEvaluationResult.java diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java similarity index 100% rename from jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java rename to jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/package-info.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/package-info.java similarity index 100% rename from jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/stepwise/package-info.java rename to jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/package-info.java From bb85e7b54154def42fba6076e2203441eba14444 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Thu, 12 Mar 2026 14:48:35 +0100 Subject: [PATCH 27/45] Added a simple main class for standalone execution of the performance tests (#23). --- .../performance/PerformanceTestExecutor.java | 59 +++++++++++-------- .../PerformanceTestStandaloneMain.java | 21 +++++++ 2 files changed, 54 insertions(+), 26 deletions(-) create mode 100644 jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java index 30bde696..1c55752c 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java @@ -19,7 +19,6 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.HashSet; import java.util.List; @@ -48,12 +47,21 @@ public class PerformanceTestExecutor { private static final Logger LOGGER = LogManager.getLogger("jamopp." + PerformanceTestExecutor.class.getSimpleName()); - private final String inputFolder = "target" + File.separator + "src-bulk" + File.separator + "TeaStore"; - private static final Path PARENT_OUTPUT = Paths.get("target", "tests", "output_performance"); - private static final Path JAVA_OUTPUT = PARENT_OUTPUT.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_JAVA); - private static final Path XMI_OUTPUT = PARENT_OUTPUT.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_XMI); - private static final Path JSON_OUTPUT = PARENT_OUTPUT.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_JSON); - private static final Path SUMMARY_FILE_PATH = PARENT_OUTPUT.resolve("summary.md"); + private final Path inputFolder; + private final Path outputFolder; + private final Path javaOutput; + private final Path xmiOutput; + private final Path jsonOutput; + private final Path summaryFile; + + public PerformanceTestExecutor(Path inputFolder, Path outputFolder) { + this.inputFolder = inputFolder; + this.outputFolder = outputFolder; + this.javaOutput = outputFolder.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_JAVA); + this.xmiOutput = outputFolder.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_XMI); + this.jsonOutput = outputFolder.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_JSON); + this.summaryFile = outputFolder.resolve("summary.md"); + } public void setupTestEnvironment() throws IOException { Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("java", new JavaResource2Factory()); @@ -61,15 +69,15 @@ public void setupTestEnvironment() throws IOException { JavaClasspath.get().clear(); JavaClasspath.get().registerStdLib(); Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("json", new JsonResourceFactory()); - if (Files.exists(JAVA_OUTPUT)) { - PathUtils.deleteDirectory(JAVA_OUTPUT); - PathUtils.deleteDirectory(XMI_OUTPUT); - PathUtils.deleteDirectory(JSON_OUTPUT); + if (Files.exists(javaOutput)) { + PathUtils.deleteDirectory(javaOutput); + PathUtils.deleteDirectory(xmiOutput); + PathUtils.deleteDirectory(jsonOutput); } try { - Files.createDirectories(JAVA_OUTPUT); - Files.createDirectories(XMI_OUTPUT); - Files.createDirectories(JSON_OUTPUT); + Files.createDirectories(javaOutput); + Files.createDirectories(xmiOutput); + Files.createDirectories(jsonOutput); } catch (IOException e1) { } } @@ -145,15 +153,15 @@ protected void measureTeaStoreSecondVariantAndFullResolution() { measurePerformance("teastore-second-variant-resolution", 1, true, false); } - public static void cleanEverything() throws IOException { + public void cleanEverything() throws IOException { calculateAndSaveAllStatistics(); } - private static void calculateAndSaveAllStatistics() throws IOException { + private void calculateAndSaveAllStatistics() throws IOException { StringBuilder builder = new StringBuilder(); Files - .walk(PARENT_OUTPUT, 1) + .walk(outputFolder, 1) .filter(path -> Files.isRegularFile(path)) .forEach(path -> { builder.append("# Results for " + path.getFileName().toString()); @@ -184,7 +192,7 @@ private static void calculateAndSaveAllStatistics() throws IOException { builder.append("\n\n"); }); - Files.writeString(SUMMARY_FILE_PATH, builder.toString()); + Files.writeString(summaryFile, builder.toString()); } protected int getNumberOfRepetitions() { @@ -193,11 +201,10 @@ protected int getNumberOfRepetitions() { private void measurePerformance(String name, int max, boolean fullResolution, boolean recover) { LOGGER.debug("Executing performance measurements for " + name); - Path target = Paths.get(this.inputFolder); JaMoPPJDTSingleFileParser parser = new JaMoPPJDTSingleFileParser(); parser.setExclusionPatterns(".*?src/test/.*?"); - Path outputMeasurement = PARENT_OUTPUT.resolve(name + ".json"); + Path outputMeasurement = outputFolder.resolve(name + ".json"); PerformanceData result; if (Files.exists(outputMeasurement)) { result = PerformanceData.load(outputMeasurement); @@ -209,7 +216,7 @@ private void measurePerformance(String name, int max, boolean fullResolution, bo System.out.println("Measurement " + i + " for " + name); PerformanceDataPoint point = new PerformanceDataPoint(); long millis = System.currentTimeMillis(); - ResourceSet set = parser.parseDirectory(target); + ResourceSet set = parser.parseDirectory(inputFolder); millis = System.currentTimeMillis() - millis; point.setParseTime(millis); if (fullResolution) { @@ -257,8 +264,8 @@ private void measurePerformance(String name, int max, boolean fullResolution, bo private List measureStorage(ResourceSet resourceSet) throws IOException { StoragePerformance javaStorage = new StoragePerformance(); javaStorage.setId(OutputUtility.SUPPORTED_FILE_EXTENSION_JAVA); - var result = OutputUtility.transferToOutput(resourceSet, JAVA_OUTPUT.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_JAVA, true); - fillStorageInformationFromTransfer(javaStorage, JAVA_OUTPUT, result); + var result = OutputUtility.transferToOutput(resourceSet, javaOutput.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_JAVA, true); + fillStorageInformationFromTransfer(javaStorage, javaOutput, result); result.sourceTargetMapping().forEach((key, value) -> { key.getContents().addAll(value.getContents()); @@ -266,8 +273,8 @@ private List measureStorage(ResourceSet resourceSet) throws StoragePerformance xmiStorage = new StoragePerformance(); xmiStorage.setId(OutputUtility.SUPPORTED_FILE_EXTENSION_XMI); - result = OutputUtility.transferToOutput(resourceSet, XMI_OUTPUT.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_XMI, true); - fillStorageInformationFromTransfer(xmiStorage, XMI_OUTPUT, result); + result = OutputUtility.transferToOutput(resourceSet, xmiOutput.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_XMI, true); + fillStorageInformationFromTransfer(xmiStorage, xmiOutput, result); result.sourceTargetMapping().forEach((key, value) -> { key.getContents().addAll(value.getContents()); @@ -275,7 +282,7 @@ private List measureStorage(ResourceSet resourceSet) throws StoragePerformance jsonStorage = new StoragePerformance(); jsonStorage.setId(OutputUtility.SUPPORTED_FILE_EXTENSION_JSON); - fillStorageInformationFromTransfer(jsonStorage, JSON_OUTPUT, OutputUtility.transferToOutput(resourceSet, JSON_OUTPUT.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_JSON, true)); + fillStorageInformationFromTransfer(jsonStorage, jsonOutput, OutputUtility.transferToOutput(resourceSet, jsonOutput.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_JSON, true)); return List.of(javaStorage, xmiStorage, jsonStorage); } diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java new file mode 100644 index 00000000..d5f7e45d --- /dev/null +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java @@ -0,0 +1,21 @@ +package tools.mdsd.jamopp.test.performance; + +import java.io.IOException; +import java.nio.file.Paths; + +public final class PerformanceTestStandaloneMain { + private PerformanceTestStandaloneMain() {} + + public static void main(String[] args) { + PerformanceTestExecutor executor = new PerformanceTestExecutor(Paths.get(""), Paths.get("")); + try { + executor.setupTestEnvironment(); + executor.measureTeaStoreSecondVariant(); + executor.measureTeaStoreWithOneLevelResolution(); + executor.measureTeaStoreFullResolution(); + executor.cleanEverything(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} From 7aa4f98dc2400ed1dd5526d1d7f4b20134cc7e7b Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Thu, 12 Mar 2026 18:24:49 +0100 Subject: [PATCH 28/45] Added a chart library to generate charts for the performance test results (#23). --- jamopp.tests/pom.xml | 4 + .../tools/mdsd/jamopp/test/ChartUtility.java | 96 +++++++++++++++++++ .../performance/PerformanceTestExecutor.java | 13 ++- pom.xml | 5 + 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java diff --git a/jamopp.tests/pom.xml b/jamopp.tests/pom.xml index 4e8faee8..1ccb5fd2 100644 --- a/jamopp.tests/pom.xml +++ b/jamopp.tests/pom.xml @@ -160,5 +160,9 @@ org.eclipse.jgit org.eclipse.jgit + + org.knowm.xchart + xchart + diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java new file mode 100644 index 00000000..05d85a8a --- /dev/null +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2026 + * Modelling for Continuous Software Engineering (MCSE) group, + * Institute of Information Security and Dependability (KASTEL), + * Karlsruhe Institute of Technology (KIT). + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Martin Armbruster (MCSE) + * - Initial implementation + ******************************************************************************/ + +package tools.mdsd.jamopp.test; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; + +import org.knowm.xchart.VectorGraphicsEncoder; +import org.knowm.xchart.VectorGraphicsEncoder.VectorGraphicsFormat; +import org.knowm.xchart.XYChartBuilder; +import org.knowm.xchart.XYSeries.XYSeriesRenderStyle; + +import tools.mdsd.jamopp.test.performance.PerformanceData; + +public final class ChartUtility { + public final static String DEFAULT_X_AXIS_TITLE = "# Measurement"; + private final static String UNIT_MILLISECONDS = "ms"; + private final static String UNIT_SECONDS = "s"; + private final static String UNIT_MINUTES = "min"; + private final static String UNIT_HOURS = "h"; + private final static double MILLISECONDS_OF_ONE_SECOND = 1000; + private final static double SECONDS_OF_ONE_MINUTE = 60; + private final static double MINUTES_OF_ONE_HOUR = 60; + private final static double MILLISECONDS_OF_ONE_MINUTE = MILLISECONDS_OF_ONE_SECOND * SECONDS_OF_ONE_MINUTE; + private final static double MILLISECONDS_OF_ONE_HOUR = MILLISECONDS_OF_ONE_MINUTE * MINUTES_OF_ONE_HOUR; + + private ChartUtility() {} + + public static void buildAndSaveChartsForPerformanceData(String dataName, PerformanceData data, Path outputDirectory) throws IOException { + double[] parsingTimes = new double[data.getPoints().size()]; + double[] resolutionTimes = new double[parsingTimes.length]; + double[] recoveryTimes = new double[resolutionTimes.length]; + + int index = 0; + for (var dataPoint : data.getPoints()) { + parsingTimes[index] = dataPoint.getParseTime(); + resolutionTimes[index] = dataPoint.getResolutionTime(); + recoveryTimes[index] = dataPoint.getRecoverTime(); + index++; + } + + buildAndSaveChart(parsingTimes, dataName + " - Parsing", + DEFAULT_X_AXIS_TITLE, "Parsing Time (" + adjustDataUnit(parsingTimes) + ")", outputDirectory.resolve(dataName + "-parsing.pdf")); + buildAndSaveChart(resolutionTimes, dataName + " - Resolution", + DEFAULT_X_AXIS_TITLE, "Resolution Time (" + adjustDataUnit(resolutionTimes) + ")", outputDirectory.resolve(dataName + "-resolution.pdf")); + buildAndSaveChart(recoveryTimes, dataName + " - Recovery", + DEFAULT_X_AXIS_TITLE, "Recovery Time (" + adjustDataUnit(recoveryTimes) + ")", outputDirectory.resolve(dataName + "-recovery.pdf")); + } + + public static void buildAndSaveChart(double[] data, String title, String xAxisTitle, String yAxisTitle, Path chartFile) throws IOException { + var chart = new XYChartBuilder().title(title).xAxisTitle(xAxisTitle).yAxisTitle(yAxisTitle).build(); + chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Line).setLegendVisible(false); + chart.addSeries(yAxisTitle, data); + VectorGraphicsEncoder.saveVectorGraphic(chart, chartFile.toAbsolutePath().toString(), VectorGraphicsFormat.PDF); + } + + private static String adjustDataUnit(double[] data) { + var min = Arrays.stream(data).min().getAsDouble(); + var minMaxDiff = Arrays.stream(data).max().getAsDouble() - min; + double unitAdjustingFactor = 0.0; + String unitName = UNIT_MILLISECONDS; + + if (min > MILLISECONDS_OF_ONE_SECOND && minMaxDiff < MILLISECONDS_OF_ONE_MINUTE / 2) { + unitAdjustingFactor = MILLISECONDS_OF_ONE_SECOND; + unitName = UNIT_SECONDS; + } else if (min > MILLISECONDS_OF_ONE_MINUTE && minMaxDiff < MILLISECONDS_OF_ONE_HOUR / 2) { + unitAdjustingFactor = MILLISECONDS_OF_ONE_MINUTE; + unitName = UNIT_MINUTES; + } else if (minMaxDiff >= MILLISECONDS_OF_ONE_HOUR / 2) { + unitAdjustingFactor = MILLISECONDS_OF_ONE_HOUR; + unitName = UNIT_HOURS; + } else { + return unitName; + } + + for (int index = 0; index < data.length; index++) { + data[index] = data[index] / unitAdjustingFactor; + } + return unitName; + } +} diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java index 1c55752c..e5ff50ba 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java @@ -38,6 +38,7 @@ import tools.mdsd.jamopp.proxy.IJavaContextDependentURIFragmentCollector; import tools.mdsd.jamopp.recovery.trivial.TrivialRecovery; import tools.mdsd.jamopp.resource.JavaResource2Factory; +import tools.mdsd.jamopp.test.ChartUtility; import tools.mdsd.jamopp.test.OutputUtility; import tools.mdsd.jamopp.test.OutputUtility.TransferResult; @@ -162,9 +163,11 @@ private void calculateAndSaveAllStatistics() throws IOException { Files .walk(outputFolder, 1) - .filter(path -> Files.isRegularFile(path)) + .filter(Files::isRegularFile) + .filter(path -> path.toString().endsWith(OutputUtility.SUPPORTED_FILE_EXTENSION_JSON)) .forEach(path -> { - builder.append("# Results for " + path.getFileName().toString()); + String name = path.getFileName().toString(); + builder.append("# Results for " + name); var data = PerformanceData.load(path); var stat = data.getStatistics(); @@ -190,6 +193,12 @@ private void calculateAndSaveAllStatistics() throws IOException { + " Bytes."); } builder.append("\n\n"); + + try { + ChartUtility.buildAndSaveChartsForPerformanceData(name, data, outputFolder); + } catch (IOException e) { + LOGGER.info("Could not create and store charts for: " + name); + } }); Files.writeString(summaryFile, builder.toString()); diff --git a/pom.xml b/pom.xml index 09204f24..b88eb697 100644 --- a/pom.xml +++ b/pom.xml @@ -168,6 +168,11 @@ org.eclipse.jgit 6.7.0.202309050840-r + + org.knowm.xchart + xchart + 3.8.8 + From b1d705b86d997c2adc5b8c4b04e27311ffffb4d4 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Thu, 12 Mar 2026 18:32:36 +0100 Subject: [PATCH 29/45] A chart with the overall time is also generated (#23). --- .../src/main/java/tools/mdsd/jamopp/test/ChartUtility.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java index 05d85a8a..720773c2 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java @@ -45,12 +45,14 @@ public static void buildAndSaveChartsForPerformanceData(String dataName, Perform double[] parsingTimes = new double[data.getPoints().size()]; double[] resolutionTimes = new double[parsingTimes.length]; double[] recoveryTimes = new double[resolutionTimes.length]; + double[] overallTimes = new double[recoveryTimes.length]; int index = 0; for (var dataPoint : data.getPoints()) { parsingTimes[index] = dataPoint.getParseTime(); resolutionTimes[index] = dataPoint.getResolutionTime(); recoveryTimes[index] = dataPoint.getRecoverTime(); + overallTimes[index] = dataPoint.getParseTime() + dataPoint.getResolutionTime() + dataPoint.getRecoverTime(); index++; } @@ -60,6 +62,8 @@ public static void buildAndSaveChartsForPerformanceData(String dataName, Perform DEFAULT_X_AXIS_TITLE, "Resolution Time (" + adjustDataUnit(resolutionTimes) + ")", outputDirectory.resolve(dataName + "-resolution.pdf")); buildAndSaveChart(recoveryTimes, dataName + " - Recovery", DEFAULT_X_AXIS_TITLE, "Recovery Time (" + adjustDataUnit(recoveryTimes) + ")", outputDirectory.resolve(dataName + "-recovery.pdf")); + buildAndSaveChart(overallTimes, dataName + " - Sum of Parsing, Resolution, and Recovery", + DEFAULT_X_AXIS_TITLE, "Overall Time (" + adjustDataUnit(overallTimes) + ")", outputDirectory.resolve(dataName + "-overall.pdf")); } public static void buildAndSaveChart(double[] data, String title, String xAxisTitle, String yAxisTitle, Path chartFile) throws IOException { From df53ff61399d471d59e6000322cd2e9b81d059e1 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Sun, 15 Mar 2026 19:50:06 +0100 Subject: [PATCH 30/45] Charts are generated ffor the step-wise performance test (#23). --- .../tools/mdsd/jamopp/test/ChartUtility.java | 32 ++++++++-- .../stepwise/StepwisePerformanceExecutor.java | 60 +++++++++++++++++-- 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java index 720773c2..2fc45a20 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java @@ -57,23 +57,43 @@ public static void buildAndSaveChartsForPerformanceData(String dataName, Perform } buildAndSaveChart(parsingTimes, dataName + " - Parsing", - DEFAULT_X_AXIS_TITLE, "Parsing Time (" + adjustDataUnit(parsingTimes) + ")", outputDirectory.resolve(dataName + "-parsing.pdf")); + DEFAULT_X_AXIS_TITLE, "Parsing Time (" + adjustTimeMillisecondsUnit(parsingTimes) + ")", outputDirectory.resolve(dataName + "-parsing.pdf")); buildAndSaveChart(resolutionTimes, dataName + " - Resolution", - DEFAULT_X_AXIS_TITLE, "Resolution Time (" + adjustDataUnit(resolutionTimes) + ")", outputDirectory.resolve(dataName + "-resolution.pdf")); + DEFAULT_X_AXIS_TITLE, "Resolution Time (" + adjustTimeMillisecondsUnit(resolutionTimes) + ")", outputDirectory.resolve(dataName + "-resolution.pdf")); buildAndSaveChart(recoveryTimes, dataName + " - Recovery", - DEFAULT_X_AXIS_TITLE, "Recovery Time (" + adjustDataUnit(recoveryTimes) + ")", outputDirectory.resolve(dataName + "-recovery.pdf")); + DEFAULT_X_AXIS_TITLE, "Recovery Time (" + adjustTimeMillisecondsUnit(recoveryTimes) + ")", outputDirectory.resolve(dataName + "-recovery.pdf")); buildAndSaveChart(overallTimes, dataName + " - Sum of Parsing, Resolution, and Recovery", - DEFAULT_X_AXIS_TITLE, "Overall Time (" + adjustDataUnit(overallTimes) + ")", outputDirectory.resolve(dataName + "-overall.pdf")); + DEFAULT_X_AXIS_TITLE, "Overall Time (" + adjustTimeMillisecondsUnit(overallTimes) + ")", outputDirectory.resolve(dataName + "-overall.pdf")); + } + + public static void buildAndSaveChartWithDiff(double[] data, String title, String xAxisTitle, String yAxisTitle, Path chartFile) throws IOException { + double[] diffData = new double[data.length - 1]; + for (var index = 0; index < diffData.length; index++) { + diffData[index] = data[index + 1] - data[index]; + } + + buildAndSaveChart(data, title, xAxisTitle, yAxisTitle, chartFile); + buildAndSaveChart(diffData, title + " (Diff)", xAxisTitle, yAxisTitle, chartFile.resolveSibling(chartFile.getFileName().toString() + "-diff.pdf")); } public static void buildAndSaveChart(double[] data, String title, String xAxisTitle, String yAxisTitle, Path chartFile) throws IOException { + buildAndSaveChart(null, data, title, xAxisTitle, yAxisTitle, chartFile); + } + + public static void buildAndSaveChart(double[] xData, double[] yData, String title, String xAxisTitle, String yAxisTitle, Path chartFile) throws IOException { var chart = new XYChartBuilder().title(title).xAxisTitle(xAxisTitle).yAxisTitle(yAxisTitle).build(); chart.getStyler().setDefaultSeriesRenderStyle(XYSeriesRenderStyle.Line).setLegendVisible(false); - chart.addSeries(yAxisTitle, data); + + if (xData == null) { + chart.addSeries(yAxisTitle, yData); + } else { + chart.addSeries(yAxisTitle, xData, yData); + } + VectorGraphicsEncoder.saveVectorGraphic(chart, chartFile.toAbsolutePath().toString(), VectorGraphicsFormat.PDF); } - private static String adjustDataUnit(double[] data) { + public static String adjustTimeMillisecondsUnit(double[] data) { var min = Arrays.stream(data).min().getAsDouble(); var minMaxDiff = Arrays.stream(data).max().getAsDouble() - min; double unitAdjustingFactor = 0.0; diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java index b03804af..0a889763 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java @@ -20,7 +20,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicLong; import org.apache.logging.log4j.Logger; @@ -39,6 +41,7 @@ import tools.mdsd.jamopp.parser.jdt.singlefile.JaMoPPJDTSingleFileParser; import tools.mdsd.jamopp.proxy.IJavaContextDependentURIFragmentCollector; +import tools.mdsd.jamopp.test.ChartUtility; import tools.mdsd.jamopp.test.OutputUtility; public class StepwisePerformanceExecutor { @@ -47,6 +50,21 @@ public class StepwisePerformanceExecutor { private static final String GIT_AUTHOR_MAIL = "noreply@null.localhost"; private static final String GIT_DIRECTORY_NAME = "models"; private static final String RESULTS_FILE_NAME = "results.json"; + private static final String CHARTS_DEFAULT_X_AXIS_NAME = "# Iteration"; + private static final String CHARTS_PROXY_COUNT_Y_AXIS_NAME = "# Proxy Objects"; + private static final String CHARTS_PROXY_COUNT_TITLE = "Proxy Objects"; + private static final String CHARTS_PROXY_COUNT_FILE_NAME = "chart-proxies.pdf"; + private static final String CHARTS_DEFAULT_TIME_Y_AXIS_NAME = "Execution Time (%s)"; + private static final String CHARTS_RESOLUTION_TIME_TITLE = "Resolution Time"; + private static final String CHARTS_RESOLUTION_TIME_FILE_NAME = "chart-resolution-time.pdf"; + private static final String CHARTS_MODEL_SAVING_TIME_TITLE = "Model Saving Time"; + private static final String CHARTS_MODEL_SAVING_TIME_FILE_NAME = "chart-model-saving-time.pdf"; + private static final String CHARTS_MODEL_COUNT_TITLE = "Models"; + private static final String CHARTS_MODEL_COUNT_Y_AXIS_NAME = "# Models"; + private static final String CHARTS_MODEL_COUNT_FILE_NAME = "chart-model-count.pdf"; + private static final String CHARTS_MODEL_SIZE_TITLE = "Model Size"; + private static final String CHARTS_MODEL_SIZE_Y_AXIS_NAME = "Model Size"; + private static final String CHARTS_MODEL_SIZE_FILE_NAME = "chart-model-size.pdf"; private Git git; private RevCommit lastCommit; @@ -129,11 +147,9 @@ public void measurePerformance(String name, Path srcDirectory, Path outputDirect res.unload(); } IJavaContextDependentURIFragmentCollector.GLOBAL_INSTANCE.getContextDependentURIFragmentMap().clear(); - + + buildAndSaveCharts(result, outputDirectory); LOGGER.info("Finished measuring: " + name); - // Planned graphs: iteration number (x axis) vs. following numbers on y axis - // Number of proxy objects, change (netto) in proxy objects, file size, change (netto) in file size, - // number of files, change (netto) in number of files, duration of resolution, duration of saving files, memory consumption } private Pair, Long> storeModelsAndCalculateSizeChanges(ResourceSet resourceSet, Path output) throws GitAPIException, IOException { @@ -182,4 +198,40 @@ private void saveResults(StepwiseEvaluationResult results, Path file) throws IOE Gson gson = new Gson(); Files.writeString(file, gson.toJson(results)); } + + private void buildAndSaveCharts(StepwiseEvaluationResult result, Path outputDir) throws IOException { + // Planned graphs: iteration number (x axis) vs. following numbers on y axis + // memory consumption + var currentData = result.getSteps().stream().mapToDouble(EvaluationStepResult::getTotalProxies).toArray(); + ChartUtility.buildAndSaveChartWithDiff(currentData, CHARTS_PROXY_COUNT_TITLE, CHARTS_DEFAULT_X_AXIS_NAME, + CHARTS_PROXY_COUNT_Y_AXIS_NAME, outputDir.resolve(CHARTS_PROXY_COUNT_FILE_NAME)); + + currentData = result.getSteps().stream().mapToDouble(EvaluationStepResult::getTimeResolution).toArray(); + ChartUtility.buildAndSaveChart(currentData, CHARTS_RESOLUTION_TIME_TITLE, CHARTS_DEFAULT_X_AXIS_NAME, + String.format(CHARTS_DEFAULT_TIME_Y_AXIS_NAME, ChartUtility.adjustTimeMillisecondsUnit(currentData)), + outputDir.resolve(CHARTS_RESOLUTION_TIME_FILE_NAME)); + + currentData = result.getSteps().stream().mapToDouble(EvaluationStepResult::getTimeModelSaving).toArray(); + ChartUtility.buildAndSaveChart(currentData, CHARTS_MODEL_SAVING_TIME_TITLE, CHARTS_DEFAULT_X_AXIS_NAME, + String.format(CHARTS_DEFAULT_TIME_Y_AXIS_NAME, ChartUtility.adjustTimeMillisecondsUnit(currentData)), + outputDir.resolve(CHARTS_MODEL_SAVING_TIME_FILE_NAME)); + + double[] sizes = new double[currentData.length]; + Map filesToSizes = new HashMap<>(); + + for (var index = 0; index < currentData.length; index++) { + var step = result.getSteps().get(index); + step.getChangedFiles().forEach(file -> { + filesToSizes.put(file.getPath(), file.getNewSize()); + }); + + currentData[index] = filesToSizes.size(); + sizes[index] = filesToSizes.entrySet().stream().mapToDouble(Map.Entry::getValue).sum(); + } + + ChartUtility.buildAndSaveChartWithDiff(currentData, CHARTS_MODEL_COUNT_TITLE, CHARTS_DEFAULT_X_AXIS_NAME, + CHARTS_MODEL_COUNT_Y_AXIS_NAME, outputDir.resolve(CHARTS_MODEL_COUNT_FILE_NAME)); + ChartUtility.buildAndSaveChartWithDiff(sizes, CHARTS_MODEL_SIZE_TITLE, CHARTS_DEFAULT_X_AXIS_NAME, + CHARTS_MODEL_SIZE_Y_AXIS_NAME, outputDir.resolve(CHARTS_MODEL_SIZE_FILE_NAME)); + } } From 6c1220c9c51e4f48c855ea7868c01d18d638dafb Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 16 Mar 2026 11:28:19 +0100 Subject: [PATCH 31/45] Fixed the step-wise performance test executor: the parser options are correctly set, and the initial Git diff is correctly calculated (#23). --- .../stepwise/EvaluationStepResult.java | 3 ++- .../stepwise/StepwisePerformanceExecutor.java | 22 ++++++++++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java index c696d67d..93853ee7 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/EvaluationStepResult.java @@ -16,6 +16,7 @@ package tools.mdsd.jamopp.test.performance.stepwise; +import java.util.ArrayList; import java.util.List; public class EvaluationStepResult { @@ -23,7 +24,7 @@ public class EvaluationStepResult { private long timeResolution; private long timeModelSaving; private long totalProxies; - private List changedFiles; + private List changedFiles = new ArrayList<>(); public int getStep() { return step; diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java index 0a889763..8e7fd1ee 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java @@ -35,10 +35,13 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.treewalk.EmptyTreeIterator; import com.google.gson.Gson; +import tools.mdsd.jamopp.options.ParserOptions; import tools.mdsd.jamopp.parser.jdt.singlefile.JaMoPPJDTSingleFileParser; import tools.mdsd.jamopp.proxy.IJavaContextDependentURIFragmentCollector; import tools.mdsd.jamopp.test.ChartUtility; @@ -67,6 +70,16 @@ public class StepwisePerformanceExecutor { private static final String CHARTS_MODEL_SIZE_FILE_NAME = "chart-model-size.pdf"; private Git git; private RevCommit lastCommit; + + private void prepareParserOptionsForSecondVariant() { + ParserOptions.CREATE_LAYOUT_INFORMATION.setValue(Boolean.TRUE); + ParserOptions.REGISTER_LOCAL.setValue(Boolean.TRUE); + ParserOptions.PREFER_BINDING_CONVERSION.setValue(Boolean.FALSE); + ParserOptions.RESOLVE_BINDINGS.setValue(Boolean.FALSE); + ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.FALSE); + ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.FALSE); + ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.FALSE); + } public void measurePerformance(String name, Path srcDirectory, Path outputDirectory) throws IOException, GitAPIException { if (Files.notExists(srcDirectory) || !Files.isDirectory(srcDirectory)) { @@ -81,6 +94,7 @@ public void measurePerformance(String name, Path srcDirectory, Path outputDirect Files.createDirectories(outputDirectory); } + prepareParserOptionsForSecondVariant(); LOGGER.info("Executing performance measurements for: " + name); StepwiseEvaluationResult result = new StepwiseEvaluationResult(); result.setName(name); @@ -171,11 +185,13 @@ private Pair, Long> storeModelsAndCalculateSizeCh // Get all changed files. var gitObjReader = this.git.getRepository().newObjectReader(); - CanonicalTreeParser oldTree = null; + AbstractTreeIterator oldTree = null; if (this.lastCommit != null) { - oldTree = new CanonicalTreeParser(null, gitObjReader, this.lastCommit); + oldTree = new CanonicalTreeParser(null, gitObjReader, this.lastCommit.getTree().getId()); + } else { + oldTree = new EmptyTreeIterator(); } - CanonicalTreeParser newTree = new CanonicalTreeParser(null, gitObjReader, recentCommit); + CanonicalTreeParser newTree = new CanonicalTreeParser(null, gitObjReader, recentCommit.getTree().getId()); var diffEntries = this.git.diff().setShowNameOnly(true).setOldTree(oldTree).setNewTree(newTree).call(); // Calculate the size for every changed file. From 2efddea1e4066512d62261a1c3cbd4fc7660dfe8 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 16 Mar 2026 11:29:26 +0100 Subject: [PATCH 32/45] The OutputUtility assumes an absolute path for the output directory (#23). --- .../src/main/java/tools/mdsd/jamopp/test/OutputUtility.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/OutputUtility.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/OutputUtility.java index f6d40a7e..8bff5551 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/OutputUtility.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/OutputUtility.java @@ -65,8 +65,7 @@ public static TransferResult transferToOutput(List sources, String out continue; } - File outputFile = new File("." + File.separator + outputFolder - + File.separator + outputFileName); + File outputFile = new File(outputFolder + File.separator + outputFileName); URI fileURI = URI.createFileURI(outputFile.getAbsolutePath()).appendFileExtension(fileExtension); Resource targetResource = targetSet.createResource(fileURI); From 3f7fa7ccd838d33db278f3a3693458b322a5eb94 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 16 Mar 2026 11:36:55 +0100 Subject: [PATCH 33/45] Extended the main class to execute different performance tests via CLI arguments (#23). --- .../performance/PerformanceTestExecutor.java | 17 +++---- .../PerformanceTestStandaloneMain.java | 44 ++++++++++++++++++- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java index e5ff50ba..ef327bb1 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java @@ -29,15 +29,11 @@ import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.util.EcoreUtil; -import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; -import org.eclipse.emfcloud.jackson.resource.JsonResourceFactory; -import tools.mdsd.jamopp.model.java.JavaClasspath; import tools.mdsd.jamopp.options.ParserOptions; import tools.mdsd.jamopp.parser.jdt.singlefile.JaMoPPJDTSingleFileParser; import tools.mdsd.jamopp.proxy.IJavaContextDependentURIFragmentCollector; import tools.mdsd.jamopp.recovery.trivial.TrivialRecovery; -import tools.mdsd.jamopp.resource.JavaResource2Factory; import tools.mdsd.jamopp.test.ChartUtility; import tools.mdsd.jamopp.test.OutputUtility; import tools.mdsd.jamopp.test.OutputUtility.TransferResult; @@ -54,6 +50,7 @@ public class PerformanceTestExecutor { private final Path xmiOutput; private final Path jsonOutput; private final Path summaryFile; + private int numberOfRepetitions; public PerformanceTestExecutor(Path inputFolder, Path outputFolder) { this.inputFolder = inputFolder; @@ -62,14 +59,10 @@ public PerformanceTestExecutor(Path inputFolder, Path outputFolder) { this.xmiOutput = outputFolder.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_XMI); this.jsonOutput = outputFolder.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_JSON); this.summaryFile = outputFolder.resolve("summary.md"); + this.numberOfRepetitions = 100; } public void setupTestEnvironment() throws IOException { - Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("java", new JavaResource2Factory()); - Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl()); - JavaClasspath.get().clear(); - JavaClasspath.get().registerStdLib(); - Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("json", new JsonResourceFactory()); if (Files.exists(javaOutput)) { PathUtils.deleteDirectory(javaOutput); PathUtils.deleteDirectory(xmiOutput); @@ -204,8 +197,12 @@ private void calculateAndSaveAllStatistics() throws IOException { Files.writeString(summaryFile, builder.toString()); } + protected void setNumberOfRepetitions(int noRepetitions) { + this.numberOfRepetitions = noRepetitions; + } + protected int getNumberOfRepetitions() { - return 100; + return this.numberOfRepetitions; } private void measurePerformance(String name, int max, boolean fullResolution, boolean recover) { diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java index d5f7e45d..a8bc314d 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java @@ -1,13 +1,55 @@ package tools.mdsd.jamopp.test.performance; import java.io.IOException; +import java.nio.file.Path; import java.nio.file.Paths; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.emf.ecore.xmi.impl.XMIResourceFactoryImpl; +import org.eclipse.emfcloud.jackson.resource.JsonResourceFactory; +import org.eclipse.jgit.api.errors.GitAPIException; + +import tools.mdsd.jamopp.model.java.JavaClasspath; +import tools.mdsd.jamopp.resource.JavaResource2Factory; +import tools.mdsd.jamopp.test.performance.stepwise.StepwisePerformanceExecutor; + public final class PerformanceTestStandaloneMain { + public final static Path DEFAULT_INPUT_PATH = Paths.get("target", "src-bulk", "TeaStore"); + public final static Path DEFAULT_OUTPUT_PATH = Paths.get("target", "tests", "output_performance"); + private final static Path MAIN_ROOT_PATH = Paths.get("jamopp.tests").toAbsolutePath(); + private final static String OPTION_NAME_STEPWISE_TEST = "stepwise"; + private final static String OPTION_NAME_FULL_TEST = "full"; + private PerformanceTestStandaloneMain() {} + public static void setupRegistries() { + Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("java", new JavaResource2Factory()); + Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put(Resource.Factory.Registry.DEFAULT_EXTENSION, new XMIResourceFactoryImpl()); + JavaClasspath.get().clear(); + JavaClasspath.get().registerStdLib(); + Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("json", new JsonResourceFactory()); + } + public static void main(String[] args) { - PerformanceTestExecutor executor = new PerformanceTestExecutor(Paths.get(""), Paths.get("")); + setupRegistries(); + + if (args.length == 1 && args[0].equals(OPTION_NAME_STEPWISE_TEST)) { + StepwisePerformanceExecutor executor = new StepwisePerformanceExecutor(); + try { + executor.measurePerformance("teastore-stepwise", MAIN_ROOT_PATH.resolve(DEFAULT_INPUT_PATH), + MAIN_ROOT_PATH.resolve(DEFAULT_OUTPUT_PATH)); + } catch (IOException | GitAPIException e) { + e.printStackTrace(); + } + return; + } + + PerformanceTestExecutor executor = new PerformanceTestExecutor(MAIN_ROOT_PATH.resolve(DEFAULT_INPUT_PATH), + MAIN_ROOT_PATH.resolve(DEFAULT_OUTPUT_PATH)); + if (!(args.length == 1 && args[0].equals(OPTION_NAME_FULL_TEST))) { + executor.setNumberOfRepetitions(1); + } + try { executor.setupTestEnvironment(); executor.measureTeaStoreSecondVariant(); From 9bd88935ec202104eead04023243f98ffe62c4ef Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 16 Mar 2026 11:49:05 +0100 Subject: [PATCH 34/45] The performance JUnit tests use the main classes (#23). --- .../test/performance/PerformanceTest.java | 296 ++---------------- .../performance/ReducedPerformanceTest.java | 9 +- 2 files changed, 24 insertions(+), 281 deletions(-) diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java index 425e4053..2213d3dd 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/PerformanceTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021, Martin Armbruster + * Copyright (c) 2021-2026, Martin Armbruster * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 @@ -13,323 +13,63 @@ package tools.mdsd.jamopp.test.performance; -import static org.junit.jupiter.api.Assertions.fail; - -import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashSet; -import java.util.List; -import java.util.Set; import org.apache.logging.log4j.Logger; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.file.PathUtils; import org.apache.logging.log4j.LogManager; -import org.eclipse.emf.common.util.URI; -import org.eclipse.emf.ecore.resource.Resource; -import org.eclipse.emf.ecore.resource.ResourceSet; -import org.eclipse.emf.ecore.util.EcoreUtil; -import org.eclipse.emfcloud.jackson.resource.JsonResourceFactory; import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; - -import tools.mdsd.jamopp.options.ParserOptions; -import tools.mdsd.jamopp.parser.jdt.singlefile.JaMoPPJDTSingleFileParser; -import tools.mdsd.jamopp.proxy.IJavaContextDependentURIFragmentCollector; -import tools.mdsd.jamopp.recovery.trivial.TrivialRecovery; -import tools.mdsd.jamopp.resource.JavaResource2; import tools.mdsd.jamopp.test.AbstractJaMoPPTests; -import tools.mdsd.jamopp.test.OutputUtility; -import tools.mdsd.jamopp.test.OutputUtility.TransferResult; -import tools.mdsd.jamopp.test.bulk.SingleFileParserBulkTests; /** * Class to perform performance tests and measurements. */ public class PerformanceTest extends AbstractJaMoPPTests { private static final Logger LOGGER = LogManager.getLogger("jamopp." - + SingleFileParserBulkTests.class.getSimpleName()); - private final String inputFolder = "target" + File.separator + "src-bulk" + File.separator + "TeaStore"; - private static final Path PARENT_OUTPUT = Paths.get("target", "tests", "output_performance"); - private static final Path JAVA_OUTPUT = PARENT_OUTPUT.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_JAVA); - private static final Path XMI_OUTPUT = PARENT_OUTPUT.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_XMI); - private static final Path JSON_OUTPUT = PARENT_OUTPUT.resolve(OutputUtility.SUPPORTED_FILE_EXTENSION_JSON); - private static final Path SUMMARY_FILE_PATH = PARENT_OUTPUT.resolve("summary.md"); + + PerformanceTest.class.getSimpleName()); + protected static PerformanceTestExecutor TEST_EXECUTOR; + + @BeforeAll + public static void setupEverything() { + PerformanceTestStandaloneMain.setupRegistries(); + TEST_EXECUTOR = new PerformanceTestExecutor(PerformanceTestStandaloneMain.DEFAULT_INPUT_PATH.toAbsolutePath(), + PerformanceTestStandaloneMain.DEFAULT_OUTPUT_PATH.toAbsolutePath()); + } @BeforeEach public void setup() throws IOException { - super.initResourceFactory(); - Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("json", new JsonResourceFactory()); - if (Files.exists(JAVA_OUTPUT)) { - PathUtils.deleteDirectory(JAVA_OUTPUT); - PathUtils.deleteDirectory(XMI_OUTPUT); - PathUtils.deleteDirectory(JSON_OUTPUT); - } - try { - Files.createDirectories(JAVA_OUTPUT); - Files.createDirectories(XMI_OUTPUT); - Files.createDirectories(JSON_OUTPUT); - } catch (IOException e1) { - } + TEST_EXECUTOR.setupTestEnvironment(); } @Test public void measureTeaStoreFullResolution() { - ParserOptions.CREATE_LAYOUT_INFORMATION.setValue(Boolean.TRUE); - ParserOptions.REGISTER_LOCAL.setValue(Boolean.TRUE); - ParserOptions.PREFER_BINDING_CONVERSION.setValue(Boolean.TRUE); - ParserOptions.RESOLVE_BINDINGS.setValue(Boolean.TRUE); - ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.TRUE); - ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.TRUE); - ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.TRUE); - measurePerformance("teastore-full-resolution", getNumberOfRepetitions(), true, false); - } - - @Test - public void measureTeaStoreWithoutResolvingEverything() { - ParserOptions.CREATE_LAYOUT_INFORMATION.setValue(Boolean.TRUE); - ParserOptions.REGISTER_LOCAL.setValue(Boolean.TRUE); - ParserOptions.PREFER_BINDING_CONVERSION.setValue(Boolean.TRUE); - ParserOptions.RESOLVE_BINDINGS.setValue(Boolean.TRUE); - ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.TRUE); - ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.FALSE); - ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.TRUE); - measurePerformance("teastore-without-resolving-everything", getNumberOfRepetitions(), true, false); - } - - private void prepareParserOptionsForOneLevelResolution() { - ParserOptions.CREATE_LAYOUT_INFORMATION.setValue(Boolean.TRUE); - ParserOptions.REGISTER_LOCAL.setValue(Boolean.TRUE); - ParserOptions.PREFER_BINDING_CONVERSION.setValue(Boolean.TRUE); - ParserOptions.RESOLVE_BINDINGS.setValue(Boolean.TRUE); - ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.TRUE); - ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.FALSE); - ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.FALSE); + TEST_EXECUTOR.measureTeaStoreFullResolution(); } @Test public void measureTeaStoreWithOneLevelResolution() { - prepareParserOptionsForOneLevelResolution(); - measurePerformance("teastore-one-level-resolution", getNumberOfRepetitions(), false, true); - } - - @Disabled("Takes several hours.") - @Test - public void measureTeaStoreWithOneLevelResolutionAndFullResolution() { - prepareParserOptionsForOneLevelResolution(); - measurePerformance("teastore-one-level-resolution-full", 1, true, false); - } - - private void prepareParserOptionsForSecondVariant() { - ParserOptions.CREATE_LAYOUT_INFORMATION.setValue(Boolean.TRUE); - ParserOptions.REGISTER_LOCAL.setValue(Boolean.TRUE); - ParserOptions.PREFER_BINDING_CONVERSION.setValue(Boolean.TRUE); - ParserOptions.RESOLVE_BINDINGS.setValue(Boolean.FALSE); - ParserOptions.RESOLVE_BINDINGS_OF_INFERABLE_TYPES.setValue(Boolean.FALSE); - ParserOptions.RESOLVE_EVERYTHING.setValue(Boolean.FALSE); - ParserOptions.RESOLVE_ALL_BINDINGS.setValue(Boolean.FALSE); + TEST_EXECUTOR.measureTeaStoreWithOneLevelResolution(); } @Test public void measureTeaStoreSecondVariant() { - prepareParserOptionsForSecondVariant(); - measurePerformance("teastore-second-variant", getNumberOfRepetitions(), false, true); + TEST_EXECUTOR.measureTeaStoreSecondVariant(); } - @Disabled("Takes several hours.") - @Test - public void measureTeaStoreSecondVariantAndFullResolution() { - prepareParserOptionsForSecondVariant(); - measurePerformance("teastore-second-variant-resolution", 1, true, false); - } - @AfterAll public static void clean() throws IOException { - calculateAndSaveAllStatistics(); - } - - private static void calculateAndSaveAllStatistics() throws IOException { - StringBuilder builder = new StringBuilder(); - - Files - .walk(PARENT_OUTPUT, 1) - .filter(path -> Files.isRegularFile(path)) - .forEach(path -> { - builder.append("# Results for " + path.getFileName().toString()); - - var data = PerformanceData.load(path); - var stat = data.getStatistics(); - builder.append("\n\nAverage time (ms): " + stat.getMean() + " (with std. " + + stat.getStandardDeviation() + " ms)\n"); - - stat = data.getStatisticsWithoutResolution(); - builder.append("Average time without resolution (ms): " + stat.getMean() + " (with std. " + + stat.getStandardDeviation() + " ms)\n"); - builder.append("Average parsing time (ms): " + data.getAverageParseTime()); - builder.append("\nAverage resolution time (ms): " + data.getAverageResolutionTime()); - builder.append("\nAverage recovery time (ms): " + data.getAverageRecoveryTime()); - - for (var storage : data.getStorage()) { - builder.append("\n\nStorage (" - + storage.getId() - + "): " - + storage.getCodeFiles() - + " code files of overall " - + storage.getOverallFiles() - + " files taking " - + storage.getTakenStorageByCodeFiles() - + " Bytes for code files of overall " - + storage.getTakenStorage() - + " Bytes."); - } - builder.append("\n\n"); - }); - - Files.writeString(SUMMARY_FILE_PATH, builder.toString()); + TEST_EXECUTOR.cleanEverything(); } @Override protected boolean isExcludedFromReprintTest(String filename) { - return false; + return true; } @Override protected String getTestInputFolder() { - return inputFolder; - } - - protected int getNumberOfRepetitions() { - return 100; - } - - private void measurePerformance(String name, int max, boolean fullResolution, boolean recover) { - String testInput = getTestInputFolder(); - LOGGER.debug("Executing performance measurements for " + name); - Path target = Paths.get(testInput); - JaMoPPJDTSingleFileParser parser = new JaMoPPJDTSingleFileParser(); - parser.setExclusionPatterns(".*?src/test/.*?"); - - Path outputMeasurement = PARENT_OUTPUT.resolve(name + ".json"); - PerformanceData result; - if (Files.exists(outputMeasurement)) { - result = PerformanceData.load(outputMeasurement); - } else { - result = new PerformanceData(); - } - int actualMax = Math.min(max, max - result.getPoints().size()); - for (int i = 0; i < actualMax; i++) { - System.out.println("Measurement " + i + " for " + name); - PerformanceDataPoint point = new PerformanceDataPoint(); - long millis = System.currentTimeMillis(); - ResourceSet set = parser.parseDirectory(target); - millis = System.currentTimeMillis() - millis; - point.setParseTime(millis); - if (fullResolution) { - millis = System.currentTimeMillis(); - EcoreUtil.resolveAll(set); - millis = System.currentTimeMillis() - millis; - } else { - var ress = new HashSet<>(set.getResources()); - millis = System.currentTimeMillis(); - for (Resource r : ress) { - EcoreUtil.resolveAll(r); - } - millis = System.currentTimeMillis() - millis; - } - point.setResolutionTime(millis); - - if (recover) { - millis = System.currentTimeMillis(); - new TrivialRecovery(set).recover(); - millis = System.currentTimeMillis() - millis; - point.setRecoverTime(millis); - } - - Set parsedFiles = new HashSet<>(set.getResources()); - LOGGER.debug("Asserting the resolution of all proxy objects."); - for (Resource res : parsedFiles) { - if (res.getContents().size() == 0 || (!fullResolution && !res.getURI().isFile())) { - continue; - } - this.assertResolveAllProxies(res); - } - - LOGGER.debug("Reprinting."); - for (Resource res : parsedFiles) { - if (res.getContents().size() == 0 || !res.getURI().isFile()) { - continue; - } - String oldUri = res.getURI().toString(); - try { - this.testReprint((JavaResource2) res); - } catch (Exception e) { - fail(e); - } - res.setURI(URI.createURI(oldUri)); - } - - result.addPoint(point); - PerformanceData.save(result, outputMeasurement); - - if (i == 0 && (fullResolution || recover)) { - try { - result.setStorage(measureStorage(set)); - } catch (IOException e) { - fail(e); - } - PerformanceData.save(result, outputMeasurement); - } - - for (Resource res : parsedFiles) { - res.unload(); - } - IJavaContextDependentURIFragmentCollector.GLOBAL_INSTANCE - .getContextDependentURIFragmentMap().clear(); - } - LOGGER.debug("Finished meausring " + name); - } - - private List measureStorage(ResourceSet resourceSet) throws IOException { - StoragePerformance javaStorage = new StoragePerformance(); - javaStorage.setId(OutputUtility.SUPPORTED_FILE_EXTENSION_JAVA); - var result = OutputUtility.transferToOutput(resourceSet, JAVA_OUTPUT.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_JAVA, true); - fillStorageInformationFromTransfer(javaStorage, JAVA_OUTPUT, result); - - result.sourceTargetMapping().forEach((key, value) -> { - key.getContents().addAll(value.getContents()); - }); - - StoragePerformance xmiStorage = new StoragePerformance(); - xmiStorage.setId(OutputUtility.SUPPORTED_FILE_EXTENSION_XMI); - result = OutputUtility.transferToOutput(resourceSet, XMI_OUTPUT.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_XMI, true); - fillStorageInformationFromTransfer(xmiStorage, XMI_OUTPUT, result); - - result.sourceTargetMapping().forEach((key, value) -> { - key.getContents().addAll(value.getContents()); - }); - - StoragePerformance jsonStorage = new StoragePerformance(); - jsonStorage.setId(OutputUtility.SUPPORTED_FILE_EXTENSION_JSON); - fillStorageInformationFromTransfer(jsonStorage, JSON_OUTPUT, OutputUtility.transferToOutput(resourceSet, JSON_OUTPUT.toString(), OutputUtility.SUPPORTED_FILE_EXTENSION_JSON, true)); - - return List.of(javaStorage, xmiStorage, jsonStorage); - } - - private void fillStorageInformationFromTransfer(StoragePerformance storage, Path outputDir, TransferResult outputResult) throws IOException { - storage.setTakenStorage(PathUtils.sizeOfDirectory(outputDir)); - long codeFiles = 0; - long codeSize = 0; - for (var entry : outputResult.sourceTargetMapping().entrySet()) { - if (entry.getKey().getURI().isFile()) { - codeFiles++; - codeSize += FileUtils.sizeOf(new File(entry.getValue().getURI().toFileString())); - } - } - storage.setCodeFiles(codeFiles); - storage.setTakenStorageByCodeFiles(codeSize); - storage.setOverallFiles(outputResult.sourceTargetMapping().entrySet().size()); + return PerformanceTestStandaloneMain.DEFAULT_INPUT_PATH.toString(); } } diff --git a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/ReducedPerformanceTest.java b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/ReducedPerformanceTest.java index fdf0a182..8fbe20e3 100644 --- a/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/ReducedPerformanceTest.java +++ b/jamopp.tests/src/test/java/tools/mdsd/jamopp/test/performance/ReducedPerformanceTest.java @@ -16,13 +16,16 @@ package tools.mdsd.jamopp.test.performance; +import org.junit.jupiter.api.BeforeAll; + /** * This class provides a reduced extent of the performance tests to save time and resources. * It acts more as a demonstration for the performance test execution. */ public class ReducedPerformanceTest extends PerformanceTest { - @Override - protected int getNumberOfRepetitions() { - return 1; + @BeforeAll() + public static void setupEverything() { + PerformanceTest.setupEverything(); + TEST_EXECUTOR.setNumberOfRepetitions(1); } } From 5962d2bcd252fa08c2d8ea7eead7d836b65e3d73 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 13 Apr 2026 11:14:11 +0200 Subject: [PATCH 35/45] Added micrometer as dependency to monitor the JVM heap size (#23). --- jamopp.tests/pom.xml | 4 ++++ pom.xml | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/jamopp.tests/pom.xml b/jamopp.tests/pom.xml index 1ccb5fd2..b8fcd0b7 100644 --- a/jamopp.tests/pom.xml +++ b/jamopp.tests/pom.xml @@ -164,5 +164,9 @@ org.knowm.xchart xchart + + io.micrometer + micrometer-core + diff --git a/pom.xml b/pom.xml index b88eb697..bd5f59dc 100644 --- a/pom.xml +++ b/pom.xml @@ -173,6 +173,13 @@ xchart 3.8.8 + + io.micrometer + micrometer-bom + 1.16.4 + pom + import + From 4f96cf9f6a9e57af031dfc7eb90fd2674237a2eb Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 13 Apr 2026 11:42:14 +0200 Subject: [PATCH 36/45] Added a StepMeterRegistry for storing JVM heap size measurements (#23). --- .../JamoppPerformanceStepMeterRegistry.java | 111 ++++++++++++++++++ .../JamoppPerformanceStepRegistryConfig.java | 41 +++++++ .../performance/monitor/MonitorConstants.java | 26 ++++ 3 files changed, 178 insertions(+) create mode 100644 jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/JamoppPerformanceStepMeterRegistry.java create mode 100644 jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/JamoppPerformanceStepRegistryConfig.java create mode 100644 jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/MonitorConstants.java diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/JamoppPerformanceStepMeterRegistry.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/JamoppPerformanceStepMeterRegistry.java new file mode 100644 index 00000000..0c01308d --- /dev/null +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/JamoppPerformanceStepMeterRegistry.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2026 + * Modelling for Continuous Software Engineering (MCSE) group, + * Institute of Information Security and Dependability (KASTEL), + * Karlsruhe Institute of Technology (KIT). + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Martin Armbruster (MCSE) + * - Initial implementation + ******************************************************************************/ + +package tools.mdsd.jamopp.test.performance.monitor; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.step.StepMeterRegistry; +import io.micrometer.core.instrument.step.StepRegistryConfig; +import io.micrometer.core.instrument.util.NamedThreadFactory; +import java.io.BufferedWriter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.concurrent.TimeUnit; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class JamoppPerformanceStepMeterRegistry extends StepMeterRegistry { + private static final Logger LOGGER = LogManager.getLogger(JamoppPerformanceStepMeterRegistry.class); + private Path outputFile; + private StepRegistryConfig config; + + public JamoppPerformanceStepMeterRegistry(StepRegistryConfig config, Clock clock, Path output) { + super(config, clock); + this.outputFile = output.toAbsolutePath(); + this.config = config; + this.start(new NamedThreadFactory("jamopp-monitor")); + } + + @Override + protected void publish() { + try (BufferedWriter writer = Files.newBufferedWriter(outputFile, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND)) { + writer.append(MonitorConstants.PUBLISHED_TIME_LINE_PREFIX + System.currentTimeMillis()); + writer.append("\n"); + + for (var meter : getMeters()) { + if (meter.getId().getName().equals(MonitorConstants.JVM_METER_USED_MEMORY)) { + writeMeter(writer, meter); + } + } + } catch (IOException e) { + LOGGER.error("Could not write metrics because: {}", e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Writes a meter and its data to the output in a JSON format. The JSON format is directly created instead of using a library + * to avoid overhead. + * + * @param writer the output to write to. + * @param meter the meter to write. + * @throws IOException if an IO error occurs. + */ + private void writeMeter(BufferedWriter writer, Meter meter) throws IOException { + writer.append("{\"name\":\""); + writer.append(meter.getId().getName()); + + writer.append("\",\"baseUnit:\":\""); + writer.append(meter.getId().getBaseUnit()); + + writer.append("\",\"tags\":["); + boolean firstItem = true; + for (var tag : meter.getId().getTags()) { + if (!firstItem) { + writer.append(","); + } + writer.append("{\"key\":\""); + writer.append(tag.getKey()); + writer.append("\",\"value\":\""); + writer.append(tag.getValue()); + writer.append("\"}"); + firstItem = false; + } + + writer.append("],\"values\":["); + firstItem = true; + for (var measurement : meter.measure()) { + if (!firstItem) { + writer.append(","); + } + writer.append("{\"value\":" + measurement.getValue()); + writer.append(",\"statistic\":\""); + writer.append(measurement.getStatistic().name()); + writer.append("\"}"); + firstItem = false; + } + + writer.append("]}\n"); + } + + @Override + protected TimeUnit getBaseTimeUnit() { + return TimeUnit.MILLISECONDS; + } +} diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/JamoppPerformanceStepRegistryConfig.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/JamoppPerformanceStepRegistryConfig.java new file mode 100644 index 00000000..df24601d --- /dev/null +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/JamoppPerformanceStepRegistryConfig.java @@ -0,0 +1,41 @@ +/******************************************************************************* + * Copyright (c) 2026 + * Modelling for Continuous Software Engineering (MCSE) group, + * Institute of Information Security and Dependability (KASTEL), + * Karlsruhe Institute of Technology (KIT). + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Martin Armbruster (MCSE) + * - Initial implementation + ******************************************************************************/ + +package tools.mdsd.jamopp.test.performance.monitor; + +import java.time.Duration; + +import io.micrometer.core.instrument.step.StepRegistryConfig; + +/** + * Configuration for the JaMoPP step meter registry. + */ +public class JamoppPerformanceStepRegistryConfig implements StepRegistryConfig { + @Override + public String get(String arg0) { + return null; + } + + @Override + public Duration step() { + return Duration.ofSeconds(15); + } + + @Override + public String prefix() { + return "jamopp-performance-step-config"; + } +} \ No newline at end of file diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/MonitorConstants.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/MonitorConstants.java new file mode 100644 index 00000000..96fd6678 --- /dev/null +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/MonitorConstants.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2026 + * Modelling for Continuous Software Engineering (MCSE) group, + * Institute of Information Security and Dependability (KASTEL), + * Karlsruhe Institute of Technology (KIT). + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Martin Armbruster (MCSE) + * - Initial implementation + ******************************************************************************/ + +package tools.mdsd.jamopp.test.performance.monitor; + +public final class MonitorConstants { + public static final String PUBLISHED_TIME_LINE_PREFIX = "=Pub: "; + public static final String JVM_METER_USED_MEMORY = "jvm.memory.used"; + public static final String JVM_METER_TAG_KEY_AREA = "area"; + public static final String JVM_METER_TAG_VALUE_HEAP = "heap"; + + private MonitorConstants() {} +} From bc0d35903445bed5d27310271e3f98ec97283523 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Fri, 24 Apr 2026 15:19:01 +0200 Subject: [PATCH 37/45] Added and integrated a monitor for the used heap memory (#23). --- .../PerformanceTestStandaloneMain.java | 13 +- .../performance/monitor/MemoryMonitor.java | 142 ++++++++++++++++++ 2 files changed, 153 insertions(+), 2 deletions(-) create mode 100644 jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/MemoryMonitor.java diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java index a8bc314d..c82cf394 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java @@ -11,6 +11,7 @@ import tools.mdsd.jamopp.model.java.JavaClasspath; import tools.mdsd.jamopp.resource.JavaResource2Factory; +import tools.mdsd.jamopp.test.performance.monitor.MemoryMonitor; import tools.mdsd.jamopp.test.performance.stepwise.StepwisePerformanceExecutor; public final class PerformanceTestStandaloneMain { @@ -31,13 +32,19 @@ public static void setupRegistries() { } public static void main(String[] args) { + var actualOutputDirectory = MAIN_ROOT_PATH.resolve(DEFAULT_OUTPUT_PATH); + var memoryMonitor = new MemoryMonitor(actualOutputDirectory.resolve("mem.txt")); + memoryMonitor.initialize(); + setupRegistries(); if (args.length == 1 && args[0].equals(OPTION_NAME_STEPWISE_TEST)) { StepwisePerformanceExecutor executor = new StepwisePerformanceExecutor(); try { executor.measurePerformance("teastore-stepwise", MAIN_ROOT_PATH.resolve(DEFAULT_INPUT_PATH), - MAIN_ROOT_PATH.resolve(DEFAULT_OUTPUT_PATH)); + actualOutputDirectory); + memoryMonitor.stop(); + memoryMonitor.readDataAndCreateChart(); } catch (IOException | GitAPIException e) { e.printStackTrace(); } @@ -45,7 +52,7 @@ public static void main(String[] args) { } PerformanceTestExecutor executor = new PerformanceTestExecutor(MAIN_ROOT_PATH.resolve(DEFAULT_INPUT_PATH), - MAIN_ROOT_PATH.resolve(DEFAULT_OUTPUT_PATH)); + actualOutputDirectory); if (!(args.length == 1 && args[0].equals(OPTION_NAME_FULL_TEST))) { executor.setNumberOfRepetitions(1); } @@ -56,6 +63,8 @@ public static void main(String[] args) { executor.measureTeaStoreWithOneLevelResolution(); executor.measureTeaStoreFullResolution(); executor.cleanEverything(); + memoryMonitor.stop(); + memoryMonitor.readDataAndCreateChart(); } catch (IOException e) { e.printStackTrace(); } diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/MemoryMonitor.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/MemoryMonitor.java new file mode 100644 index 00000000..4de7988b --- /dev/null +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/MemoryMonitor.java @@ -0,0 +1,142 @@ +/******************************************************************************* + * Copyright (c) 2026 + * Modelling for Continuous Software Engineering (MCSE) group, + * Institute of Information Security and Dependability (KASTEL), + * Karlsruhe Institute of Technology (KIT). + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Martin Armbruster (MCSE) + * - Initial implementation + ******************************************************************************/ + +package tools.mdsd.jamopp.test.performance.monitor; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.google.gson.Gson; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.Metrics; +import io.micrometer.core.instrument.Statistic; +import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics; +import tools.mdsd.jamopp.test.ChartUtility; + +public class MemoryMonitor { + private Path outputFile; + + public MemoryMonitor(Path outputFile) { + this.outputFile = outputFile; + } + + public void initialize() { + Metrics.globalRegistry.add( + new JamoppPerformanceStepMeterRegistry( + new JamoppPerformanceStepRegistryConfig(), + Clock.SYSTEM, + this.outputFile + ) + ); + new JvmMemoryMetrics().bindTo(Metrics.globalRegistry); + } + + public void stop() { + Metrics.globalRegistry.close(); + } + + public void readDataAndCreateChart() throws IOException { + var data = readAndParseMonitorData(); + createMemoryChart(data); + } + + private List readAndParseMonitorData() throws IOException { + List frames = new ArrayList<>(); + MeasurementFrame currentFrame = new MeasurementFrame(); + Gson gson = new Gson(); + + try (var reader = Files.newBufferedReader(outputFile)) { + String readLine; + + while ((readLine = reader.readLine()) != null) { + if (readLine.startsWith(MonitorConstants.PUBLISHED_TIME_LINE_PREFIX)) { + currentFrame = new MeasurementFrame(); + frames.add(currentFrame); + long time = Long.parseLong(readLine.substring(MonitorConstants.PUBLISHED_TIME_LINE_PREFIX.length())); + currentFrame.measurementTime = time; + } else { + var measurement = gson.fromJson(readLine, Measurement.class); + currentFrame.measurements.add(measurement); + } + } + } + + return frames; + } + + private void createMemoryChart(List data) throws IOException { + double[] xValues = new double[data.size()]; + double[] yUsedMemory = new double[xValues.length]; + long lastTime = 0; + + for (var index = 0; index < xValues.length; index++) { + var frame = data.get(index); + if (index != 0) { + xValues[index] = (frame.measurementTime - lastTime) + xValues[index - 1]; + } else { + xValues[0] = 0; + } + + lastTime = frame.measurementTime; + + yUsedMemory[index] = frame + .measurements + .stream() + .filter(measurement -> { + for (var tag : measurement.tags) { + if (tag.key.equals(MonitorConstants.JVM_METER_TAG_KEY_AREA) + && tag.value.equals(MonitorConstants.JVM_METER_TAG_VALUE_HEAP)) { + return true; + } + } + return false; + }) + .flatMap(measurement -> Arrays.stream(measurement.values)) + .mapToDouble(measurement -> measurement.value) + .sum(); + } + + ChartUtility.buildAndSaveChart(xValues, yUsedMemory, + "Used Memory", "Time (ms)", "Used Memory (bytes)", outputFile.resolveSibling("memory.pdf")); + } + + private static class MeasurementFrame { + private long measurementTime; + private List measurements = new ArrayList<>(); + } + + private static class Measurement { + private String name; + private String baseUnit; + private Tag[] tags; + private MeasurementValue[] values; + } + + private static class Tag { + private String key; + private String value; + } + + private static class MeasurementValue { + private double value; + private Statistic statistic; + } +} From c1777dc0d3845430c09b1e2cae34a1a93c068eb8 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 10 Aug 2026 13:13:55 +0200 Subject: [PATCH 38/45] The test jar can be executed (#23). --- jamopp.tests/pom.xml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/jamopp.tests/pom.xml b/jamopp.tests/pom.xml index b8fcd0b7..4594497f 100644 --- a/jamopp.tests/pom.xml +++ b/jamopp.tests/pom.xml @@ -68,11 +68,23 @@ org.apache.maven.plugins maven-jar-plugin + + + + true + dependency + tools.mdsd.jamopp.test.performance.PerformanceTestStandaloneMain + + + + + + org.apache.maven.plugins + maven-dependency-plugin - package - test-jar + copy-dependencies From 5f77db1e315367b130f05c79faf0c678cb4dcb9e Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 10 Aug 2026 13:16:08 +0200 Subject: [PATCH 39/45] Added a docker file for a JDK 17, which will include the src.zip directory. Copied from https://github.com/adoptium/containers/tree/d8d18d67a32e2cbc765de68c30ea0998009df325/17/jdk/alpine/3.24 (#23). --- docker/entrypoint.sh | 157 +++++++++++++++++++++++++++++++++++ docker/jdk-17-src.Dockerfile | 93 +++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 docker/entrypoint.sh create mode 100644 docker/jdk-17-src.Dockerfile diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 00000000..f6a15563 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env sh +# ------------------------------------------------------------------------------ +# NOTE: THIS FILE IS GENERATED VIA "generate_dockerfiles.py" +# +# PLEASE DO NOT EDIT IT DIRECTLY. +# ------------------------------------------------------------------------------ +# +# 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 +# +# https://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. +# +# This script defines `sh` as the interpreter, which is available in all POSIX environments. However, it might get +# started with `bash` as the shell to support dotted.environment.variable.names which are not supported by POSIX, but +# are supported by `sh` in some Linux flavours. + +set -e + +TMPDIR=${TMPDIR:-/tmp} + +# JDK truststore location +JRE_CACERTS_PATH=$JAVA_HOME/lib/security/cacerts + +# Opt-in is only activated if the environment variable is set +if [ -n "$USE_SYSTEM_CA_CERTS" ]; then + + if [ ! -w "$TMPDIR" ]; then + echo "Using additional CA certificates requires write permissions to $TMPDIR. Cannot create truststore." + exit 1 + fi + + # Wrap keytool truststore access. JDK 9+ uses -cacerts (added in JDK 9) + # to avoid the "Warning: use -cacerts option to access cacerts keystore" + # that keytool emits when -keystore points at the default cacerts file. + # JDK 8 has no -cacerts and uses -keystore. The temporary-truststore + # branch below rebinds the JDK 9+ wrapper to -keystore as well, since + # -cacerts would still resolve to the read-only default. -importkeystore + # is not routed through this wrapper: its -destkeystore/-srckeystore do + # not trigger the warning and have no -cacerts form. + keytool_truststore() { + keytool -cacerts "$@" + } + + # Figure out whether we can write to the JVM truststore. If we can, we'll add the certificates there. If not, + # we'll use a temporary truststore. + if [ ! -w "$JRE_CACERTS_PATH" ]; then + # We cannot write to the JVM truststore, so we create a temporary one + JRE_CACERTS_PATH_NEW=$(mktemp) + echo "Using a temporary truststore at $JRE_CACERTS_PATH_NEW" + cp "$JRE_CACERTS_PATH" "$JRE_CACERTS_PATH_NEW" + JRE_CACERTS_PATH=$JRE_CACERTS_PATH_NEW + # If we use a custom truststore, we need to make sure that the JVM uses it + export JAVA_TOOL_OPTIONS="${JAVA_TOOL_OPTIONS} -Djavax.net.ssl.trustStore=${JRE_CACERTS_PATH} -Djavax.net.ssl.trustStorePassword=changeit" + # Rebind: -cacerts would still resolve to the read-only default. + keytool_truststore() { + keytool -keystore "$JRE_CACERTS_PATH" "$@" + } + fi + + tmp_store=$(mktemp) + + # Copy full system CA store to a temporary location + trust extract --overwrite --format=java-cacerts --filter=ca-anchors --purpose=server-auth "$tmp_store" > /dev/null + + # Add the system CA certificates to the JVM truststore. + keytool -importkeystore -destkeystore "$JRE_CACERTS_PATH" -srckeystore "$tmp_store" -srcstorepass changeit -deststorepass changeit -noprompt > /dev/null + + # Clean up the temporary truststore + rm -f "$tmp_store" + + # Import the additional certificate into JVM truststore + find -L /certificates -path '*/..*' -prune -o -type f -name "*crt" -print 2>/dev/null | sort | while IFS= read -r i; do + tmp_dir=$(mktemp -d) + BASENAME=$(basename "$i" .crt) + + # We might have multiple certificates in the file. Split this file into single files. The reason is that + # `keytool` does not accept multi-certificate files + csplit -s -z -b %02d.crt -f "$tmp_dir/$BASENAME-" "$i" '/-----BEGIN CERTIFICATE-----/' '{*}' + + for crt in "$tmp_dir/$BASENAME"-*; do + # Extract the Common Name (CN) from the certificate + CN=$(openssl x509 -in "$crt" -noout -subject -nameopt -space_eq | sed -n 's/^.*CN=\([^,]*\).*$/\1/p') + + # Compute the certificate SHA-256 fingerprint. It is used both to skip certificates that are + # already present and to build a collision-free alias below. A certificate that openssl cannot + # parse yields an empty fingerprint; skip it rather than risk a non-unique alias. + FINGERPRINT=$(openssl x509 -in "$crt" -noout -fingerprint -sha256 2>/dev/null | cut -d'=' -f2) + if [ -z "$FINGERPRINT" ]; then + echo "Could not read the fingerprint of a certificate in $i, skipping" + continue + fi + + # Check if the certificate is already in the JVM truststore by fingerprint. This prevents + # failures on container restart when the certificate was added to the system CA store in a + # previous run and is now being re-imported via keytool -importkeystore. + if keytool_truststore -list -storepass changeit -v 2>/dev/null | grep -qiF "$FINGERPRINT"; then + echo "Certificate with CN=$CN is already in the JVM truststore, skipping" + continue + fi + + # Normalized, globally-unique fingerprint suffix used to disambiguate aliases. The serial + # number is not reliable for this: CA roots can share a non-unique serial (e.g. 00) and may + # have no CN at all, which previously collapsed every such cert to the same alias. + FP=$(printf '%s' "$FINGERPRINT" | tr -d ':' | tr 'A-Z' 'a-z') + + if [ -n "$CN" ]; then + # Use the CN as the alias, falling back to the fingerprint on collision + ALIAS=$CN + if keytool_truststore -list -storepass changeit -alias "$ALIAS" >/dev/null 2>&1; then + ALIAS="${CN}_${FP}" + fi + else + # No CN available: derive a unique, deterministic alias from the fingerprint + ALIAS="adoptium_${FP}" + fi + + echo "Adding certificate with alias $ALIAS to the JVM truststore" + + # Add the certificate to the JVM truststore + keytool_truststore -import -noprompt -alias "$ALIAS" -file "$crt" -storepass changeit >/dev/null + done + done + + # Add additional certificates to the system CA store. This requires write permissions to several system + # locations, which is not possible in a container with read-only filesystem and/or non-root container. + if [ "$(id -u)" -eq 0 ]; then + + # Copy certificates from /certificates to the system truststore, but only if the directory exists and is not empty. + # The reason why this is not part of the opt-in is because it leaves open the option to mount certificates at the + # system location, for whatever reason. + if [ -d /certificates ] && [ "$(ls -A /certificates 2>/dev/null)" ]; then + find -L /certificates -path '*/..*' -prune -o -type f -name "*crt" -print 2>/dev/null | while IFS= read -r _crt; do + _rel="${_crt#/certificates/}" + _dst_rel="${_rel//_/__}" + _dst_rel="${_dst_rel//\//_}" + cp -L "$_crt" "/usr/local/share/ca-certificates/${_dst_rel}" + done + fi + update-ca-certificates + else + # If we are not root, we cannot update the system truststore. That's bad news for tools like `curl` and `wget`, + # but since the JVM is the primary focus here, we can live with that. + true + fi +fi + +# Let's provide a variable with the correct path for tools that want or need to use it +export JRE_CACERTS_PATH + +exec "$@" diff --git a/docker/jdk-17-src.Dockerfile b/docker/jdk-17-src.Dockerfile new file mode 100644 index 00000000..9036d185 --- /dev/null +++ b/docker/jdk-17-src.Dockerfile @@ -0,0 +1,93 @@ +# ------------------------------------------------------------------------------ +# NOTE: THIS FILE IS GENERATED VIA "generate_dockerfiles.py" +# +# PLEASE DO NOT EDIT IT DIRECTLY. +# ------------------------------------------------------------------------------ +# +# 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 +# +# https://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. +# + +FROM alpine:3.24 + +ENV JAVA_HOME=/opt/java/openjdk +ENV PATH=$JAVA_HOME/bin:$PATH + +# Default to UTF-8 file.encoding +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' LC_ALL='en_US.UTF-8' + +RUN set -eux; \ + apk add --no-cache \ + # java.lang.UnsatisfiedLinkError: libfontmanager.so: libfreetype.so.6: cannot open shared object file: No such file or directory + # java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11FontManager + # https://github.com/docker-library/openjdk/pull/235#issuecomment-424466077 + fontconfig ttf-dejavu \ + # gnupg required to verify the signature + gnupg \ + # utilities for keeping Alpine and OpenJDK CA certificates in sync + # https://github.com/adoptium/containers/issues/293 + ca-certificates p11-kit-trust \ + # locales ensures proper character encoding and locale-specific behaviors using en_US.UTF-8 + musl-locales musl-locales-lang \ + # jlink --strip-debug on 13+ needs objcopy: https://github.com/docker-library/openjdk/issues/351 + # Error: java.io.IOException: Cannot run program "objcopy": error=2, No such file or directory + binutils \ + tzdata \ + # Contains `csplit` used for splitting multiple certificates in one file to multiple files, since keytool can + # only import one at a time. + coreutils \ + # Needed to extract CN and generate aliases for certificates + openssl \ + ; \ + rm -rf /var/cache/apk/* + +ENV JAVA_VERSION=jdk-17.0.20+8 + +RUN set -eux; \ + ARCH="$(apk --print-arch)"; \ + case "${ARCH}" in \ + x86_64) \ + ESUM='c8bb5bc6984762dbce2ab7403d90832b6897c07f36f8706e4a315aa7a566d04d'; \ + BINARY_URL='https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.20%2B8/OpenJDK17U-jdk_x64_alpine-linux_hotspot_17.0.20_8.tar.gz'; \ + ;; \ + *) \ + echo "Unsupported arch: ${ARCH}"; \ + exit 1; \ + ;; \ + esac; \ + wget -O /tmp/openjdk.tar.gz ${BINARY_URL}; \ + wget -O /tmp/openjdk.tar.gz.sig ${BINARY_URL}.sig; \ + export GNUPGHOME="$(mktemp -d)"; \ + # gpg: key 843C48A565F8F04B: "Adoptium GPG Key (DEB/RPM Signing Key) " imported + gpg --batch --keyserver keyserver.ubuntu.com --recv-keys 3B04D753C9050D9A5D343F39843C48A565F8F04B; \ + gpg --batch --verify /tmp/openjdk.tar.gz.sig /tmp/openjdk.tar.gz; \ + rm -rf "${GNUPGHOME}" /tmp/openjdk.tar.gz.sig; \ + echo "${ESUM} */tmp/openjdk.tar.gz" | sha256sum -c -; \ + mkdir -p "$JAVA_HOME"; \ + tar --extract \ + --file /tmp/openjdk.tar.gz \ + --directory "$JAVA_HOME" \ + --strip-components 1 \ + --no-same-owner \ + ; \ + rm -f /tmp/openjdk.tar.gz; + +RUN set -eux; \ + echo "Verifying install ..."; \ + fileEncoding="$(echo 'System.out.println(System.getProperty("file.encoding"))' | jshell -s -)"; [ "$fileEncoding" = 'UTF-8' ]; rm -rf ~/.java; \ + echo "javac --version"; javac --version; \ + echo "java --version"; java --version; \ + echo "Complete." +COPY --chmod=755 entrypoint.sh /__cacert_entrypoint.sh +ENTRYPOINT ["/__cacert_entrypoint.sh"] + +CMD ["jshell"] From ae930239672bfac7efe24a3bc9b6cd3419b5b390 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 10 Aug 2026 13:16:30 +0200 Subject: [PATCH 40/45] Added a docker file for the performance tests (#23). --- docker/Dockerfile | 32 ++++++++++++++++++++++++++++++++ docker/docker-bake.hcl | 19 +++++++++++++++++++ docker/entrypoint-jamopp.sh | 1 + 3 files changed, 52 insertions(+) create mode 100644 docker/Dockerfile create mode 100644 docker/docker-bake.hcl create mode 100755 docker/entrypoint-jamopp.sh diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..10a8f5fd --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,32 @@ +FROM eclipse-temurin:11.0.31_11-jdk-alpine-3.23 AS teastore-build + +COPY . /etc/jamopp +WORKDIR /etc/jamopp +RUN apk update &&\ + apk add git &&\ + git submodule init &&\ + git submodule update &&\ + apk del git +WORKDIR /etc/jamopp/jamopp.tests/target/src-bulk/TeaStore +RUN cp /etc/jamopp/mvnw ./mvnw &&\ + cp -r /etc/jamopp/.mvn ./.mvn &&\ + ./mvnw install -Dmaven.test.skip=true &&\ + ./mvnw dependency:copy-dependencies &&\ + rm -r ./.mvn &&\ + rm ./mvnw + +FROM jdk-17-src AS build + +COPY . /etc/jamopp +WORKDIR /etc/jamopp +RUN ./mvnw package -Dmaven.test.skip=true + +FROM jdk-17-src + +COPY --from=build /etc/jamopp/jamopp.tests/target/jamopp.tests-6.0.0-SNAPSHOT.jar /app/jamopp.tests-6.0.0-SNAPSHOT.jar +COPY --from=build /etc/jamopp/jamopp.tests/target/dependency /app/dependency/ +COPY --from=teastore-build /etc/jamopp/jamopp.tests/target/src-bulk/TeaStore /app/jamopp.tests/target/src-bulk/TeaStore/ +COPY ./docker/entrypoint-jamopp.sh /app/entrypoint-jamopp.sh + +WORKDIR /app +ENTRYPOINT ["sh", "-c", "./entrypoint-jamopp.sh"] diff --git a/docker/docker-bake.hcl b/docker/docker-bake.hcl new file mode 100644 index 00000000..89a64fc4 --- /dev/null +++ b/docker/docker-bake.hcl @@ -0,0 +1,19 @@ +group "default" { + targets = ["jamopp"] +} + +target "jdk-17-src" { + context = "." + dockerfile = "jdk-17-src.Dockerfile" +} + +target "jamopp" { + context = ".." + dockerfile = "./docker/Dockerfile" + contexts = { + jdk-17-src = "target:jdk-17-src" + } + network = "host" + tags = ["tools.mdsd/jamopp-performance-tests:6.0.0-SNAPSHOT"] +} +docker run --mount type=bind,source=./ttt,target=/app/jamopp.tests/target/tests/output_performance tools.mdsd/jamopp-performance-tests:6.0.0-SNAPSHOT \ No newline at end of file diff --git a/docker/entrypoint-jamopp.sh b/docker/entrypoint-jamopp.sh new file mode 100755 index 00000000..5a34dd7c --- /dev/null +++ b/docker/entrypoint-jamopp.sh @@ -0,0 +1 @@ +java -jar /app/jamopp.tests-6.0.0-SNAPSHOT.jar $1 From 158857fba990324fa50a43722ede8393ba8359f0 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 17 Aug 2026 14:02:12 +0200 Subject: [PATCH 41/45] Corrected the start of the jar file in the Docker image so that arguments can be given from the ouside (#23). --- docker/Dockerfile | 3 +-- docker/docker-bake.hcl | 1 - docker/entrypoint-jamopp.sh | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) delete mode 100755 docker/entrypoint-jamopp.sh diff --git a/docker/Dockerfile b/docker/Dockerfile index 10a8f5fd..9ca97005 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -26,7 +26,6 @@ FROM jdk-17-src COPY --from=build /etc/jamopp/jamopp.tests/target/jamopp.tests-6.0.0-SNAPSHOT.jar /app/jamopp.tests-6.0.0-SNAPSHOT.jar COPY --from=build /etc/jamopp/jamopp.tests/target/dependency /app/dependency/ COPY --from=teastore-build /etc/jamopp/jamopp.tests/target/src-bulk/TeaStore /app/jamopp.tests/target/src-bulk/TeaStore/ -COPY ./docker/entrypoint-jamopp.sh /app/entrypoint-jamopp.sh WORKDIR /app -ENTRYPOINT ["sh", "-c", "./entrypoint-jamopp.sh"] +ENTRYPOINT ["java", "-jar", "/app/jamopp.tests-6.0.0-SNAPSHOT.jar"] diff --git a/docker/docker-bake.hcl b/docker/docker-bake.hcl index 89a64fc4..97e8a5fa 100644 --- a/docker/docker-bake.hcl +++ b/docker/docker-bake.hcl @@ -16,4 +16,3 @@ target "jamopp" { network = "host" tags = ["tools.mdsd/jamopp-performance-tests:6.0.0-SNAPSHOT"] } -docker run --mount type=bind,source=./ttt,target=/app/jamopp.tests/target/tests/output_performance tools.mdsd/jamopp-performance-tests:6.0.0-SNAPSHOT \ No newline at end of file diff --git a/docker/entrypoint-jamopp.sh b/docker/entrypoint-jamopp.sh deleted file mode 100755 index 5a34dd7c..00000000 --- a/docker/entrypoint-jamopp.sh +++ /dev/null @@ -1 +0,0 @@ -java -jar /app/jamopp.tests-6.0.0-SNAPSHOT.jar $1 From 2a49f81d3702917b9e5664baec747df4731fc583 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Mon, 17 Aug 2026 14:05:10 +0200 Subject: [PATCH 42/45] Added an execution script and README for the Docker files (#23). --- docker/README.md | 14 ++++++++++++++ docker/execute.sh | 3 +++ 2 files changed, 17 insertions(+) create mode 100644 docker/README.md create mode 100644 docker/execute.sh diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..c186f447 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,14 @@ +# Docker Files for JaMoPP Performance Tests + +This directory contains Docker files to build and execute the JaMoPP performance tests in Docker. + +* It currently supports **Linux only**. +* We provide the `execute.sh` script, which executes the relevant Docker commands for building and executing the Docker image. + * For execution, the current working directory must point to this directory. + * The resources for the executed Docker container are limited to *4 CPU cores* and *16 GB RAM*. If you want to decrease or increase these limits, you can change them directly in the `execute.sh` script. + * The performance tests support three modes. To enable the `full` or `stepwise` mode, you need to append the word ` full` or ` stepwise` (with the preceding space) in the `execute.sh` script at the end of the `docker run` command. + 1. By default, the performance tests execute one run and measurement per configuration. Currently, three different parsing configurations of JaMoPP are considered. + 2. `full`: In this mode, the performance tests execute 100 runs and measurements per configuration (the same three configurations as before). This execution takes several hours. + 3. `stepwise`: In this special mode, the performance tests execute one run and measurement of the complete second reference resolution variant. This can take more than 24 hours. Contrary to the previous modes, this mode measures metrics for each resolution step. + * The results are stored in the `target` directory within this directory. +* The actual Docker image for the JaMoPP performance tests are based on an adapted Docker image for the JDK 17, which is also built during building the actual Docker image. In contrast to the official JDK 17 images, the adapted Docker image contains the `src.zip` directory, which contains the source code of the Java standard library, which is currently required by JaMoPP to run. diff --git a/docker/execute.sh b/docker/execute.sh new file mode 100644 index 00000000..8aff13da --- /dev/null +++ b/docker/execute.sh @@ -0,0 +1,3 @@ +mkdir target +docker buildx bake --allow=network.host +docker run --cpus 4 --memory 16GB --mount type=bind,source=./target,target=/app/jamopp.tests/target/tests/output_performance tools.mdsd/jamopp-performance-tests:6.0.0-SNAPSHOT From 972bd1ee621663ec2d77245e312c8a55cf3f5cce Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Thu, 20 Aug 2026 10:34:16 +0200 Subject: [PATCH 43/45] Removed duplicated code after the last merge (#23). --- .../jamopp/recovery/trivial/TrivialRecovery.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/jamopp.resolution/src/main/java/tools/mdsd/jamopp/recovery/trivial/TrivialRecovery.java b/jamopp.resolution/src/main/java/tools/mdsd/jamopp/recovery/trivial/TrivialRecovery.java index 389fe326..e2ffc715 100644 --- a/jamopp.resolution/src/main/java/tools/mdsd/jamopp/recovery/trivial/TrivialRecovery.java +++ b/jamopp.resolution/src/main/java/tools/mdsd/jamopp/recovery/trivial/TrivialRecovery.java @@ -50,7 +50,6 @@ public class TrivialRecovery { private HashMap artClasses = new HashMap<>(); private HashMap artAnnotations = new HashMap<>(); private HashMap artFields = new HashMap<>(); - private HashMap artConstants = new HashMap<>(); private HashMap artClassMethods = new HashMap<>(); private HashMap artInterfaceMethods = new HashMap<>(); private HashMap artEnumConstants = new HashMap<>(); @@ -116,15 +115,6 @@ private EObject recoverActualElement(EObject obj) { this.artificialClass.getMembers().add(result); this.artFields.put(name, result); return result; - } else if (obj instanceof EnumConstant) { - if (this.artConstants.containsKey(obj)) { - return this.artConstants.get(obj); - } - var result = MembersFactory.eINSTANCE.createEnumConstant(); - result.setName(name); - this.artificialEnum.getConstants().add(result); - this.artConstants.put(name, result); - return result; } else if (obj instanceof ClassMethod) { if (this.artClassMethods.containsKey(name)) { return this.artClassMethods.get(name); @@ -202,10 +192,6 @@ private void initArtificialResource() { this.objectClass = findObjectClass(); this.artClasses.put("Object", objectClass); - - this.artificialEnum = ClassifiersFactory.eINSTANCE.createEnumeration(); - this.artificialEnum.setName("SyntheticEnum"); - this.artificialCU.getClassifiers().add(this.artificialEnum); } } From 25c42f97abb475c61e51f8ba9926155430a0594e Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Thu, 20 Aug 2026 15:27:25 +0200 Subject: [PATCH 44/45] Fixed minor formatting issues (#23). --- jamopp.tests/pom.xml | 24 +++++++++---------- .../performance/PerformanceTestExecutor.java | 2 +- .../jamopp/test/performance/package-info.java | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/jamopp.tests/pom.xml b/jamopp.tests/pom.xml index 4594497f..4d4a69b1 100644 --- a/jamopp.tests/pom.xml +++ b/jamopp.tests/pom.xml @@ -7,7 +7,7 @@ jamopp.tests jar Extended JaMoPP Tests - This package contains only tests for the extended JaMoPP. + This package contains only tests for the extended JaMoPP. tools.mdsd @@ -157,21 +157,21 @@ commons-compress - commons-io - commons-io - - - org.eclipse.emfcloud - emfjson-jackson - + commons-io + commons-io + + + org.eclipse.emfcloud + emfjson-jackson + org.apache.commons commons-math4-legacy - - org.eclipse.jgit - org.eclipse.jgit - + + org.eclipse.jgit + org.eclipse.jgit + org.knowm.xchart xchart diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java index ef327bb1..1eb15b34 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestExecutor.java @@ -264,7 +264,7 @@ private void measurePerformance(String name, int max, boolean fullResolution, bo IJavaContextDependentURIFragmentCollector.GLOBAL_INSTANCE .getContextDependentURIFragmentMap().clear(); } - LOGGER.debug("Finished meausring " + name); + LOGGER.debug("Finished measuring " + name); } private List measureStorage(ResourceSet resourceSet) throws IOException { diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/package-info.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/package-info.java index e492afcf..a9a90044 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/package-info.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/package-info.java @@ -13,4 +13,4 @@ /** * Contains tests for performance measurements. */ -package tools.mdsd.jamopp.test.performance; \ No newline at end of file +package tools.mdsd.jamopp.test.performance; From 314b95174d9c360eb032692d54c36782528d5370 Mon Sep 17 00:00:00 2001 From: Martin Armbruster Date: Thu, 20 Aug 2026 15:54:26 +0200 Subject: [PATCH 45/45] Minor improvements in the error handling (#23). --- .../src/main/java/tools/mdsd/jamopp/test/ChartUtility.java | 2 +- .../test/performance/PerformanceTestStandaloneMain.java | 6 ++++-- .../jamopp/test/performance/monitor/MemoryMonitor.java | 6 ++++++ .../performance/stepwise/StepwisePerformanceExecutor.java | 7 +++++-- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java index 2fc45a20..fca77727 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/ChartUtility.java @@ -67,7 +67,7 @@ public static void buildAndSaveChartsForPerformanceData(String dataName, Perform } public static void buildAndSaveChartWithDiff(double[] data, String title, String xAxisTitle, String yAxisTitle, Path chartFile) throws IOException { - double[] diffData = new double[data.length - 1]; + double[] diffData = new double[data.length <= 1 ? 0 : data.length - 1]; for (var index = 0; index < diffData.length; index++) { diffData[index] = data[index + 1] - data[index]; } diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java index c82cf394..62b43f70 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/PerformanceTestStandaloneMain.java @@ -9,6 +9,8 @@ import org.eclipse.emfcloud.jackson.resource.JsonResourceFactory; import org.eclipse.jgit.api.errors.GitAPIException; +import com.google.gson.JsonSyntaxException; + import tools.mdsd.jamopp.model.java.JavaClasspath; import tools.mdsd.jamopp.resource.JavaResource2Factory; import tools.mdsd.jamopp.test.performance.monitor.MemoryMonitor; @@ -45,7 +47,7 @@ public static void main(String[] args) { actualOutputDirectory); memoryMonitor.stop(); memoryMonitor.readDataAndCreateChart(); - } catch (IOException | GitAPIException e) { + } catch (IOException | GitAPIException | NumberFormatException | JsonSyntaxException e) { e.printStackTrace(); } return; @@ -65,7 +67,7 @@ public static void main(String[] args) { executor.cleanEverything(); memoryMonitor.stop(); memoryMonitor.readDataAndCreateChart(); - } catch (IOException e) { + } catch (IOException | NumberFormatException | JsonSyntaxException e) { e.printStackTrace(); } } diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/MemoryMonitor.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/MemoryMonitor.java index 4de7988b..0912eb03 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/MemoryMonitor.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/monitor/MemoryMonitor.java @@ -32,6 +32,7 @@ import tools.mdsd.jamopp.test.ChartUtility; public class MemoryMonitor { + private boolean initialized = false; private Path outputFile; public MemoryMonitor(Path outputFile) { @@ -39,6 +40,10 @@ public MemoryMonitor(Path outputFile) { } public void initialize() { + if (this.initialized) { + return; + } + Metrics.globalRegistry.add( new JamoppPerformanceStepMeterRegistry( new JamoppPerformanceStepRegistryConfig(), @@ -47,6 +52,7 @@ public void initialize() { ) ); new JvmMemoryMetrics().bindTo(Metrics.globalRegistry); + this.initialized = true; } public void stop() { diff --git a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java index 8e7fd1ee..dce7301a 100644 --- a/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java +++ b/jamopp.tests/src/main/java/tools/mdsd/jamopp/test/performance/stepwise/StepwisePerformanceExecutor.java @@ -102,6 +102,7 @@ public void measurePerformance(String name, Path srcDirectory, Path outputDirect JaMoPPJDTSingleFileParser parser = new JaMoPPJDTSingleFileParser(); parser.setExclusionPatterns(".*?src/test/.*?"); + LOGGER.info("Parsing the directory " + srcDirectory.toString()); long millis = System.currentTimeMillis(); ResourceSet set = parser.parseDirectory(srcDirectory); result.setParsingTime(System.currentTimeMillis() - millis); @@ -120,18 +121,20 @@ public void measurePerformance(String name, Path srcDirectory, Path outputDirect stepResult.setTimeModelSaving(outputResult.getRight()); outputResult.getLeft().forEach(stepResult::addChangedFiles); this.saveResults(result, resultFile); - + + LOGGER.info("Resolving proxy objects."); List oldResources = List.of(); int iteration = 1; do { oldResources = new ArrayList<>(set.getResources()); + LOGGER.info("Having " + oldResources.size() + " resources to check for proxy objects."); for (Resource resource : oldResources) { if (EcoreUtil.ProxyCrossReferencer.find(resource).size() == 0) { continue; } - System.out.println(resource.getURI().toString()); + LOGGER.info("Step " + iteration + "."); millis = System.currentTimeMillis(); EcoreUtil.resolveAll(resource);