This is an automated email from the ASF dual-hosted git repository.

kbowers pushed a commit to branch main
in repository 
https://gitbox.apache.org/repos/asf/incubator-kie-kogito-benchmarks.git

commit 34569dc753972c66647fe975d95b219daa886d8f
Author: Marián Macik <[email protected]>
AuthorDate: Fri Apr 30 13:34:19 2021 +0200

    Initial hierarchy, build and start methods, 3 REST clients, appended log 
measurements
---
 .gitignore                                         |  23 +
 framework/pom.xml                                  | 107 ++++
 .../org/kie/kogito/benchmarks/framework/App.java   |  61 +++
 .../kogito/benchmarks/framework/BuildResult.java   |  28 +
 .../kie/kogito/benchmarks/framework/Commands.java  | 604 +++++++++++++++++++++
 .../benchmarks/framework/HTTPRequestInfo.java      |  93 ++++
 .../kogito/benchmarks/framework/LogBuilder.java    | 201 +++++++
 .../org/kie/kogito/benchmarks/framework/Logs.java  | 336 ++++++++++++
 .../kie/kogito/benchmarks/framework/MvnCmds.java   |  53 ++
 .../kie/kogito/benchmarks/framework/RunInfo.java   |  28 +
 .../kogito/benchmarks/framework/URLContent.java    |  33 ++
 .../kogito/benchmarks/framework/WebpageTester.java |  78 +++
 .../benchmarks/framework/WhitelistLogLines.java    | 112 ++++
 pom.xml                                            |  30 +
 sample-kogito-app/pom.xml                          | 115 ++++
 .../src/main/java/mypackage/GreetingEndpoint.java  |  47 ++
 .../src/main/resources/LoanApplication.bpmn        | 259 +++++++++
 .../src/main/resources/MortgageApproval.dmn        |  81 +++
 .../src/main/resources/application.properties      |  36 ++
 sample-kogito-app/threshold.properties             |   6 +
 tests/pom.xml                                      |  41 ++
 .../kogito/benchmarks/AbstractTemplateTest.java    | 367 +++++++++++++
 .../kie/kogito/benchmarks/QuarkusLargeTest.java    |  69 +++
 .../kie/kogito/benchmarks/QuarkusSmallTest.java    |  22 +
 .../org/kie/kogito/benchmarks/QuarkusTest.java     |   9 +
 .../org/kie/kogito/benchmarks/StartStopTest.java   | 214 ++++++++
 tests/src/test/resources/log4j2.xml                |  13 +
 27 files changed, 3066 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..96c49bd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+target/
+/local
+
+logs/
+
+**/.idea
+
+# Repository wide ignore mac DS_Store files
+.DS_Store
+
+# Eclipse, Netbeans and IntelliJ files
+!.gitignore
+!.github
+/nbproject
+/*.ipr
+/*.iws
+*.iml
+.settings/
+.project
+.classpath
+.factorypath
+.vscode
+.run/
\ No newline at end of file
diff --git a/framework/pom.xml b/framework/pom.xml
new file mode 100644
index 0000000..bf46da0
--- /dev/null
+++ b/framework/pom.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.kie.kogito</groupId>
+    <artifactId>kogito-benchmarks</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>framework</artifactId>
+
+  <name></name>
+  <description></description>
+
+  <properties>
+    <log4j.version>2.13.2</log4j.version>
+  </properties>
+
+  <dependencyManagement>
+    <dependencies>
+      <!-- Override test scope coming from parent -->
+      <dependency>
+        <groupId>org.junit.jupiter</groupId>
+        <artifactId>junit-jupiter-api</artifactId>
+        <version>${version.org.junit.jupiter}</version>
+        <scope>compile</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.junit.jupiter</groupId>
+        <artifactId>junit-jupiter</artifactId>
+        <version>${version.org.junit.jupiter}</version>
+        <scope>compile</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.junit.jupiter</groupId>
+        <artifactId>junit-jupiter-engine</artifactId>
+        <version>${version.org.junit.jupiter}</version>
+        <scope>compile</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.junit.jupiter</groupId>
+        <artifactId>junit-jupiter-params</artifactId>
+        <version>${version.org.junit.jupiter}</version>
+        <scope>compile</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.junit.platform</groupId>
+        <artifactId>junit-platform-engine</artifactId>
+        <version>${version.org.junit.platform}</version>
+        <scope>compile</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.junit.platform</groupId>
+        <artifactId>junit-platform-commons</artifactId>
+        <version>${version.org.junit.platform}</version>
+        <scope>compile</scope>
+      </dependency>
+
+      <dependency>
+        <groupId>org.apache.logging.log4j</groupId>
+        <artifactId>log4j-api</artifactId>
+        <version>${log4j.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.logging.log4j</groupId>
+        <artifactId>log4j-core</artifactId>
+        <version>${log4j.version}</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.jboss.logging</groupId>
+      <artifactId>jboss-logging</artifactId>
+      <version>3.4.1.Final</version> <!-- TODO find another logging library-->
+    </dependency>
+    <dependency>
+      <groupId>org.junit.jupiter</groupId>
+      <artifactId>junit-jupiter</artifactId>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.assertj</groupId>
+      <artifactId>assertj-core</artifactId>
+      <scope>compile</scope>
+    </dependency>
+
+    <!-- Logging -->
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j</groupId>
+      <artifactId>log4j-core</artifactId>
+    </dependency>
+  </dependencies>
+
+</project>
\ No newline at end of file
diff --git 
a/framework/src/main/java/org/kie/kogito/benchmarks/framework/App.java 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/App.java
new file mode 100644
index 0000000..ab39a3a
--- /dev/null
+++ b/framework/src/main/java/org/kie/kogito/benchmarks/framework/App.java
@@ -0,0 +1,61 @@
+package org.kie.kogito.benchmarks.framework;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.lang3.StringUtils;
+
+import static org.kie.kogito.benchmarks.framework.Commands.BASE_DIR;
+
+public enum App {
+    SAMPLE_KOGITO_APP_QUARKUS_JVM("sample-kogito-app", MvnCmds.QUARKUS_JVM, 
URLContent.SAMPLE_KOGITO_APP, WhitelistLogLines.SAMPLE_KOGITO_APP),
+    SAMPLE_KOGITO_APP_SPRING_BOOT("sample-kogito-app", 
MvnCmds.SPRING_BOOT_JVM, URLContent.SAMPLE_KOGITO_APP, 
WhitelistLogLines.SAMPLE_KOGITO_APP);
+    //    JAX_RS_MINIMAL("app-jax-rs-minimal", URLContent.JAX_RS_MINIMAL, 
WhitelistLogLines.JAX_RS_MINIMAL),
+    //    FULL_MICROPROFILE("app-full-microprofile", 
URLContent.FULL_MICROPROFILE, WhitelistLogLines.FULL_MICROPROFILE),
+    //    GENERATED_SKELETON("app-generated-skeleton", 
URLContent.GENERATED_SKELETON, WhitelistLogLines.GENERATED_SKELETON);
+
+    public final String dir;
+    public final MvnCmds mavenCommands;
+    public final URLContent urlContent;
+    public final WhitelistLogLines whitelistLogLines;
+    public final Map<String, Long> thresholdProperties = new HashMap<>();
+
+    App(String dir, MvnCmds mavenCommands, URLContent urlContent, 
WhitelistLogLines whitelistLogLines) {
+        this.dir = dir;
+        this.mavenCommands = mavenCommands;
+        this.urlContent = urlContent;
+        this.whitelistLogLines = whitelistLogLines;
+        File tpFile = new File(BASE_DIR + File.separator + dir + 
File.separator + "threshold.properties");
+        String appDirNormalized = dir.toUpperCase().replace('-', '_') + "_";
+        try (InputStream input = new FileInputStream(tpFile)) {
+            Properties props = new Properties();
+            props.load(input);
+            for (String pn : props.stringPropertyNames()) {
+                String normPn = pn.toUpperCase().replace('.', '_');
+                String env = System.getenv().get(appDirNormalized + normPn);
+                if (StringUtils.isNotBlank(env)) {
+                    props.replace(pn, env);
+                }
+                String sys = System.getProperty(appDirNormalized + normPn);
+                if (StringUtils.isNotBlank(sys)) {
+                    props.replace(pn, sys);
+                }
+                thresholdProperties.put(pn, 
Long.parseLong(props.getProperty(pn)));
+            }
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("Check threshold.properties and 
Sys and Env variables (upper case, underscores instead of dots). " +
+                    "All values are expected to be of type long.");
+        } catch (IOException e) {
+            throw new RuntimeException("Couldn't find " + 
tpFile.getAbsolutePath());
+        }
+    }
+
+    public File getAppDir(String parent) {
+        return new File(parent + File.separator + dir);
+    }
+}
diff --git 
a/framework/src/main/java/org/kie/kogito/benchmarks/framework/BuildResult.java 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/BuildResult.java
new file mode 100644
index 0000000..19ab591
--- /dev/null
+++ 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/BuildResult.java
@@ -0,0 +1,28 @@
+package org.kie.kogito.benchmarks.framework;
+
+import java.io.File;
+
+public class BuildResult {
+
+    private final long buildTimeMs;
+    private final File buildLog;
+    private final int exitCode;
+
+    public BuildResult(long buildTimeMs, File buildLog, int exitCode) {
+        this.buildTimeMs = buildTimeMs;
+        this.buildLog = buildLog;
+        this.exitCode = exitCode;
+    }
+
+    public long getBuildTimeMs() {
+        return buildTimeMs;
+    }
+
+    public File getBuildLog() {
+        return buildLog;
+    }
+
+    public int getExitCode() {
+        return exitCode;
+    }
+}
diff --git 
a/framework/src/main/java/org/kie/kogito/benchmarks/framework/Commands.java 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/Commands.java
new file mode 100644
index 0000000..de70e59
--- /dev/null
+++ b/framework/src/main/java/org/kie/kogito/benchmarks/framework/Commands.java
@@ -0,0 +1,604 @@
+package org.kie.kogito.benchmarks.framework;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Scanner;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jboss.logging.Logger;
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import static org.kie.kogito.benchmarks.framework.Logs.appendln;
+import static org.kie.kogito.benchmarks.framework.Logs.appendlnSection;
+
+public class Commands {
+    private static final Logger LOGGER = 
Logger.getLogger(Commands.class.getName());
+
+    public static final String BASE_DIR = getBaseDir();
+    public static final String MVNW = Commands.isThisWindows ? "mvnw.cmd" : 
"./mvnw";
+    public static final boolean isThisWindows = 
System.getProperty("os.name").matches(".*[Ww]indows.*");
+    private static final Pattern numPattern = Pattern.compile("[ \t]*[0-9]+[ 
\t]*");
+    private static final Pattern quarkusVersionPattern = Pattern.compile("[ 
\t]*<quarkus.version>([^<]*)</quarkus.version>.*");
+    private static final Pattern trailingSlash = Pattern.compile("/+$");
+
+    public static String getArtifactGeneBaseDir() {
+        for (String p : new String[] { "ARTIFACT_GENERATOR_WORKSPACE", 
"artifact.generator.workspace" }) {
+            String env = System.getenv().get(p);
+            if (StringUtils.isNotBlank(env)) {
+                return env;
+            }
+            String sys = System.getProperty(p);
+            if (StringUtils.isNotBlank(sys)) {
+                return sys;
+            }
+        }
+        return System.getProperty("java.io.tmpdir");
+    }
+
+    public static String getLocalMavenRepoDir() {
+        for (String p : new String[] { "TESTS_MAVEN_REPO_LOCAL", 
"tests.maven.repo.local" }) {
+            String env = System.getenv().get(p);
+            if (StringUtils.isNotBlank(env)) {
+                return env;
+            }
+            String sys = System.getProperty(p);
+            if (StringUtils.isNotBlank(sys)) {
+                return sys;
+            }
+        }
+        return System.getProperty("user.home") + File.separator + ".m2" + 
File.separator + "repository";
+    }
+
+    /**
+     * Get system properties starting with `quarkus.native` prefix, for 
example quarkus.native.builder-image
+     * 
+     * @return List of `-Dquarkus.native.xyz=foo` strings
+     */
+    public static List<String> getQuarkusNativeProperties() {
+        List<String> quarkusNativeProperties = 
System.getProperties().entrySet().stream()
+                .filter(e -> e.getKey().toString().contains("quarkus.native"))
+                .map(e -> "-D" + e.getKey() + "=" + e.getValue())
+                .collect(Collectors.toList());
+        return quarkusNativeProperties;
+    }
+
+    public static String getQuarkusPlatformVersion() {
+        for (String p : new String[] { "QUARKUS_PLATFORM_VERSION", 
"quarkus.platform.version" }) {
+            String env = System.getenv().get(p);
+            if (StringUtils.isNotBlank(env)) {
+                return env;
+            }
+            String sys = System.getProperty(p);
+            if (StringUtils.isNotBlank(sys)) {
+                return sys;
+            }
+        }
+        LOGGER.warn("Failed to detect 
quarkus.platform.version/QUARKUS_PLATFORM_VERSION, defaulting to 
getQuarkusVersion().");
+        return getQuarkusVersion();
+    }
+
+    public static String getQuarkusVersion() {
+        for (String p : new String[] { "QUARKUS_VERSION", "quarkus.version" }) 
{
+            String env = System.getenv().get(p);
+            if (StringUtils.isNotBlank(env)) {
+                return env;
+            }
+            String sys = System.getProperty(p);
+            if (StringUtils.isNotBlank(sys)) {
+                return sys;
+            }
+        }
+        String failure = "Failed to determine quarkus.version. Check pom.xm, 
check env and sys vars QUARKUS_VERSION";
+        try (Scanner sc = new Scanner(new File(getBaseDir() + File.separator + 
"pom.xml"), StandardCharsets.UTF_8)) {
+            while (sc.hasNextLine()) {
+                String line = sc.nextLine();
+                Matcher m = quarkusVersionPattern.matcher(line);
+                if (m.matches()) {
+                    return m.group(1);
+                }
+            }
+        } catch (IOException e) {
+            throw new IllegalArgumentException(failure);
+        }
+        throw new IllegalArgumentException(failure);
+    }
+
+    public static String getBaseDir() {
+        String env = System.getenv().get("basedir");
+        String sys = System.getProperty("basedir");
+        if (StringUtils.isNotBlank(env)) {
+            return new File(env).getParent();
+        }
+        if (StringUtils.isBlank(sys)) {
+            throw new IllegalArgumentException("Unable to determine 
project.basedir.");
+        }
+        return new File(sys).getParent();
+    }
+
+    public static String getCodeQuarkusURL() {
+        return getCodeQuarkusURL("https://code.quarkus.io";);
+    }
+
+    public static String getCodeQuarkusURL(String fallbackURL) {
+        String url = null;
+        for (String p : new String[] { "CODE_QUARKUS_URL", "code.quarkus.url" 
}) {
+            String env = System.getenv().get(p);
+            if (StringUtils.isNotBlank(env)) {
+                url = env;
+                break;
+            }
+            String sys = System.getProperty(p);
+            if (StringUtils.isNotBlank(sys)) {
+                url = sys;
+                break;
+            }
+        }
+        if (url == null) {
+            url = fallbackURL;
+            LOGGER.warn("Failed to detect code.quarkus.url/CODE_QUARKUS_URL 
env/sys props, defaulting to " + url);
+            return url;
+        }
+        Matcher m = trailingSlash.matcher(url);
+        if (m.find()) {
+            url = m.replaceAll("");
+        }
+        return url;
+    }
+
+    public static void cleanTarget(App app) {
+        String target = BASE_DIR + File.separator + app.dir + File.separator + 
"target";
+        String logs = BASE_DIR + File.separator + app.dir + File.separator + 
"logs";
+        cleanDirOrFile(target, logs);
+    }
+
+    public static BuildResult buildApp(App app, String methodName, String 
className, StringBuilder whatIDidReport) throws InterruptedException {
+        File appDir = app.getAppDir(BASE_DIR);
+        File buildLogA = new File(appDir.getAbsolutePath() + File.separator + 
"logs" + File.separator + app.mavenCommands.name().toLowerCase() + 
"-build.log");
+        ExecutorService buildService = Executors.newFixedThreadPool(1);
+
+        List<String> baseBuildCmd = new 
ArrayList<>(Arrays.asList(app.mavenCommands.mvnCmds[0]));
+        //baseBuildCmd.add("-Dquarkus.version=" + getQuarkusVersion());
+        List<String> cmd = getBuildCommand(baseBuildCmd.toArray(new 
String[0]));
+
+        buildService.submit(new Commands.ProcessRunner(appDir, buildLogA, cmd, 
20)); // TODO exit code handling
+        appendln(whatIDidReport, "# " + className + ", " + methodName);
+        appendln(whatIDidReport, (new Date()).toString());
+        appendln(whatIDidReport, appDir.getAbsolutePath());
+        appendlnSection(whatIDidReport, String.join(" ", cmd));
+        long buildStarts = System.currentTimeMillis();
+        buildService.shutdown();
+        buildService.awaitTermination(30, TimeUnit.MINUTES);
+        long buildEnds = System.currentTimeMillis();
+        long buildTimeMs = buildEnds - buildStarts;
+
+        return new BuildResult(buildTimeMs, buildLogA, 0);
+    }
+
+    public static RunInfo startApp(App app, StringBuilder whatIDidReport) 
throws IOException, InterruptedException {
+        File appDir = app.getAppDir(BASE_DIR);
+        File runLogA = new File(appDir.getAbsolutePath() + File.separator + 
"logs" + File.separator + app.mavenCommands.name().toLowerCase() + "-run.log");
+        List<String> cmd = getRunCommand(app.mavenCommands.mvnCmds[1]);
+        appendln(whatIDidReport, appDir.getAbsolutePath());
+        appendlnSection(whatIDidReport, String.join(" ", cmd));
+        long runStarts = System.currentTimeMillis();
+        Process pA = runCommand(cmd, appDir, runLogA);
+        long runEnds = System.currentTimeMillis();
+        System.out.println("RunEnds (" + runEnds + ") - RunStarts (" + 
runStarts + ") : " + (runEnds - runStarts));
+        // Test web pages
+        long timeToFirstOKRequest = 
WebpageTester.testWeb(app.urlContent.urlContent[0][0], 10, 
app.urlContent.urlContent[0][1], true);
+        LOGGER.info("Testing web page content...");
+        for (String[] urlContent : app.urlContent.urlContent) {
+            WebpageTester.testWeb(urlContent[0], 5, urlContent[1], false);
+        }
+
+        return new RunInfo(pA, runLogA, timeToFirstOKRequest);
+    }
+
+    public static void cleanDirOrFile(String... path) {
+        for (String s : path) {
+            try {
+                Files.walk(Paths.get(s))
+                        .sorted(Comparator.reverseOrder())
+                        .map(Path::toFile)
+                        .forEach(File::delete);
+                //FileUtils.forceDelete(new File(s));
+            } catch (IOException e) {
+                //Silence is golden
+            }
+        }
+    }
+
+    public static List<String> getRunCommand(String[] baseCommand) {
+        List<String> runCmd = new ArrayList<>();
+        if (isThisWindows) {
+            runCmd.add("cmd");
+            runCmd.add("/C");
+        }
+        runCmd.addAll(Arrays.asList(baseCommand));
+
+        return Collections.unmodifiableList(runCmd);
+    }
+
+    public static List<String> getBuildCommand(String[] baseCommand) {
+        List<String> buildCmd = new ArrayList<>();
+        if (isThisWindows) {
+            buildCmd.add("cmd");
+            buildCmd.add("/C");
+        }
+        buildCmd.addAll(Arrays.asList(baseCommand));
+        buildCmd.add("-Dmaven.repo.local=" + getLocalMavenRepoDir());
+
+        return Collections.unmodifiableList(buildCmd);
+    }
+
+    //    public static List<String> getBuildCommand(String[] baseCommand, 
String repoDir) {
+    //        List<String> buildCmd = new ArrayList<>();
+    //        if (isThisWindows) {
+    //            buildCmd.add("cmd");
+    //            buildCmd.add("/C");
+    //        }
+    //        buildCmd.addAll(Arrays.asList(baseCommand));
+    //        buildCmd.add("-Dmaven.repo.local=" + repoDir);
+    //        buildCmd.add("--settings=" + BASE_DIR + File.separator + 
App.GENERATED_SKELETON.dir + File.separator + "settings.xml");
+    //
+    //        return Collections.unmodifiableList(buildCmd);
+    //    }
+
+    //    public static List<String> getGeneratorCommand(Set<TestFlags> flags, 
String[] baseCommand, String[] extensions, String repoDir) {
+    //        List<String> generatorCmd = new ArrayList<>();
+    //        if (isThisWindows) {
+    //            generatorCmd.add("cmd");
+    //            generatorCmd.add("/C");
+    //        }
+    //        generatorCmd.addAll(Arrays.asList(baseCommand));
+    //        if (flags.contains(TestFlags.PRODUCT_BOM)) {
+    //            generatorCmd.add("-DplatformArtifactId=quarkus-product-bom");
+    //            generatorCmd.add("-DplatformGroupId=com.redhat.quarkus");
+    //            generatorCmd.add("-DplatformVersion=" + 
getQuarkusPlatformVersion());
+    //        } else if (flags.contains(TestFlags.QUARKUS_BOM)) {
+    //            generatorCmd.add("-DplatformArtifactId=quarkus-bom");
+    //            generatorCmd.add("-DplatformVersion=" + getQuarkusVersion());
+    //        } else if (flags.contains(TestFlags.UNIVERSE_BOM)) {
+    //            
generatorCmd.add("-DplatformArtifactId=quarkus-universe-bom");
+    //            generatorCmd.add("-DplatformVersion=" + getQuarkusVersion());
+    //        } else if (flags.contains(TestFlags.UNIVERSE_PRODUCT_BOM)) {
+    //            
generatorCmd.add("-DplatformArtifactId=quarkus-universe-bom");
+    //            generatorCmd.add("-DplatformGroupId=com.redhat.quarkus");
+    //            generatorCmd.add("-DplatformVersion=" + 
getQuarkusPlatformVersion());
+    //        }
+    //        generatorCmd.add("-Dextensions=" + String.join(",", extensions));
+    //        generatorCmd.add("-Dmaven.repo.local=" + repoDir);
+    //        generatorCmd.add("--settings=" + BASE_DIR + File.separator + 
App.GENERATED_SKELETON.dir + File.separator + "settings.xml");
+    //
+    //        return Collections.unmodifiableList(generatorCmd);
+    //    }
+
+    public static List<String> getGeneratorCommand(String[] baseCommand, 
String[] extensions) {
+        List<String> generatorCmd = new ArrayList<>();
+        if (isThisWindows) {
+            generatorCmd.add("cmd");
+            generatorCmd.add("/C");
+        }
+        generatorCmd.addAll(Arrays.asList(baseCommand));
+        generatorCmd.add("-DplatformVersion=" + getQuarkusVersion());
+        generatorCmd.add("-Dextensions=" + String.join(",", extensions));
+        generatorCmd.add("-Dmaven.repo.local=" + getLocalMavenRepoDir());
+
+        return Collections.unmodifiableList(generatorCmd);
+    }
+
+    //    /**
+    //     * Download a zip file with an example project
+    //     *
+    //     * @param extensions         collection of extension codes, @See 
{@link io.quarkus.ts.startstop.utils.CodeQuarkusExtensions}
+    //     * @param destinationZipFile path where the zip file will be written
+    //     * @return the actual URL used for audit and logging purposes
+    //     * @throws IOException
+    //     */
+    //    public static String download(Collection<CodeQuarkusExtensions> 
extensions, String destinationZipFile) throws IOException {
+    //        String downloadURL = getCodeQuarkusURL() + "/api/download?s=" +
+    //                extensions.stream().map(x -> 
x.shortId).collect(Collectors.joining("."));
+    //        try (ReadableByteChannel readableByteChannel = 
Channels.newChannel(
+    //                new URL(downloadURL).openStream());
+    //             FileChannel fileChannel = new 
FileOutputStream(destinationZipFile).getChannel()) {
+    //            fileChannel.transferFrom(readableByteChannel, 0, 
Long.MAX_VALUE);
+    //        }
+    //        return downloadURL;
+    //    }
+
+    public static File unzip(String zipFilePath, String destinationDir) throws 
InterruptedException, IOException {
+        ProcessBuilder pb;
+        if (isThisWindows) {
+            pb = new ProcessBuilder("powershell", "-c", "Expand-Archive", 
"-Path", zipFilePath, "-DestinationPath", destinationDir, "-Force");
+        } else {
+            pb = new ProcessBuilder("unzip", "-o", zipFilePath, "-d", 
destinationDir);
+        }
+        Map<String, String> env = pb.environment();
+        env.put("PATH", System.getenv("PATH"));
+        pb.directory(new File(destinationDir));
+        pb.redirectErrorStream(true);
+        File unzipLog = new File(zipFilePath + ".log");
+        unzipLog.delete();
+        pb.redirectOutput(ProcessBuilder.Redirect.to(unzipLog));
+        Process p = pb.start();
+        p.waitFor(3, TimeUnit.MINUTES);
+        return unzipLog;
+    }
+
+    public static void removeRepositoriesAndPluginRepositories(String 
pomFilePath) throws Exception {
+        File pomFile = new File(pomFilePath);
+        Document doc = 
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(pomFile);
+        NodeList repositories = doc.getElementsByTagName("repositories");
+        if (repositories.getLength() == 1) {
+            Node node = repositories.item(0);
+            node.getParentNode().removeChild(node);
+        }
+        NodeList pluginRepositories = 
doc.getElementsByTagName("pluginRepositories");
+        if (pluginRepositories.getLength() == 1) {
+            Node node = pluginRepositories.item(0);
+            node.getParentNode().removeChild(node);
+        }
+        Transformer transformer = 
TransformerFactory.newInstance().newTransformer();
+        transformer.transform(new DOMSource(doc), new StreamResult(pomFile));
+    }
+
+    public static boolean waitForTcpClosed(String host, int port, long 
loopTimeoutS) throws InterruptedException, UnknownHostException {
+        InetAddress address = InetAddress.getByName(host);
+        long now = System.currentTimeMillis();
+        long startTime = now;
+        InetSocketAddress socketAddr = new InetSocketAddress(address, port);
+        while (now - startTime < 1000 * loopTimeoutS) {
+            try (Socket socket = new Socket()) {
+                // If it let's you write something there, it is still ready.
+                socket.connect(socketAddr, 1000);
+                socket.setSendBufferSize(1);
+                socket.getOutputStream().write(1);
+                socket.shutdownInput();
+                socket.shutdownOutput();
+                LOGGER.info("Socket still available: " + host + ":" + port);
+            } catch (IOException e) {
+                // Exception thrown - socket is likely closed.
+                return true;
+            }
+            Thread.sleep(1000);
+            now = System.currentTimeMillis();
+        }
+        return false;
+    }
+
+    //    // TODO we should get rid of it once Quarkus progresses with walid 
config per extension in generated examples
+    //    public static void confAppPropsForSkeleton(String appDir) throws 
IOException {
+    //        // Config, see app-generated-skeleton/README.md
+    //        String appPropsSrc = BASE_DIR + File.separator + 
App.GENERATED_SKELETON.dir + File.separator + "application.properties";
+    //        String appPropsDst = appDir + File.separator + "src" + 
File.separator + "main" + File.separator + "resources" + File.separator + 
"application.properties";
+    //        Files.copy(Paths.get(appPropsSrc),
+    //                Paths.get(appPropsDst), 
StandardCopyOption.REPLACE_EXISTING);
+    //    }
+
+    public static void adjustPrettyPrintForJsonLogging(String appDir) throws 
IOException {
+        Path appProps = Paths.get(appDir + File.separator + "src" + 
File.separator + "main" + File.separator + "resources" + File.separator + 
"application.properties");
+        Path appYaml = Paths.get(appDir + File.separator + "src" + 
File.separator + "main" + File.separator + "resources" + File.separator + 
"application.yml");
+
+        adjustFileContent(appProps, 
"quarkus.log.console.json.pretty-print=true", 
"quarkus.log.console.json.pretty-print=false");
+        adjustFileContent(appYaml, "pretty-print: true", "pretty-print: fase");
+    }
+
+    private static void adjustFileContent(Path path, String regex, String 
replacement) throws IOException {
+        if (Files.exists(path)) {
+            String content = new String(Files.readAllBytes(path), 
StandardCharsets.UTF_8);
+            content = content.replaceAll(regex, replacement);
+            Files.write(path, content.getBytes(StandardCharsets.UTF_8));
+        }
+    }
+
+    public static int parsePort(String url) {
+        return Integer.parseInt(url.split(":")[2].split("/")[0]);
+    }
+
+    public static Process runCommand(List<String> command, File directory, 
File logFile) {
+        ProcessBuilder pa = new ProcessBuilder(command);
+        Map<String, String> envA = pa.environment();
+        envA.put("PATH", System.getenv("PATH"));
+        pa.directory(directory);
+        pa.redirectErrorStream(true);
+        pa.redirectOutput(ProcessBuilder.Redirect.to(logFile));
+        Process pA = null;
+        try {
+            pA = pa.start();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return pA;
+    }
+
+    public static void pidKiller(long pid, boolean force) {
+        try {
+            if (isThisWindows) {
+                if (!force) {
+                    Process p = Runtime.getRuntime().exec(new String[] {
+                            BASE_DIR + File.separator + "testsuite" + 
File.separator + "src" + File.separator + "it" + File.separator + "resources" + 
File.separator +
+                                    "CtrlC.exe ",
+                            Long.toString(pid) });
+                    p.waitFor(1, TimeUnit.MINUTES);
+                }
+                Runtime.getRuntime().exec(new String[] { "cmd", "/C", 
"taskkill", "/PID", Long.toString(pid), "/F", "/T" });
+            } else {
+                Runtime.getRuntime().exec(new String[] { "kill", force ? "-9" 
: "-15", Long.toString(pid) });
+            }
+        } catch (IOException | InterruptedException e) {
+            LOGGER.error(e.getMessage(), e);
+        }
+    }
+
+    public static long getRSSkB(long pid) throws IOException, 
InterruptedException {
+        ProcessBuilder pa;
+        if (isThisWindows) {
+            // Note that PeakWorkingSetSize might be better, but we would need 
to change it on Linux too...
+            // 
https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process
+            pa = new ProcessBuilder("wmic", "process", "where", "processid=" + 
pid, "get", "WorkingSetSize");
+        } else {
+            pa = new ProcessBuilder("ps", "-p", Long.toString(pid), "-o", 
"rss=");
+        }
+        Map<String, String> envA = pa.environment();
+        envA.put("PATH", System.getenv("PATH"));
+        pa.redirectErrorStream(true);
+        Process p = pa.start();
+        try (BufferedReader processOutputReader =
+                new BufferedReader(new InputStreamReader(p.getInputStream(), 
StandardCharsets.UTF_8))) {
+            String l;
+            while ((l = processOutputReader.readLine()) != null) {
+                if (numPattern.matcher(l).matches()) {
+                    if (isThisWindows) {
+                        // Qualifiers: DisplayName ("Working Set Size"), Units 
("bytes")
+                        return Long.parseLong(l.trim()) / 1024L;
+                    } else {
+                        return Long.parseLong(l.trim());
+                    }
+                }
+            }
+            p.waitFor();
+        }
+        return -1L;
+    }
+
+    public static long getOpenedFDs(long pid) throws IOException, 
InterruptedException {
+        ProcessBuilder pa;
+        long count = 0;
+        if (isThisWindows) {
+            pa = new ProcessBuilder("wmic", "process", "where", "processid=" + 
pid, "get", "HandleCount");
+        } else {
+            pa = new ProcessBuilder("lsof", "-F0n", "-p", Long.toString(pid));
+        }
+        Map<String, String> envA = pa.environment();
+        envA.put("PATH", System.getenv("PATH"));
+        pa.redirectErrorStream(true);
+        Process p = pa.start();
+        try (BufferedReader processOutputReader =
+                new BufferedReader(new InputStreamReader(p.getInputStream(), 
StandardCharsets.UTF_8))) {
+            if (isThisWindows) {
+                String l;
+                // TODO: We just get a magical number with all FDs... Is it 
O.K.?
+                while ((l = processOutputReader.readLine()) != null) {
+                    if (numPattern.matcher(l).matches()) {
+                        return Long.parseLong(l.trim());
+                    }
+                }
+            } else {
+                // TODO: For the time being we count apples and oranges; we 
might want to distinguish .so and .jar ?
+                while (processOutputReader.readLine() != null) {
+                    count++;
+                }
+            }
+            p.waitFor();
+        }
+        return count;
+    }
+
+    /*
+     * TODO: CPU cycles used
+     * 
+     * Pros: good data
+     * Cons: dependency on perf tool; will not translate to Windows data
+     * 
+     * karm@local:~/workspaceRH/fooBar$ perf stat java -jar 
target/fooBar-1.0.0-SNAPSHOT-runner.jar
+     * 2020-02-25 16:07:00,870 INFO [io.quarkus] (main) fooBar 1.0.0-SNAPSHOT 
(running on Quarkus 999-SNAPSHOT) started in 0.776s.
+     * 2020-02-25 16:07:00,873 INFO [io.quarkus] (main) Profile prod activated.
+     * 2020-02-25 16:07:00,873 INFO [io.quarkus] (main) Installed features: 
[amazon-lambda, cdi, resteasy]
+     * 2020-02-25 16:07:03,360 INFO [io.quarkus] (main) fooBar stopped in 
0.018s
+     * 
+     * Performance counter stats for 'java -jar 
target/fooBar-1.0.0-SNAPSHOT-runner.jar':
+     * 
+     * 1688.910052 task-clock:u (msec) # 0.486 CPUs utilized
+     * 0 context-switches:u # 0.000 K/sec
+     * 0 cpu-migrations:u # 0.000 K/sec
+     * 12,865 page-faults:u # 0.008 M/sec
+     * 4,274,799,448 cycles:u # 2.531 GHz
+     * 4,325,761,598 instructions:u # 1.01 insn per cycle
+     * 919,713,769 branches:u # 544.561 M/sec
+     * 29,310,015 branch-misses:u # 3.19% of all branches
+     * 
+     * 3.473028811 seconds time elapsed
+     */
+
+    // Ask Karm about this
+    public static void processStopper(Process p, boolean force) throws 
InterruptedException, IOException {
+        p.children().forEach(child -> {
+            if (child.supportsNormalTermination()) {
+                child.destroy();
+            }
+            pidKiller(child.pid(), force);
+        });
+        if (p.supportsNormalTermination()) {
+            p.destroy();
+            p.waitFor(3, TimeUnit.MINUTES);
+        }
+        pidKiller(p.pid(), force);
+    }
+
+    public static class ProcessRunner implements Runnable {
+        final File directory;
+        final File log;
+        final List<String> command;
+        final long timeoutMinutes;
+
+        public ProcessRunner(File directory, File log, List<String> command, 
long timeoutMinutes) {
+            this.directory = directory;
+            this.log = log;
+            this.command = command;
+            this.timeoutMinutes = timeoutMinutes;
+        }
+
+        @Override
+        public void run() {
+            ProcessBuilder pb = new ProcessBuilder(command);
+            Map<String, String> env = pb.environment();
+            env.put("PATH", System.getenv("PATH"));
+            pb.directory(directory);
+            pb.redirectErrorStream(true);
+            pb.redirectOutput(ProcessBuilder.Redirect.to(log));
+            Process p = null;
+            try {
+                p = pb.start();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            try {
+                Objects.requireNonNull(p).waitFor(timeoutMinutes, 
TimeUnit.MINUTES);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}
diff --git 
a/framework/src/main/java/org/kie/kogito/benchmarks/framework/HTTPRequestInfo.java
 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/HTTPRequestInfo.java
new file mode 100644
index 0000000..74d0ecc
--- /dev/null
+++ 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/HTTPRequestInfo.java
@@ -0,0 +1,93 @@
+package org.kie.kogito.benchmarks.framework;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class HTTPRequestInfo {
+
+    private String uri;
+    private String body;
+    private String method;
+    private Map<String, String> headers = new HashMap<>();
+    private int expectedResponseStatusCode;
+
+    private void setURI(String uri) {
+        this.uri = uri;
+    }
+
+    private void setBody(String body) {
+        this.body = body;
+    }
+
+    private void setMethod(String method) {
+        this.method = method;
+    }
+
+    private void addHeader(String name, String value) {
+        this.headers.put(name, value);
+    }
+
+    private void setExpectedResponseStatusCode(int expectedResponseStatusCode) 
{
+        this.expectedResponseStatusCode = expectedResponseStatusCode;
+    }
+
+    public String getURI() {
+        return uri;
+    }
+
+    public String getBody() {
+        return body;
+    }
+
+    public String getMethod() {
+        return method;
+    }
+
+    public Map<String, String> getHeaders() {
+        return headers;
+    }
+
+    public int getExpectedResponseStatusCode() {
+        return expectedResponseStatusCode;
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+
+        private final HTTPRequestInfo instance = new HTTPRequestInfo();
+
+        public Builder URI(String uri) {
+            instance.setURI(uri);
+            return this;
+        }
+
+        public Builder body(String body) {
+            instance.setBody(body);
+            return this;
+        }
+
+        public Builder method(String method) {
+            instance.setMethod(method);
+            return this;
+        }
+
+        public Builder header(String name, String value) {
+            instance.addHeader(name, value);
+            return this;
+        }
+
+        public Builder expectedResponseStatusCode(int statusCode) {
+            instance.setExpectedResponseStatusCode(statusCode);
+            return this;
+        }
+
+        public HTTPRequestInfo build() {
+            return instance;
+        }
+
+    }
+
+}
diff --git 
a/framework/src/main/java/org/kie/kogito/benchmarks/framework/LogBuilder.java 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/LogBuilder.java
new file mode 100644
index 0000000..2294df5
--- /dev/null
+++ 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/LogBuilder.java
@@ -0,0 +1,201 @@
+package org.kie.kogito.benchmarks.framework;
+
+import java.util.Objects;
+
+public class LogBuilder {
+
+    public static class Log {
+        public final String headerCSV;
+        public final String headerMarkdown;
+        public final String lineCSV;
+        public final String lineMarkdown;
+
+        public Log(String headerCSV, String headerMarkdown, String lineCSV, 
String lineMarkdown) {
+            this.headerCSV = headerCSV;
+            this.headerMarkdown = headerMarkdown;
+            this.lineCSV = lineCSV;
+            this.lineMarkdown = lineMarkdown;
+        }
+    }
+
+    private static final String buildTimeMsHeader = "buildTimeMs";
+    private long buildTimeMs = -1L;
+    private static final String timeToFirstOKRequestMsHeader = 
"timeToFirstOKRequestMs";
+    private long timeToFirstOKRequestMs = -1L;
+    private static final String timeToReloadedOKRequestHeader = 
"timeToReloadMs";
+    private long timeToReloadedOKRequest = -1L;
+    private static final String startedInMsHeader = "startedInMs";
+    private long startedInMs = -1L;
+    private static final String stoppedInMsHeader = "stoppedInMs";
+    private long stoppedInMs = -1L;
+    private static final String rssKbHeader = "RSSKb";
+    private long rssKb = -1L;
+    private static final String rssKbFinalHeader = "RSSKbFinal";
+    private long rssKbFinal = -1L;
+    private static final String openedFilesHeader = "FDs";
+    private long openedFiles = -1L;
+    private static final String appHeader = "App";
+    private App app = null;
+    private static final String modeHeader = "Mode";
+    private MvnCmds mode = null;
+
+    public LogBuilder buildTimeMs(long buildTimeMs) {
+        if (buildTimeMs <= 0) {
+            throw new IllegalArgumentException("buildTimeMs must be a positive 
long, was: " + buildTimeMs);
+        }
+        this.buildTimeMs = buildTimeMs;
+        return this;
+    }
+
+    public LogBuilder timeToFirstOKRequestMs(long timeToFirstOKRequestMs) {
+        if (timeToFirstOKRequestMs <= 0) {
+            throw new IllegalArgumentException("timeToFirstOKRequestMs must be 
a positive long, was: " + timeToFirstOKRequestMs);
+        }
+        this.timeToFirstOKRequestMs = timeToFirstOKRequestMs;
+        return this;
+    }
+
+    public LogBuilder timeToReloadedOKRequest(long timeToReloadedOKRequest) {
+        if (timeToReloadedOKRequest <= 0) {
+            throw new IllegalArgumentException("timeToReloadedOKRequest must 
be a positive long, was: " + timeToFirstOKRequestMs);
+        }
+        this.timeToReloadedOKRequest = timeToReloadedOKRequest;
+        return this;
+    }
+
+    public LogBuilder startedInMs(long startedInMs) {
+        if (startedInMs <= 0) {
+            throw new IllegalArgumentException("startedInMs must be a positive 
long, was: " + startedInMs);
+        }
+        this.startedInMs = startedInMs;
+        return this;
+    }
+
+    public LogBuilder stoppedInMs(long stoppedInMs) {
+        if (stoppedInMs <= 0) {
+            throw new IllegalArgumentException("stoppedInMs must be a positive 
long, was: " + stoppedInMs);
+        }
+        this.stoppedInMs = stoppedInMs;
+        return this;
+    }
+
+    public LogBuilder rssKb(long rssKb) {
+        if (rssKb <= 0) {
+            throw new IllegalArgumentException("rssKb must be a positive long, 
was: " + rssKb);
+        }
+        this.rssKb = rssKb;
+        return this;
+    }
+
+    public LogBuilder rssKbFinal(long rssKbFinal) {
+        if (rssKbFinal <= 0) {
+            throw new IllegalArgumentException("rssKb must be a positive long, 
was: " + rssKb);
+        }
+        this.rssKbFinal = rssKbFinal;
+        return this;
+    }
+
+    public LogBuilder openedFiles(long openedFiles) {
+        if (openedFiles <= 0) {
+            throw new IllegalArgumentException("openedFiles must be a positive 
long, was: " + openedFiles);
+        }
+        this.openedFiles = openedFiles;
+        return this;
+    }
+
+    public LogBuilder app(App app) {
+        Objects.requireNonNull(app, "Valid app flavour must be provided");
+        this.app = app;
+        return this;
+    }
+
+    public LogBuilder mode(MvnCmds mode) {
+        Objects.requireNonNull(mode, "Valid app flavour must be provided");
+        this.mode = mode;
+        return this;
+    }
+
+    public Log build() {
+        StringBuilder h = new StringBuilder(512);
+        StringBuilder l = new StringBuilder(512);
+        int sections = 0;
+        if (app != null) {
+            h.append(appHeader);
+            h.append(',');
+            l.append(app);
+            l.append(',');
+            sections++;
+        }
+        if (mode != null) {
+            h.append(modeHeader);
+            h.append(',');
+            l.append(mode);
+            l.append(',');
+            sections++;
+        }
+        if (buildTimeMs != -1L) {
+            h.append(buildTimeMsHeader);
+            h.append(',');
+            l.append(buildTimeMs);
+            l.append(',');
+            sections++;
+        }
+        if (timeToFirstOKRequestMs != -1L) {
+            h.append(timeToFirstOKRequestMsHeader);
+            h.append(',');
+            l.append(timeToFirstOKRequestMs);
+            l.append(',');
+            sections++;
+        }
+        if (timeToReloadedOKRequest != -1L) {
+            h.append(timeToReloadedOKRequestHeader);
+            h.append(',');
+            l.append(timeToReloadedOKRequest);
+            l.append(',');
+            sections++;
+        }
+        if (startedInMs != -1L) {
+            h.append(startedInMsHeader);
+            h.append(',');
+            l.append(startedInMs);
+            l.append(',');
+            sections++;
+        }
+        if (stoppedInMs != -1L) {
+            h.append(stoppedInMsHeader);
+            h.append(',');
+            l.append(stoppedInMs);
+            l.append(',');
+            sections++;
+        }
+        if (rssKb != -1L) {
+            h.append(rssKbHeader);
+            h.append(',');
+            l.append(rssKb);
+            l.append(',');
+            sections++;
+        }
+        if (rssKbFinal != -1L) {
+            h.append(rssKbFinalHeader);
+            h.append(',');
+            l.append(rssKbFinal);
+            l.append(',');
+            sections++;
+        }
+        if (openedFiles != -1L) {
+            h.append(openedFilesHeader);
+            h.append(',');
+            l.append(openedFiles);
+            l.append(',');
+            sections++;
+        }
+        String header = h.toString();
+        // Strip trailing ',' for CSV
+        String headerCSV = header.substring(0, header.length() - 1);
+        String headerMarkdown = "|" + header.replaceAll(",", "|") + "\n|" + " 
--- |".repeat(sections);
+        String line = l.toString();
+        String lineCSV = line.substring(0, line.length() - 1);
+        String lineMarkdown = "|" + line.replaceAll(",", "|");
+        return new Log(headerCSV, headerMarkdown, lineCSV, lineMarkdown);
+    }
+}
diff --git 
a/framework/src/main/java/org/kie/kogito/benchmarks/framework/Logs.java 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/Logs.java
new file mode 100644
index 0000000..dba7874
--- /dev/null
+++ b/framework/src/main/java/org/kie/kogito/benchmarks/framework/Logs.java
@@ -0,0 +1,336 @@
+package org.kie.kogito.benchmarks.framework;
+
+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.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Scanner;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jboss.logging.Logger;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.kie.kogito.benchmarks.framework.Commands.BASE_DIR;
+import static org.kie.kogito.benchmarks.framework.Commands.isThisWindows;
+
+public class Logs {
+    private static final Logger LOGGER = 
Logger.getLogger(Logs.class.getName());
+
+    public static final String jarSuffix = "redhat";
+    private static final Pattern jarNamePattern = Pattern.compile("^((?!" + 
jarSuffix + ").)*jar$");
+
+    private static final Pattern startedPattern = Pattern.compile(".* started 
in ([0-9\\.]+)s.*", Pattern.DOTALL);
+    private static final Pattern stoppedPattern = Pattern.compile(".* stopped 
in ([0-9\\.]+)s.*", Pattern.DOTALL);
+    /*
+     * Due to console colouring, Windows has control characters in the 
sequence.
+     * So "1.778s" in "started in 1.778s." becomes "[38;5;188m1.778".
+     * e.g.
+     * //started in 1.228s.
+     * //stopped in 0.024s
+     * 
+     * Although when run from Jenkins service account; those symbols might not 
be present
+     * depending on whether you checked AllowInteractingWithDesktop.
+     * // TODO to make it smoother?
+     */
+    private static final Pattern startedPatternControlSymbols = 
Pattern.compile(".* started in .*188m([0-9\\.]+).*", Pattern.DOTALL);
+    private static final Pattern stoppedPatternControlSymbols = 
Pattern.compile(".* stopped in .*188m([0-9\\.]+).*", Pattern.DOTALL);
+
+    private static final Pattern warnErrorDetectionPattern = 
Pattern.compile("(?i:.*(ERROR|WARN|SLF4J:).*)");
+    private static final Pattern listeningOnDetectionPattern = 
Pattern.compile("(?i:.*Listening on:.*)");
+    private static final Pattern devExpectedHostPattern = 
Pattern.compile("(?i:.*localhost:.*)");
+    private static final Pattern defaultExpectedHostPattern = 
Pattern.compile("(?i:.*0.0.0.0:.*)");
+
+    public static final long SKIP = -1L;
+
+    public static void checkLog(String testClass, String testMethod, App app, 
MvnCmds cmd, File log) throws IOException {
+        try (Scanner sc = new Scanner(log, UTF_8)) {
+            Set<String> offendingLines = new HashSet<>();
+            while (sc.hasNextLine()) {
+                String line = sc.nextLine();
+                boolean error = 
warnErrorDetectionPattern.matcher(line).matches();
+                if (error) {
+                    if (isWhiteListed(app.whitelistLogLines.errs, line)) {
+                        LOGGER.info(cmd.name() + " log for " + testMethod + " 
contains whitelisted error: `" + line + "'");
+                    } else if 
(isWhiteListed(app.whitelistLogLines.platformErrs(), line)) {
+                        LOGGER.info(cmd.name() + " log for " + testMethod + " 
contains platform specific whitelisted error: `" + line + "'");
+                    } else {
+                        offendingLines.add(line);
+                    }
+                }
+            }
+            assertTrue(offendingLines.isEmpty(),
+                    cmd.name() + " log should not contain error or warning 
lines that are not whitelisted. " +
+                            "See testsuite" + File.separator + "target" + 
File.separator + "archived-logs" +
+                            File.separator + testClass + File.separator + 
testMethod + File.separator + log.getName() +
+                            " and check these offending lines: \n" + 
String.join("\n", offendingLines));
+        }
+    }
+
+    public static void checkListeningHost(String testClass, String testMethod, 
MvnCmds cmd, File log) throws IOException {
+        boolean isOffending = true;
+        try (Scanner sc = new Scanner(log, UTF_8)) {
+            while (sc.hasNextLine()) {
+                String line = sc.nextLine();
+                if (listeningOnDetectionPattern.matcher(line).matches()) {
+                    Pattern expectedHostPattern = defaultExpectedHostPattern;
+                    if (cmd == MvnCmds.DEV || cmd == MvnCmds.MVNW_DEV) {
+                        expectedHostPattern = devExpectedHostPattern;
+                    }
+
+                    isOffending = !expectedHostPattern.matcher(line).matches();
+                }
+            }
+        }
+
+        assertFalse(isOffending,
+                cmd.name() + " log should contain expected listening host. " +
+                        "See testsuite" + File.separator + "target" + 
File.separator + "archived-logs" +
+                        File.separator + testClass + File.separator + 
testMethod + File.separator + log.getName() +
+                        " and check the listening host.");
+    }
+
+    private static boolean isWhiteListed(Pattern[] patterns, String line) {
+        for (Pattern p : patterns) {
+            if (p.matcher(line).matches()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    //    public static void checkJarSuffixes(Set<TestFlags> flags, File 
appDir) throws IOException {
+    //        if (flags.contains(TestFlags.PRODUCT_BOM) || 
flags.contains(TestFlags.UNIVERSE_PRODUCT_BOM)) {
+    //            List<Path> possiblyUnwantedArtifacts = 
Logs.listJarsFailingNameCheck(
+    //                    appDir.getAbsolutePath() + File.separator + "target" 
+ File.separator + "lib");
+    //            List<String> reportArtifacts = new ArrayList<>();
+    //            boolean containsNotWhitelisted = false;
+    //            for (Path p : possiblyUnwantedArtifacts) {
+    //                boolean found = false;
+    //                for (String w : 
WhitelistProductBomJars.PRODUCT_BOM.jarNames) {
+    //                    if (p.toString().contains(w)) {
+    //                        found = true;
+    //                        break;
+    //                    }
+    //                }
+    //                if (found) {
+    //                    reportArtifacts.add("WHITELISTED: " + p);
+    //                } else {
+    //                    containsNotWhitelisted = true;
+    //                    reportArtifacts.add(p.toString());
+    //                }
+    //            }
+    //            assertFalse(containsNotWhitelisted, "There are 
not-whitelisted artifacts without expected string " + jarSuffix + " suffix, 
see: \n"
+    //                    + String.join("\n", reportArtifacts));
+    //            if (!reportArtifacts.isEmpty()) {
+    //                LOGGER.warn("There are whitelisted artifacts without 
expected string " + jarSuffix + " suffix, see: \n"
+    //                                    + String.join("\n", 
reportArtifacts));
+    //            }
+    //        }
+    //    }
+
+    public static void checkThreshold(App app, MvnCmds cmd, long rssKb, long 
timeToFirstOKRequest, long timeToReloadedOKRequest) {
+        String propPrefix = isThisWindows ? "windows" : "linux";
+        if (cmd == MvnCmds.QUARKUS_JVM) {
+            propPrefix += ".jvm";
+        } else if (cmd == MvnCmds.NATIVE) {
+            propPrefix += ".native";
+        } else if (cmd == MvnCmds.DEV) {
+            propPrefix += ".dev";
+            //        } else if (cmd == MvnCmds.GENERATOR) {
+            //            propPrefix += ".generated.dev";
+        } else {
+            throw new IllegalArgumentException("Unexpected mode. Check 
MvnCmds.java.");
+        }
+        if (timeToFirstOKRequest != SKIP) {
+            long timeToFirstOKRequestThresholdMs = 
app.thresholdProperties.get(propPrefix + 
".time.to.first.ok.request.threshold.ms");
+            assertTrue(timeToFirstOKRequest <= timeToFirstOKRequestThresholdMs,
+                    "Application " + app + " in " + cmd + " mode took " + 
timeToFirstOKRequest
+                            + " ms to get the first OK request, which is over 
" +
+                            timeToFirstOKRequestThresholdMs + " ms 
threshold.");
+        }
+        if (rssKb != SKIP) {
+            long rssThresholdKb = app.thresholdProperties.get(propPrefix + 
".RSS.threshold.kB");
+            assertTrue(rssKb <= rssThresholdKb,
+                    "Application " + app + " in " + cmd + " consumed " + rssKb 
+ " kB, which is over " +
+                            rssThresholdKb + " kB threshold.");
+        }
+        if (timeToReloadedOKRequest != SKIP) {
+            long timeToReloadedOKRequestThresholdMs = 
app.thresholdProperties.get(propPrefix + ".time.to.reload.threshold.ms");
+            assertTrue(timeToReloadedOKRequest <= 
timeToReloadedOKRequestThresholdMs,
+                    "Application " + app + " in " + cmd + " mode took " + 
timeToReloadedOKRequest
+                            + " ms to get the first OK request after dev mode 
reload, which is over " +
+                            timeToReloadedOKRequestThresholdMs + " ms 
threshold.");
+        }
+    }
+
+    public static void archiveLog(String testClass, String testMethod, File 
log) throws IOException {
+        if (log == null || !log.exists()) {
+            LOGGER.warn("log must be a valid, existing file. Skipping 
operation.");
+            return;
+        }
+        if (StringUtils.isBlank(testClass)) {
+            throw new IllegalArgumentException("testClass must not be blank");
+        }
+        if (StringUtils.isBlank(testMethod)) {
+            throw new IllegalArgumentException("testMethod must not be blank");
+        }
+        Path destDir = getLogsDir(testClass, testMethod);
+        Files.createDirectories(destDir);
+        String filename = log.getName();
+        Files.copy(log.toPath(), Paths.get(destDir.toString(), filename), 
REPLACE_EXISTING);
+    }
+
+    public static void writeReport(String testClass, String testMethod, String 
text) throws IOException {
+        Path destDir = getLogsDir(testClass, testMethod);
+        Files.createDirectories(destDir);
+        Files.write(Paths.get(destDir.toString(), "report.md"), 
text.getBytes(UTF_8), StandardOpenOption.CREATE, 
StandardOpenOption.TRUNCATE_EXISTING);
+        Path agregateReport = Paths.get(getLogsDir().toString(), 
"aggregated-report.md");
+        if (Files.notExists(agregateReport)) {
+            Files.write(agregateReport, ("# Aggregated 
Report\n\n").getBytes(UTF_8), StandardOpenOption.CREATE);
+        }
+        Files.write(agregateReport, text.getBytes(UTF_8), 
StandardOpenOption.APPEND);
+    }
+
+    /**
+     * Markdown needs two newlines to make a new paragraph.
+     */
+    public static void appendln(StringBuilder s, String text) {
+        s.append(text);
+        s.append("\n\n");
+    }
+
+    public static void appendlnSection(StringBuilder s, String text) {
+        s.append(text);
+        s.append("\n\n---\n");
+    }
+
+    public static Path getLogsDir(String testClass, String testMethod) throws 
IOException {
+        Path destDir = new File(getLogsDir(testClass).toString() + 
File.separator + testMethod).toPath();
+        Files.createDirectories(destDir);
+        return destDir;
+    }
+
+    public static Path getLogsDir(String testClass) throws IOException {
+        Path destDir = new File(getLogsDir().toString() + File.separator + 
testClass).toPath();
+        Files.createDirectories(destDir);
+        return destDir;
+    }
+
+    public static Path getLogsDir() throws IOException {
+        Path destDir = new File(BASE_DIR + File.separator + "testsuite" + 
File.separator + "target" +
+                File.separator + "archived-logs").toPath();
+        Files.createDirectories(destDir);
+        return destDir;
+    }
+
+    public static void logMeasurements(LogBuilder.Log log, Path path) throws 
IOException {
+        if (Files.notExists(path)) {
+            Files.write(path, (log.headerCSV + "\n").getBytes(UTF_8), 
StandardOpenOption.CREATE);
+        }
+        Files.write(path, (log.lineCSV + "\n").getBytes(UTF_8), 
StandardOpenOption.APPEND);
+        LOGGER.info("\n" + log.headerCSV + "\n" + log.lineCSV);
+    }
+
+    public static void logMeasurementsSummary(LogBuilder.Log log, Path path) 
throws IOException {
+        if (Files.notExists(path)) {
+            Files.write(path, (log.headerCSV + "\n").getBytes(UTF_8), 
StandardOpenOption.CREATE);
+            Files.write(path, (log.lineCSV + "\n").getBytes(UTF_8), 
StandardOpenOption.APPEND);
+            return;
+        }
+
+        List<String> lines = 
Files.lines(path).collect(Collectors.toCollection(ArrayList::new));
+        String currentHeader = lines.get(0);
+        String headerWithoutAppAndMode = 
Arrays.stream(log.headerCSV.split(",")).skip(2).collect(Collectors.joining(","));
+        if (!currentHeader.contains(headerWithoutAppAndMode)) {
+            lines.set(0, currentHeader + "," + headerWithoutAppAndMode);
+            currentHeader = lines.get(0);
+        }
+
+        String lastLine = lines.get(lines.size() - 1);
+
+        long headerLength = currentHeader.chars().filter(value -> value == 
',').count();
+        long lastLineLength = lastLine.chars().filter(value -> value == 
',').count();
+        if (lastLineLength < headerLength) {
+            String newDataWithoutAppAndMode = 
Arrays.stream(log.lineCSV.split(",")).skip(2).collect(Collectors.joining(","));
+            lines.set(lines.size() - 1, lastLine + "," + 
newDataWithoutAppAndMode);
+        } else {
+            lines.add(log.lineCSV);
+        }
+
+        Files.write(path, 
(lines.stream().collect(Collectors.joining(System.lineSeparator())) + 
System.lineSeparator()).getBytes());
+
+        LOGGER.info("\n" + log.headerCSV + "\n" + log.lineCSV);
+    }
+
+    /**
+     * List Jar file names failing regexp pattern check
+     *
+     * Note the pattern is hardcoded to look for jars not containing word 
'redhat',
+     * but it could be easily generalized if needed.
+     *
+     * @param path to the root of directory tree
+     * @return list of offending jar paths
+     */
+    public static List<Path> listJarsFailingNameCheck(String path) throws 
IOException {
+        return Files.find(Paths.get(path),
+                500, //if this is not enough, something is broken anyway
+                (filePath, fileAttr) -> fileAttr.isRegularFile() && 
jarNamePattern.matcher(filePath.getFileName().toString()).matches())
+                .collect(Collectors.toList());
+    }
+
+    public static float[] parseStartStopTimestamps(File log) throws 
IOException {
+        float[] startedStopped = new float[] { -1f, -1f };
+        try (Scanner sc = new Scanner(log, UTF_8)) {
+            while (sc.hasNextLine()) {
+                String line = sc.nextLine();
+
+                Matcher m = startedPatternControlSymbols.matcher(line);
+                if (startedStopped[0] == -1f && m.matches()) {
+                    startedStopped[0] = Float.parseFloat(m.group(1));
+                    continue;
+                }
+
+                m = startedPattern.matcher(line);
+                if (startedStopped[0] == -1f && m.matches()) {
+                    startedStopped[0] = Float.parseFloat(m.group(1));
+                    continue;
+                }
+
+                m = stoppedPatternControlSymbols.matcher(line);
+                if (startedStopped[1] == -1f && m.matches()) {
+                    startedStopped[1] = Float.parseFloat(m.group(1));
+                    continue;
+                }
+
+                m = stoppedPattern.matcher(line);
+                if (startedStopped[1] == -1f && m.matches()) {
+                    startedStopped[1] = Float.parseFloat(m.group(1));
+                }
+            }
+        }
+        if (startedStopped[0] == -1f) {
+            LOGGER.error("Parsing start time from log failed. " +
+                    "Might not be the right time to call this method. The 
process might have ben killed before it wrote to log." +
+                    "Find " + log.getName() + " in your target dir.");
+        }
+        if (startedStopped[1] == -1f) {
+            LOGGER.error("Parsing stop time from log failed. " +
+                    "Might not be the right time to call this method. The 
process might have been killed before it wrote to log." +
+                    "Find " + log.getName() + " in your target dir.");
+        }
+        return startedStopped;
+    }
+}
diff --git 
a/framework/src/main/java/org/kie/kogito/benchmarks/framework/MvnCmds.java 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/MvnCmds.java
new file mode 100644
index 0000000..e4bbeb4
--- /dev/null
+++ b/framework/src/main/java/org/kie/kogito/benchmarks/framework/MvnCmds.java
@@ -0,0 +1,53 @@
+package org.kie.kogito.benchmarks.framework;
+
+import java.util.stream.Stream;
+
+import static 
org.kie.kogito.benchmarks.framework.Commands.getLocalMavenRepoDir;
+import static 
org.kie.kogito.benchmarks.framework.Commands.getQuarkusNativeProperties;
+
+public enum MvnCmds {
+    QUARKUS_JVM(new String[][] {
+            new String[] { "mvn", "clean", "package", /* "quarkus:build", 
*/"-Dquarkus.package.output-name=quarkus" },
+            new String[] { "java", "-jar", "target/quarkus-runner.jar" }
+    }),
+    SPRING_BOOT_JVM(new String[][] {
+            new String[] { "mvn", "clean", "package", /* "quarkus:build", 
*/"-Dquarkus.package.output-name=quarkus" },
+            new String[] { "java", "-jar", "target/quarkus-runner.jar" }
+    }),
+    DEV(new String[][] {
+            new String[] { "mvn", "clean", "quarkus:dev", 
"-Dmaven.repo.local=" + getLocalMavenRepoDir() }
+    }),
+    NATIVE(new String[][] {
+            Stream.concat(Stream.of("mvn", "clean", "compile", "package", 
"-Pnative"),
+                    
getQuarkusNativeProperties().stream()).toArray(String[]::new),
+            new String[] { Commands.isThisWindows ? "target\\quarkus-runner" : 
"./target/quarkus-runner" }
+    }),
+    //    GENERATOR(new String[][] {
+    //            new String[] {
+    //                    "mvn",
+    //                    "io.quarkus:quarkus-maven-plugin:" + 
getQuarkusVersion() + ":create",
+    //                    "-DprojectGroupId=my-groupId",
+    //                    "-DprojectArtifactId=" + App.GENERATED_SKELETON.dir,
+    //                    "-DprojectVersion=1.0.0-SNAPSHOT",
+    //                    "-DpackageName=org.my.group"
+    //            }
+    //    }),
+    MVNW_DEV(new String[][] {
+            new String[] { Commands.MVNW, "quarkus:dev" }
+    }),
+    MVNW_JVM(new String[][] {
+            new String[] { Commands.MVNW, "clean", "compile", "quarkus:build", 
"-Dquarkus.package.output-name=quarkus" },
+            new String[] { "java", "-jar", 
"target/quarkus-app/quarkus-run.jar" }
+    }),
+    MVNW_NATIVE(new String[][] {
+            Stream.concat(Stream.of(Commands.MVNW, "clean", "compile", 
"package", "-Pnative", "-Dquarkus.package.output-name=quarkus"),
+                    
getQuarkusNativeProperties().stream()).toArray(String[]::new),
+            new String[] { Commands.isThisWindows ? "target\\quarkus-runner" : 
"./target/quarkus-runner" }
+    });
+
+    public final String[][] mvnCmds;
+
+    MvnCmds(String[][] mvnCmds) {
+        this.mvnCmds = mvnCmds;
+    }
+}
diff --git 
a/framework/src/main/java/org/kie/kogito/benchmarks/framework/RunInfo.java 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/RunInfo.java
new file mode 100644
index 0000000..e7f4599
--- /dev/null
+++ b/framework/src/main/java/org/kie/kogito/benchmarks/framework/RunInfo.java
@@ -0,0 +1,28 @@
+package org.kie.kogito.benchmarks.framework;
+
+import java.io.File;
+
+public class RunInfo {
+
+    private final Process process;
+    private final File runLog;
+    private final long timeToFirstOKRequest;
+
+    public RunInfo(Process process, File runLog, long timeToFirstOKRequest) {
+        this.process = process;
+        this.runLog = runLog;
+        this.timeToFirstOKRequest = timeToFirstOKRequest;
+    }
+
+    public Process getProcess() {
+        return process;
+    }
+
+    public File getRunLog() {
+        return runLog;
+    }
+
+    public long getTimeToFirstOKRequest() {
+        return timeToFirstOKRequest;
+    }
+}
diff --git 
a/framework/src/main/java/org/kie/kogito/benchmarks/framework/URLContent.java 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/URLContent.java
new file mode 100644
index 0000000..d44dc8e
--- /dev/null
+++ 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/URLContent.java
@@ -0,0 +1,33 @@
+package org.kie.kogito.benchmarks.framework;
+
+public enum URLContent {
+    SAMPLE_KOGITO_APP(new String[][] { new String[] { 
"http://localhost:8080/LoanApplication";, "[]" },
+            new String[] { "http://localhost:8080/greeting";, "1" } }),
+    JAX_RS_MINIMAL(new String[][] {
+            new String[] { "http://localhost:8080";, "Hello from a simple 
JAX-RS app." },
+            new String[] { "http://localhost:8080/data/hello";, "Hello World" }
+    }),
+    FULL_MICROPROFILE(new String[][] {
+            new String[] { "http://localhost:8080";, "Hello from a full 
MicroProfile suite" },
+            new String[] { "http://localhost:8080/data/hello";, "Hello World" },
+            new String[] { "http://localhost:8080/data/config/injected";, 
"Config value as Injected by CDI Injected value" },
+            new String[] { "http://localhost:8080/data/config/lookup";, "Config 
value from ConfigProvider lookup value" },
+            new String[] { "http://localhost:8080/data/resilience";, "Fallback 
answer due to timeout" },
+            new String[] { "http://localhost:8080/health";, "\"UP\"" },
+            new String[] { "http://localhost:8080/data/metric/timed";, "Request 
is used in statistics, check with the Metrics call." },
+            new String[] { "http://localhost:8080/metrics";, 
"ontroller_timed_request_seconds_count" },
+            new String[] { "http://localhost:8080/data/secured/test";, "Jessie 
specific value" },
+            new String[] { "http://localhost:8080/openapi";, "/resilience" },
+            new String[] { 
"http://localhost:8080/data/client/test/parameterValue=xxx";, "Processed 
parameter value 'parameterValue=xxx'" }
+    }),
+    GENERATED_SKELETON(new String[][] {
+            new String[] { "http://localhost:8080";, "Congratulations" },
+            new String[] { "http://localhost:8080/hello-spring";, "Bye Spring" }
+    });
+
+    public final String[][] urlContent;
+
+    URLContent(String[][] urlContent) {
+        this.urlContent = urlContent;
+    }
+}
diff --git 
a/framework/src/main/java/org/kie/kogito/benchmarks/framework/WebpageTester.java
 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/WebpageTester.java
new file mode 100644
index 0000000..f88118e
--- /dev/null
+++ 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/WebpageTester.java
@@ -0,0 +1,78 @@
+package org.kie.kogito.benchmarks.framework;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.Scanner;
+
+import org.apache.commons.lang3.StringUtils;
+import org.jboss.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class WebpageTester {
+    private static final Logger LOGGER = 
Logger.getLogger(WebpageTester.class.getName());
+
+    /**
+     * Patiently try to wait for a web page and examine it
+     *
+     * @param url address
+     * @param timeoutS in seconds
+     * @param stringToLookFor string must be present on the page
+     */
+    public static long testWeb(String url, long timeoutS, String 
stringToLookFor, boolean measureTime) throws InterruptedException, IOException {
+        if (StringUtils.isBlank(url)) {
+            throw new IllegalArgumentException("url must not be empty");
+        }
+        if (timeoutS < 0) {
+            throw new IllegalArgumentException("timeoutS must be positive");
+        }
+        if (StringUtils.isBlank(stringToLookFor)) {
+            throw new IllegalArgumentException("stringToLookFor must contain a 
non-empty string");
+        }
+        String webPage = "";
+
+        long now = System.currentTimeMillis();
+        System.out.println("Now in the testWeb method: " + now);
+        final long startTime = now;
+        boolean found = false;
+        long foundTimestamp = -1L;
+        while (now - startTime < 1000 * timeoutS) {
+            URLConnection c = new URL(url).openConnection();
+            c.setRequestProperty("Accept", "*/*");
+            c.setConnectTimeout(500);
+            long requestStart = System.currentTimeMillis();
+            try (Scanner scanner = new Scanner(c.getInputStream(), 
StandardCharsets.UTF_8.toString())) {
+                scanner.useDelimiter("\\A");
+                webPage = scanner.hasNext() ? scanner.next() : "";
+            } catch (Exception e) {
+                LOGGER.debug("Waiting `" + stringToLookFor + "' to appear on " 
+ url);
+            }
+            if (webPage.contains(stringToLookFor)) {
+                found = true;
+                if (measureTime) {
+                    foundTimestamp = System.currentTimeMillis();
+                    System.out.println("Found timestamp " + foundTimestamp);
+                    System.out.println("Request took " + (foundTimestamp - 
requestStart));
+                }
+                break;
+            }
+            if (!measureTime) {
+                Thread.sleep(500);
+            } else {
+                Thread.sleep(0, 100000);
+            }
+            now = System.currentTimeMillis();
+        }
+
+        String failureMessage = "Timeout " + timeoutS + "s was reached. " +
+                (StringUtils.isNotBlank(webPage) ? webPage + " must contain 
string: " : "Empty webpage does not contain string: ") +
+                "`" + stringToLookFor + "'";
+        if (!found) {
+            LOGGER.info(failureMessage);
+        }
+        assertTrue(found, failureMessage);
+        return foundTimestamp - startTime;
+    }
+}
diff --git 
a/framework/src/main/java/org/kie/kogito/benchmarks/framework/WhitelistLogLines.java
 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/WhitelistLogLines.java
new file mode 100644
index 0000000..41aa220
--- /dev/null
+++ 
b/framework/src/main/java/org/kie/kogito/benchmarks/framework/WhitelistLogLines.java
@@ -0,0 +1,112 @@
+package org.kie.kogito.benchmarks.framework;
+
+import java.util.regex.Pattern;
+
+/**
+ * Whitelists errors in log files.
+ *
+ * @author Michal Karm Babacek <[email protected]>
+ */
+public enum WhitelistLogLines {
+    SAMPLE_KOGITO_APP(new Pattern[] { Pattern.compile(".*") }),
+    JAX_RS_MINIMAL(new Pattern[] {
+            // Some artifacts names...
+            Pattern.compile(".*maven-error-diagnostics.*"),
+            Pattern.compile(".*errorprone.*"),
+    }),
+    FULL_MICROPROFILE(new Pattern[] {
+            // Some artifacts names...
+            Pattern.compile(".*maven-error-diagnostics.*"),
+            Pattern.compile(".*errorprone.*"),
+            // Needs fixing in the demo app?
+            Pattern.compile(".*TestSecureController.java.*"),
+            // Well, the RestClient demo probably should do some cleanup 
before shutdown...?
+            Pattern.compile(".*Closing a class 
org.jboss.resteasy.client.jaxrs.engines.ApacheHttpClient.*"),
+    }),
+    GENERATED_SKELETON(new Pattern[] {
+            // It so happens that the dummy skeleton tries to find Mongo. This 
is expected.
+            // See app-generated-skeleton/README.md for explanation of the 
scope.
+            Pattern.compile(".*The remote computer refused the network 
connection.*"),
+            // Harmless warning
+            Pattern.compile(".*The Agroal dependency is present but no JDBC 
datasources have been defined.*"),
+            // Due to our not exactly accurate application.properties, these 
expected warnings occur...
+            Pattern.compile(".*Unrecognized configuration key[ \\\\\"]*(" +
+                    "quarkus.oidc.auth-server-url|" +
+                    "quarkus.oidc.client-id|" +
+                    "quarkus.oidc-client.auth-server-url|" +
+                    "quarkus.oidc-client.client-id|" +
+                    "quarkus.oidc-client.token-path|" +
+                    "quarkus.oidc-client.discovery-enabled|" +
+                    "quarkus.smallrye-jwt.enabled|" +
+                    "quarkus.jaeger.service-name|" +
+                    "quarkus.jaeger.sampler-param|" +
+                    "quarkus.jaeger.endpoint|" +
+                    "quarkus.jaeger.sampler-type" +
+                    ")[ \\\\\"]*was provided.*"),
+            // Some artifacts names...
+            Pattern.compile(".*maven-error-diagnostics.*"),
+            Pattern.compile(".*errorprone.*"),
+            Pattern.compile(".*google-cloud-errorreporting-bom.*"),
+            // When GraalVM is used; unrelated to the test
+            Pattern.compile(".*forcing TieredStopAtLevel to full optimization 
because JVMCI is enabled.*"),
+            Pattern.compile(".*error_prone_annotations.*"),
+            Pattern.compile(".*SRGQL010000: Schema is null, or it has no 
operations. Not bootstrapping SmallRye GraphQL*"),
+            Pattern.compile(".*No WebJars were found in the project.*"),
+            Pattern.compile(
+                    ".*This application uses the MP Metrics API. The 
micrometer extension currently provides a compatibility layer that supports the 
MP Metrics API, but metric names and recorded values will be different. Note 
that the MP Metrics compatibility layer will move to a different extension in 
the future.*"),
+            // kubernetes-client tries to configure client from service account
+            Pattern.compile(".*Error reading service account token from.*"),
+            // hibernate-orm issues this warning when default datasource is 
ambiguous
+            // (no explicit configuration, none or multiple JDBC driver 
extensions)
+            // Result of DevServices support 
https://github.com/quarkusio/quarkus/pull/14960
+            Pattern.compile(".*Unable to determine a database type for default 
datasource.*"),
+    });
+
+    public final Pattern[] errs;
+
+    WhitelistLogLines(Pattern[] errs) {
+        this.errs = errs;
+    }
+
+    public final Pattern[] platformErrs() {
+        switch (OS.current()) {
+            case MAC:
+                return new Pattern[] {
+                        Pattern.compile(".*objcopy executable not found in 
PATH. Debug symbols will not be separated from executable.*"),
+                        Pattern.compile(".*That will result in a larger native 
image with debug symbols embedded in it.*"),
+                };
+        }
+        return new Pattern[] {};
+    }
+
+    enum OS {
+        MAC,
+        LINUX,
+        WINDOWS,
+        UNKNOWN;
+
+        public static OS current() {
+            if (isMac()) {
+                return MAC;
+            } else if (isWindows()) {
+                return WINDOWS;
+            } else if (isLinux()) {
+                return LINUX;
+            } else {
+                return UNKNOWN;
+            }
+        }
+    }
+
+    private static boolean isMac() {
+        return System.getProperty("os.name").toLowerCase().contains("mac");
+    }
+
+    private static boolean isWindows() {
+        return System.getProperty("os.name").toLowerCase().contains("windows");
+    }
+
+    private static boolean isLinux() {
+        return System.getProperty("os.name").toLowerCase().contains("linux");
+    }
+}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..c37181a
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.kie.kogito</groupId>
+    <artifactId>kogito-build-parent</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>kogito-benchmarks</artifactId>
+  <packaging>pom</packaging>
+
+  <name></name>
+  <description></description>
+
+  <properties>
+    <maven.compiler.release>11</maven.compiler.release> <!-- Mainly because of 
the additions to the Process API introduced in Java 9 -->
+  </properties>
+
+  <modules>
+    <module>framework</module>
+    <module>sample-kogito-app</module>
+    <module>tests</module>
+  </modules>
+
+
+</project>
\ No newline at end of file
diff --git a/sample-kogito-app/pom.xml b/sample-kogito-app/pom.xml
new file mode 100644
index 0000000..e33c2b2
--- /dev/null
+++ b/sample-kogito-app/pom.xml
@@ -0,0 +1,115 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.kie.kogito</groupId>
+    <artifactId>kogito-benchmarks</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>sample-kogito-app</artifactId>
+
+  <properties>
+    <!-- Skip tests by default -->
+    <skipTests>true</skipTests>
+    <quarkus.version>1.11.5.Final</quarkus.version>
+    <kogito.version>1.4.1.Final</kogito.version>
+    <surefire.version>2.22.2</surefire.version>
+    <compiler.version>3.8.1</compiler.version>
+
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <maven.compiler.release>11</maven.compiler.release>
+  </properties>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.kie.kogito</groupId>
+        <artifactId>kogito-quarkus-bom</artifactId>
+        <version>${kogito.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.kie.kogito</groupId>
+      <artifactId>kogito-quarkus</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-resteasy</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-resteasy-jackson</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-smallrye-openapi</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-smallrye-health</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-arc</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>io.quarkus</groupId>
+      <artifactId>quarkus-junit5</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>io.rest-assured</groupId>
+      <artifactId>rest-assured</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>${compiler.version}</version>
+        <configuration>
+          <release>11</release>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>${surefire.version}</version>
+        <configuration>
+          <systemPropertyVariables>
+            
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
+            <maven.home>${maven.home}</maven.home>
+          </systemPropertyVariables>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>io.quarkus</groupId>
+        <artifactId>quarkus-maven-plugin</artifactId>
+        <version>${quarkus.version}</version>
+        <executions>
+          <execution>
+            <goals>
+              <goal>build</goal>
+              <goal>generate-code</goal>
+              <goal>generate-code-tests</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>
\ No newline at end of file
diff --git a/sample-kogito-app/src/main/java/mypackage/GreetingEndpoint.java 
b/sample-kogito-app/src/main/java/mypackage/GreetingEndpoint.java
new file mode 100644
index 0000000..812c1b5
--- /dev/null
+++ b/sample-kogito-app/src/main/java/mypackage/GreetingEndpoint.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package mypackage;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.enterprise.event.Observes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+
+import io.quarkus.runtime.StartupEvent;
+
+@Path("/")
+public class GreetingEndpoint {
+
+    private static final String template = "Hello, %s!";
+
+    @GET
+    @Path("/greeting")
+    @Produces(MediaType.APPLICATION_JSON)
+    public String greeting(@QueryParam("name") String name) {
+        System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new 
java.util.Date(System.currentTimeMillis())));
+        String suffix = name != null ? name : "World";
+        return String.valueOf(System.currentTimeMillis());
+    }
+
+    void onStart(@Observes StartupEvent startup) {
+        System.out.println(new SimpleDateFormat("HH:mm:ss.SSS").format(new 
Date()));
+    }
+}
\ No newline at end of file
diff --git a/sample-kogito-app/src/main/resources/LoanApplication.bpmn 
b/sample-kogito-app/src/main/resources/LoanApplication.bpmn
new file mode 100644
index 0000000..df67fc3
--- /dev/null
+++ b/sample-kogito-app/src/main/resources/LoanApplication.bpmn
@@ -0,0 +1,259 @@
+<bpmn2:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"; 
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"; 
xmlns:bpsim="http://www.bpsim.org/schemas/1.0"; 
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"; 
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"; 
xmlns:drools="http://www.jboss.org/drools"; id="_an5RsHt5EDmPCqW-qXr3vw" 
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd 
http://www. [...]
+  <bpmn2:itemDefinition id="_amountItem" structureRef="Integer"/>
+  <bpmn2:itemDefinition id="_approvedItem" structureRef="Boolean"/>
+  <bpmn2:itemDefinition 
id="__A27A8002-A5DB-4448-9DC8-FC702E9AF322_namespaceInputXItem" 
structureRef="java.lang.String"/>
+  <bpmn2:itemDefinition 
id="__A27A8002-A5DB-4448-9DC8-FC702E9AF322_modelInputXItem" 
structureRef="java.lang.String"/>
+  <bpmn2:itemDefinition 
id="__A27A8002-A5DB-4448-9DC8-FC702E9AF322_decisionInputXItem" 
structureRef="java.lang.String"/>
+  <bpmn2:itemDefinition 
id="__A27A8002-A5DB-4448-9DC8-FC702E9AF322_amountInputXItem" 
structureRef="Integer"/>
+  <bpmn2:itemDefinition 
id="__A27A8002-A5DB-4448-9DC8-FC702E9AF322_mortgageApprovedOutputXItem" 
structureRef="Boolean"/>
+  <bpmn2:process id="LoanApplication" drools:packageName="com.example" 
drools:version="1.0" drools:adHoc="false" name="LoanApplication" 
isExecutable="true" processType="Public">
+    <bpmn2:property id="amount" itemSubjectRef="_amountItem" name="amount">
+      <bpmn2:extensionElements>
+        <drools:metaData name="customTags">
+          <drools:metaValue><![CDATA[tracked]]></drools:metaValue>
+        </drools:metaData>
+      </bpmn2:extensionElements>
+    </bpmn2:property>
+    <bpmn2:property id="approved" itemSubjectRef="_approvedItem" 
name="approved">
+      <bpmn2:extensionElements>
+        <drools:metaData name="customTags">
+          <drools:metaValue><![CDATA[tracked]]></drools:metaValue>
+        </drools:metaData>
+      </bpmn2:extensionElements>
+    </bpmn2:property>
+    <bpmn2:sequenceFlow id="_CA9E497D-E2DE-47B3-8CB6-7CEB6BCC302A" 
sourceRef="_293C116F-8607-4D9C-AFFE-9901DF5B9D33" 
targetRef="_E7D37FE2-7E1C-4AE2-8D5B-22D6AF4F0F6E"/>
+    <bpmn2:sequenceFlow id="_2C3AB5A8-9862-4AEB-8B74-7274A67CAB71" 
sourceRef="_7CA472D9-AB07-42B5-9D05-C2F6661F033C" 
targetRef="_C4FB5974-F764-42C7-95AA-DE6F6F14D167"/>
+    <bpmn2:sequenceFlow id="_33084556-2A2C-44ED-92D6-53C4957E6670" 
sourceRef="_7CA472D9-AB07-42B5-9D05-C2F6661F033C" 
targetRef="_293C116F-8607-4D9C-AFFE-9901DF5B9D33">
+      <bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression" 
language="http://www.java.com/java";><![CDATA[return 
approved;]]></bpmn2:conditionExpression>
+    </bpmn2:sequenceFlow>
+    <bpmn2:sequenceFlow id="_CC99D2A0-69E2-4488-A0DB-F01FB1DBE2CD" 
sourceRef="_A27A8002-A5DB-4448-9DC8-FC702E9AF322" 
targetRef="_7CA472D9-AB07-42B5-9D05-C2F6661F033C"/>
+    <bpmn2:sequenceFlow id="_C7CF5D2E-E5B1-4240-8DA8-4B75CE6EB936" 
sourceRef="_C4FB5974-F764-42C7-95AA-DE6F6F14D167" 
targetRef="_C8496017-134C-4C70-9D16-9E67C7D3CEBF"/>
+    <bpmn2:sequenceFlow id="_6FC874CA-F759-4F96-AE0E-9C397789E1C2" 
sourceRef="_0002A37F-EAEE-447C-A9D2-11458967B7AE" 
targetRef="_A27A8002-A5DB-4448-9DC8-FC702E9AF322">
+      <bpmn2:extensionElements>
+        <drools:metaData name="isAutoConnection.source">
+          <drools:metaValue><![CDATA[true]]></drools:metaValue>
+        </drools:metaData>
+        <drools:metaData name="isAutoConnection.target">
+          <drools:metaValue><![CDATA[true]]></drools:metaValue>
+        </drools:metaData>
+      </bpmn2:extensionElements>
+    </bpmn2:sequenceFlow>
+    <bpmn2:scriptTask id="_C4FB5974-F764-42C7-95AA-DE6F6F14D167" 
name="Declined" scriptFormat="http://www.java.com/java";>
+      <bpmn2:extensionElements>
+        <drools:metaData name="elementname">
+          <drools:metaValue><![CDATA[Declined]]></drools:metaValue>
+        </drools:metaData>
+      </bpmn2:extensionElements>
+      <bpmn2:incoming>_2C3AB5A8-9862-4AEB-8B74-7274A67CAB71</bpmn2:incoming>
+      <bpmn2:outgoing>_C7CF5D2E-E5B1-4240-8DA8-4B75CE6EB936</bpmn2:outgoing>
+      <bpmn2:script>System.out.println("Mortgage has been 
DECLINED");</bpmn2:script>
+    </bpmn2:scriptTask>
+    <bpmn2:scriptTask id="_293C116F-8607-4D9C-AFFE-9901DF5B9D33" 
name="Approved" scriptFormat="http://www.java.com/java";>
+      <bpmn2:extensionElements>
+        <drools:metaData name="elementname">
+          <drools:metaValue><![CDATA[Approved]]></drools:metaValue>
+        </drools:metaData>
+      </bpmn2:extensionElements>
+      <bpmn2:incoming>_33084556-2A2C-44ED-92D6-53C4957E6670</bpmn2:incoming>
+      <bpmn2:outgoing>_CA9E497D-E2DE-47B3-8CB6-7CEB6BCC302A</bpmn2:outgoing>
+      <bpmn2:script>System.out.println("Mortgage has been 
APPROVED");</bpmn2:script>
+    </bpmn2:scriptTask>
+    <bpmn2:endEvent id="_E7D37FE2-7E1C-4AE2-8D5B-22D6AF4F0F6E">
+      <bpmn2:incoming>_CA9E497D-E2DE-47B3-8CB6-7CEB6BCC302A</bpmn2:incoming>
+    </bpmn2:endEvent>
+    <bpmn2:exclusiveGateway id="_7CA472D9-AB07-42B5-9D05-C2F6661F033C" 
drools:dg="_2C3AB5A8-9862-4AEB-8B74-7274A67CAB71" gatewayDirection="Diverging" 
default="_2C3AB5A8-9862-4AEB-8B74-7274A67CAB71">
+      <bpmn2:incoming>_CC99D2A0-69E2-4488-A0DB-F01FB1DBE2CD</bpmn2:incoming>
+      <bpmn2:outgoing>_33084556-2A2C-44ED-92D6-53C4957E6670</bpmn2:outgoing>
+      <bpmn2:outgoing>_2C3AB5A8-9862-4AEB-8B74-7274A67CAB71</bpmn2:outgoing>
+    </bpmn2:exclusiveGateway>
+    <bpmn2:businessRuleTask id="_A27A8002-A5DB-4448-9DC8-FC702E9AF322" 
name="Mortgage Approval" implementation="http://www.jboss.org/drools/dmn";>
+      <bpmn2:extensionElements>
+        <drools:metaData name="elementname">
+          <drools:metaValue><![CDATA[Mortgage Approval]]></drools:metaValue>
+        </drools:metaData>
+      </bpmn2:extensionElements>
+      <bpmn2:incoming>_6FC874CA-F759-4F96-AE0E-9C397789E1C2</bpmn2:incoming>
+      <bpmn2:outgoing>_CC99D2A0-69E2-4488-A0DB-F01FB1DBE2CD</bpmn2:outgoing>
+      <bpmn2:ioSpecification>
+        <bpmn2:dataInput 
id="_A27A8002-A5DB-4448-9DC8-FC702E9AF322_namespaceInputX" 
drools:dtype="java.lang.String" 
itemSubjectRef="__A27A8002-A5DB-4448-9DC8-FC702E9AF322_namespaceInputXItem" 
name="namespace"/>
+        <bpmn2:dataInput 
id="_A27A8002-A5DB-4448-9DC8-FC702E9AF322_decisionInputX" 
drools:dtype="java.lang.String" 
itemSubjectRef="__A27A8002-A5DB-4448-9DC8-FC702E9AF322_decisionInputXItem" 
name="decision"/>
+        <bpmn2:dataInput 
id="_A27A8002-A5DB-4448-9DC8-FC702E9AF322_modelInputX" 
drools:dtype="java.lang.String" 
itemSubjectRef="__A27A8002-A5DB-4448-9DC8-FC702E9AF322_modelInputXItem" 
name="model"/>
+        <bpmn2:dataInput 
id="_A27A8002-A5DB-4448-9DC8-FC702E9AF322_amountInputX" drools:dtype="Integer" 
itemSubjectRef="__A27A8002-A5DB-4448-9DC8-FC702E9AF322_amountInputXItem" 
name="amount"/>
+        <bpmn2:dataOutput 
id="_A27A8002-A5DB-4448-9DC8-FC702E9AF322_mortgageApprovedOutputX" 
drools:dtype="Boolean" 
itemSubjectRef="__A27A8002-A5DB-4448-9DC8-FC702E9AF322_mortgageApprovedOutputXItem"
 name="mortgageApproved"/>
+        <bpmn2:inputSet>
+          
<bpmn2:dataInputRefs>_A27A8002-A5DB-4448-9DC8-FC702E9AF322_namespaceInputX</bpmn2:dataInputRefs>
+          
<bpmn2:dataInputRefs>_A27A8002-A5DB-4448-9DC8-FC702E9AF322_decisionInputX</bpmn2:dataInputRefs>
+          
<bpmn2:dataInputRefs>_A27A8002-A5DB-4448-9DC8-FC702E9AF322_modelInputX</bpmn2:dataInputRefs>
+          
<bpmn2:dataInputRefs>_A27A8002-A5DB-4448-9DC8-FC702E9AF322_amountInputX</bpmn2:dataInputRefs>
+        </bpmn2:inputSet>
+        <bpmn2:outputSet>
+          
<bpmn2:dataOutputRefs>_A27A8002-A5DB-4448-9DC8-FC702E9AF322_mortgageApprovedOutputX</bpmn2:dataOutputRefs>
+        </bpmn2:outputSet>
+      </bpmn2:ioSpecification>
+      <bpmn2:dataInputAssociation>
+        
<bpmn2:targetRef>_A27A8002-A5DB-4448-9DC8-FC702E9AF322_namespaceInputX</bpmn2:targetRef>
+        <bpmn2:assignment>
+          <bpmn2:from 
xsi:type="bpmn2:tFormalExpression"><![CDATA[https://kiegroup.org/dmn/_28B07541-BCAA-4D8D-B754-05ED035496BA]]></bpmn2:from>
+          <bpmn2:to 
xsi:type="bpmn2:tFormalExpression"><![CDATA[_A27A8002-A5DB-4448-9DC8-FC702E9AF322_namespaceInputX]]></bpmn2:to>
+        </bpmn2:assignment>
+      </bpmn2:dataInputAssociation>
+      <bpmn2:dataInputAssociation>
+        
<bpmn2:targetRef>_A27A8002-A5DB-4448-9DC8-FC702E9AF322_decisionInputX</bpmn2:targetRef>
+        <bpmn2:assignment>
+          <bpmn2:from 
xsi:type="bpmn2:tFormalExpression"><![CDATA[MortgageApproved]]></bpmn2:from>
+          <bpmn2:to 
xsi:type="bpmn2:tFormalExpression"><![CDATA[_A27A8002-A5DB-4448-9DC8-FC702E9AF322_decisionInputX]]></bpmn2:to>
+        </bpmn2:assignment>
+      </bpmn2:dataInputAssociation>
+      <bpmn2:dataInputAssociation>
+        
<bpmn2:targetRef>_A27A8002-A5DB-4448-9DC8-FC702E9AF322_modelInputX</bpmn2:targetRef>
+        <bpmn2:assignment>
+          <bpmn2:from 
xsi:type="bpmn2:tFormalExpression"><![CDATA[MortgageApproval]]></bpmn2:from>
+          <bpmn2:to 
xsi:type="bpmn2:tFormalExpression"><![CDATA[_A27A8002-A5DB-4448-9DC8-FC702E9AF322_modelInputX]]></bpmn2:to>
+        </bpmn2:assignment>
+      </bpmn2:dataInputAssociation>
+      <bpmn2:dataInputAssociation>
+        <bpmn2:sourceRef>amount</bpmn2:sourceRef>
+        
<bpmn2:targetRef>_A27A8002-A5DB-4448-9DC8-FC702E9AF322_amountInputX</bpmn2:targetRef>
+      </bpmn2:dataInputAssociation>
+      <bpmn2:dataOutputAssociation>
+        
<bpmn2:sourceRef>_A27A8002-A5DB-4448-9DC8-FC702E9AF322_mortgageApprovedOutputX</bpmn2:sourceRef>
+        <bpmn2:targetRef>approved</bpmn2:targetRef>
+      </bpmn2:dataOutputAssociation>
+    </bpmn2:businessRuleTask>
+    <bpmn2:endEvent id="_C8496017-134C-4C70-9D16-9E67C7D3CEBF">
+      <bpmn2:incoming>_C7CF5D2E-E5B1-4240-8DA8-4B75CE6EB936</bpmn2:incoming>
+    </bpmn2:endEvent>
+    <bpmn2:startEvent id="_0002A37F-EAEE-447C-A9D2-11458967B7AE">
+      <bpmn2:outgoing>_6FC874CA-F759-4F96-AE0E-9C397789E1C2</bpmn2:outgoing>
+    </bpmn2:startEvent>
+  </bpmn2:process>
+  <bpmndi:BPMNDiagram>
+    <bpmndi:BPMNPlane bpmnElement="LoanApplication">
+      <bpmndi:BPMNShape id="shape__0002A37F-EAEE-447C-A9D2-11458967B7AE" 
bpmnElement="_0002A37F-EAEE-447C-A9D2-11458967B7AE">
+        <dc:Bounds height="56" width="56" x="134" y="186"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="shape__C8496017-134C-4C70-9D16-9E67C7D3CEBF" 
bpmnElement="_C8496017-134C-4C70-9D16-9E67C7D3CEBF">
+        <dc:Bounds height="56" width="56" x="830" y="262"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="shape__A27A8002-A5DB-4448-9DC8-FC702E9AF322" 
bpmnElement="_A27A8002-A5DB-4448-9DC8-FC702E9AF322">
+        <dc:Bounds height="102" width="154" x="270" y="163"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="shape__7CA472D9-AB07-42B5-9D05-C2F6661F033C" 
bpmnElement="_7CA472D9-AB07-42B5-9D05-C2F6661F033C">
+        <dc:Bounds height="56" width="56" x="504" y="186"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="shape__E7D37FE2-7E1C-4AE2-8D5B-22D6AF4F0F6E" 
bpmnElement="_E7D37FE2-7E1C-4AE2-8D5B-22D6AF4F0F6E">
+        <dc:Bounds height="56" width="56" x="830" y="102"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="shape__293C116F-8607-4D9C-AFFE-9901DF5B9D33" 
bpmnElement="_293C116F-8607-4D9C-AFFE-9901DF5B9D33">
+        <dc:Bounds height="102" width="154" x="618" y="79"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNShape id="shape__C4FB5974-F764-42C7-95AA-DE6F6F14D167" 
bpmnElement="_C4FB5974-F764-42C7-95AA-DE6F6F14D167">
+        <dc:Bounds height="102" width="154" x="618" y="239"/>
+      </bpmndi:BPMNShape>
+      <bpmndi:BPMNEdge 
id="edge_shape__0002A37F-EAEE-447C-A9D2-11458967B7AE_to_shape__A27A8002-A5DB-4448-9DC8-FC702E9AF322"
 bpmnElement="_6FC874CA-F759-4F96-AE0E-9C397789E1C2">
+        <di:waypoint x="190" y="214"/>
+        <di:waypoint x="270" y="214"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge 
id="edge_shape__C4FB5974-F764-42C7-95AA-DE6F6F14D167_to_shape__C8496017-134C-4C70-9D16-9E67C7D3CEBF"
 bpmnElement="_C7CF5D2E-E5B1-4240-8DA8-4B75CE6EB936">
+        <di:waypoint x="695" y="290"/>
+        <di:waypoint x="830" y="290"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge 
id="edge_shape__A27A8002-A5DB-4448-9DC8-FC702E9AF322_to_shape__7CA472D9-AB07-42B5-9D05-C2F6661F033C"
 bpmnElement="_CC99D2A0-69E2-4488-A0DB-F01FB1DBE2CD">
+        <di:waypoint x="424" y="214"/>
+        <di:waypoint x="504" y="214"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge 
id="edge_shape__7CA472D9-AB07-42B5-9D05-C2F6661F033C_to_shape__293C116F-8607-4D9C-AFFE-9901DF5B9D33"
 bpmnElement="_33084556-2A2C-44ED-92D6-53C4957E6670">
+        <di:waypoint x="532" y="186"/>
+        <di:waypoint x="532" y="130"/>
+        <di:waypoint x="618" y="130"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge 
id="edge_shape__7CA472D9-AB07-42B5-9D05-C2F6661F033C_to_shape__C4FB5974-F764-42C7-95AA-DE6F6F14D167"
 bpmnElement="_2C3AB5A8-9862-4AEB-8B74-7274A67CAB71">
+        <di:waypoint x="532" y="242"/>
+        <di:waypoint x="532.0000000000026" y="290"/>
+        <di:waypoint x="618" y="290"/>
+      </bpmndi:BPMNEdge>
+      <bpmndi:BPMNEdge 
id="edge_shape__293C116F-8607-4D9C-AFFE-9901DF5B9D33_to_shape__E7D37FE2-7E1C-4AE2-8D5B-22D6AF4F0F6E"
 bpmnElement="_CA9E497D-E2DE-47B3-8CB6-7CEB6BCC302A">
+        <di:waypoint x="695" y="130"/>
+        <di:waypoint x="830" y="130"/>
+      </bpmndi:BPMNEdge>
+    </bpmndi:BPMNPlane>
+  </bpmndi:BPMNDiagram>
+  <bpmn2:relationship type="BPSimData">
+    <bpmn2:extensionElements>
+      <bpsim:BPSimData>
+        <bpsim:Scenario id="default" name="Simulationscenario">
+          <bpsim:ScenarioParameters/>
+          <bpsim:ElementParameters 
elementRef="_0002A37F-EAEE-447C-A9D2-11458967B7AE">
+            <bpsim:TimeParameters>
+              <bpsim:ProcessingTime>
+                <bpsim:NormalDistribution mean="0" standardDeviation="0"/>
+              </bpsim:ProcessingTime>
+            </bpsim:TimeParameters>
+          </bpsim:ElementParameters>
+          <bpsim:ElementParameters 
elementRef="_A27A8002-A5DB-4448-9DC8-FC702E9AF322">
+            <bpsim:TimeParameters>
+              <bpsim:ProcessingTime>
+                <bpsim:NormalDistribution mean="0" standardDeviation="0"/>
+              </bpsim:ProcessingTime>
+            </bpsim:TimeParameters>
+            <bpsim:ResourceParameters>
+              <bpsim:Availability>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:Availability>
+              <bpsim:Quantity>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:Quantity>
+            </bpsim:ResourceParameters>
+            <bpsim:CostParameters>
+              <bpsim:UnitCost>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:UnitCost>
+            </bpsim:CostParameters>
+          </bpsim:ElementParameters>
+          <bpsim:ElementParameters 
elementRef="_293C116F-8607-4D9C-AFFE-9901DF5B9D33">
+            <bpsim:TimeParameters>
+              <bpsim:ProcessingTime>
+                <bpsim:NormalDistribution mean="0" standardDeviation="0"/>
+              </bpsim:ProcessingTime>
+            </bpsim:TimeParameters>
+            <bpsim:ResourceParameters>
+              <bpsim:Availability>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:Availability>
+              <bpsim:Quantity>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:Quantity>
+            </bpsim:ResourceParameters>
+            <bpsim:CostParameters>
+              <bpsim:UnitCost>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:UnitCost>
+            </bpsim:CostParameters>
+          </bpsim:ElementParameters>
+          <bpsim:ElementParameters 
elementRef="_C4FB5974-F764-42C7-95AA-DE6F6F14D167">
+            <bpsim:TimeParameters>
+              <bpsim:ProcessingTime>
+                <bpsim:NormalDistribution mean="0" standardDeviation="0"/>
+              </bpsim:ProcessingTime>
+            </bpsim:TimeParameters>
+            <bpsim:ResourceParameters>
+              <bpsim:Availability>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:Availability>
+              <bpsim:Quantity>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:Quantity>
+            </bpsim:ResourceParameters>
+            <bpsim:CostParameters>
+              <bpsim:UnitCost>
+                <bpsim:FloatingParameter value="0"/>
+              </bpsim:UnitCost>
+            </bpsim:CostParameters>
+          </bpsim:ElementParameters>
+        </bpsim:Scenario>
+      </bpsim:BPSimData>
+    </bpmn2:extensionElements>
+    <bpmn2:source>_an5RsHt5EDmPCqW-qXr3vw</bpmn2:source>
+    <bpmn2:target>_an5RsHt5EDmPCqW-qXr3vw</bpmn2:target>
+  </bpmn2:relationship>
+</bpmn2:definitions>
\ No newline at end of file
diff --git a/sample-kogito-app/src/main/resources/MortgageApproval.dmn 
b/sample-kogito-app/src/main/resources/MortgageApproval.dmn
new file mode 100644
index 0000000..3ca0ed3
--- /dev/null
+++ b/sample-kogito-app/src/main/resources/MortgageApproval.dmn
@@ -0,0 +1,81 @@
+<dmn:definitions xmlns:dmn="http://www.omg.org/spec/DMN/20180521/MODEL/"; 
xmlns="https://kiegroup.org/dmn/_28B07541-BCAA-4D8D-B754-05ED035496BA"; 
xmlns:feel="http://www.omg.org/spec/DMN/20180521/FEEL/"; 
xmlns:kie="http://www.drools.org/kie/dmn/1.2"; 
xmlns:dmndi="http://www.omg.org/spec/DMN/20180521/DMNDI/"; 
xmlns:di="http://www.omg.org/spec/DMN/20180521/DI/"; 
xmlns:dc="http://www.omg.org/spec/DMN/20180521/DC/"; 
id="_905E3E6C-C346-4DA3-AA28-9ED076578E03" name="MortgageApproval" 
typeLanguage="htt [...]
+  <dmn:extensionElements/>
+  <dmn:inputData id="_23558138-A90D-4685-9B08-1ABD1DDD0A2A" name="amount">
+    <dmn:extensionElements/>
+    <dmn:variable id="_F42026F3-B207-41FF-8177-9954910B16A7" name="amount"/>
+  </dmn:inputData>
+  <dmn:decision id="_B0928930-EC40-4726-B8B1-4D9D5AEF1C82" 
name="mortgageApproved">
+    <dmn:extensionElements/>
+    <dmn:variable id="_C54E9DA3-878C-4713-B790-606CB9C01721" 
name="mortgageApproved" typeRef="boolean"/>
+    <dmn:informationRequirement id="_AF4E07ED-DE25-4E44-BB37-3FDF52056005">
+      <dmn:requiredInput href="#_23558138-A90D-4685-9B08-1ABD1DDD0A2A"/>
+    </dmn:informationRequirement>
+    <dmn:decisionTable id="_1D72E97A-473F-460A-A2F1-6602AF93FE3B" 
hitPolicy="FIRST" preferredOrientation="Rule-as-Row">
+      <dmn:input id="_3E5D7B3E-6C23-46B0-B385-2362EEB99ADB">
+        <dmn:inputExpression id="_D4F176DB-19F7-4B89-A1C6-57C126B9C4C3" 
typeRef="number">
+          <dmn:text>amount</dmn:text>
+        </dmn:inputExpression>
+      </dmn:input>
+      <dmn:output id="_2F517F3D-D449-4AAF-B150-B8DF82E3A1C3"/>
+      <dmn:annotation name="Description"/>
+      <dmn:rule id="_8C26AAB7-6CCE-4BD3-9F40-A0B7F0CC3848">
+        <dmn:inputEntry id="_7E7C0CAF-CDCA-4313-BDA9-EE13BCA8CB84">
+          <dmn:text>&lt;5000</dmn:text>
+        </dmn:inputEntry>
+        <dmn:outputEntry id="_67260421-5E3A-4131-BB54-8ACA4B36B2C3">
+          <dmn:text>true</dmn:text>
+        </dmn:outputEntry>
+        <dmn:annotationEntry>
+          <dmn:text>Less than 5000 is OK</dmn:text>
+        </dmn:annotationEntry>
+      </dmn:rule>
+      <dmn:rule id="_55C03D40-47FF-43D9-A15E-B08E5C53AE0E">
+        <dmn:inputEntry id="_07A49F5D-8932-441D-8AF7-59C93E7E5752">
+          <dmn:text>&gt;=5000</dmn:text>
+        </dmn:inputEntry>
+        <dmn:outputEntry id="_ABECCAE3-5CEA-45CC-B111-E504B0947318">
+          <dmn:text>false</dmn:text>
+        </dmn:outputEntry>
+        <dmn:annotationEntry>
+          <dmn:text>5000 and more is too much</dmn:text>
+        </dmn:annotationEntry>
+      </dmn:rule>
+    </dmn:decisionTable>
+  </dmn:decision>
+  <dmndi:DMNDI>
+    <dmndi:DMNDiagram id="_18B53C8E-F1CA-40CC-B2FF-4310382BDF60" name="DRG">
+      <di:extension>
+        <kie:ComponentsWidthsExtension>
+          <kie:ComponentWidths 
dmnElementRef="_1D72E97A-473F-460A-A2F1-6602AF93FE3B">
+            <kie:width>50</kie:width>
+            <kie:width>100</kie:width>
+            <kie:width>190</kie:width>
+            <kie:width>355</kie:width>
+          </kie:ComponentWidths>
+        </kie:ComponentsWidthsExtension>
+      </di:extension>
+      <dmndi:DMNShape id="dmnshape-drg-_23558138-A90D-4685-9B08-1ABD1DDD0A2A" 
dmnElementRef="_23558138-A90D-4685-9B08-1ABD1DDD0A2A" isCollapsed="false">
+        <dmndi:DMNStyle>
+          <dmndi:FillColor red="255" green="255" blue="255"/>
+          <dmndi:StrokeColor red="0" green="0" blue="0"/>
+          <dmndi:FontColor red="0" green="0" blue="0"/>
+        </dmndi:DMNStyle>
+        <dc:Bounds x="281" y="266" width="100" height="50"/>
+        <dmndi:DMNLabel/>
+      </dmndi:DMNShape>
+      <dmndi:DMNShape id="dmnshape-drg-_B0928930-EC40-4726-B8B1-4D9D5AEF1C82" 
dmnElementRef="_B0928930-EC40-4726-B8B1-4D9D5AEF1C82" isCollapsed="false">
+        <dmndi:DMNStyle>
+          <dmndi:FillColor red="255" green="255" blue="255"/>
+          <dmndi:StrokeColor red="0" green="0" blue="0"/>
+          <dmndi:FontColor red="0" green="0" blue="0"/>
+        </dmndi:DMNStyle>
+        <dc:Bounds x="255" y="108" width="154" height="72"/>
+        <dmndi:DMNLabel/>
+      </dmndi:DMNShape>
+      <dmndi:DMNEdge id="dmnedge-drg-_AF4E07ED-DE25-4E44-BB37-3FDF52056005" 
dmnElementRef="_AF4E07ED-DE25-4E44-BB37-3FDF52056005">
+        <di:waypoint x="331" y="291"/>
+        <di:waypoint x="332" y="180"/>
+      </dmndi:DMNEdge>
+    </dmndi:DMNDiagram>
+  </dmndi:DMNDI>
+</dmn:definitions>
\ No newline at end of file
diff --git a/sample-kogito-app/src/main/resources/application.properties 
b/sample-kogito-app/src/main/resources/application.properties
new file mode 100644
index 0000000..a733bf3
--- /dev/null
+++ b/sample-kogito-app/src/main/resources/application.properties
@@ -0,0 +1,36 @@
+#
+# Copyright 2020 Red Hat, Inc. and/or its affiliates.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+#https://quarkus.io/guides/openapi-swaggerui
+quarkus.swagger-ui.always-include=true
+
+kogito.service.url=http://localhost:8080
+
+# events configuration
+
+kafka.bootstrap.servers=localhost:9092
+
+mp.messaging.outgoing.kogito-processinstances-events.connector=smallrye-kafka
+mp.messaging.outgoing.kogito-processinstances-events.topic=kogito-processinstances-events
+mp.messaging.outgoing.kogito-processinstances-events.value.serializer=org.apache.kafka.common.serialization.StringSerializer
+
+mp.messaging.outgoing.kogito-usertaskinstances-events.connector=smallrye-kafka
+mp.messaging.outgoing.kogito-usertaskinstances-events.topic=kogito-usertaskinstances-events
+mp.messaging.outgoing.kogito-usertaskinstances-events.value.serializer=org.apache.kafka.common.serialization.StringSerializer
+
+mp.messaging.outgoing.kogito-variables-events.connector=smallrye-kafka
+mp.messaging.outgoing.kogito-variables-events.topic=kogito-variables-events
+mp.messaging.outgoing.kogito-variables-events.value.serializer=org.apache.kafka.common.serialization.StringSerializer
\ No newline at end of file
diff --git a/sample-kogito-app/threshold.properties 
b/sample-kogito-app/threshold.properties
new file mode 100644
index 0000000..ea23577
--- /dev/null
+++ b/sample-kogito-app/threshold.properties
@@ -0,0 +1,6 @@
+linux.jvm.time.to.first.ok.request.threshold.ms=3000
+linux.jvm.RSS.threshold.kB=380000
+linux.native.time.to.first.ok.request.threshold.ms=35
+linux.native.RSS.threshold.kB=90000
+windows.jvm.time.to.first.ok.request.threshold.ms=2000
+windows.jvm.RSS.threshold.kB=4000
diff --git a/tests/pom.xml b/tests/pom.xml
new file mode 100644
index 0000000..38a9d04
--- /dev/null
+++ b/tests/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.kie.kogito</groupId>
+    <artifactId>kogito-benchmarks</artifactId>
+    <version>2.0.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>tests</artifactId>
+
+  <name></name>
+  <description></description>
+
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.kie.kogito</groupId>
+        <artifactId>framework</artifactId>
+        <version>${project.version}</version>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.kie.kogito</groupId>
+      <artifactId>framework</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+      <version>4.5.13</version>
+    </dependency>
+  </dependencies>
+
+</project>
\ No newline at end of file
diff --git 
a/tests/src/test/java/org/kie/kogito/benchmarks/AbstractTemplateTest.java 
b/tests/src/test/java/org/kie/kogito/benchmarks/AbstractTemplateTest.java
new file mode 100644
index 0000000..e9d93dd
--- /dev/null
+++ b/tests/src/test/java/org/kie/kogito/benchmarks/AbstractTemplateTest.java
@@ -0,0 +1,367 @@
+package org.kie.kogito.benchmarks;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Scanner;
+import java.util.stream.Collectors;
+
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.util.EntityUtils;
+import org.assertj.core.api.Assertions;
+import org.jboss.logging.Logger;
+import org.junit.jupiter.api.TestInfo;
+import org.kie.kogito.benchmarks.framework.App;
+import org.kie.kogito.benchmarks.framework.BuildResult;
+import org.kie.kogito.benchmarks.framework.HTTPRequestInfo;
+import org.kie.kogito.benchmarks.framework.LogBuilder;
+import org.kie.kogito.benchmarks.framework.Logs;
+import org.kie.kogito.benchmarks.framework.MvnCmds;
+import org.kie.kogito.benchmarks.framework.RunInfo;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.kie.kogito.benchmarks.framework.Commands.BASE_DIR;
+import static org.kie.kogito.benchmarks.framework.Commands.buildApp;
+import static org.kie.kogito.benchmarks.framework.Commands.cleanTarget;
+import static org.kie.kogito.benchmarks.framework.Commands.getOpenedFDs;
+import static org.kie.kogito.benchmarks.framework.Commands.getRSSkB;
+import static org.kie.kogito.benchmarks.framework.Commands.parsePort;
+import static org.kie.kogito.benchmarks.framework.Commands.processStopper;
+import static org.kie.kogito.benchmarks.framework.Commands.startApp;
+import static org.kie.kogito.benchmarks.framework.Commands.waitForTcpClosed;
+import static org.kie.kogito.benchmarks.framework.Logs.SKIP;
+import static org.kie.kogito.benchmarks.framework.Logs.appendln;
+import static org.kie.kogito.benchmarks.framework.Logs.archiveLog;
+import static org.kie.kogito.benchmarks.framework.Logs.checkListeningHost;
+import static org.kie.kogito.benchmarks.framework.Logs.checkLog;
+import static org.kie.kogito.benchmarks.framework.Logs.checkThreshold;
+import static org.kie.kogito.benchmarks.framework.Logs.getLogsDir;
+import static 
org.kie.kogito.benchmarks.framework.Logs.parseStartStopTimestamps;
+import static org.kie.kogito.benchmarks.framework.Logs.writeReport;
+
+public abstract class AbstractTemplateTest {
+
+    private static final Logger LOGGER = 
Logger.getLogger(StartStopTest.class.getName());
+
+    public static final int START_STOP_ITERATIONS = 3;
+    public static final String LOCALHOST = "http://localhost:8080";;
+
+    public void startStop(TestInfo testInfo, App app) throws IOException, 
InterruptedException {
+        LOGGER.info("Testing app startStop: " + app.toString() + ", mode: " + 
app.mavenCommands.toString());
+
+        Process pA = null;
+        File buildLogA = null;
+        File runLogA = null;
+        StringBuilder whatIDidReport = new StringBuilder();
+        File appDir = app.getAppDir(BASE_DIR);
+        MvnCmds mvnCmds = app.mavenCommands;
+        String cn = testInfo.getTestClass().get().getCanonicalName();
+        String mn = testInfo.getTestMethod().get().getName();
+        try {
+            // Cleanup
+            cleanTarget(app);
+            Files.createDirectories(Paths.get(appDir.getAbsolutePath() + 
File.separator + "logs"));
+
+            // Build
+            BuildResult buildResult = buildApp(app, mn, cn, whatIDidReport);
+            buildLogA = buildResult.getBuildLog();
+
+            assertTrue(buildLogA.exists());
+            checkLog(cn, mn, app, mvnCmds, buildLogA);
+
+            // Prepare for run
+            List<Long> rssKbValues = new ArrayList<>(START_STOP_ITERATIONS);
+            List<Long> timeToFirstOKRequestValues = new 
ArrayList<>(START_STOP_ITERATIONS);
+            List<Long> startedInMsValues = new 
ArrayList<>(START_STOP_ITERATIONS);
+            List<Long> stoppedInMsValues = new 
ArrayList<>(START_STOP_ITERATIONS);
+            List<Long> openedFilesValues = new 
ArrayList<>(START_STOP_ITERATIONS);
+
+            for (int i = 0; i < START_STOP_ITERATIONS; i++) {
+                // Run
+                LOGGER.info("Running... round " + i);
+                RunInfo runInfo = startApp(app, whatIDidReport);
+                pA = runInfo.getProcess();
+                runLogA = runInfo.getRunLog();
+
+                LOGGER.info("Terminate and scan logs...");
+                pA.getInputStream().available(); // TODO Ask Karm
+
+                long rssKb = getRSSkB(pA.pid());
+                long openedFiles = getOpenedFDs(pA.pid());
+
+                processStopper(pA, false);
+
+                LOGGER.info("Gonna wait for ports closed...");
+                // Release ports
+                assertTrue(waitForTcpClosed("localhost", 
parsePort(app.urlContent.urlContent[0][0]), 60),
+                        "Main port is still open");
+                checkLog(cn, mn, app, mvnCmds, runLogA);
+                checkListeningHost(cn, mn, mvnCmds, runLogA);
+
+                float[] startedStopped = parseStartStopTimestamps(runLogA);
+                long startedInMs = (long) (startedStopped[0] * 1000);
+                long stoppedInMs = (long) (startedStopped[1] * 1000);
+
+                Path measurementsLog = Paths.get(getLogsDir(cn, 
mn).toString(), "measurements.csv");
+                LogBuilder.Log log = new LogBuilder()
+                        .app(app)
+                        .mode(mvnCmds)
+                        .buildTimeMs(buildResult.getBuildTimeMs())
+                        
.timeToFirstOKRequestMs(runInfo.getTimeToFirstOKRequest())
+                        .startedInMs(startedInMs)
+                        .stoppedInMs(stoppedInMs)
+                        .rssKb(rssKb)
+                        .openedFiles(openedFiles)
+                        .build();
+                Logs.logMeasurements(log, measurementsLog);
+                appendln(whatIDidReport, "Measurements:");
+                appendln(whatIDidReport, log.headerMarkdown + "\n" + 
log.lineMarkdown);
+
+                rssKbValues.add(rssKb);
+                openedFilesValues.add(openedFiles);
+                
timeToFirstOKRequestValues.add(runInfo.getTimeToFirstOKRequest());
+                startedInMsValues.add(startedInMs);
+                stoppedInMsValues.add(stoppedInMs);
+            }
+
+            long rssKbAvgWithoutMinMax = getAvgWithoutMinMax(rssKbValues);
+            long openedFilesAvgWithoutMinMax = 
getAvgWithoutMinMax(openedFilesValues);
+            long timeToFirstOKRequestAvgWithoutMinMax = 
getAvgWithoutMinMax(timeToFirstOKRequestValues);
+            long startedInMsAvgWithoutMinMax = 
getAvgWithoutMinMax(startedInMsValues);
+            long stoppedInMsAvgWithoutMinMax = 
getAvgWithoutMinMax(stoppedInMsValues);
+
+            Path measurementsSummary = Paths.get(getLogsDir(cn).toString(), 
"measurementsSummary.csv");
+
+            LogBuilder.Log log = new LogBuilder()
+                    .app(app)
+                    .mode(mvnCmds)
+                    .buildTimeMs(buildResult.getBuildTimeMs())
+                    
.timeToFirstOKRequestMs(timeToFirstOKRequestAvgWithoutMinMax)
+                    .startedInMs(startedInMsAvgWithoutMinMax)
+                    .stoppedInMs(stoppedInMsAvgWithoutMinMax)
+                    .rssKb(rssKbAvgWithoutMinMax)
+                    .openedFiles(openedFilesAvgWithoutMinMax)
+                    .build();
+            Logs.logMeasurementsSummary(log, measurementsSummary);
+
+            LOGGER.info("AVG timeToFirstOKRequest without min and max values: 
" + timeToFirstOKRequestAvgWithoutMinMax);
+            LOGGER.info("AVG rssKb without min and max values: " + 
rssKbAvgWithoutMinMax);
+            checkThreshold(app, mvnCmds, rssKbAvgWithoutMinMax, 
timeToFirstOKRequestAvgWithoutMinMax, SKIP);
+        } finally {
+            // Make sure processes are down even if there was an exception / 
failure
+            if (pA != null) {
+                processStopper(pA, true);
+            }
+            // Archive logs no matter what
+            archiveLog(cn, mn, buildLogA);
+            archiveLog(cn, mn, runLogA);
+            writeReport(cn, mn, whatIDidReport.toString());
+            //cleanTarget(app);
+        }
+    }
+
+    private long getAvgWithoutMinMax(List<Long> listOfValues) { // TODO Median?
+        listOfValues.remove(Collections.min(listOfValues));
+        listOfValues.remove(Collections.max(listOfValues));
+        return (long) listOfValues.stream().mapToLong(val -> 
val).average().orElse(Long.MAX_VALUE);
+    }
+
+    public void loadTest(TestInfo testInfo, App app, HTTPRequestInfo 
requestInfo) throws IOException, InterruptedException {
+        LOGGER.info("Testing app startStop: " + app.toString() + ", mode: " + 
app.mavenCommands.toString());
+
+        Process pA = null;
+        File buildLogA = null;
+        File runLogA = null;
+        StringBuilder whatIDidReport = new StringBuilder();
+        File appDir = app.getAppDir(BASE_DIR);
+        MvnCmds mvnCmds = app.mavenCommands;
+        String cn = testInfo.getTestClass().get().getCanonicalName();
+        String mn = testInfo.getTestMethod().get().getName();
+        try {
+            // Cleanup
+            cleanTarget(app);
+            Files.createDirectories(Paths.get(appDir.getAbsolutePath() + 
File.separator + "logs"));
+
+            // Build
+            BuildResult buildResult = buildApp(app, mn, cn, whatIDidReport);
+            buildLogA = buildResult.getBuildLog();
+
+            assertTrue(buildLogA.exists());
+            checkLog(cn, mn, app, mvnCmds, buildLogA);
+
+            // Start the App
+            RunInfo runInfo = startApp(app, whatIDidReport);
+            pA = runInfo.getProcess();
+            runLogA = runInfo.getRunLog();
+
+            long rssKb = getRSSkB(pA.pid());
+
+            List<Long> values = new ArrayList<>(20000);
+
+
+            // Plain OLD Java "HTTP Client"
+//            long startTime = System.currentTimeMillis();
+//            for (int i = 0; i < 20000; i++) {
+//                long requestStartTime = System.nanoTime();
+//                HttpURLConnection c = (HttpURLConnection) new 
URL(requestInfo.getURI()).openConnection();
+//                requestInfo.getHeaders().forEach(c::setRequestProperty);
+//                c.setRequestMethod(requestInfo.getMethod());
+//                c.setDoOutput(true);
+//                c.setConnectTimeout(500);
+//
+//                try (OutputStream os = c.getOutputStream()) {
+//                    os.write(requestInfo.getBody().getBytes());
+//                }
+//
+//                try (Scanner scanner = new Scanner(c.getInputStream(), 
StandardCharsets.UTF_8.toString())) {
+//                    
Assertions.assertThat(c.getResponseCode()).isEqualTo(requestInfo.getExpectedResponseStatusCode());
+////                    System.out.println("Response code: " + 
c.getResponseCode());
+//                    scanner.useDelimiter("\\A");
+//                    String webPage = scanner.hasNext() ? scanner.next() : "";
+////                    System.out.println("Page is: " + webPage);
+//                } catch (Exception e) {
+//                    LOGGER.info("Error when executing request on " + 
requestInfo.getURI(), e);
+//                }
+//
+//                long requestEndTime = System.nanoTime();
+//                long duration = requestEndTime - requestStartTime;
+//                values.add(duration);
+////                System.out.println(duration);
+//            }
+//            long endTime = System.currentTimeMillis();
+
+
+            // Java 11 HTTP Client
+
+//            HttpClient httpClient = HttpClient.newHttpClient();
+//            HttpRequest.Builder requestBuilder = 
HttpRequest.newBuilder(URI.create(requestInfo.getURI()))
+//                    
.POST(HttpRequest.BodyPublishers.ofString(requestInfo.getBody()));
+//            requestInfo.getHeaders().forEach(requestBuilder::header);
+//            HttpRequest request = requestBuilder.build();
+//
+//
+//
+//
+//            long startTime = System.currentTimeMillis();
+//            for (int i = 0; i < 20000; i++) {
+//                long requestStartTime = System.nanoTime();
+//                HttpResponse<String> response = httpClient.send(request, 
HttpResponse.BodyHandlers.ofString());
+//                
Assertions.assertThat(response.statusCode()).isEqualTo(requestInfo.getExpectedResponseStatusCode());
+////                System.out.println("Response code: " + 
response.statusCode());
+////                System.out.println("Page is: " + response.body());
+//                long requestEndTime = System.nanoTime();
+//                long duration = requestEndTime - requestStartTime;
+//                values.add(duration);
+////                System.out.println(duration);
+//            }
+//            long endTime = System.currentTimeMillis();
+
+
+
+            // Apache HTTP Client 4
+            long startTime;
+            try (CloseableHttpClient client = HttpClients.createDefault()){
+                HttpPost postRequest = new HttpPost(requestInfo.getURI());
+                postRequest.setEntity(new StringEntity(requestInfo.getBody()));
+                requestInfo.getHeaders().forEach(postRequest::setHeader);
+
+                startTime = System.currentTimeMillis();
+                for (int i = 0; i < 20000; i++) {
+                    long requestStartTime = System.nanoTime();
+                    try (CloseableHttpResponse response = 
client.execute(postRequest)) {
+                        
Assertions.assertThat(response.getStatusLine().getStatusCode()).isEqualTo(requestInfo.getExpectedResponseStatusCode());
+//                        System.out.println("Response code: " + 
response.getStatusLine().getStatusCode());
+//                        System.out.println("Page is: " + 
EntityUtils.toString(response.getEntity()));
+                        EntityUtils.consume(response.getEntity());
+                    }
+                    long requestEndTime = System.nanoTime();
+                    long duration = requestEndTime - requestStartTime;
+                    values.add(duration);
+                }
+            }
+            long endTime = System.currentTimeMillis();
+
+            System.out.println("First response time: " + values.get(0));
+            System.out.println("Second response time: " + values.get(1));
+            System.out.println("Third response time: " + values.get(2));
+            System.out.println("Average response time: " + 
values.stream().mapToLong(Long::longValue).skip(1).average());
+            System.out.println("Total duration: " + (endTime - startTime));
+
+            long rssKbFinal = getRSSkB(pA.pid());
+            long openedFiles = getOpenedFDs(pA.pid()); // TODO also do before 
the "test" itself?
+
+            // Stop the App
+            processStopper(pA, false);
+
+            LOGGER.info("Gonna wait for ports closed...");
+            // Release ports
+            assertTrue(waitForTcpClosed("localhost", 
parsePort(app.urlContent.urlContent[0][0]), 60),
+                       "Main port is still open");
+            checkLog(cn, mn, app, mvnCmds, runLogA);
+            checkListeningHost(cn, mn, mvnCmds, runLogA);
+
+            float[] startedStopped = parseStartStopTimestamps(runLogA);// 
Don't need this in the load test?
+            long startedInMs = (long) (startedStopped[0] * 1000);
+            long stoppedInMs = (long) (startedStopped[1] * 1000);
+
+            Path measurementsLog = Paths.get(getLogsDir(cn, mn).toString(), 
"measurements.csv");
+            Path measurementsSummaryLog = Paths.get(getLogsDir(cn).toString(), 
"measurementsSummary.csv");
+            LogBuilder.Log log = new LogBuilder()
+                    .app(app)
+                    .mode(mvnCmds)
+                    .buildTimeMs(buildResult.getBuildTimeMs())
+                    .timeToFirstOKRequestMs(runInfo.getTimeToFirstOKRequest())
+                    .startedInMs(startedInMs)
+                    .stoppedInMs(stoppedInMs)
+                    .rssKb(rssKb)
+                    .rssKbFinal(rssKbFinal)
+                    .openedFiles(openedFiles)
+                    .build();
+
+            Logs.logMeasurements(log, measurementsLog);
+
+            LogBuilder.Log summaryLog = new LogBuilder()
+                    .app(app)
+                    .mode(mvnCmds)
+                    .rssKbFinal(rssKbFinal)
+                    .build();
+            Logs.logMeasurementsSummary(summaryLog, measurementsSummaryLog);
+            appendln(whatIDidReport, "Measurements:");
+            appendln(whatIDidReport, log.headerMarkdown + "\n" + 
log.lineMarkdown);
+
+            //checkThreshold(app, mvnCmds, rssKbAvgWithoutMinMax, 
timeToFirstOKRequestAvgWithoutMinMax, SKIP);
+        } finally {
+            // Make sure processes are down even if there was an exception / 
failure
+            if (pA != null) {
+                processStopper(pA, true);
+            }
+            // Archive logs no matter what
+            archiveLog(cn, mn, buildLogA);
+            archiveLog(cn, mn, runLogA);
+            writeReport(cn, mn, whatIDidReport.toString());
+            //cleanTarget(app);
+
+        }
+    }
+
+}
diff --git 
a/tests/src/test/java/org/kie/kogito/benchmarks/QuarkusLargeTest.java 
b/tests/src/test/java/org/kie/kogito/benchmarks/QuarkusLargeTest.java
new file mode 100644
index 0000000..bce1104
--- /dev/null
+++ b/tests/src/test/java/org/kie/kogito/benchmarks/QuarkusLargeTest.java
@@ -0,0 +1,69 @@
+package org.kie.kogito.benchmarks;
+
+import static org.kie.kogito.benchmarks.framework.Logs.getLogsDir;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.kie.kogito.benchmarks.framework.App;
+import org.kie.kogito.benchmarks.framework.HTTPRequestInfo;
+import org.kie.kogito.benchmarks.framework.LogBuilder;
+import org.kie.kogito.benchmarks.framework.Logs;
+
+public class QuarkusLargeTest extends AbstractTemplateTest {
+
+    private static final App APP_TO_TEST = App.SAMPLE_KOGITO_APP_QUARKUS_JVM;
+
+    @Test
+    public void startStop(TestInfo testInfo) throws IOException, 
InterruptedException {
+        startStop(testInfo, APP_TO_TEST);
+    }
+
+    @Test
+    public void loadTest(TestInfo testInfo) throws IOException, 
InterruptedException {
+        HTTPRequestInfo requestInfo = HTTPRequestInfo.builder()
+                .URI(LOCALHOST + "/LoanApplication")
+                .body("{\"amount\":\"2000\"}")
+                .method("POST")
+                .header("Accept", "*/*")
+                .header("Content-Type", "application/json")
+                .expectedResponseStatusCode(201)
+                .build(); // This may be directly replaced for example by 
Apache-specific class, but this keeps
+                          // it detached from any framework
+
+        loadTest(testInfo, APP_TO_TEST, requestInfo);
+
+//        Path measurementLogSummary = 
Paths.get(getLogsDir(testInfo.getTestClass().get().getCanonicalName()).toString(),
 "measurementsSummary.csv");
+//
+//        for (App app : new App[]{APP_TO_TEST, 
App.SAMPLE_KOGITO_APP_SPRING_BOOT}) {
+//            LogBuilder.Log log = new LogBuilder()
+//                    .app(app)
+//                    .mode(app.mavenCommands)
+//                    .buildTimeMs(100)
+//                    .timeToFirstOKRequestMs(200)
+//                    .startedInMs(300)
+//                    .stoppedInMs(400)
+//                    .rssKb(500)
+//                    .openedFiles(700)
+//                    .build();
+//
+//            LogBuilder.Log log2 = new LogBuilder()
+//                    .app(app)
+//                    .mode(app.mavenCommands)
+//                    .rssKbFinal(600)
+//                    .build();
+//
+//            Logs.logMeasurementsSummary(log, measurementLogSummary);
+//            Logs.logMeasurementsSummary(log2, measurementLogSummary);
+//
+//
+//            Logs.logMeasurementsSummary(log, measurementLogSummary);
+//            Logs.logMeasurementsSummary(log2, measurementLogSummary);
+//        }
+
+
+    }
+}
diff --git 
a/tests/src/test/java/org/kie/kogito/benchmarks/QuarkusSmallTest.java 
b/tests/src/test/java/org/kie/kogito/benchmarks/QuarkusSmallTest.java
new file mode 100644
index 0000000..cbaf805
--- /dev/null
+++ b/tests/src/test/java/org/kie/kogito/benchmarks/QuarkusSmallTest.java
@@ -0,0 +1,22 @@
+package org.kie.kogito.benchmarks;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.kie.kogito.benchmarks.framework.App;
+
+public class QuarkusSmallTest extends AbstractTemplateTest {
+
+    private static final App APP_TO_TEST = App.SAMPLE_KOGITO_APP_QUARKUS_JVM;
+
+    @Test
+    public void startStop(TestInfo testInfo) throws IOException, 
InterruptedException {
+        startStop(testInfo, APP_TO_TEST);
+    }
+
+    @Test
+    public void loadTest(TestInfo testInfo) {
+        // TODO
+    }
+}
diff --git a/tests/src/test/java/org/kie/kogito/benchmarks/QuarkusTest.java 
b/tests/src/test/java/org/kie/kogito/benchmarks/QuarkusTest.java
new file mode 100644
index 0000000..0a598e3
--- /dev/null
+++ b/tests/src/test/java/org/kie/kogito/benchmarks/QuarkusTest.java
@@ -0,0 +1,9 @@
+package org.kie.kogito.benchmarks;
+
+import org.kie.kogito.benchmarks.framework.MvnCmds;
+
+public abstract class QuarkusTest extends AbstractTemplateTest {
+
+    protected static final MvnCmds MavenCommands = MvnCmds.QUARKUS_JVM;
+
+}
diff --git a/tests/src/test/java/org/kie/kogito/benchmarks/StartStopTest.java 
b/tests/src/test/java/org/kie/kogito/benchmarks/StartStopTest.java
new file mode 100644
index 0000000..04c50e9
--- /dev/null
+++ b/tests/src/test/java/org/kie/kogito/benchmarks/StartStopTest.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *       http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.kie.kogito.benchmarks;
+
+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.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.jboss.logging.Logger;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.kie.kogito.benchmarks.framework.App;
+import org.kie.kogito.benchmarks.framework.Commands;
+import org.kie.kogito.benchmarks.framework.LogBuilder;
+import org.kie.kogito.benchmarks.framework.Logs;
+import org.kie.kogito.benchmarks.framework.MvnCmds;
+import org.kie.kogito.benchmarks.framework.WebpageTester;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.kie.kogito.benchmarks.framework.Commands.cleanTarget;
+import static org.kie.kogito.benchmarks.framework.Commands.getBaseDir;
+import static org.kie.kogito.benchmarks.framework.Commands.getBuildCommand;
+import static org.kie.kogito.benchmarks.framework.Commands.getOpenedFDs;
+import static org.kie.kogito.benchmarks.framework.Commands.getRSSkB;
+import static org.kie.kogito.benchmarks.framework.Commands.getRunCommand;
+import static org.kie.kogito.benchmarks.framework.Commands.parsePort;
+import static org.kie.kogito.benchmarks.framework.Commands.processStopper;
+import static org.kie.kogito.benchmarks.framework.Commands.runCommand;
+import static org.kie.kogito.benchmarks.framework.Commands.waitForTcpClosed;
+import static org.kie.kogito.benchmarks.framework.Logs.SKIP;
+import static org.kie.kogito.benchmarks.framework.Logs.appendln;
+import static org.kie.kogito.benchmarks.framework.Logs.appendlnSection;
+import static org.kie.kogito.benchmarks.framework.Logs.archiveLog;
+import static org.kie.kogito.benchmarks.framework.Logs.checkListeningHost;
+import static org.kie.kogito.benchmarks.framework.Logs.checkLog;
+import static org.kie.kogito.benchmarks.framework.Logs.checkThreshold;
+import static org.kie.kogito.benchmarks.framework.Logs.getLogsDir;
+import static 
org.kie.kogito.benchmarks.framework.Logs.parseStartStopTimestamps;
+import static org.kie.kogito.benchmarks.framework.Logs.writeReport;
+
+@Tag("startstop")
+public class StartStopTest {
+
+    private static final Logger LOGGER = 
Logger.getLogger(StartStopTest.class.getName());
+
+    public static final String BASE_DIR = getBaseDir();
+
+    public void testRuntime(TestInfo testInfo, App app, MvnCmds mvnCmds) 
throws IOException, InterruptedException {
+        LOGGER.info("Testing app: " + app.toString() + ", mode: " + 
mvnCmds.toString());
+
+        Process pA = null;
+        File buildLogA = null;
+        File runLogA = null;
+        StringBuilder whatIDidReport = new StringBuilder();
+        File appDir = new File(BASE_DIR + File.separator + app.dir);
+        String cn = testInfo.getTestClass().get().getCanonicalName();
+        String mn = testInfo.getTestMethod().get().getName();
+        try {
+            // Cleanup
+            cleanTarget(app);
+            Files.createDirectories(Paths.get(appDir.getAbsolutePath() + 
File.separator + "logs"));
+
+            // Build
+            buildLogA = new File(appDir.getAbsolutePath() + File.separator + 
"logs" + File.separator + mvnCmds.name().toLowerCase() + "-build.log");
+            ExecutorService buildService = Executors.newFixedThreadPool(1);
+
+            List<String> baseBuildCmd = new ArrayList<>();
+            baseBuildCmd.addAll(Arrays.asList(mvnCmds.mvnCmds[0]));
+            //baseBuildCmd.add("-Dquarkus.version=" + getQuarkusVersion());
+            List<String> cmd = getBuildCommand(baseBuildCmd.toArray(new 
String[0]));
+
+            buildService.submit(new Commands.ProcessRunner(appDir, buildLogA, 
cmd, 20)); // TODO exit code handling
+            appendln(whatIDidReport, "# " + cn + ", " + mn);
+            appendln(whatIDidReport, (new Date()).toString());
+            appendln(whatIDidReport, appDir.getAbsolutePath());
+            appendlnSection(whatIDidReport, String.join(" ", cmd));
+            long buildStarts = System.currentTimeMillis();
+            buildService.shutdown();
+            buildService.awaitTermination(30, TimeUnit.MINUTES);
+            long buildEnds = System.currentTimeMillis();
+
+            assertTrue(buildLogA.exists());
+            checkLog(cn, mn, app, mvnCmds, buildLogA);
+
+            List<Long> rssKbList = new ArrayList<>(10);
+            List<Long> timeToFirstOKRequestList = new ArrayList<>(10);
+            for (int i = 0; i < 1; i++) {
+                // Run
+                LOGGER.info("Running... round " + i);
+                runLogA = new File(appDir.getAbsolutePath() + File.separator + 
"logs" + File.separator + mvnCmds.name().toLowerCase() + "-run.log");
+                cmd = getRunCommand(mvnCmds.mvnCmds[1]);
+                appendln(whatIDidReport, appDir.getAbsolutePath());
+                appendlnSection(whatIDidReport, String.join(" ", cmd));
+                long runStarts = System.currentTimeMillis();
+                pA = runCommand(cmd, appDir, runLogA);
+                long runEnds = System.currentTimeMillis();
+                System.out.println("RunEnds (" + runEnds + ") - RunStarts (" + 
runStarts + ") : " + (runEnds - runStarts));
+                // Test web pages
+                long timeToFirstOKRequest = 
WebpageTester.testWeb(app.urlContent.urlContent[0][0], 10, 
app.urlContent.urlContent[0][1], true);
+                LOGGER.info("Testing web page content...");
+                for (String[] urlContent : app.urlContent.urlContent) {
+                    WebpageTester.testWeb(urlContent[0], 5, urlContent[1], 
false);
+                }
+
+                LOGGER.info("Terminate and scan logs...");
+                pA.getInputStream().available(); // TODO Ask Karm
+
+                long rssKb = getRSSkB(pA.pid());
+                long openedFiles = getOpenedFDs(pA.pid());
+
+                processStopper(pA, false);
+
+                LOGGER.info("Gonna wait for ports closed...");
+                // Release ports
+                assertTrue(waitForTcpClosed("localhost", 
parsePort(app.urlContent.urlContent[0][0]), 60),
+                        "Main port is still open");
+                checkLog(cn, mn, app, mvnCmds, runLogA);
+                checkListeningHost(cn, mn, mvnCmds, runLogA);
+
+                float[] startedStopped = parseStartStopTimestamps(runLogA);
+
+                Path measurementsLog = Paths.get(getLogsDir(cn, 
mn).toString(), "measurements.csv");
+                LogBuilder.Log log = new LogBuilder()
+                        .app(app)
+                        .mode(mvnCmds)
+                        .buildTimeMs(buildEnds - buildStarts)
+                        .timeToFirstOKRequestMs(timeToFirstOKRequest)
+                        .startedInMs((long) (startedStopped[0] * 1000))
+                        .stoppedInMs((long) (startedStopped[1] * 1000))
+                        .rssKb(rssKb)
+                        .openedFiles(openedFiles)
+                        .build();
+                Logs.logMeasurements(log, measurementsLog);
+                appendln(whatIDidReport, "Measurements:");
+                appendln(whatIDidReport, log.headerMarkdown + "\n" + 
log.lineMarkdown);
+
+                rssKbList.add(rssKb);
+                timeToFirstOKRequestList.add(timeToFirstOKRequest);
+            }
+
+            long rssKbAvgWithoutMinMax = getAvgWithoutMinMax(rssKbList);
+            long timeToFirstOKRequestAvgWithoutMinMax = 
getAvgWithoutMinMax(timeToFirstOKRequestList);
+            LOGGER.info("AVG timeToFirstOKRequest without min and max values: 
" + timeToFirstOKRequestAvgWithoutMinMax);
+            LOGGER.info("AVG rssKb without min and max values: " + 
rssKbAvgWithoutMinMax);
+            checkThreshold(app, mvnCmds, rssKbAvgWithoutMinMax, 
timeToFirstOKRequestAvgWithoutMinMax, SKIP);
+        } finally {
+            // Make sure processes are down even if there was an exception / 
failure
+            if (pA != null) {
+                processStopper(pA, true);
+            }
+            // Archive logs no matter what
+            archiveLog(cn, mn, buildLogA);
+            archiveLog(cn, mn, runLogA);
+            writeReport(cn, mn, whatIDidReport.toString());
+            //cleanTarget(app);
+        }
+    }
+
+    private long getAvgWithoutMinMax(List<Long> listOfValues) {
+        //        listOfValues.remove(Collections.min(listOfValues));
+        //        listOfValues.remove(Collections.max(listOfValues));
+        return (long) listOfValues.stream().mapToLong(val -> 
val).average().orElse(Long.MAX_VALUE);
+    }
+
+    @Test
+    public void kogito(TestInfo testInfo) throws IOException, 
InterruptedException {
+        testRuntime(testInfo, App.SAMPLE_KOGITO_APP_QUARKUS_JVM, 
MvnCmds.QUARKUS_JVM);
+    }
+
+    //    @Test
+    //    public void jaxRsMinimalJVM(TestInfo testInfo) throws IOException, 
InterruptedException {
+    //        testRuntime(testInfo, App.JAX_RS_MINIMAL, MvnCmds.JVM);
+    //    }
+    //
+    //    @Test
+    //    @Tag("native")
+    //    public void jaxRsMinimalNative(TestInfo testInfo) throws 
IOException, InterruptedException {
+    //        testRuntime(testInfo, App.JAX_RS_MINIMAL, MvnCmds.NATIVE);
+    //    }
+    //
+    //    @Test
+    //    public void fullMicroProfileJVM(TestInfo testInfo) throws 
IOException, InterruptedException {
+    //        testRuntime(testInfo, App.FULL_MICROPROFILE, MvnCmds.JVM);
+    //    }
+    //
+    //    @Test
+    //    @Tag("native")
+    //    public void fullMicroProfileNative(TestInfo testInfo) throws 
IOException, InterruptedException {
+    //        testRuntime(testInfo, App.FULL_MICROPROFILE, MvnCmds.NATIVE);
+    //    }
+}
diff --git a/tests/src/test/resources/log4j2.xml 
b/tests/src/test/resources/log4j2.xml
new file mode 100644
index 0000000..1faad0e
--- /dev/null
+++ b/tests/src/test/resources/log4j2.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN">
+    <Appenders>
+        <Console name="Console" target="SYSTEM_OUT">
+            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} 
%highlight{%-5p}{FATAL=red blink, ERROR=red, WARN=yellow bold, INFO=green, 
DEBUG=green bold, TRACE=blue} [%style{%C{1.}}{cyan}] (%style{%M}{magenta}) 
%m%n"/>
+        </Console>
+    </Appenders>
+    <Loggers>
+        <Root level="info">
+            <AppenderRef ref="Console"/>
+        </Root>
+    </Loggers>
+</Configuration>
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to