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 [38;5;188m1.228[39ms. + * //stopped in [38;5;188m0.024[39ms[39m[38;5;203m[39m[38;5;227m + * + * 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><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>>=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]
