From c1609a953f23a3eeec13e2929a6f131a3ae5abf2 Mon Sep 17 00:00:00 2001 From: Mark Miller Date: Thu, 7 Oct 2021 20:10:02 -0500 Subject: [PATCH] Drive JMH benchmarks via Apache Zeppelin. --- zeppelin-jmh-interpreter/.gitignore | 10 + zeppelin-jmh-interpreter/README.md | 48 + zeppelin-jmh-interpreter/build.gradle | 102 ++ .../lucene/gradle/WrapperDownloader.java | 129 ++ .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes .../gradle/wrapper/gradle-wrapper.jar.sha256 | 1 + .../gradle/wrapper/gradle-wrapper.jar.version | 1 + .../gradle/wrapper/gradle-wrapper.properties | 5 + zeppelin-jmh-interpreter/gradlew | 185 +++ zeppelin-jmh-interpreter/settings.gradle | 5 + .../java/org/apache/jmh/JMHInterpreter.java | 1408 +++++++++++++++++ .../main/java/org/apache/jmh/JMHUtils.java | 187 +++ .../java/org/apache/jmh/JsonMapFlattener.java | 438 +++++ .../java/org/apache/jmh/StatusServlet.java | 84 + .../java/org/apache/jmh/noggit/CharArr.java | 394 +++++ .../org/apache/jmh/noggit/JSONParser.java | 1296 +++++++++++++++ .../java/org/apache/jmh/noggit/JSONUtil.java | 203 +++ .../org/apache/jmh/noggit/JSONWriter.java | 359 +++++ .../org/apache/jmh/noggit/ObjectBuilder.java | 207 +++ .../org/apache/jmh/noggit/package-info.java | 23 + .../main/resources/interpreter-setting.json | 33 + .../org/apache/jmh/JMHInterpreterTest.java | 690 ++++++++ .../src/test/resources/log4j.properties | 14 + 23 files changed, 5822 insertions(+) create mode 100755 zeppelin-jmh-interpreter/.gitignore create mode 100755 zeppelin-jmh-interpreter/README.md create mode 100755 zeppelin-jmh-interpreter/build.gradle create mode 100755 zeppelin-jmh-interpreter/buildSrc/src/main/java/org/apache/lucene/gradle/WrapperDownloader.java create mode 100755 zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.jar create mode 100755 zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.jar.sha256 create mode 100755 zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.jar.version create mode 100755 zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.properties create mode 100755 zeppelin-jmh-interpreter/gradlew create mode 100755 zeppelin-jmh-interpreter/settings.gradle create mode 100755 zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/JMHInterpreter.java create mode 100755 zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/JMHUtils.java create mode 100755 zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/JsonMapFlattener.java create mode 100755 zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/StatusServlet.java create mode 100755 zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/CharArr.java create mode 100755 zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/JSONParser.java create mode 100755 zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/JSONUtil.java create mode 100755 zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/JSONWriter.java create mode 100755 zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/ObjectBuilder.java create mode 100755 zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/package-info.java create mode 100755 zeppelin-jmh-interpreter/src/main/resources/interpreter-setting.json create mode 100755 zeppelin-jmh-interpreter/src/test/java/org/apache/jmh/JMHInterpreterTest.java create mode 100755 zeppelin-jmh-interpreter/src/test/resources/log4j.properties diff --git a/zeppelin-jmh-interpreter/.gitignore b/zeppelin-jmh-interpreter/.gitignore new file mode 100755 index 00000000000..747662e3a07 --- /dev/null +++ b/zeppelin-jmh-interpreter/.gitignore @@ -0,0 +1,10 @@ +.idea +.run +.gradle +build +.classpath +.project +.settings/ +.DS_Store +out +logs \ No newline at end of file diff --git a/zeppelin-jmh-interpreter/README.md b/zeppelin-jmh-interpreter/README.md new file mode 100755 index 00000000000..431edd0d8fe --- /dev/null +++ b/zeppelin-jmh-interpreter/README.md @@ -0,0 +1,48 @@ +# zeppelin-jmh-interpreter +JMH interpreter for Apache Zeppelin. + + +## Build + +```sh +./gradlew build +``` + +## Deployment + +* Update `$ZEPPELIN_HOME/conf/zeppeln-site.xml` +```xml +' + zeppelin.interpreters + ...,org.apache.jmh.JMHInterpreter + +``` +* Create `$ZEPPELIN_HOME/interpreter/jmh` +* Copy interpreter jar in `$ZEPPELIN_HOME/interpreter/jmh` + + +## Configuration + +TODO + + +
ParameterDefault valueDescription
+ +## How to use + +In Zeppelin, use `%jmh` in a paragraph. + + +Examples: +```javascript +%jmh + +// Display a table +db.zipcodes.find({ "city":"CHICAGO", "state": "IL" }).table() +``` + +## Examples + + + + diff --git a/zeppelin-jmh-interpreter/build.gradle b/zeppelin-jmh-interpreter/build.gradle new file mode 100755 index 00000000000..ec72452a2fc --- /dev/null +++ b/zeppelin-jmh-interpreter/build.gradle @@ -0,0 +1,102 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +plugins { + id 'java' + id 'maven-publish' + id 'com.github.johnrengelman.shadow' version '6.1.0' + id 'net.nemerosa.versioning' version '2.6.1' +} + +apply plugin: 'com.github.johnrengelman.shadow' + +configurations.all { + resolutionStrategy { + cacheChangingModulesFor 0, 'seconds' + } +} + +repositories { + mavenLocal() + maven { + url = uri('http://repository.apache.org/snapshots') + } + + maven { + url = uri('https://repo.maven.apache.org/maven2/') + } + maven { + url 'https://repo.maven.apache.org/maven2' + name 'Maven Central' + } +} + +dependencies { + implementation ('org.slf4j:slf4j-api:1.7.32') + implementation 'org.apache.commons:commons-exec:1.3' + implementation 'org.apache.commons:commons-lang3:3.12.0' + implementation 'commons-io:commons-io:2.5' + implementation 'commons-cli:commons-cli:20040117.000000' + implementation 'org.jline:jline-reader:3.20.0' + implementation 'org.apache.commons:commons-csv:1.8' + implementation 'net.openhft:chronicle-map:3.21ea82' // TODO: remove + implementation (group: 'org.apache.solr', name: 'solr-solrj', version: '8.9.0', { + exclude group: "io.netty", module: "*" + exclude group: "org.slf4j", module: "*" + exclude group: "org.eclipse.jetty", module: "*" + }) + implementation ('org.eclipse.jetty:jetty-server:11.0.6' + , { + exclude group: "org.slf4j", module: "*" + }) + implementation ('org.eclipse.jetty:jetty-servlet:11.0.6' + , { + exclude group: "org.slf4j", module: "*" + }) + implementation ('org.eclipse.jetty:jetty-http:11.0.6' + , { + exclude group: "org.slf4j", module: "*" + }) + implementation ('org.eclipse.jetty.http2:http2-common:11.0.6' + , { + exclude group: "org.slf4j", module: "*" + }) + + // compileOnly 'javax.servlet:javax.servlet-api:3.1.0' + + testImplementation 'junit:junit:4.13.2' + compileOnly 'org.apache.zeppelin:zeppelin-interpreter:0.10.1-SNAPSHOT' + testImplementation 'org.apache.zeppelin:zeppelin-interpreter:0.10.1-SNAPSHOT' + testImplementation 'org.apache.zeppelin:zeppelin-common:0.10.1-SNAPSHOT' + testRuntime 'log4j:log4j:1.2.17' +} + + +group = 'jmh' +version = '0.1.0' +description = 'Zeppelin: JMH interpreter' +java.targetCompatibility = JavaVersion.VERSION_11 + +jar { + manifest { + attributes( + 'Built-By' : System.properties['user.name'], + 'Build-Timestamp': new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format(new Date()), + 'Build-Revision' : versioning.info.commit, + 'Created-By' : "Gradle ${gradle.gradleVersion}", + 'Build-Jdk' : "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})", + 'Build-OS' : "${System.properties['os.name']} ${System.properties['os.arch']} ${System.properties['os.version']}" + ) + } +} + +shadowJar { + outputs.upToDateWhen { false } + archiveBaseName = 'jmh' + relocate 'org.eclipse.jetty', 'shadow.jetty' +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} diff --git a/zeppelin-jmh-interpreter/buildSrc/src/main/java/org/apache/lucene/gradle/WrapperDownloader.java b/zeppelin-jmh-interpreter/buildSrc/src/main/java/org/apache/lucene/gradle/WrapperDownloader.java new file mode 100755 index 00000000000..8bbcc8d75d3 --- /dev/null +++ b/zeppelin-jmh-interpreter/buildSrc/src/main/java/org/apache/lucene/gradle/WrapperDownloader.java @@ -0,0 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.lucene.gradle; + +import java.io.IOException; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.EnumSet; +import java.util.Locale; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static java.nio.file.StandardOpenOption.APPEND; + +/** + * Standalone class that can be used to download a gradle-wrapper.jar + *

+ * Has no dependencies outside of standard java libraries + */ +public class WrapperDownloader { + public static void main(String[] args) { + if (args.length != 1) { + System.err.println("Usage: java WrapperDownloader.java "); + System.exit(1); + } + + try { + new WrapperDownloader().run(Paths.get(args[0])); + } catch (Exception e) { + System.err.println("ERROR: " + e.getMessage()); + System.exit(1); + } + } + + public void run(Path destination) throws IOException, NoSuchAlgorithmException { + Path checksumPath = destination.resolveSibling(destination.getFileName().toString() + ".sha256"); + if (!Files.exists(checksumPath)) { + throw new IOException("Checksum file not found: " + checksumPath); + } + String expectedChecksum = Files.readString(checksumPath, StandardCharsets.UTF_8).trim(); + + Path versionPath = destination.resolveSibling(destination.getFileName().toString() + ".version"); + if (!Files.exists(versionPath)) { + throw new IOException("Wrapper version file not found: " + versionPath); + } + String wrapperVersion = Files.readString(versionPath, StandardCharsets.UTF_8).trim(); + + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + + if (Files.exists(destination)) { + if (checksum(digest, destination).equalsIgnoreCase(expectedChecksum)) { + // File exists, checksum matches, good to go! + return; + } else { + System.err.println("Checksum mismatch, will attempt to re-download gradle-wrapper.jar"); + System.out.println(destination); + Files.delete(destination); + } + } + + URL url = new URL("https://github.com/gradle/gradle/raw/v" + wrapperVersion + "/gradle/wrapper/gradle-wrapper.jar"); + System.err.println("Downloading gradle-wrapper.jar from " + url); + + // As of v6.0.1 the wrapper is approximately 60K + // Can increase this if gradle wrapper ever goes beyond 500K, but keep a safety check + final int maxSize = 512 * 1024; + + // Zero-copy save the jar to a temp file + Path temp = Files.createTempFile(destination.getParent(), ".gradle-wrapper", ".tmp"); + try { + try (ReadableByteChannel in = Channels.newChannel(url.openStream()); + FileChannel out = FileChannel.open(temp, EnumSet.of(APPEND))) { + out.transferFrom(in, 0, maxSize); + } catch (IOException e) { + throw new IOException("Could not download gradle-wrapper.jar (" + e.getMessage() + ")."); + } + + String checksum = checksum(digest, temp); + if (!checksum.equalsIgnoreCase(expectedChecksum)) { + throw new IOException(String.format(Locale.ROOT, + "Checksum mismatch on downloaded gradle-wrapper.jar (was: %s, expected: %s).", + checksum, + expectedChecksum)); + } + + Files.move(temp, destination, REPLACE_EXISTING); + temp = null; + } finally { + if (temp != null) { + Files.deleteIfExists(temp); + } + } + } + + private String checksum(MessageDigest messageDigest, Path path) throws IOException { + try { + char[] hex = "0123456789abcdef".toCharArray(); + byte[] digest = messageDigest.digest(Files.readAllBytes(path)); + StringBuilder sb = new StringBuilder(); + for (byte b : digest) { + sb.append(hex[(b >> 4) & 0xf]).append(hex[b & 0xf]); + } + return sb.toString(); + } catch (IOException e) { + throw new IOException("Could not compute digest of file: " + path + " (" + e.getMessage() + ")"); + } + } +} diff --git a/zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.jar b/zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.jar new file mode 100755 index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f GIT binary patch literal 59203 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

K7UpUK$>l@ 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 E2O(~oYXATM literal 0 HcmV?d00001 diff --git a/zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.jar.sha256 b/zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.jar.sha256 new file mode 100755 index 00000000000..e85f20f9fe4 --- /dev/null +++ b/zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.jar.sha256 @@ -0,0 +1 @@ +e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637 diff --git a/zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.jar.version b/zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.jar.version new file mode 100755 index 00000000000..021c9405b1d --- /dev/null +++ b/zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.jar.version @@ -0,0 +1 @@ +6.8.3 diff --git a/zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.properties b/zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.properties new file mode 100755 index 00000000000..442d9132ea3 --- /dev/null +++ b/zeppelin-jmh-interpreter/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/zeppelin-jmh-interpreter/gradlew b/zeppelin-jmh-interpreter/gradlew new file mode 100755 index 00000000000..4f906e0c811 --- /dev/null +++ b/zeppelin-jmh-interpreter/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/zeppelin-jmh-interpreter/settings.gradle b/zeppelin-jmh-interpreter/settings.gradle new file mode 100755 index 00000000000..a5c9b3422ec --- /dev/null +++ b/zeppelin-jmh-interpreter/settings.gradle @@ -0,0 +1,5 @@ +/* + * This file was generated by the Gradle 'init' task. + */ + +rootProject.name = 'jmh' diff --git a/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/JMHInterpreter.java b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/JMHInterpreter.java new file mode 100755 index 00000000000..a02f82d553e --- /dev/null +++ b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/JMHInterpreter.java @@ -0,0 +1,1408 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * 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. + */ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.jmh; + +import net.openhft.chronicle.core.io.IORuntimeException; +import net.openhft.chronicle.map.ChronicleMap; +import net.openhft.chronicle.map.ChronicleMapBuilder; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVParser; +import org.apache.commons.csv.CSVPrinter; +import org.apache.commons.csv.CSVRecord; +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.ExecuteException; +import org.apache.commons.exec.ExecuteWatchdog; +import org.apache.commons.exec.PumpStreamHandler; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.apache.solr.client.solrj.impl.BinaryRequestWriter; +import org.apache.solr.common.util.IOUtils; +import org.apache.solr.common.util.JavaBinCodec; +import org.apache.zeppelin.interpreter.Interpreter; +import org.apache.zeppelin.interpreter.InterpreterContext; +import org.apache.zeppelin.interpreter.InterpreterException; +import org.apache.zeppelin.interpreter.InterpreterResult; +import org.apache.zeppelin.interpreter.InterpreterResult.Code; +import org.apache.zeppelin.interpreter.ZeppelinContext; +import org.apache.zeppelin.interpreter.thrift.InterpreterCompletion; +import org.apache.zeppelin.scheduler.Scheduler; +import org.apache.zeppelin.scheduler.SchedulerFactory; +import org.eclipse.jetty.io.RuntimeIOException; +import org.eclipse.jetty.server.Connector; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.ResourceHandler; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.jetbrains.annotations.NotNull; +import org.eclipse.jetty.server.Server; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +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.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +import static org.apache.commons.io.FileUtils.*; + +// gc jcmd 999 VM.log output="file=gc.log" output_options="filecount=5,filesize=10m" what="gc=debug" decorators="time,level" + +/** + * JMH benchmark module interpreter for Zeppelin. + */ +public class JMHInterpreter extends Interpreter { + + private static final Logger log = LoggerFactory.getLogger(JMHInterpreter.class); + + private static final String TIMEOUT_PROPERTY = "jmh.command.timeout"; + + private static final String DEFAULT_TIMEOUT = "60000"; + private static final String DEFAULT_CHECK_INTERVAL = "5000"; + private static final String DIRECTORY_USER_HOME = "shell.working.directory.user.home"; + public static final String[] SYS_TRACE_HEADERS = {"epoch", "usr", "sys", "idl", "wai", "stl", "1m", "5m", "15m", "run", "blk", "new", "int", "csw", "read", + "writ", "recv", "send", "read", "writ", "in", "out", "used", "free", "buff", "cach", "files", "inodes", "lis", "act", "syn", "tim", "clo"}; + private static final boolean STD_TABLE = true; + public static final String MAP_STORE = "map-store"; + public static final File RESOURCE_SERVE_DIR = new File(new File(System.getProperty("user.home"), "jmh"), "public"); + public static final String TMP_JMH_HEAP_DUMP = "/tmp/jmh-heap-dump"; + public static final String PROFILE_RESULTS = "jmh-profile-results"; + private static final AtomicInteger ID_CNT = new AtomicInteger(); + + private static final Pattern COMPLETION = Pattern.compile("Run progress: (\\d+\\.\\d+)% complete"); + public static final String SYSTRACE_2_CSV = "systrace-2.csv"; + public static final String SYSTRACE = "systrace"; + public static final String SYSTRACE_CSV = "systrace.csv"; + public static final String HOSTS = "hosts"; + public static final String SPACER = " ;\n"; + public static final String LARGE_SPACE = " });\n"; + public static final String RESULT_SET = "ResultSet"; + public static final String PROFILE_DIR = "$PROFILE_DIR"; + public static final String LOCAL = "local"; + public static final String ARTIFACT_CHAFF = " artifactChaff:"; + + private static String DSTAT_CSV_SCRIPT = " success=true\n" + "rm -f $SYSTRACE_CSV\ntouch $SYSTRACE_CSV\n" + + "dstat -Tclpydnrgm --fs --tcp --noheaders --noupdate --nocolor --output $SYSTRACE_CSV 5 &\n" + + "dstatPid=$!\n" + "START=$(date +%s.%N)\n" + "$cmd\n" + + "success=$?\n" + "if [ $success -ne 0 ]; then\n" + " success=false\n" + "fi\n" + "\n" + "END=$(date +%s.%N)\n" + + "DIFF=$(echo ${END} - ${START} | bc)\n" + "DIFF=$(echo ${DIFF}/1 | bc )\n" + "kill -s TERM ${dstatPid}" + "\n" + "wait ${dstatPid}\n" + + "wait\n" + "\n" + "tail -n +8 $SYSTRACE_CSV > $SYSTRACE_CSV.tmp && mv $SYSTRACE_CSV.tmp $SYSTRACE_CSV"; + + + private ConcurrentHashMap executorMap; + + private ConcurrentHashMap semaphores; + + private ConcurrentHashMap outputs; + + private ConcurrentHashMap contextMap; + + private ConcurrentHashMap currentBenchmark; + + private final ScheduledExecutorService shellOutputCheckExecutor = + Executors.newSingleThreadScheduledExecutor(); + + // TODO: remove - interesting but unessary - everything as folders/files accessable by jetty + ChronicleMap artifactChaff; + private Server server; + static { + try { + if (Files.exists(Paths.get("/home/markmiller/Sync"))) { + Files.writeString(Paths.get("/home/markmiller/Sync/hacklog.log"), "hacklog init", StandardOpenOption.CREATE); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public JMHInterpreter(Properties property) { + super(property); + } + + @Override + public List completion(String buf, int cursor, + InterpreterContext interpreterContext) throws InterpreterException { + + log.info("completion {}, {}, {}", buf, cursor, ""); + + if (cursor < 5) { + return Collections.singletonList(new InterpreterCompletion("cmd=", "cmd=", "jmh command")); + } + + return Collections.emptyList(); + } + + + @Override + public void open() { + long timeoutThreshold = Long.parseLong(getProperty(TIMEOUT_PROPERTY, DEFAULT_TIMEOUT)); + long timeoutCheckInterval = Long.parseLong(DEFAULT_CHECK_INTERVAL); + log.info("Command timeout property: {}", timeoutThreshold); + + + executorMap = new ConcurrentHashMap<>(); + currentBenchmark = new ConcurrentHashMap<>(); + contextMap = new ConcurrentHashMap<>(); + semaphores = new ConcurrentHashMap<>(); + outputs = new ConcurrentHashMap<>(); + + server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(8191); + server.setConnectors(new Connector[] {connector}); + ResourceHandler resourceHandler = new ResourceHandler(); + File base = RESOURCE_SERVE_DIR; + base.mkdirs(); + resourceHandler.setResourceBase(base.getAbsolutePath()); + // server.setHandler(resourceHandler); + // HandlerCollection container = new HandlerCollection(); + // container.addHandler(resourceHandler); + // ServletContextHandler servletHandler = new ServletContextHandler(container, "/"); + // ServletContext servletContext = new ServletContext(); + ServletContextHandler servletHandler = new ServletContextHandler(); + servletHandler.getServletContext().setAttribute("artifactChaff", artifactChaff); + servletHandler.getServletContext().setAttribute("contextMap", contextMap); + servletHandler.addServlet(StatusServlet.class, "/status"); + servletHandler.setHandler(resourceHandler); + server.setHandler(servletHandler); + + + // servletHandler.setHandler(resourceHandler); + + try { + server.start(); + + artifactChaff = ChronicleMapBuilder + .of(CharSequence.class, byte[].class) + .name("jmh-artifact-chaff") + .entries(5_000_000) + .averageKey("2GD28CD9S") + .averageValueSize(1024) + .createPersistedTo(new File(new File(System.getProperty("user.home"), "jmh"), MAP_STORE)); + } catch (Exception e) { + throw new RuntimeException(e); + } + + shellOutputCheckExecutor.scheduleAtFixedRate(() -> { + try { + for (Map.Entry entry : executorMap.entrySet()) { + String paragraphId = entry.getKey(); + InterpreterContext context = contextMap.get(paragraphId); + if (context == null) { + log.warn("No InterpreterContext associated with paragraph: {}", paragraphId); + continue; + } + + String output = context.out().toString(); + + Matcher m = COMPLETION.matcher(output); + double complete = 0.0; + while (m.find()) { + complete = Double.parseDouble(m.group(1)); + } + + if (context.getStringLocalProperty("workdir2", null) != null) { + if (currentBenchmark.get(context.getParagraphId()) == 1) { + complete = complete / 2; + } else if (currentBenchmark.get(context.getParagraphId()) == 2) { + complete = complete / 2 + 50.0; + } + } + + int progress = (int) Math.round(complete); + if (progress == 0) { + progress = 1; + } + if (progress > context.getIntLocalProperty("progress", 0)) { + context.getLocalProperties().put("progress", Integer.toString(progress)); + context.setProgress(progress); + } + if (output.length() > 1024) { + context.out.clear(false); + context.out.write(output.substring(1024)); + StringBuilder saveOutput = outputs.computeIfAbsent(context.getParagraphId(), s -> new StringBuilder(64)); + saveOutput.append(output); + } + +// if ((System.currentTimeMillis() - context.out.getLastWriteTimestamp()) > +// timeoutThreshold) { +// LOGGER.info("No output for paragraph {} for the last {} milli-seconds, so kill it", +// paragraphId, timeoutThreshold); +// executor.getWatchdog().destroyProcess(); +// } + } + } catch (Exception e) { + log.error("Error when checking shell command timeout", e); + } + }, timeoutCheckInterval, timeoutCheckInterval, TimeUnit.MILLISECONDS); + } + + @Override + public void close() { + try { + for (String executorKey : executorMap.keySet()) { + DefaultExecutor executor = executorMap.remove(executorKey); + if (executor != null) { + try { + executor.getWatchdog().destroyProcess(); + } catch (Exception e) { + log.error("error destroying executor for paragraphId: " + executorKey, e); + } + } + } + + if (shellOutputCheckExecutor != null) { + shellOutputCheckExecutor.shutdown(); + try { + shellOutputCheckExecutor.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } finally { + IOUtils.closeQuietly(artifactChaff); + try { + server.stop(); + } catch (Exception e) { + log.warn("", e); + } + } + } + + @Override public InterpreterResult interpret(String st, InterpreterContext context) throws InterpreterException { + + try { + InterpreterContext.set(context); + ZeppelinContext z = getZeppelinContext(); + if (z != null) { + z.setGui(context.getGui()); + z.setNoteGui(context.getNoteGui()); + z.setInterpreterContext(context); + } + boolean interpolate = isInterpolate() || Boolean.parseBoolean(context.getLocalProperties().getOrDefault("interpolate", "false")); + // if (interpolate) { + // st = super.interpolate(st, context.getResourcePool()); + // } + return internalInterpret(st, context); + } finally { + outputs.remove(context.getParagraphId()); + } + } + + + protected boolean isInterpolate() { + return Boolean.parseBoolean(getProperty("jmh.interpolation", "true")); + } + + + public ZeppelinContext getZeppelinContext() { + return null; + } + + public InterpreterResult internalInterpret(String script, InterpreterContext context) { + log.info("Run jmh script '{}' properties='{}'", script, properties); + + if (script.isBlank()) { + return new InterpreterResult(Code.KEEP_PREVIOUS_RESULT); + } + + String ssh = context.getStringLocalProperty("ssh", null); + hackLog("internalInterpret key=" + (ssh == null ? LOCAL : ssh) + " semaphores=" + semaphores); + Semaphore semaphore = semaphores.computeIfAbsent(ssh == null ? LOCAL : ssh, s -> new Semaphore(1)); + try { + semaphore.acquire(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.error("Interrupted while waiting for semaphore", e); + throw new RuntimeException(e); + } + StringBuilder saveOutput = outputs.computeIfAbsent(context.getParagraphId(), s -> new StringBuilder(64)); + try { + // String sshWorkingDir = null; + + if (contextMap != null) { + contextMap.put(context.getParagraphId(), context); + } + + context.out.setEnableTableAppend(true); + + currentBenchmark.put(context.getParagraphId(), 1); + + + long startTime = System.currentTimeMillis(); + + boolean systrace = context.getBooleanLocalProperty(SYSTRACE, false); + hackLog("systrace is:" + systrace + " localprops=" + context.getLocalProperties()); + + boolean heapdump = context.getBooleanLocalProperty("heapdump", false); + hackLog("heapdump is:" + heapdump + " localprops=" + context.getLocalProperties()); + + String workingDir = getProperty("jmh.workdir", null); + workingDir = context.getStringLocalProperty("workdir", workingDir); + + log.info("Found working dir '{}'", workingDir); + + String trimCmd = script.trim(); + log.info("Trim command '{}'", trimCmd); + if (trimCmd.startsWith("cmd=")) return runCmd(ssh, script, context, trimCmd); + + log.info("Script not a cmd '{}'", script); + + + String jmhShCmd; + + if (heapdump) { + jmhShCmd = "./jmh.sh -rf json -jvmArgsAppend -Ddumpheap=" + TMP_JMH_HEAP_DUMP + " "; + } else { + jmhShCmd = "./jmh.sh -rf json "; + } + + if (ssh != null) { + log.info("running ssh version"); +// sshWorkingDir = workingDir; +// workingDir = getProperty("jmh.workdir", null); + } + + String cmpWorkDir = context.getStringLocalProperty("workdir2", null); + + String[] constantDataAndTable; + + boolean profileDir = script.contains(PROFILE_DIR); + if (profileDir) { + script = script.replace("\\;", "\\\\;"); + + Path profileResults = Paths.get(workingDir, "work", PROFILE_RESULTS); + + if (Files.exists(profileResults)) { + FileUtils.deleteDirectory(profileResults.toFile()); + Files.createDirectories(profileResults ); + } + } + + Path profileResultsDir = null; + if (cmpWorkDir != null) { + log.info("found cmp work dir of {}",cmpWorkDir); + + if (profileDir) { + profileResultsDir = Paths.get(cmpWorkDir, "work", PROFILE_RESULTS); + deleteDirectory(profileResultsDir.toFile()); + } + + List> flatMapsCmp = runCmpBenchmark(script, + context, ssh, systrace, jmhShCmd, cmpWorkDir); + + if (profileDir) { + profileResultsDir = Paths.get(workingDir, "work", PROFILE_RESULTS); + deleteDirectory(profileResultsDir.toFile()); + } + currentBenchmark.put(context.getParagraphId(), 2); + String json = runBenchmark((profileDir ? script.replace(PROFILE_DIR, profileResultsDir.toString()) : script), + context, ssh, workingDir, systrace, jmhShCmd, SYSTRACE_CSV); + log.info("json=\n{}", json); + + List> flatMaps = JsonMapFlattener.flatMapsFromResult(json); + + flatMaps.forEach(flatMap -> flatMap.put(RESULT_SET, "After")); + +// Set headerKeys = flatMapsCmp.iterator().next().keySet(); +// flatMaps.forEach(fm -> { +// Iterator> it = fm.entrySet().iterator(); +// it.forEachRemaining(entry -> { +// if (!headerKeys.contains(entry.getKey())) { +// it.remove(); +// } +// } ); +// }); +// +// Set headerKeys2 = flatMaps.iterator().next().keySet(); +// flatMapsCmp.forEach(fm -> { +// Iterator> it = fm.entrySet().iterator(); +// it.forEachRemaining(entry -> { +// if (!headerKeys2.contains(entry.getKey())) { +// it.remove(); +// } +// } ); +// }); +// +// if (flatMaps.size() != flatMapsCmp.size()) { +// throw new IllegalStateException("flatmaps=\n" + flatMaps + "\n\nflatmapsCmp\n" + flatMapsCmp ); +// } + + flatMapsCmp.addAll(flatMaps); + + if (flatMaps.isEmpty()) { + throw new IllegalStateException("empty results from second run"); + } + if (flatMapsCmp.isEmpty()) { + throw new IllegalStateException("empty results from first run"); + } + + constantDataAndTable = JsonMapFlattener.flatMapsToZeppelinTable(flatMapsCmp); + + } else { + if (profileDir) { + profileResultsDir = Paths.get(workingDir, "work", PROFILE_RESULTS); + deleteDirectory(profileResultsDir.toFile()); + } + + String json = runBenchmark((profileDir ? script.replace(PROFILE_DIR, profileResultsDir.toString()) : script), + context, ssh, workingDir, systrace, jmhShCmd, SYSTRACE_CSV); + log.info("json=\n{}", json); + + constantDataAndTable = JsonMapFlattener.resultsToZeppelinTable(json); + + if (systrace) { + fechAndStoreTraceFile(context, ssh, workingDir, SYSTRACE_CSV, SYSTRACE_2_CSV); + } + } + + log.info("do heapdump={}", heapdump); + List paths = null; + if (heapdump) { + InterpreterResult result = new InterpreterResult(Code.SUCCESS); + paths = JMHUtils.processHeapDumpFolder(new File(TMP_JMH_HEAP_DUMP), RESOURCE_SERVE_DIR, + Paths.get(context.getNoteId(), String.valueOf(startTime), "heap-reports")); + + int height = context.getIntLocalProperty("height",1200); + + String[] titles = new String[] {"JXray", "JOverflow", "MAT Suspects", "MAT Top Components", "MAT Overview"}; + int titleIndex = 0; + StringBuilder sb = new StringBuilder(); + for (String path : paths) { + String iframe = ""; + sb.append(showHideDiv( "" + titles[titleIndex++] + "", iframe, context)).append("
"); + } + context.out.clear(); + context.out.write("%html " + sb); + return result; + } + + // System.out.println("zepoutput:" + zeppelinTable); + + context.out.clear(); + +// String constDataTable = +// StringUtils.replace(START_HTML, "$ID", "cnst-tbl-" + context.getParagraphId()).replaceAll("Cmd Output", "Constant Data") + constantDataAndTable[0] +// + StringUtils.replace(END_HTML, "$ID", "cnst-tbl-" + context.getParagraphId()); + + String constDataTable = showHideDiv("Constant Data", constantDataAndTable[0], context); + + // + '\n' + "%table " + constantDataAndTable[1]); + String tsvTable = constantDataAndTable[1]; + + CSVFormat format = CSVFormat.DEFAULT.withDelimiter('\t').withRecordSeparator('\n').withIgnoreSurroundingSpaces() + .withNullString("(NULL)").withTrim().withAutoFlush(true); + CSVParser records; + StringReader recordsStringReader = new StringReader(tsvTable); + records = format.withFirstRecordAsHeader().parse(recordsStringReader); + + log.info("tsvTable: {}", tsvTable); + + + if (profileDir) { + return writeProfileResults(context, startTime, workingDir); + } else { + writeTableToContext(context, records, format, constDataTable); + } + + // context.out.write(constantDataAndTable[0]); + // context.out.write(constantDataAndTable[1]); + + return new InterpreterResult(Code.SUCCESS); + + } catch (ExecuteException e) { + int exitValue = e.getExitValue(); + log.error("Can not run command: " + script, e); + shellOutputCheckExecutor.shutdown(); + Code code = Code.ERROR; + StringBuilder messageBuilder = new StringBuilder(); + if (exitValue == 143) { + code = Code.INCOMPLETE; + messageBuilder.append("Paragraph received a SIGTERM\n"); + log.info("The paragraph {} stopped executing: {}", context.getParagraphId(), messageBuilder); + } + messageBuilder.append("ExitValue: ").append(exitValue); + + // context.out.clear(true); + InterpreterResult result = new InterpreterResult(Code.ERROR,saveOutput.toString()); + result.add(messageBuilder + " " + e.getMessage()); + + return result; + } catch (Exception e) { + log.error("Error running script: " + script, e); + shellOutputCheckExecutor.shutdown(); + // context.out.clear(true); + InterpreterResult result = new InterpreterResult(Code.ERROR, saveOutput.toString()); + result.add(e.getMessage()); + return result; + } finally { + if (context.getParagraphId() != null) { + executorMap.remove(context.getParagraphId()); + if (contextMap != null) { + contextMap.remove(context.getParagraphId()); + } + if (currentBenchmark != null) { + currentBenchmark.remove(context.getParagraphId()); + } + } + semaphore.release(); + } + } + + private static AtomicInteger SHOW_HIDE_ID = new AtomicInteger(); + private String showHideDiv(String title, String content, InterpreterContext context) { + int inc = SHOW_HIDE_ID.incrementAndGet(); + String id = context.getParagraphId() + "-" + inc; + String startHtml = "\n" + "

\n" + + "â–²" + title + "\n" + "\n" + "
\n" + "\n" + + ""; + + return startHtml + content + endHtml; + } + + @NotNull private InterpreterResult writeProfileResults(InterpreterContext context, long startTime, String workingDir) throws IOException { + // InterpreterResult result = new InterpreterResult(Code.SUCCESS, InterpreterResult.Type.HTML, ""); + // String iframe = ""; + // StringBuilder html = new StringBuilder(); + // Path profileResults = Paths.get(workingDir, "work", PROFILE_RESULTS); + // + // Files.list(profileResults).forEach(path -> { + // Path dest = Paths.get(RESOURCE_SERVE_DIR.getAbsolutePath(), PROFILE_RESULTS); + // try { + // + // if (Files.exists(Paths.get(dest.toString(), path.getFileName().toString()))) { + // FileUtils.deleteDirectory(Paths.get(dest.toString(), path.getFileName().toString()).toFile()); + // } + // org.apache.commons.io.FileUtils.moveDirectoryToDirectory(path.toFile(), dest.toFile(), true); + // } catch (IOException e) { + // throw new RuntimeIOException(e); + // } + // + // Pattern extractFlameGraph = Pattern.compile("(\n" + + + "
\n" + " \n" + "
\n\n" + + ""); + + // data = Object.assign(d3.csvParse(await FileAttachment("alphabet.csv").text(), ({letter, frequency}) => ({name: letter, value: +frequency})).sort((a, b) => d3.descending(a.value, b.value)), {format: "%"}) + + } + + @NotNull private InterpreterResult runCmd(String ssh, String cmd, InterpreterContext context, String trimCmd) throws IOException { + if (trimCmd.startsWith("cmd=systrace")) { + // section systraceGet + log.info("cmd is systrace ssh={}", ssh); + + String[] systraceGetParams = trimCmd.substring("cmd=systrace ".length()).split(" "); + + int index = 0; + + try { + index = systraceGetParams.length < 1 ? 0 : Integer.parseInt(systraceGetParams[0]); + } catch(NumberFormatException e) { + // expected + } + + log.info("artifactChaff={}", artifactChaff); + Map hostsMap; + byte[] data = artifactChaff.get(HOSTS); + try { + if (data == null) { + throw new IllegalArgumentException(artifactChaff.toString()); + } else { + hostsMap = (Map) frmJavaBin(data); + } + } catch(Exception e) { + throw new RuntimeException("note id="+context.getNoteId() + ARTIFACT_CHAFF + artifactChaff.toString(), e); + } + + Map hostMap = (Map) hostsMap.get(ssh == null ? LOCAL : ssh); + + if (hostMap == null) { + throw new ResourceNotFoundException("host=" + (ssh == null ? LOCAL : ssh) + " hostsMap:" + hostsMap + ARTIFACT_CHAFF + + artifactChaff); + } + + Map systraceMap = (Map) hostMap.get(SYSTRACE); + + if (systraceMap == null) { + throw new ResourceNotFoundException("key=systrace" + ARTIFACT_CHAFF + artifactChaff); + } + + + Object systraceData = systraceMap.get(SYSTRACE_CSV); + if (systraceData == null) { + throw new ResourceNotFoundException("key=systrace.csv" + ARTIFACT_CHAFF + artifactChaff); + } + + log.info("got systrace data {}", systraceData); + + + Object systraceData2 = systraceMap.get(SYSTRACE_2_CSV); + if (systraceData2 != null) { + + log.info("got systrace data2 {}", systraceData2); + } + +// Iterable records = CSVFormat.DEFAULT +// .withDelimiter(',') +// .withRecordSeparator('\n') +// .withSkipHeaderRecord(true).withHeader(SYS_TRACE_HEADERS).parse(new StringReader((String) systraceData)); + + + // System.out.println("parsed csv file to "); + // records.forEach(i -> System.out.println("rec:" + i)); + List paramsList = Arrays.asList(systraceGetParams); + List headers = paramsList.subList(index, paramsList.size()); + + List actualHeaders = new ArrayList<>(); + if (systraceData2 != null) { + actualHeaders.add(RESULT_SET); + actualHeaders.addAll(headers); + } else { + actualHeaders = headers; + } + StringWriter sw = new StringWriter(); + CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT + .withDelimiter('\t') + .withRecordSeparator('\n') + .withHeader(actualHeaders.toArray(new String[0]))); + + if (systraceData2 != null) { + CSVParser records = CSVFormat.DEFAULT.withDelimiter(',').withRecordSeparator('\n').withSkipHeaderRecord(true).withHeader(SYS_TRACE_HEADERS) + .parse(new StringReader((String) systraceData2)); + + //.withSkipHeaderRecord(false)); + for (CSVRecord rec : records) { + List vals = new ArrayList<>(); + + vals.add("Before"); + + for (String type : headers) { + String val = rec.get(type); + vals.add(val); + } + printer.printRecord(vals); + } + } + CSVParser records = CSVFormat.DEFAULT.withDelimiter(',').withRecordSeparator('\n').withSkipHeaderRecord(true).withHeader(SYS_TRACE_HEADERS) + .parse(new StringReader((String) systraceData)); + //.withSkipHeaderRecord(false)); + for (CSVRecord rec : records) { + List vals = new ArrayList<>(); + if (systraceData2 != null) { + vals.add("After"); + } + for (String type : headers) { + String val; + try { + val = rec.get(type); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(type, e); + } + + vals.add(val); + } + printer.printRecord(vals); + } + + printer.close(true); + + context.out.write("%table " + sw); + return new InterpreterResult(Code.SUCCESS); + } if (trimCmd.startsWith("cmd=chart")) { + String[] systraceGetParams = trimCmd.substring("cmd=chart ".length()).split(" "); + + + //context.out.write("%text " + msg); + String msg = ""; + return new InterpreterResult(Code.SUCCESS, msg); + } else { + throw new IllegalArgumentException(cmd + ":" + trimCmd); + } + } + + private Object frmJavaBin(byte[] data) throws IOException { + try (JavaBinCodec jbc = new JavaBinCodec()) { + Object val = jbc.unmarshal(new ByteArrayInputStream(data)); + log.info("unmarshalled={}", val); + return val; + } + } + + private byte[] toJavaBin(Object object) throws IOException { + try (final JavaBinCodec jbc = new JavaBinCodec()) { + BinaryRequestWriter.BAOS baos = new BinaryRequestWriter.BAOS(); + jbc.marshal(object, baos); + return baos.getbuf(); + } + + } + + private String parseTraceFile1(Reader in) throws IOException { + + // System.out.println("read csv file " + new File(workingDir, cmdFile + ".csv")); + // System.out.println("file: " + Files.readString(Paths.get(workingDir, cmdFile + ".csv"))); + Iterable records = CSVFormat.DEFAULT.withDelimiter(',').withRecordSeparator('\n').withSkipHeaderRecord(true) + .withHeader(SYS_TRACE_HEADERS).parse(in); + + // System.out.println("parsed csv file to "); + // records.forEach(i -> System.out.println("rec:" + i)); + StringWriter sw = new StringWriter(); + CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withDelimiter(',').withRecordSeparator('\n') + .withHeader(SYS_TRACE_HEADERS)); + // .withFirstRecordAsHeader() + //.withSkipHeaderRecord(false)); + for (CSVRecord rec : records) { + for (String s : rec) { + printer.print(s); + } + printer.println(); + } + + printer.close(true); + return sw.toString(); + + } + + private DefaultExecutor runCmds(InterpreterContext context, String workingDir) { + DefaultExecutor executor = new DefaultExecutor(); + + log.info("working dir is {}", workingDir); + if (workingDir !=null) { + executor.setWorkingDirectory(new File(workingDir)); + } + executor.setStreamHandler(new PumpStreamHandler(context.out, context.out)); + executor.setWatchdog(new ExecuteWatchdog(Long.MAX_VALUE)); + executorMap.put(context.getParagraphId(), executor); + return executor; + } + + @Override + public void cancel(InterpreterContext context) { + DefaultExecutor executor = executorMap.remove(context.getParagraphId()); + if (executor != null) { + try { + executor.getWatchdog().destroyProcess(); + } catch (Exception e){ + log.error("error destroying executor for paragraphId: " + context.getParagraphId(), e); + } + } + } + + @Override + public FormType getFormType() { + return FormType.SIMPLE; + } + + @Override + public int getProgress(InterpreterContext context) { + + if (context == null || context.out == null) { + return 0; + } + + String output = context.out().toString(); + + Matcher m = COMPLETION.matcher(output); + double complete = 1.0; + while (m.find()) { + complete = Double.parseDouble(m.group(1)); + } + + return (int) Math.round(complete); + } + + @Override + public Scheduler getScheduler() { + return SchedulerFactory.singleton().createOrGetParallelScheduler( + JMHInterpreter.class.getName() + this.hashCode(), 10); + } + + public Map getExecutorMap() { + return executorMap; + } + + private static class ResourceNotFoundException extends RuntimeException { + public ResourceNotFoundException(String msg) { + super(msg); + } + } + + public static void hackLog(String msg) { +// try { +// if (Files.exists(Paths.get("/home/markmiller/Sync"))) { +// Files.writeString(Paths.get("/home/markmiller/Sync/hacklog.log"), msg + '\n', StandardOpenOption.APPEND); +// } +// } catch (IOException e) { +// e.printStackTrace(); +// } + } +} \ No newline at end of file diff --git a/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/JMHUtils.java b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/JMHUtils.java new file mode 100755 index 00000000000..e9f9fbb6dfc --- /dev/null +++ b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/JMHUtils.java @@ -0,0 +1,187 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.jmh; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +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.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.ExecuteException; +import org.apache.commons.exec.PumpStreamHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JMHUtils { + + private static final Logger log = LoggerFactory.getLogger(JMHUtils.class); + + public static final String USER_HOME = "user.home"; + private static final String JOVERFLOW_LOC = System.getProperty(USER_HOME) + "/Sync/joverflow"; + + private static final String JXRAY_LOC = System.getProperty(USER_HOME) + "/Sync/jxray"; + private static final String MAT_LOC = System.getProperty(USER_HOME) + "/Sync/mat"; + + private JMHUtils() { + } + + public static List processHeapDumpFolder(File directory, File resourceDir, Path resourcePath) throws IOException { + Path dest = Paths.get(resourceDir.getAbsolutePath(), resourcePath.toString()); + log.info("process heapdump folder: {} destDir: {}", directory, dest); + Files.createDirectories(dest); + String[] files = directory.list((dir, name) -> name.endsWith(".hprof")); + List paths = new ArrayList<>(); + for (String file : files) { + log.info("Processing heapdump: {}", file); + paths.addAll(processJXray(directory, file, dest, resourcePath)); + paths.addAll(processJoverflow(directory, file, dest, resourcePath)); + paths.addAll(processMat(directory, file, dest, resourcePath)); + } + + return paths; + } + + private static List processMat(File directory, String file, Path destDir, Path resourcePath) throws IOException { + DefaultExecutor executor = new DefaultExecutor(); + executor.setWorkingDirectory(new File(MAT_LOC)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); + executor.setStreamHandler(new PumpStreamHandler(baos, baos)); + CommandLine cmdLine = CommandLine.parse("bash"); + + cmdLine.addArgument("ParseHeapDump.sh", false); + cmdLine.addArgument(new File(directory, file).getAbsolutePath(), false); + cmdLine.addArgument("org.eclipse.mat.api:suspects", false); + cmdLine.addArgument("org.eclipse.mat.api:top_components", false); + cmdLine.addArgument("org.eclipse.mat.api:overview", false); + + //System.out.println("execute: " + cmdLine); + try { + int exitVal = executor.execute(cmdLine); + } catch(ExecuteException e) { + log.error("{} :{}", e.getMessage(), baos.toString(UTF_8)); + throw e; + } + + String[] files = directory.list((dir, name) -> name.endsWith(".zip")); + List paths = new ArrayList<>(); + for (String zipFile : files) { + unzip(new File(directory, zipFile), new File(new File(destDir.toAbsolutePath().toString(), "mat"), zipFile)); + paths.add(resourcePath + "/mat/" + zipFile + "/index.html"); + } + return paths; + } + + private static List processJoverflow(File directory, String file, Path destDir, Path resourcePath) throws IOException { + DefaultExecutor executor = new DefaultExecutor(); + executor.setWorkingDirectory(new File(JOVERFLOW_LOC)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); + executor.setStreamHandler(new PumpStreamHandler(baos, baos)); + CommandLine cmdLine = CommandLine.parse("java"); + + cmdLine.addArgument("-cp", false); + cmdLine.addArgument("joverflow.jar:jmc.common.jar:org.lz4.lz4-java_1.7.1.jar", false); + cmdLine.addArgument("org.openjdk.jmc.joverflow.Main", false); + cmdLine.addArgument("-use_mmap", false); + cmdLine.addArgument(new File(directory, file).getAbsolutePath(), false); + + //System.out.println("execute: " + cmdLine); + int exitVal = executor.execute(cmdLine); + + Path outDir = Paths.get(destDir.toAbsolutePath().toString(), "joverflow"); + Files.createDirectories(outDir); + Files.writeString(Paths.get(outDir.toString(), "report.txt"), baos.toString(UTF_8)); + return List.of(Paths.get(resourcePath.toString(), "joverflow", "report.txt").toString()); + } + + private static List processJXray(File directory, String file, Path destDir, Path resourcePath) throws IOException { + DefaultExecutor executor = new DefaultExecutor(); + executor.setWorkingDirectory(new File(JXRAY_LOC)); + ByteArrayOutputStream baos = new ByteArrayOutputStream(1024); + executor.setStreamHandler(new PumpStreamHandler(baos, baos)); + CommandLine cmdLine = CommandLine.parse("bash"); + + File outDir = new File(destDir.toAbsolutePath().toString(), "jxray"); + outDir.mkdirs(); + + cmdLine.addArgument("jxray.sh", false); + cmdLine.addArgument(new File(directory, file).getAbsolutePath(), false); + cmdLine.addArgument(new File(outDir, "index.html").getAbsolutePath(), false); + + //System.out.println("execute: " + cmdLine); + int exitVal = executor.execute(cmdLine); + + return List.of(Paths.get(resourcePath.toString(), "jxray", "index.html").toString()); + + } + + public static void unzip(File fileZip, File destDir) throws IOException { + + final byte[] buffer = new byte[1024]; + final ZipInputStream zis = new ZipInputStream(Files.newInputStream(fileZip.toPath())); + ZipEntry zipEntry = zis.getNextEntry(); + while (zipEntry != null) { + final File newFile = newFile(destDir, zipEntry); + if (zipEntry.isDirectory()) { + if (!newFile.isDirectory() && !newFile.mkdirs()) { + throw new IOException("Failed to create directory " + newFile); + } + } else { + File parent = newFile.getParentFile(); + if (!parent.isDirectory() && !parent.mkdirs()) { + throw new IOException("Failed to create directory " + parent); + } + + final FileOutputStream fos = new FileOutputStream(newFile); + int len; + while ((len = zis.read(buffer)) > 0) { + fos.write(buffer, 0, len); + } + fos.close(); + } + zipEntry = zis.getNextEntry(); + } + zis.closeEntry(); + zis.close(); + } + + /** + * https://snyk.io/research/zip-slip-vulnerability + */ + public static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException { + File destFile = new File(destinationDir, zipEntry.getName()); + + String destDirPath = destinationDir.getCanonicalPath(); + String destFilePath = destFile.getCanonicalPath(); + + if (!destFilePath.startsWith(destDirPath + File.separator)) { + throw new IOException("Entry is outside of the target dir: " + zipEntry.getName()); + } + + return destFile; + } + +} diff --git a/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/JsonMapFlattener.java b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/JsonMapFlattener.java new file mode 100755 index 00000000000..bf9f02a9257 --- /dev/null +++ b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/JsonMapFlattener.java @@ -0,0 +1,438 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.jmh; + +import org.apache.jmh.noggit.JSONParser; +import org.apache.jmh.noggit.ObjectBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.function.UnaryOperator; + +public abstract class JsonMapFlattener { + + private static final Logger log = LoggerFactory.getLogger(JsonMapFlattener.class); + public static final String PRIMARY_METRIC_SCORE_UNIT = "primaryMetric.scoreUnit"; + public static final String PRIMARY_METRIC_SCORE = "primaryMetric.score"; + public static final String SCORE_UNIT = "scoreUnit"; + + public static final String BENCHMARK = "benchmark"; + private static List PREVENT_CONSTANT_PULL = Arrays.asList("score", "metric", BENCHMARK); + + private JsonMapFlattener() { + } + + public static String mapToHtmlTable(Map map) { + + StringBuilder stringMapTable = new StringBuilder(); + stringMapTable.append(""); + + for (Map.Entry entry : map.entrySet()) { + stringMapTable.append(""); + // System.out.println(entry.getKey() + " = " + entry.getValue()); + } + + return stringMapTable.toString(); + } + + public static Map flatten(Map map) { + Map result = new LinkedHashMap<>(); + flatten("", map.entrySet().iterator(), result, UnaryOperator.identity()); + return result; + } + + public static Map flattenToStringMap(Map map) { + Map result = new LinkedHashMap<>(); + flatten("", map.entrySet().iterator(), result, it -> it == null ? null : it.toString()); + return result; + } + + private static void flatten(String prefix, Iterator> inputMap, Map resultMap, + Function valueTransformer) { + prefix = prefix.trim(); + if (prefix.equals("secondaryMetrics")) { + prefix = ""; + } + if (prefix.trim().length() > 0) { + prefix = prefix + "."; + } + while (inputMap.hasNext()) { + Entry entry = inputMap.next(); + flatten(prefix.concat(entry.getKey()), entry.getValue(), resultMap, valueTransformer); + } + } + + @SuppressWarnings("unchecked") private static void flatten(String prefix, Object source, Map result, Function valueTransformer) { + if (source instanceof Iterable) { + flattenCollection(prefix, (Iterable) source, result, valueTransformer); + return; + } + if (source instanceof Map) { + flatten(prefix, ((Map) source).entrySet().iterator(), result, valueTransformer); + return; + } + ((Map) result).put(prefix, valueTransformer.apply(source)); + } + + private static void flattenCollection(String prefix, Iterable iterable, Map resultMap, Function valueTransformer) { + int cnt = 0; + for (Object element : iterable) { + flatten(prefix + "[" + cnt + "]", element, resultMap, valueTransformer); + cnt++; + } + } + + public static StringBuilder zeppelinTableFromFlatMapHeader(Map rows) { + final StringBuilder table = new StringBuilder(256); + + int[] cnt = new int[] {0}; + rows.keySet().forEach(key -> { + + table.append(key); + if (cnt[0]++ < rows.size() - 1) { + table.append("\t"); + } + }); + + table.append("\n"); + + return table; + } + + public static StringBuilder zeppelinTableFromFlatMapAddTo(StringBuilder table, Map rows) { + int[] cnt = new int[] {0}; + rows.values().forEach(value -> { + table.append(value); + if (cnt[0]++ < rows.size() - 1) { + table.append("\t"); + } + }); + table.append("\n"); + + return table; + } + + public static String[] resultsToZeppelinTable(String json) throws IOException { + log.info("json=\n{}", json); + List> flatMaps = flatMapsFromResult(json); + + //System.out.println("flatMaps: " + flatMaps); + + return flatMapsToZeppelinTable(flatMaps); + } + + + public static String[] flatMapsToZeppelinTable(List> flatMaps) { + String[] returnString = new String[2]; + + + try { + + //System.out.println("flatMaps: " + flatMaps); + + Map constantData = removeConstantData(flatMaps); + + Iterator> flatMapIt = flatMaps.iterator(); + + // System.out.println("handle header"); + StringBuilder[] sb = new StringBuilder[1]; + Map firstMap = flatMapIt.next(); + sb[0] = zeppelinTableFromFlatMapHeader(firstMap); + + // System.out.println("handle rows"); + Set headerKeys = firstMap.keySet(); + flatMaps.forEach(fm -> { + Iterator> it = fm.entrySet().iterator(); + it.forEachRemaining(entry -> { + if (!headerKeys.contains(entry.getKey()) && !entry.getKey().startsWith( + PRIMARY_METRIC_SCORE)) { + it.remove(); + } + } ); + }); + + + flatMaps.forEach(fm -> zeppelinTableFromFlatMapAddTo(sb[0], fm)); + + returnString[0] = mapToHtmlTable(constantData); + + returnString[1] = sb[0].toString(); + + + // } else { + // throw new UnsupportedOperationException(); + // @SuppressWarnings("unchecked") + // Map flatMap = flatten((Map) value); + // //System.out.println("flat map:\n" + flatMap); + // System.out.println(zeppelinTableFromFlatMapHeader(flatMap)); + // } + + } catch (Exception e) { + throw new RuntimeException(e); + } + + // String zeppelinOutput = returnString[0].toString(); + // System.out.println("zeppelinOutput: " + zeppelinOutput); + + return returnString; + } + + public static List> flatMapsFromResult(String json) throws IOException { + //System.out.println("json string: " + json); + JSONParser jsonParser = new JSONParser(json); + List> value = (List>) new ObjectBuilder(jsonParser).getValStrict(); + +// if (!(value instanceof List)) { +// } else { +// throw new UnsupportedOperationException(); +// } + + List> flatMaps = new ArrayList<>(); + + //Iterator> it = ((List>) value).iterator(); + //Map first = it.next(); + //Map flatMap = flatten(first); + + + // flatMaps.add(flatMap); + AtomicInteger sz = new AtomicInteger(-1); + value.forEach(map -> { + log.info("json size {}",value.size()); + + @SuppressWarnings("rawtypes") Map fm = flatten(map); + + if (sz.get() > -1) { + // assert fm.size() == sz.get() : fm.keySet(); + } + sz.set(fm.size()); + log.info("map keys: {}", fm.keySet()); + log.info("flat map: {}", fm); + + Set headerKeys = map.keySet(); + flatMaps.forEach(m -> { + Iterator> it = m.entrySet().iterator(); + it.forEachRemaining(entry -> { + if (entry.getKey().equals(PRIMARY_METRIC_SCORE) || entry.getKey().equals("ops/s") || entry.getKey().equals( + PRIMARY_METRIC_SCORE_UNIT)) { + return; + } + if (!headerKeys.contains(entry.getKey())) { + it.remove(); + } + } ); + }); + + if (!flatMaps.isEmpty()) { + Set headerKeys2 = flatMaps.iterator().next().keySet(); + Iterator> it = map.entrySet().iterator(); + it.forEachRemaining(e -> { + if (e.getKey().equals(PRIMARY_METRIC_SCORE) || e.getKey().equals("ops/s") || e.getKey().equals( + SCORE_UNIT)|| e.getKey().equals( + PRIMARY_METRIC_SCORE_UNIT)) { + return; + } + if (!headerKeys2.contains(e.getKey())) { + it.remove(); + } + + }); + } + + flatMaps.add(process(fm)); + }); + return flatMaps; + } +// + private static Map process(Map map) { + Iterator> it = map.entrySet().iterator(); + it.forEachRemaining(e -> { + if (e.getKey().toLowerCase(Locale.ROOT).contains("raw") || e.getKey().toLowerCase(Locale.ROOT).contains("error") + || e.getKey().toLowerCase(Locale.ROOT).contains("confidence") || e.getKey().toLowerCase(Locale.ROOT).contains("percentile") + || e.getKey().toLowerCase(Locale.ROOT).contains("jvmargs")) { // + it.remove(); + } + }); + return map; + } + + private static Map removeConstantData(List> mapList) { + Map constantData = new LinkedHashMap<>(); + // List doesNotVary = new ArrayList<>(); + Iterator> it = mapList.iterator(); + if (!it.hasNext()) return constantData; + constantData.putAll(it.next()); + + for (Map map : mapList) { + Set> entries = map.entrySet(); + for (Entry entry : entries) { + Object prev = constantData.get(entry.getKey()); + + if (!entry.getValue().equals(prev) || preventConstantPull(entry.getKey())) { + constantData.remove(entry.getKey()); + } + } + + String shortBenchmark = null; + if (map.containsKey(BENCHMARK)) { + String val = (String) map.get(BENCHMARK); + shortBenchmark = val.substring(val.lastIndexOf('.') + 1); + map.replace(BENCHMARK, shortBenchmark); + } + + if (map.containsKey(PRIMARY_METRIC_SCORE)) { + + Map newMap = new LinkedHashMap<>(map.size()); + + Object val = map.remove(PRIMARY_METRIC_SCORE); + Object unit = map.remove(PRIMARY_METRIC_SCORE_UNIT); + + if (shortBenchmark != null) { + map.remove(BENCHMARK); + newMap.put(BENCHMARK, shortBenchmark); + } + newMap.put(unit.toString(), val); + newMap.putAll(map); + + map.clear(); + map.putAll(newMap); + } + + + } + + for (String key : constantData.keySet()) { + for (Map map : mapList) { + map.remove(key); + } + } + for (Map map : mapList) { + log.info("map after constant data remove={}", map); + } + + + return constantData; + } + + private static boolean preventConstantPull(String key) { + boolean prevent = false; + for (String val : PREVENT_CONSTANT_PULL) { + if (key.toLowerCase(Locale.ROOT).contains(val.toLowerCase(Locale.ROOT))) { + prevent = true; + break; + } + } + return prevent; + } + + public static String flatMapsToJSArray( List> flatMaps) { + String returnString; +// data = Array(26) [ +// 0: Object { +// name: "E" +// value: 0.12702 +// } +// 1: Object { +// name: "T" +// value: 0.09056 +// } +// 2: Object {name: "A", value: 0.08167} +// 3: Object {name: "O", value: 0.07507} +// 4: Object {name: "I", value: 0.06966} +// 5: Object {name: "N", value: 0.06749} +// 6: Object {name: "S", value: 0.06327} +// 7: Object {name: "H", value: 0.06094} +// 8: Object {name: "R", value: 0.05987} +// 9: Object {name: "D", value: 0.04253} +// 10: Object {name: "L", value: 0.04025} +// 11: Object {name: "C", value: 0.02782} +// 12: Object {name: "U", value: 0.02758} +// 13: Object {name: "M", value: 0.02406} +// 14: Object {name: "W", value: 0.0236} +// 15: Object {name: "F", value: 0.02288} +// 16: Object {name: "G", value: 0.02015} +// 17: Object {name: "Y", value: 0.01974} +// 18: Object {name: "P", value: 0.01929} +// 19: Object {name: "B", value: 0.01492} +// 20: Object {name: "V", value: 0.00978} +// 21: Object {name: "K", value: 0.00772} +// 22: Object {name: "J", value: 0.00153} +// 23: Object {name: "X", value: 0.0015} +// 24: Object {name: "Q", value: 0.00095} +// 25: Object {name: "Z", value: 0.00074} +// columns: Array(2) [ +// 0: "letter" +// 1: "frequency" +//] +// format: "%" +// y: "↑ Frequency" +//] + + try { + + //System.out.println("flatMaps: " + flatMaps); + + Map constantData = removeConstantData(flatMaps); + + Iterator> flatMapIt = flatMaps.iterator(); + + // System.out.println("handle header"); + StringBuilder[] sb = new StringBuilder[1]; + sb[0] = zeppelinTableFromFlatMapHeader(flatMapIt.next()); + + // System.out.println("handle rows"); + flatMapIt.forEachRemaining(fm -> zeppelinTableFromFlatMapAddTo(sb[0], fm)); + + + + returnString = sb[0].toString(); + + + // } else { + // throw new UnsupportedOperationException(); + // @SuppressWarnings("unchecked") + // Map flatMap = flatten((Map) value); + // //System.out.println("flat map:\n" + flatMap); + // System.out.println(zeppelinTableFromFlatMapHeader(flatMap)); + // } + + } catch (Exception e) { + throw new RuntimeException(e); + } + + // String zeppelinOutput = returnString[0].toString(); + // System.out.println("zeppelinOutput: " + zeppelinOutput); + + return returnString; + } + + public static void main(String[] args) throws IOException { + System.out.println(resultsToZeppelinTable("/data3-ext4/apache-solr/solr/benchmark")); + } + +} \ No newline at end of file diff --git a/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/StatusServlet.java b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/StatusServlet.java new file mode 100755 index 00000000000..dcf6a53d0ec --- /dev/null +++ b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/StatusServlet.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.jmh; + +import jakarta.servlet.ServletConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.zeppelin.interpreter.InterpreterContext; + +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.ConcurrentHashMap; +import java.util.jar.Manifest; + +public class StatusServlet extends HttpServlet { + + public StatusServlet() { + super(); + } + + @Override public void init(ServletConfig config) throws ServletException { + super.init(config); + } + + @Override + public void init() throws ServletException { + /* Nothing to do */ + } + + @Override + public void destroy() { + /* Nothing to do */ + } + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + response.setContentType("text/html; charset=UTF-8"); + response.setStatus(HttpServletResponse.SC_OK); + ConcurrentHashMap contextMap = (ConcurrentHashMap) request.getServletContext() + .getAttribute("contextMap"); + response.getWriter().println("# in contextMap=" + contextMap.size()); + + contextMap.forEach((s, interpreterContext) -> { + try { + response.getWriter().println("LocalProperties: " + s + " ->" + interpreterContext.getLocalProperties()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + + response.getWriter().println("MapStore: " + request.getServletContext().getAttribute("artifactChaff")); + + + try { + // ServletContext context = request.getServletContext(); + InputStream inputStream = JMHInterpreter.class.getResourceAsStream("META-INF/MANIFEST.MF"); + Manifest manifest = new Manifest(inputStream); + // do stuff with it + // Attributes attr = manifest.getMainAttributes(); + // String value = attr.getValue("Manifest-Version"); + response.getWriter().println("Manifest:\n" + manifest.getEntries()); + + + } catch (IOException E) { + // handle + } + + } +} diff --git a/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/CharArr.java b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/CharArr.java new file mode 100755 index 00000000000..681e5966bd7 --- /dev/null +++ b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/CharArr.java @@ -0,0 +1,394 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.jmh.noggit; + + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.CharBuffer; + +public class CharArr implements CharSequence, Appendable { + protected char[] buf; + protected int start; + protected int end; + + public CharArr() { + this(32); + } + + public CharArr(int size) { + buf = new char[size]; + } + + public CharArr(char[] arr, int start, int end) { + set(arr, start, end); + } + + public void setStart(int start) { + this.start = start; + } + + public void setEnd(int end) { + this.end = end; + } + + public void set(char[] arr, int start, int end) { + this.buf = arr; + this.start = start; + this.end = end; + } + + public char[] getArray() { + return buf; + } + + public int getStart() { + return start; + } + + public int getEnd() { + return end; + } + + public int size() { + return end - start; + } + + @Override + public int length() { + return size(); + } + + /** + * The capacity of the buffer when empty (getArray().size()) + */ + public int capacity() { + return buf.length; + } + + + @Override + public char charAt(int index) { + return buf[start + index]; + } + + @Override + public CharArr subSequence(int start, int end) { + return new CharArr(buf, this.start + start, this.start + end); + } + + public int read() throws IOException { + if (start >= end) return -1; + return buf[start++]; + } + + public int read(char cbuf[], int off, int len) { + //TODO + return 0; + } + + public void unsafeWrite(char b) { + buf[end++] = b; + } + + public void unsafeWrite(int b) { + unsafeWrite((char) b); + } + + public void unsafeWrite(char b[], int off, int len) { + System.arraycopy(b, off, buf, end, len); + end += len; + } + + protected void resize(int len) { + char newbuf[] = new char[Math.max(buf.length << 1, len)]; + System.arraycopy(buf, start, newbuf, 0, size()); + buf = newbuf; + } + + public void reserve(int num) { + if (end + num > buf.length) resize(end + num); + } + + public void write(char b) { + if (end >= buf.length) { + resize(end + 1); + } + unsafeWrite(b); + } + + public final void write(int b) { + write((char) b); + } + + public final void write(char[] b) { + write(b, 0, b.length); + } + + public void write(char b[], int off, int len) { + reserve(len); + unsafeWrite(b, off, len); + } + + public final void write(CharArr arr) { + write(arr.buf, arr.start, arr.end - arr.start); + } + + public final void write(String s) { + write(s, 0, s.length()); + } + + public void write(String s, int stringOffset, int len) { + reserve(len); + s.getChars(stringOffset, len, buf, end); + end += len; + } + + public void flush() { + } + + public final void reset() { + start = end = 0; + } + + public void close() { + } + + public char[] toCharArray() { + char newbuf[] = new char[size()]; + System.arraycopy(buf, start, newbuf, 0, size()); + return newbuf; + } + + + @Override + public String toString() { + return new String(buf, start, size()); + } + + public int read(CharBuffer cb) throws IOException { + + /*** + int sz = size(); + if (sz<=0) return -1; + if (sz>0) cb.put(buf, start, sz); + return -1; + ***/ + + int sz = size(); + if (sz > 0) cb.put(buf, start, sz); + start = end; + while (true) { + fill(); + int s = size(); + if (s == 0) return sz == 0 ? -1 : sz; + sz += s; + cb.put(buf, start, s); + } + } + + + public int fill() throws IOException { + return 0; // or -1? + } + + //////////////// Appendable methods ///////////// + @Override + public final Appendable append(CharSequence csq) throws IOException { + return append(csq, 0, csq.length()); + } + + @Override + public Appendable append(CharSequence csq, int start, int end) throws IOException { + write(csq.subSequence(start, end).toString()); + return null; + } + + @Override + public final Appendable append(char c) throws IOException { + write(c); + return this; + } + + + static class NullCharArr extends CharArr { + public NullCharArr() { + super(new char[1], 0, 0); + } + + @Override + public void unsafeWrite(char b) { + } + + @Override + public void unsafeWrite(char b[], int off, int len) { + } + + @Override + public void unsafeWrite(int b) { + } + + @Override + public void write(char b) { + } + + @Override + public void write(char b[], int off, int len) { + } + + @Override + public void reserve(int num) { + } + + @Override + protected void resize(int len) { + } + + @Override + public Appendable append(CharSequence csq, int start, int end) throws IOException { + return this; + } + + @Override + public char charAt(int index) { + return 0; + } + + @Override + public void write(String s, int stringOffset, int len) { + } + } + + + // IDEA: a subclass that refills the array from a reader? + class CharArrReader extends CharArr { + protected final Reader in; + + public CharArrReader(Reader in, int size) { + super(size); + this.in = in; + } + + @Override + public int read() throws IOException { + if (start >= end) fill(); + return start >= end ? -1 : buf[start++]; + } + + @Override + public int read(CharBuffer cb) throws IOException { + // empty the buffer and then read direct + int sz = size(); + if (sz > 0) cb.put(buf, start, end); + int sz2 = in.read(cb); + if (sz2 >= 0) return sz + sz2; + return sz > 0 ? sz : -1; + } + + @Override + public int fill() throws IOException { + if (start >= end) { + reset(); + } else if (start > 0) { + System.arraycopy(buf, start, buf, 0, size()); + end = size(); + start = 0; + } + /*** + // fill fully or not??? + do { + int sz = in.read(buf,end,buf.length-end); + if (sz==-1) return; + end+=sz; + } while (end < buf.length); + ***/ + + int sz = in.read(buf, end, buf.length - end); + if (sz > 0) end += sz; + return sz; + } + + } + + + class CharArrWriter extends CharArr { + protected Writer sink; + + @Override + public void flush() { + try { + sink.write(buf, start, end - start); + } catch (IOException e) { + throw new RuntimeException(e); + } + start = end = 0; + } + + @Override + public void write(char b) { + if (end >= buf.length) { + flush(); + } + unsafeWrite(b); + } + + @Override + public void write(char b[], int off, int len) { + int space = buf.length - end; + if (len < space) { + unsafeWrite(b, off, len); + } else if (len < buf.length) { + unsafeWrite(b, off, space); + flush(); + unsafeWrite(b, off + space, len - space); + } else { + flush(); + try { + sink.write(b, off, len); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + @Override + public void write(String s, int stringOffset, int len) { + int space = buf.length - end; + if (len < space) { + s.getChars(stringOffset, stringOffset + len, buf, end); + end += len; + } else if (len < buf.length) { + // if the data to write is small enough, buffer it. + s.getChars(stringOffset, stringOffset + space, buf, end); + flush(); + s.getChars(stringOffset + space, stringOffset + len, buf, 0); + end = len - space; + } else { + flush(); + // don't buffer, just write to sink + try { + sink.write(s, stringOffset, len); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + } + } +} diff --git a/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/JSONParser.java b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/JSONParser.java new file mode 100755 index 00000000000..c294ff1a863 --- /dev/null +++ b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/JSONParser.java @@ -0,0 +1,1296 @@ +/* + * Copyright 2006- Yonik Seeley + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.jmh.noggit; + +import java.io.IOException; +import java.io.Reader; + +public class JSONParser { + + /** + * Event indicating a JSON string value, including member names of objects + */ + public static final int STRING = 1; + /** + * Event indicating a JSON number value which fits into a signed 64 bit integer + */ + public static final int LONG = 2; + /** + * Event indicating a JSON number value which has a fractional part or an exponent + * and with string length <= 23 chars not including sign. This covers + * all representations of normal values for Double.toString(). + */ + public static final int NUMBER = 3; + /** + * Event indicating a JSON number value that was not produced by toString of any + * Java primitive numerics such as Double or Long. It is either + * an integer outside the range of a 64 bit signed integer, or a floating + * point value with a string representation of more than 23 chars. + */ + public static final int BIGNUMBER = 4; + /** + * Event indicating a JSON boolean + */ + public static final int BOOLEAN = 5; + /** + * Event indicating a JSON null + */ + public static final int NULL = 6; + /** + * Event indicating the start of a JSON object + */ + public static final int OBJECT_START = 7; + /** + * Event indicating the end of a JSON object + */ + public static final int OBJECT_END = 8; + /** + * Event indicating the start of a JSON array + */ + public static final int ARRAY_START = 9; + /** + * Event indicating the end of a JSON array + */ + public static final int ARRAY_END = 10; + /** + * Event indicating the end of input has been reached + */ + public static final int EOF = 11; + + + /** + * Flags to control parsing behavior + */ + public static final int ALLOW_COMMENTS = 1 << 0; + public static final int ALLOW_SINGLE_QUOTES = 1 << 1; + public static final int ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER = 1 << 2; + public static final int ALLOW_UNQUOTED_KEYS = 1 << 3; + public static final int ALLOW_UNQUOTED_STRING_VALUES = 1 << 4; + /** + * ALLOW_EXTRA_COMMAS causes any number of extra commas in arrays and objects to be ignored + * Note that a trailing comma in [] would be [,] (hence calling the feature "trailing" commas + * is either limiting or misleading. Since trailing commas is fundamentally incompatible with any future + * "fill-in-missing-values-with-null", it was decided to extend this feature to handle any + * number of extra commas. + */ + public static final int ALLOW_EXTRA_COMMAS = 1 << 5; + public static final int ALLOW_MISSING_COLON_COMMA_BEFORE_OBJECT = 1 << 6; + public static final int OPTIONAL_OUTER_BRACES = 1 << 7; + + public static final int FLAGS_STRICT = 0; + public static final int FLAGS_DEFAULT = ALLOW_COMMENTS | ALLOW_SINGLE_QUOTES | ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER | ALLOW_UNQUOTED_KEYS | ALLOW_UNQUOTED_STRING_VALUES | ALLOW_EXTRA_COMMAS; + + public static class ParseException extends RuntimeException { + public ParseException(String msg) { + super(msg); + } + } + + public static String getEventString(int e) { + switch (e) { + case STRING: + return "STRING"; + case LONG: + return "LONG"; + case NUMBER: + return "NUMBER"; + case BIGNUMBER: + return "BIGNUMBER"; + case BOOLEAN: + return "BOOLEAN"; + case NULL: + return "NULL"; + case OBJECT_START: + return "OBJECT_START"; + case OBJECT_END: + return "OBJECT_END"; + case ARRAY_START: + return "ARRAY_START"; + case ARRAY_END: + return "ARRAY_END"; + case EOF: + return "EOF"; + } + return "Unknown: " + e; + } + + private static final CharArr devNull = new CharArr.NullCharArr(); + + protected int flags = FLAGS_DEFAULT; + + protected final char[] buf; // input buffer with JSON text in it + protected int start; // current position in the buffer + protected int end; // end position in the buffer (one past last valid index) + protected final Reader in; // optional reader to obtain data from + protected boolean eof = false; // true if the end of the stream was reached. + protected long gpos; // global position = gpos + start + + protected int event; // last event read + + protected int stringTerm; // The terminator for the last string we read: single quote, double quote, or 0 for unterminated. + + protected boolean missingOpeningBrace = false; + + public JSONParser(Reader in) { + this(in, new char[8192]); + // 8192 matches the default buffer size of a BufferedReader so double + // buffering of the data is avoided. + } + + public JSONParser(Reader in, char[] buffer) { + this.in = in; + this.buf = buffer; + } + + // idea - if someone passes us a CharArrayReader, we could + // directly use that buffer as it's protected. + + public JSONParser(char[] data, int start, int end) { + this.in = null; + this.buf = data; + this.start = start; + this.end = end; + } + + public JSONParser(String data) { + this(data, 0, data.length()); + } + + public JSONParser(String data, int start, int end) { + this.in = null; + this.start = start; + this.end = end; + this.buf = new char[end - start]; + data.getChars(start, end, buf, 0); + } + + public int getFlags() { + return flags; + } + + public int setFlags(int flags) { + int oldFlags = flags; + this.flags = flags; + return oldFlags; + } + + // temporary output buffer + private final CharArr out = new CharArr(64); + + // We need to keep some state in order to (at a minimum) know if + // we should skip ',' or ':'. + private byte[] stack = new byte[16]; + private int ptr = 0; // pointer into the stack of parser states + private byte state = 0; // current parser state + + // parser states stored in the stack + private static final byte DID_OBJSTART = 1; // '{' just read + private static final byte DID_ARRSTART = 2; // '[' just read + private static final byte DID_ARRELEM = 3; // array element just read + private static final byte DID_MEMNAME = 4; // object member name (map key) just read + private static final byte DID_MEMVAL = 5; // object member value (map val) just read + + // info about value that was just read (or is in the middle of being read) + private int valstate; + + // push current parser state (use at start of new container) + private final void push() { + if (ptr >= stack.length) { + // doubling here is probably overkill, but anything that needs to double more than + // once (32 levels deep) is very atypical anyway. + byte[] newstack = new byte[stack.length << 1]; + System.arraycopy(stack, 0, newstack, 0, stack.length); + stack = newstack; + } + stack[ptr++] = state; + } + + // pop parser state (use at end of container) + private final void pop() { + if (--ptr < 0) { + throw err("Unbalanced container"); + } else { + state = stack[ptr]; + } + } + + protected void fill() throws IOException { + if (in != null) { + gpos += end; + start = 0; + int num = in.read(buf, 0, buf.length); + end = num >= 0 ? num : 0; + } + if (start >= end) eof = true; + } + + private void getMore() throws IOException { + fill(); + if (start >= end) { + throw err(null); + } + } + + protected int getChar() throws IOException { + if (start >= end) { + fill(); + if (start >= end) return -1; + } + return buf[start++]; + } + + /** + * Returns true if the given character is considered to be whitespace. + * One difference between Java's Character.isWhitespace() is that this method + * considers a hard space (non-breaking space, or nbsp) to be whitespace. + */ + protected static final boolean isWhitespace(int ch) { + return (Character.isWhitespace(ch) || ch == 0x00a0); + } + + private static final long WS_MASK = (1L << ' ') | (1L << '\t') | (1L << '\r') | (1L << '\n') | (1L << '#') | (1L << '/') | (0x01); // set 1 bit so 0xA0 will be flagged as whitespace + + protected int getCharNWS() throws IOException { + for (; ; ) { + int ch = getChar(); + // getCharNWS is normally called in the context of expecting certain JSON special characters + // such as ":}"]," + // all of these characters are below 64 (including comment chars '/' and '#', so we can make this the fast path + // even w/o checking the range first. We'll only get some false-positives while using bare strings (chars "IJMc") + if (((WS_MASK >> ch) & 0x01) == 0) { + return ch; + } else if (ch <= ' ') { // this will only be true if one of the whitespace bits was set + continue; + } else if (ch == '/') { + getSlashComment(); + } else if (ch == '#') { + getNewlineComment(); + } else if (!isWhitespace(ch)) { // we'll only reach here with certain bare strings, errors, or strange whitespace like 0xa0 + return ch; + } + + /*** + // getCharNWS is normally called in the context of expecting certain JSON special characters + // such as ":}"]," + // all of these characters are below 64 (including comment chars '/' and '#', so we can make this the fast path + if (ch < 64) { + if (((WS_MASK >> ch) & 0x01) == 0) return ch; + if (ch <= ' ') continue; // whitespace below a normal space + if (ch=='/') { + getSlashComment(); + } else if (ch=='#') { + getNewlineComment(); + } + } else if (!isWhitespace(ch)) { // check for higher whitespace like 0xA0 + return ch; + } + ***/ + + /** older code + switch (ch) { + case ' ' : + case '\t' : + case '\r' : + case '\n' : + continue outer; + case '#' : + getNewlineComment(); + continue outer; + case '/' : + getSlashComment(); + continue outer; + default: + return ch; + } + **/ + } + } + + protected int getCharNWS(int ch) throws IOException { + for (; ; ) { + // getCharNWS is normally called in the context of expecting certain JSON special characters + // such as ":}"]," + // all of these characters are below 64 (including comment chars '/' and '#', so we can make this the fast path + // even w/o checking the range first. We'll only get some false-positives while using bare strings (chars "IJMc") + if (((WS_MASK >> ch) & 0x01) == 0) { + return ch; + } else if (ch <= ' ') { // this will only be true if one of the whitespace bits was set + // whitespace... get new char at bottom of loop + } else if (ch == '/') { + getSlashComment(); + } else if (ch == '#') { + getNewlineComment(); + } else if (!isWhitespace(ch)) { // we'll only reach here with certain bare strings, errors, or strange whitespace like 0xa0 + return ch; + } + ch = getChar(); + } + } + + protected int getCharExpected(int expected) throws IOException { + for (; ; ) { + int ch = getChar(); + if (ch == expected) return expected; + if (ch == ' ') continue; + return getCharNWS(ch); + } + } + + protected void getNewlineComment() throws IOException { + // read a # or a //, so go until newline + for (; ; ) { + int ch = getChar(); + // don't worry about DOS /r/n... we'll stop on the \r and let the rest of the whitespace + // eater consume the \n + if (ch == '\n' || ch == '\r' || ch == -1) { + return; + } + } + } + + protected void getSlashComment() throws IOException { + int ch = getChar(); + if (ch == '/') { + getNewlineComment(); + return; + } + + if (ch != '*') { + throw err("Invalid comment: expected //, /*, or #"); + } + + ch = getChar(); + for (; ; ) { + if (ch == '*') { + ch = getChar(); + if (ch == '/') { + return; + } else if (ch == '*') { + // handle cases of *******/ + continue; + } + } + if (ch == -1) { + return; + } + ch = getChar(); + } + } + + + protected boolean matchBareWord(char[] arr) throws IOException { + for (int i = 1; i < arr.length; i++) { + int ch = getChar(); + if (ch != arr[i]) { + if ((flags & ALLOW_UNQUOTED_STRING_VALUES) == 0) { + throw err("Expected " + new String(arr)); + } else { + stringTerm = 0; + out.reset(); + out.write(arr, 0, i); + if (!eof) { + start--; + } + return false; + } + } + } + + // if we don't allow bare strings, we don't need to check that the string actually terminates... just + // let things fail as the parser tries to continue + if ((flags & ALLOW_UNQUOTED_STRING_VALUES) == 0) { + return true; + } + + // check that the string actually terminates... for example trueX should return false + int ch = getChar(); + if (eof) { + return true; + } else if (!isUnquotedStringChar(ch)) { + start--; + return true; + } + + // we encountered something like "trueX" when matching "true" + stringTerm = 0; + out.reset(); + out.unsafeWrite(arr, 0, arr.length); + out.unsafeWrite(ch); + return false; + } + + protected ParseException err(String msg) { + // We can't tell if EOF was hit by comparing start<=end + // because the illegal char could have been the last in the buffer + // or in the stream. To deal with this, the "eof" var was introduced + if (!eof && start > 0) start--; // backup one char + String chs = "char=" + ((start >= end) ? "(EOF)" : "" + buf[start]); + String pos = "position=" + (gpos + start); + String tot = chs + ',' + pos + getContext(); + if (msg == null) { + if (start >= end) msg = "Unexpected EOF"; + else msg = "JSON Parse Error"; + } + return new ParseException(msg + ": " + tot); + } + + private String getContext() { + String context = ""; + if (start >= 0) { + context += " AFTER='" + errEscape(Math.max(start - 60, 0), start + 1) + "'"; + } + if (start < end) { + context += " BEFORE='" + errEscape(start + 1, start + 40) + "'"; + } + return context; + } + + private String errEscape(int a, int b) { + b = Math.min(b, end); + if (a >= b) return ""; + return new String(buf, a, b - a).replaceAll("\\s+", " "); + } + + + private boolean bool; // boolean value read + private long lval; // long value read + private int nstate; // current state while reading a number + private static final int HAS_FRACTION = 0x01; // nstate flag, '.' already read + private static final int HAS_EXPONENT = 0x02; // nstate flag, '[eE][+-]?[0-9]' already read + + /** + * Returns the long read... only significant if valstate==LONG after + * this call. firstChar should be the first numeric digit read. + */ + private long readNumber(int firstChar, boolean isNeg) throws IOException { + out.unsafeWrite(firstChar); // unsafe OK since we know output is big enough + // We build up the number in the negative plane since it's larger (by one) than + // the positive plane. + long v = '0' - firstChar; + // can't overflow a long in 18 decimal digits (i.e. 17 additional after the first). + // we also need 22 additional to handle double so we'll handle in 2 separate loops. + int i; + for (i = 0; i < 17; i++) { + int ch = getChar(); + // TODO: is this switch faster as an if-then-else? + switch (ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + v = v * 10 - (ch - '0'); + out.unsafeWrite(ch); + continue; + case '.': + out.unsafeWrite('.'); + valstate = readFrac(out, 22 - i); + return 0; + case 'e': + case 'E': + out.unsafeWrite(ch); + nstate = 0; + valstate = readExp(out, 22 - i); + return 0; + default: + // return the number, relying on nextEvent() to return an error + // for invalid chars following the number. + if (ch != -1) --start; // push back last char if not EOF + + valstate = LONG; + return isNeg ? v : -v; + } + } + + // after this, we could overflow a long and need to do extra checking + boolean overflow = false; + long maxval = isNeg ? Long.MIN_VALUE : -Long.MAX_VALUE; + + for (; i < 22; i++) { + int ch = getChar(); + switch (ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (v < (0x8000000000000000L / 10)) overflow = true; // can't multiply by 10 w/o overflowing + v *= 10; + int digit = ch - '0'; + if (v < maxval + digit) overflow = true; // can't add digit w/o overflowing + v -= digit; + out.unsafeWrite(ch); + continue; + case '.': + out.unsafeWrite('.'); + valstate = readFrac(out, 22 - i); + return 0; + case 'e': + case 'E': + out.unsafeWrite(ch); + nstate = 0; + valstate = readExp(out, 22 - i); + return 0; + default: + // return the number, relying on nextEvent() to return an error + // for invalid chars following the number. + if (ch != -1) --start; // push back last char if not EOF + + valstate = overflow ? BIGNUMBER : LONG; + return isNeg ? v : -v; + } + } + + + nstate = 0; + valstate = BIGNUMBER; + return 0; + } + + + // read digits right of decimal point + private int readFrac(CharArr arr, int lim) throws IOException { + nstate = HAS_FRACTION; // deliberate set instead of '|' + while (--lim >= 0) { + int ch = getChar(); + if (ch >= '0' && ch <= '9') { + arr.write(ch); + } else if (ch == 'e' || ch == 'E') { + arr.write(ch); + return readExp(arr, lim); + } else { + if (ch != -1) start--; // back up + return NUMBER; + } + } + return BIGNUMBER; + } + + + // call after 'e' or 'E' has been seen to read the rest of the exponent + private int readExp(CharArr arr, int lim) throws IOException { + nstate |= HAS_EXPONENT; + int ch = getChar(); + lim--; + + if (ch == '+' || ch == '-') { + arr.write(ch); + ch = getChar(); + lim--; + } + + // make sure at least one digit is read. + if (ch < '0' || ch > '9') { + throw err("missing exponent number"); + } + arr.write(ch); + + return readExpDigits(arr, lim); + } + + // continuation of readExpStart + private int readExpDigits(CharArr arr, int lim) throws IOException { + while (--lim >= 0) { + int ch = getChar(); + if (ch >= '0' && ch <= '9') { + arr.write(ch); + } else { + if (ch != -1) start--; // back up + return NUMBER; + } + } + return BIGNUMBER; + } + + private void continueNumber(CharArr arr) throws IOException { + if (arr != out) arr.write(out); + + if ((nstate & HAS_EXPONENT) != 0) { + readExpDigits(arr, Integer.MAX_VALUE); + return; + } + if (nstate != 0) { + readFrac(arr, Integer.MAX_VALUE); + return; + } + + for (; ; ) { + int ch = getChar(); + if (ch >= '0' && ch <= '9') { + arr.write(ch); + } else if (ch == '.') { + arr.write(ch); + readFrac(arr, Integer.MAX_VALUE); + return; + } else if (ch == 'e' || ch == 'E') { + arr.write(ch); + readExp(arr, Integer.MAX_VALUE); + return; + } else { + if (ch != -1) start--; + return; + } + } + } + + + private int hexval(int hexdig) { + if (hexdig >= '0' && hexdig <= '9') { + return hexdig - '0'; + } else if (hexdig >= 'A' && hexdig <= 'F') { + return hexdig + (10 - 'A'); + } else if (hexdig >= 'a' && hexdig <= 'f') { + return hexdig + (10 - 'a'); + } + throw err("invalid hex digit"); + } + + // backslash has already been read when this is called + private char readEscapedChar() throws IOException { + int ch = getChar(); + switch (ch) { + case '"': + return '"'; + case '\'': + return '\''; + case '\\': + return '\\'; + case '/': + return '/'; + case 'n': + return '\n'; + case 'r': + return '\r'; + case 't': + return '\t'; + case 'f': + return '\f'; + case 'b': + return '\b'; + case 'u': + return (char) ( + (hexval(getChar()) << 12) + | (hexval(getChar()) << 8) + | (hexval(getChar()) << 4) + | (hexval(getChar()))); + } + if ((flags & ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER) != 0 && ch != EOF) { + return (char) ch; + } + throw err("Invalid character escape"); + } + + // a dummy buffer we can use to point at other buffers + private final CharArr tmp = new CharArr(null, 0, 0); + + private CharArr readStringChars() throws IOException { + if (stringTerm == 0) { + // "out" will already contain the first part of the bare string, so don't reset it + readStringBare(out); + return out; + } + + char terminator = (char) stringTerm; + int i; + for (i = start; i < end; i++) { + char c = buf[i]; + if (c == terminator) { + tmp.set(buf, start, i); // directly use input buffer + start = i + 1; // advance past last '"' + return tmp; + } else if (c == '\\') { + break; + } + } + out.reset(); + readStringChars2(out, i); + return out; + } + + + // middle is the pointer to the middle of a buffer to start scanning for a non-string + // character ('"' or "/"). start<=middle= end) { + arr.write(buf, start, middle - start); + start = middle; + getMore(); + middle = start; + } + int ch = buf[middle++]; + if (ch == terminator) { + int len = middle - start - 1; + if (len > 0) arr.write(buf, start, len); + start = middle; + return; + } else if (ch == '\\') { + int len = middle - start - 1; + if (len > 0) arr.write(buf, start, len); + start = middle; + arr.write(readEscapedChar()); + middle = start; + } + } + } + + private void readStringBare(CharArr arr) throws IOException { + if (arr != out) { + arr.append(out); + } + + for (; ; ) { + int ch = getChar(); + if (!isUnquotedStringChar(ch)) { + if (ch == -1) break; + if (ch == '\\') { + arr.write(readEscapedChar()); + continue; + } + start--; + break; + } + + if (ch == '\\') { + arr.write(readEscapedChar()); + continue; + } + + arr.write(ch); + } + } + + + // isName==true if this is a field name (as opposed to a value) + protected void handleNonDoubleQuoteString(int ch, boolean isName) throws IOException { + if (ch == '\'') { + stringTerm = ch; + if ((flags & ALLOW_SINGLE_QUOTES) == 0) { + throw err("Single quoted strings not allowed"); + } + } else { + if (isName && (flags & ALLOW_UNQUOTED_KEYS) == 0 + || !isName && (flags & ALLOW_UNQUOTED_STRING_VALUES) == 0 + || eof) { + if (isName) { + throw err("Expected quoted string"); + } else { + throw err(null); + } + } + + if (!isUnquotedStringStart(ch)) { + throw err(null); + } + + stringTerm = 0; // signal for unquoted string + out.reset(); + out.unsafeWrite(ch); + } + } + + private static boolean isUnquotedStringStart(int ch) { + return Character.isJavaIdentifierStart(ch); + } + + // What characters are allowed to continue an unquoted string + // once we know we are in one. + private static boolean isUnquotedStringChar(int ch) { + return Character.isJavaIdentifierPart(ch) + || ch == '.' + || ch == '-' + || ch == '/'; + + // would checking for a-z first speed up the common case? + + // possibly much more liberal unquoted string handling... + /*** + switch (ch) { + case -1: + case ' ': + case '\t': + case '\r': + case '\n': + case '}': + case ']': + case ',': + case ':': + case '=': // reserved for future use + case '\\': // check for backslash should come after this function call + return false; + } + return true; + ***/ + } + + + /*** alternate implementation + // middle is the pointer to the middle of a buffer to start scanning for a non-string + // character ('"' or "/"). start<=middle=end) { + getMore(); + middle=start; + } else { + start = middle+1; // set buffer pointer to correct spot + if (ch=='"') { + valstate=0; + return; + } else if (ch=='\\') { + arr.write(readEscapedChar()); + if (start>=end) getMore(); + middle=start; + } + } + } + } + ***/ + + + // return the next event when parser is in a neutral state (no + // map separators or array element separators to read + private int next(int ch) throws IOException { + // TODO: try my own form of indirect jump... look up char class and index directly into handling implementation? + for (; ; ) { + switch (ch) { + case ' ': // this is not the exclusive list of whitespace chars... the rest are handled in default: + case '\t': + case '\r': + case '\n': + ch = getCharNWS(); // calling getCharNWS here seems faster than letting the switch handle it + break; + case '"': + stringTerm = '"'; + valstate = STRING; + return STRING; + case '\'': + if ((flags & ALLOW_SINGLE_QUOTES) == 0) { + throw err("Single quoted strings not allowed"); + } + stringTerm = '\''; + valstate = STRING; + return STRING; + case '{': + push(); + state = DID_OBJSTART; + return OBJECT_START; + case '[': + push(); + state = DID_ARRSTART; + return ARRAY_START; + case '0': + out.reset(); + //special case '0'? If next char isn't '.' val=0 + ch = getChar(); + if (ch == '.') { + start--; + ch = '0'; + readNumber('0', false); + return valstate; + } else if (ch > '9' || ch < '0') { + out.unsafeWrite('0'); + if (ch != -1) start--; + lval = 0; + valstate = LONG; + return LONG; + } else { + throw err("Leading zeros not allowed"); + } + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + out.reset(); + lval = readNumber(ch, false); + return valstate; + case '-': + out.reset(); + out.unsafeWrite('-'); + ch = getChar(); + if (ch < '0' || ch > '9') throw err("expected digit after '-'"); + lval = readNumber(ch, true); + return valstate; + case 't': + // TODO: test performance of this non-branching inline version. + // if ((('r'-getChar())|('u'-getChar())|('e'-getChar())) != 0) throw err(""); + if (matchBareWord(JSONUtil.TRUE_CHARS)) { + bool = true; + valstate = BOOLEAN; + return valstate; + } else { + valstate = STRING; + return STRING; + } + case 'f': + if (matchBareWord(JSONUtil.FALSE_CHARS)) { + bool = false; + valstate = BOOLEAN; + return valstate; + } else { + valstate = STRING; + return STRING; + } + case 'n': + if (matchBareWord(JSONUtil.NULL_CHARS)) { + valstate = NULL; + return valstate; + } else { + valstate = STRING; + return STRING; + } + case '/': + getSlashComment(); + ch = getChar(); + break; + case '#': + getNewlineComment(); + ch = getChar(); + break; + case ']': // This only happens with a trailing comma (or an error) + if (state != DID_ARRELEM || (flags & ALLOW_EXTRA_COMMAS) == 0) { + throw err("Unexpected array closer ]"); + } + pop(); + return event = ARRAY_END; + case '}': // This only happens with a trailing comma (or an error) + if (state != DID_MEMVAL || (flags & ALLOW_EXTRA_COMMAS) == 0) { + throw err("Unexpected object closer }"); + } + pop(); + return event = ARRAY_END; + case ',': // This only happens with input like [1,] + if ((state != DID_ARRELEM && state != DID_MEMVAL) || (flags & ALLOW_EXTRA_COMMAS) == 0) { + throw err("Unexpected comma"); + } + ch = getChar(); + break; + case -1: + if (getLevel() > 0) throw err("Premature EOF"); + return EOF; + default: + // Handle unusual unicode whitespace like no-break space (0xA0) + if (isWhitespace(ch)) { + ch = getChar(); // getCharNWS() would also work + break; + } + handleNonDoubleQuoteString(ch, false); + valstate = STRING; + return STRING; + // throw err(null); + } + + } + } + + @Override + public String toString() { + return "start=" + start + ",end=" + end + ",state=" + state + "valstate=" + valstate; + } + + + /** + * Returns the next event encountered in the JSON stream, one of + *
    + *
  • {@link #STRING}
  • + *
  • {@link #LONG}
  • + *
  • {@link #NUMBER}
  • + *
  • {@link #BIGNUMBER}
  • + *
  • {@link #BOOLEAN}
  • + *
  • {@link #NULL}
  • + *
  • {@link #OBJECT_START}
  • + *
  • {@link #OBJECT_END}
  • + *
  • {@link #OBJECT_END}
  • + *
  • {@link #ARRAY_START}
  • + *
  • {@link #ARRAY_END}
  • + *
  • {@link #EOF}
  • + *
+ */ + public int nextEvent() throws IOException { + if (valstate != 0) { + if (valstate == STRING) { + readStringChars2(devNull, start); + } else if (valstate == BIGNUMBER) { + continueNumber(devNull); + } + valstate = 0; + } + + int ch; + outer: + for (; ; ) { + switch (state) { + case 0: + event = next(getChar()); + if (event == STRING && (flags & OPTIONAL_OUTER_BRACES) != 0) { + if (start > 0) start--; + missingOpeningBrace = true; + stringTerm = 0; + valstate = 0; + event = next('{'); + } + return event; + case DID_OBJSTART: + ch = getCharExpected('"'); + if (ch == '}') { + pop(); + return event = OBJECT_END; + } + if (ch == '"') { + stringTerm = ch; + } else if (ch == ',' && (flags & ALLOW_EXTRA_COMMAS) != 0) { + continue outer; + } else { + handleNonDoubleQuoteString(ch, true); + } + state = DID_MEMNAME; + valstate = STRING; + return event = STRING; + case DID_MEMNAME: + ch = getCharExpected(':'); + if (ch != ':') { + if ((ch == '{' || ch == '[') && (flags & ALLOW_MISSING_COLON_COMMA_BEFORE_OBJECT) != 0) { + start--; + } else { + throw err("Expected key,value separator ':'"); + } + } + state = DID_MEMVAL; // set state first because it might be pushed... + return event = next(getChar()); + case DID_MEMVAL: + ch = getCharExpected(','); + if (ch == '}') { + pop(); + return event = OBJECT_END; + } else if (ch != ',') { + if ((flags & ALLOW_EXTRA_COMMAS) != 0 && (ch == '\'' || ch == '"' || Character.isLetter(ch))) { + start--; + } else if (missingOpeningBrace && ch == -1 && (flags & OPTIONAL_OUTER_BRACES) != 0) { + missingOpeningBrace = false; + pop(); + return event = OBJECT_END; + } else throw err("Expected ',' or '}'"); + } + ch = getCharExpected('"'); + if (ch == '"') { + stringTerm = ch; + } else if ((ch == ',' || ch == '}') && (flags & ALLOW_EXTRA_COMMAS) != 0) { + if (ch == ',') continue outer; + pop(); + return event = OBJECT_END; + } else { + handleNonDoubleQuoteString(ch, true); + } + state = DID_MEMNAME; + valstate = STRING; + return event = STRING; + case DID_ARRSTART: + ch = getCharNWS(); + if (ch == ']') { + pop(); + return event = ARRAY_END; + } + state = DID_ARRELEM; // set state first, might be pushed... + return event = next(ch); + case DID_ARRELEM: + ch = getCharExpected(','); + if (ch == ',') { + // state = DID_ARRELEM; // redundant + return event = next(getChar()); + } else if (ch == ']') { + pop(); + return event = ARRAY_END; + } else { + if ((ch == '{' || ch == '[') && (flags & ALLOW_MISSING_COLON_COMMA_BEFORE_OBJECT) != 0) { + return event = next(ch); + } else { + throw err("Expected ',' or ']'"); + } + } + } + } // end for(;;) + } + + public int lastEvent() { + return event; + } + + public boolean wasKey() { + return state == DID_MEMNAME; + } + + + private void goTo(int what) throws IOException { + if (valstate == what) { + valstate = 0; + return; + } + if (valstate == 0) { + /*int ev = */ + nextEvent(); // TODO + if (valstate != what) { + throw err("type mismatch"); + } + valstate = 0; + } else { + throw err("type mismatch"); + } + } + + /** + * Returns the JSON string value, decoding any escaped characters. + */ + public String getString() throws IOException { + return getStringChars().toString(); + } + + /** + * Returns the characters of a JSON string value, decoding any escaped characters. + * The underlying buffer of the returned CharArr should *not* be + * modified as it may be shared with the input buffer. + * The returned CharArr will only be valid up until + * the next JSONParser method is called. Any required data should be + * read before that point. + */ + public CharArr getStringChars() throws IOException { + goTo(STRING); + return readStringChars(); + } + + /** + * Reads a JSON string into the output, decoding any escaped characters. + */ + public void getString(CharArr output) throws IOException { + goTo(STRING); + readStringChars2(output, start); + } + + /** + * Reads a number from the input stream and parses it as a long, only if + * the value will in fact fit into a signed 64 bit integer. + */ + public long getLong() throws IOException { + goTo(LONG); + return lval; + } + + /** + * Reads a number from the input stream and parses it as a double + */ + public double getDouble() throws IOException { + return Double.parseDouble(getNumberChars().toString()); + } + + /** + * Returns the characters of a JSON numeric value. + *

The underlying buffer of the returned CharArr should *not* be + * modified as it may be shared with the input buffer. + *

The returned CharArr will only be valid up until + * the next JSONParser method is called. Any required data should be + * read before that point. + */ + public CharArr getNumberChars() throws IOException { + int ev = 0; + if (valstate == 0) ev = nextEvent(); + + if (valstate == LONG || valstate == NUMBER) { + valstate = 0; + return out; + } else if (valstate == BIGNUMBER) { + continueNumber(out); + valstate = 0; + return out; + } else { + throw err("Unexpected " + ev); + } + } + + /** + * Reads a JSON numeric value into the output. + */ + public void getNumberChars(CharArr output) throws IOException { + int ev = 0; + if (valstate == 0) ev = nextEvent(); + if (valstate == LONG || valstate == NUMBER) output.write(this.out); + else if (valstate == BIGNUMBER) { + continueNumber(output); + } else { + throw err("Unexpected " + ev); + } + valstate = 0; + } + + /** + * Reads a boolean value + */ + public boolean getBoolean() throws IOException { + goTo(BOOLEAN); + return bool; + } + + /** + * Reads a null value + */ + public void getNull() throws IOException { + goTo(NULL); + } + + /** + * @return the current nesting level, the number of parent objects or arrays. + */ + public int getLevel() { + return ptr; + } + + public long getPosition() { + return gpos + start; + } +} \ No newline at end of file diff --git a/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/JSONUtil.java b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/JSONUtil.java new file mode 100755 index 00000000000..715058da610 --- /dev/null +++ b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/JSONUtil.java @@ -0,0 +1,203 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.jmh.noggit; + + +public class JSONUtil { + public static final char[] TRUE_CHARS = new char[]{'t', 'r', 'u', 'e'}; + public static final char[] FALSE_CHARS = new char[]{'f', 'a', 'l', 's', 'e'}; + public static final char[] NULL_CHARS = new char[]{'n', 'u', 'l', 'l'}; + public static final char[] HEX_CHARS = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + public static final char VALUE_SEPARATOR = ','; + public static final char NAME_SEPARATOR = ':'; + public static final char OBJECT_START = '{'; + public static final char OBJECT_END = '}'; + public static final char ARRAY_START = '['; + public static final char ARRAY_END = ']'; + + public static String toJSON(Object o) { + CharArr out = new CharArr(); + new JSONWriter(out).write(o); + return out.toString(); + } + + /** + * @param o The object to convert to JSON + * @param indentSize The number of space characters to use as an indent (default 2). 0=newlines but no spaces, -1=no indent at all. + */ + public static String toJSON(Object o, int indentSize) { + CharArr out = new CharArr(); + new JSONWriter(out, indentSize).write(o); + return out.toString(); + } + + public static void writeNumber(int number, CharArr out) { + out.write(Integer.toString(number)); + } + + public static void writeNumber(long number, CharArr out) { + out.write(Long.toString(number)); + } + + public static void writeNumber(float number, CharArr out) { + out.write(Float.toString(number)); + } + + public static void writeNumber(double number, CharArr out) { + out.write(Double.toString(number)); + } + + public static void writeString(CharArr val, CharArr out) { + writeString(val.getArray(), val.getStart(), val.getEnd(), out); + } + + public static void writeString(char[] val, int start, int end, CharArr out) { + out.write('"'); + writeStringPart(val, start, end, out); + out.write('"'); + } + + public static void writeString(String val, int start, int end, CharArr out) { + out.write('"'); + writeStringPart(val, start, end, out); + out.write('"'); + } + + public static void writeString(CharSequence val, int start, int end, CharArr out) { + out.write('"'); + writeStringPart(val, start, end, out); + out.write('"'); + } + + public static void writeStringPart(char[] val, int start, int end, CharArr out) { + for (int i = start; i < end; i++) { + char ch = val[i]; + // When ch>=1f, (ch*146087937)&0xd6a01f80) is 0 only for characters that need escaping: " \\ u2028 u2029 + // and has 7 false positives: 204a 4051 802f c022 c044 e04a e04b + if (ch > 0x1f && ((ch * 146087937) & 0xd6a01f80) != 0) { + out.write(ch); + } else { + writeChar(ch, out); + } + } + } + + public static void writeChar(char ch, CharArr out) { + switch (ch) { + case '"': + case '\\': + out.write('\\'); + out.write(ch); + break; + case '\r': + out.write('\\'); + out.write('r'); + break; + case '\n': + out.write('\\'); + out.write('n'); + break; + case '\t': + out.write('\\'); + out.write('t'); + break; + case '\b': + out.write('\\'); + out.write('b'); + break; + case '\f': + out.write('\\'); + out.write('f'); + break; + // case '/': + case '\u2028': // valid JSON, but not valid json script + case '\u2029': + unicodeEscape(ch, out); + break; + default: + if (ch <= 0x1F) { + unicodeEscape(ch, out); + } else { + out.write(ch); + } + } + } + + + public static void writeStringPart(String chars, int start, int end, CharArr out) { + // TODO: write in chunks? + + int toWrite = end - start; + char[] arr = out.getArray(); + int pos = out.getEnd(); + int space = arr.length - pos; + if (space < toWrite) { + writeStringPart((CharSequence) chars, start, end, out); + return; + } + + // get chars directly from String into output array + chars.getChars(start, end, arr, pos); + + int endInOut = pos + toWrite; + out.setEnd(endInOut); + for (int i = pos; i < endInOut; i++) { + char ch = arr[i]; + + // When ch>=1f, (ch*146087937)&0xd6a01f80) is 0 only for characters that need escaping: " \\ u2028 u2029 + // and has 7 false positives: 204a 4051 802f c022 c044 e04a e04b + if (ch <= 0x1f || ((ch * 146087937) & 0xd6a01f80) == 0) { + // We hit a char that needs escaping. do the rest char by char. + out.setEnd(i); + writeStringPart((CharSequence) chars, start + (i - pos), end, out); + return; + } + } + } + + public static void writeStringPart(CharSequence chars, int start, int end, CharArr out) { + for (int i = start; i < end; i++) { + char ch = chars.charAt(i); + // When ch>=1f, (ch*146087937)&0xd6a01f80) is 0 only for characters that need escaping: " \\ u2028 u2029 + // and has 7 false positives: 204a 4051 802f c022 c044 e04a e04b + if (ch > 0x1f && ((ch * 146087937) & 0xd6a01f80) != 0) { + out.write(ch); + } else { + writeChar(ch, out); + } + } + } + + + public static void unicodeEscape(int ch, CharArr out) { + out.write('\\'); + out.write('u'); + out.write(HEX_CHARS[ch >>> 12]); + out.write(HEX_CHARS[(ch >>> 8) & 0xf]); + out.write(HEX_CHARS[(ch >>> 4) & 0xf]); + out.write(HEX_CHARS[ch & 0xf]); + } + + public static void writeNull(CharArr out) { + out.write(NULL_CHARS); + } + + public static void writeBoolean(boolean val, CharArr out) { + out.write(val ? TRUE_CHARS : FALSE_CHARS); + } + +} \ No newline at end of file diff --git a/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/JSONWriter.java b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/JSONWriter.java new file mode 100755 index 00000000000..1ca34eb5ab7 --- /dev/null +++ b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/JSONWriter.java @@ -0,0 +1,359 @@ +/* + * Copyright 2006- Yonik Seeley + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.jmh.noggit; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Map; + +public class JSONWriter { + + /** + * Implement this interface on your class to support serialization + */ + public static interface Writable { + public void write(JSONWriter writer); + } + + protected int level; + protected int indent; + protected final CharArr out; + + /** + * @param out the CharArr to write the output to. + * @param indentSize The number of space characters to use as an indent (default 2). 0=newlines but no spaces, -1=no indent at all. + */ + public JSONWriter(CharArr out, int indentSize) { + this.out = out; + this.indent = indentSize; + } + + public JSONWriter(CharArr out) { + this(out, 2); + } + + public void setIndentSize(int indentSize) { + this.indent = indentSize; + } + + public void indent() { + if (indent >= 0) { + out.write('\n'); + if (indent > 0) { + int spaces = level * indent; + out.reserve(spaces); + for (int i = 0; i < spaces; i++) { + out.unsafeWrite(' '); + } + } + } + } + + public void write(Object o) { + // NOTE: an instance-of chain was about 50% faster than hashing on the classes, even with perfect hashing. + if (o == null) { + writeNull(); + } else if (o instanceof String) { + writeString((String) o); + } else if (o instanceof Number) { + if (o instanceof Integer || o instanceof Long) { + write(((Number) o).longValue()); + } else if (o instanceof Float || o instanceof Double) { + write(((Number) o).doubleValue()); + } else { + CharArr arr = new CharArr(); + arr.write(o.toString()); + writeNumber(arr); + } + } else if (o instanceof Map) { + write((Map) o); + } else if (o instanceof Collection) { + write((Collection) o); + } else if (o instanceof Boolean) { + write(((Boolean) o).booleanValue()); + } else if (o instanceof CharSequence) { + writeString((CharSequence) o); + } else if (o instanceof Writable) { + ((Writable) o).write(this); + } else if (o instanceof Object[]) { + write(Arrays.asList((Object[]) o)); + } else if (o instanceof int[]) { + write((int[]) o); + } else if (o instanceof float[]) { + write((float[]) o); + } else if (o instanceof long[]) { + write((long[]) o); + } else if (o instanceof double[]) { + write((double[]) o); + } else if (o instanceof short[]) { + write((short[]) o); + } else if (o instanceof boolean[]) { + write((boolean[]) o); + } else if (o instanceof char[]) { + write((char[]) o); + } else if (o instanceof byte[]) { + write((byte[]) o); + } else { + handleUnknownClass(o); + } + } + + /** + * Override this method for custom handling of unknown classes. Also see the Writable interface. + */ + public void handleUnknownClass(Object o) { + writeString(o.toString()); + } + + public void write(Map val) { + startObject(); + int sz = val.size(); + boolean first = true; + for (Map.Entry entry : val.entrySet()) { + if (first) { + first = false; + } else { + writeValueSeparator(); + } + if (sz > 1) indent(); + writeString(entry.getKey().toString()); + writeNameSeparator(); + write(entry.getValue()); + } + endObject(); + } + + public void write(Collection val) { + startArray(); + int sz = val.size(); + boolean first = true; + for (Object o : val) { + if (first) { + first = false; + } else { + writeValueSeparator(); + } + if (sz > 1) indent(); + write(o); + } + endArray(); + } + + /** + * A byte[] may be either a single logical value, or a list of small integers. + * It's up to the implementation to decide. + */ + public void write(byte[] val) { + startArray(); + boolean first = true; + for (short v : val) { + if (first) { + first = false; + } else { + writeValueSeparator(); + } + write(v); + } + endArray(); + } + + public void write(short[] val) { + startArray(); + boolean first = true; + for (short v : val) { + if (first) { + first = false; + } else { + writeValueSeparator(); + } + write(v); + } + endArray(); + } + + public void write(int[] val) { + startArray(); + boolean first = true; + for (int v : val) { + if (first) { + first = false; + } else { + writeValueSeparator(); + } + write(v); + } + endArray(); + } + + public void write(long[] val) { + startArray(); + boolean first = true; + for (long v : val) { + if (first) { + first = false; + } else { + writeValueSeparator(); + } + write(v); + } + endArray(); + } + + public void write(float[] val) { + startArray(); + boolean first = true; + for (float v : val) { + if (first) { + first = false; + } else { + writeValueSeparator(); + } + write(v); + } + endArray(); + } + + public void write(double[] val) { + startArray(); + boolean first = true; + for (double v : val) { + if (first) { + first = false; + } else { + writeValueSeparator(); + } + write(v); + } + endArray(); + } + + public void write(boolean[] val) { + startArray(); + boolean first = true; + for (boolean v : val) { + if (first) { + first = false; + } else { + writeValueSeparator(); + } + write(v); + } + endArray(); + } + + + public void write(short number) { + write((int) number); + } + + public void write(byte number) { + write((int) number); + } + + + public void writeNull() { + JSONUtil.writeNull(out); + } + + public void writeString(String str) { + JSONUtil.writeString(str, 0, str.length(), out); + } + + public void writeString(CharSequence str) { + JSONUtil.writeString(str, 0, str.length(), out); + } + + public void writeString(CharArr str) { + JSONUtil.writeString(str, out); + } + + public void writeStringStart() { + out.write('"'); + } + + public void writeStringChars(CharArr partialStr) { + JSONUtil.writeStringPart(partialStr.getArray(), partialStr.getStart(), partialStr.getEnd(), out); + } + + public void writeStringEnd() { + out.write('"'); + } + + public void write(long number) { + JSONUtil.writeNumber(number, out); + } + + public void write(int number) { + JSONUtil.writeNumber(number, out); + } + + public void write(double number) { + JSONUtil.writeNumber(number, out); + } + + public void write(float number) { + JSONUtil.writeNumber(number, out); + } + + public void write(boolean bool) { + JSONUtil.writeBoolean(bool, out); + } + + public void write(char[] val) { + JSONUtil.writeString(val, 0, val.length, out); + } + + public void writeNumber(CharArr digits) { + out.write(digits); + } + + public void writePartialNumber(CharArr digits) { + out.write(digits); + } + + public void startObject() { + out.write('{'); + level++; + } + + public void endObject() { + out.write('}'); + level--; + } + + public void startArray() { + out.write('['); + level++; + } + + public void endArray() { + out.write(']'); + level--; + } + + public void writeValueSeparator() { + out.write(','); + } + + public void writeNameSeparator() { + out.write(':'); + } + +} diff --git a/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/ObjectBuilder.java b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/ObjectBuilder.java new file mode 100755 index 00000000000..9b743495336 --- /dev/null +++ b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/ObjectBuilder.java @@ -0,0 +1,207 @@ +/* + * Copyright 2006- Yonik Seeley + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.jmh.noggit; + + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class ObjectBuilder { + + /** consider to use {@link #fromJSONStrict(String)}*/ + public static Object fromJSON(String json) throws IOException { + JSONParser p = new JSONParser(json); + return getVal(p); + } + + /** like {@link #fromJSON(String)}, but also check that there is nothing + * remaining in the given string after closing bracket. + * Throws ParseException otherwise.*/ + public static Object fromJSONStrict(String json) throws IOException { + JSONParser p = new JSONParser(json); + final Object val = getVal(p); + checkEOF(p); + return val; + } + + public static void checkEOF(JSONParser p) throws IOException { + if (p.nextEvent()!=JSONParser.EOF) { + throw p.err("Expecting single object only."); + } + } + + /** consider to use {@link #getValStrict()}*/ + public static Object getVal(JSONParser parser) throws IOException { + return new ObjectBuilder(parser).getVal(); + } + + /** like {@link #getVal()}, but also check that there is nothing + * remaining in the given stream after closing bracket. + * Throws {@link ParseException} otherwise.*/ + public static Object getValStrict(JSONParser parser) throws IOException { + final Object val = getVal(parser); + checkEOF(parser); + return val; + } + + /** like {@link #getVal()}, but also check that there is nothing + * remaining in the given stream after closing bracket. + * Throws {@link ParseException} otherwise.*/ + public Object getValStrict() throws IOException { + final Object val = getVal(); + checkEOF(parser); + return val; + } + + final JSONParser parser; + + public ObjectBuilder(JSONParser parser) throws IOException { + this.parser = parser; + if (parser.lastEvent() == 0) parser.nextEvent(); + } + + + public Object getVal() throws IOException { + int ev = parser.lastEvent(); + switch (ev) { + case JSONParser.STRING: + return getString(); + case JSONParser.LONG: + return getLong(); + case JSONParser.NUMBER: + return getNumber(); + case JSONParser.BIGNUMBER: + return getBigNumber(); + case JSONParser.BOOLEAN: + return getBoolean(); + case JSONParser.NULL: + return getNull(); + case JSONParser.OBJECT_START: + return getObject(); + case JSONParser.OBJECT_END: + return null; // OR ERROR? + case JSONParser.ARRAY_START: + return getArray(); + case JSONParser.ARRAY_END: + return null; // OR ERROR? + case JSONParser.EOF: + return null; // OR ERROR? + default: + return null; // OR ERROR? + } + } + + + public Object getString() throws IOException { + return parser.getString(); + } + + public Object getLong() throws IOException { + return Long.valueOf(parser.getLong()); + } + + public Object getNumber() throws IOException { + CharArr num = parser.getNumberChars(); + String numstr = num.toString(); + double d = Double.parseDouble(numstr); + if (!Double.isInfinite(d)) return Double.valueOf(d); + // TODO: use more efficient constructor in Java5 + return new BigDecimal(num.buf, num.start, num.size()); + } + + public Object getBigNumber() throws IOException { + CharArr num = parser.getNumberChars(); + String numstr = num.toString(); + for (int ch; (ch = num.read()) != -1; ) { + if (ch == '.' || ch == 'e' || ch == 'E') return new BigDecimal(numstr); + } + return new BigInteger(numstr); + } + + public Object getBoolean() throws IOException { + return parser.getBoolean(); + } + + public Object getNull() throws IOException { + parser.getNull(); + return null; + } + + public Object newObject() throws IOException { + return new LinkedHashMap(); + } + + public Object getKey() throws IOException { + return parser.getString(); + } + + @SuppressWarnings("unchecked") + public void addKeyVal(Object map, Object key, Object val) throws IOException { + /* Object prev = */ + ((Map) map).put(key, val); + // TODO: test for repeated value? + } + + public Object objectEnd(Object obj) { + return obj; + } + + + public Object getObject() throws IOException { + Object m = newObject(); + for (; ; ) { + int ev = parser.nextEvent(); + if (ev == JSONParser.OBJECT_END) return objectEnd(m); + Object key = getKey(); + ev = parser.nextEvent(); + Object val = getVal(); + addKeyVal(m, key, val); + } + } + + public Object newArray() { + return new ArrayList(); + } + + @SuppressWarnings("unchecked") + public void addArrayVal(Object arr, Object val) throws IOException { + ((List) arr).add(val); + } + + public Object endArray(Object arr) { + return arr; + } + + public Object getArray() throws IOException { + Object arr = newArray(); + for (; ; ) { + int ev = parser.nextEvent(); + if (ev == JSONParser.ARRAY_END) return endArray(arr); + Object val = getVal(); + addArrayVal(arr, val); + } + } + +} diff --git a/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/package-info.java b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/package-info.java new file mode 100755 index 00000000000..ce42d9c193a --- /dev/null +++ b/zeppelin-jmh-interpreter/src/main/java/org/apache/jmh/noggit/package-info.java @@ -0,0 +1,23 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +/** + * Fork of the Noggit JSON parser. + */ +package org.apache.jmh.noggit; + + diff --git a/zeppelin-jmh-interpreter/src/main/resources/interpreter-setting.json b/zeppelin-jmh-interpreter/src/main/resources/interpreter-setting.json new file mode 100755 index 00000000000..5fb5659c507 --- /dev/null +++ b/zeppelin-jmh-interpreter/src/main/resources/interpreter-setting.json @@ -0,0 +1,33 @@ +[ + { + "group": "jmh", + "name": "jmh", + "className": "org.apache.jmh.JMHInterpreter", + "defaultInterpreter": true, + "remote": false, + "perNote": "shared", + "perUser": "shared", + "setPermission": false, + "owners": [], + "isUserImpersonate": false, + "properties": { + "jmh.workdir": { + "envName": null, + "propertyName": "jmh.workdir", + "defaultValue": "", + "description": "JMH Module root path" + }, + "jmh.command.timeout": { + "envName": null, + "propertyName": "jmh.command.timeout", + "defaultValue": "600000", + "description": "jmh command timeout" + } + }, + "editor": { + "language": "sh", + "editOnDblClick": false, + "completionSupport": true + } + } +] diff --git a/zeppelin-jmh-interpreter/src/test/java/org/apache/jmh/JMHInterpreterTest.java b/zeppelin-jmh-interpreter/src/test/java/org/apache/jmh/JMHInterpreterTest.java new file mode 100755 index 00000000000..767b28c6309 --- /dev/null +++ b/zeppelin-jmh-interpreter/src/test/java/org/apache/jmh/JMHInterpreterTest.java @@ -0,0 +1,690 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.jmh; + +import org.apache.http.util.ByteArrayBuffer; +import org.apache.zeppelin.interpreter.InterpreterContext; +import org.apache.zeppelin.interpreter.InterpreterException; +import org.apache.zeppelin.interpreter.InterpreterOutput; +import org.apache.zeppelin.interpreter.InterpreterOutputListener; +import org.apache.zeppelin.interpreter.InterpreterResult; +import org.apache.zeppelin.interpreter.InterpreterResult.Code; +import org.apache.zeppelin.interpreter.InterpreterResultMessageOutput; +import org.junit.After; +import org.junit.Assume; +import org.junit.Before; +import org.junit.BeforeClass; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.Properties; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +public class JMHInterpreterTest implements InterpreterOutputListener { + + private static final Logger log = LoggerFactory.getLogger(JMHInterpreterTest.class); + + private static final boolean IS_WINDOWS = System.getProperty("os.name") + .startsWith("Windows"); + public static final String SSH_DEST = "127.0.0.1"; // "markmiller@cm4" + public static final String SSH_BENCH_PATH_1 = "/home/markmiller/apache-solr/solr/benchmark"; + public static final String SSH_BENCH_PATH_2 = "/home/markmiller/apache-solr-2/solr/benchmark"; + + int cnt; + private Properties props; + private JMHInterpreter interpreter; + private InterpreterOutput out; + private InterpreterContext context; + + private ByteArrayBuffer buffer; + + @BeforeClass + public static void setup() { + } + + @Before + public void init() { + buffer = new ByteArrayBuffer(64000); + props = new Properties(); + + props.put("jmh.workdir", SSH_BENCH_PATH_1); + + out = new InterpreterOutput(this); + cnt++; + context = InterpreterContext.builder() + .setInterpreterOut(out).setNoteId("test" + cnt).setNoteName("test" + cnt).setParagraphId("test" + cnt).build(); + interpreter = new JMHInterpreter(props); + interpreter.open(); + } + + @After + public void cleanup() { + interpreter.close(); + } + + @Test + public void testSuccess() throws Exception { + final String userScript = "JsonFaceting -wi 1 -i 1 -r 3 -w 3 -p docCount=10"; + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + } + + @Test + public void testAsyncProfilerFlamegraph() throws Exception { + final String userScript = "JavaBinBasicPerf -wi 1 -i 1 -r 3 -w 3 -prof gc -prof async:output=flamegraph;dir=$PROFILE_DIR"; + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + log.info("result=\n {}", res.message()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + } + + + + @Test + public void testHeapDump() throws Exception{ + context.getLocalProperties().put("heapdump", "true"); + + final String userScript = "JsonFaceting -wi 1 -i 1 -r 3 -w 3 -p docCount=10"; + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + log.info("result=\n {}", res.message()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + + HttpClient client = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_2) + .followRedirects(HttpClient.Redirect.NORMAL) + .build(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://127.0.0.1:8191/heap-reports")) + .timeout(Duration.ofMinutes(1)) + .GET() + .build(); + HttpResponse response = + client.send(request, HttpResponse.BodyHandlers.ofString()); + System.out.println(response.statusCode()); + System.out.println(response.body()); + } + + @Test + public void testStatus() throws Exception { + + HttpClient client = HttpClient.newBuilder() + .version(HttpClient.Version.HTTP_2) + .followRedirects(HttpClient.Redirect.NORMAL) + .build(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://127.0.0.1:8191/status")) + .timeout(Duration.ofMinutes(1)) + .GET() + .build(); + HttpResponse response = + client.send(request, HttpResponse.BodyHandlers.ofString()); + System.out.println(response.statusCode()); + System.out.println(response.body()); + } + + @Test + public void testSsh() throws Exception { + final String userScript = "JsonFaceting -wi 1 -i 1 -r 5 -w 2 -p docCount=10"; + context.getLocalProperties().put("ssh", SSH_DEST); + context.getLocalProperties().put("workdir", SSH_BENCH_PATH_1); + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + } + + @Test + public void testCmp() throws Exception { + final String userScript = "JsonFaceting -wi 1 -i 1 -r 5 -w 2 -p docCount=10"; + + context.getLocalProperties().put("workdir2", SSH_BENCH_PATH_2); + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + assertTrue("Check RESULT", resultScript.contains("benchmark")); + assertTrue("Check RESULT", resultScript.contains("ResultSet")); + assertTrue("Check RESULT", resultScript.contains("Before")); + assertTrue("Check RESULT", resultScript.contains("After")); + } + + @Test + public void testCmpNested() throws Exception { + final String userScript = "JavaBinBasicPerf -p content=nested -t 3 -wi 1 -i 1 -r 5 -w 2"; + + context.getLocalProperties().put("workdir2", SSH_BENCH_PATH_2); + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + assertTrue("Check RESULT", resultScript.contains("benchmark")); + assertTrue("Check RESULT", resultScript.contains("ResultSet")); + assertTrue("Check RESULT", resultScript.contains("Before")); + assertTrue("Check RESULT", resultScript.contains("After")); + assertTrue("Check RESULT", resultScript.contains("ops/s")); + + } + + @Test + public void testCmpCloudIndexing() throws Exception { + final String userScript = "CloudIndexing.indexSmallDoc -wi 1 -i 1 -r 2 -w 2"; + + context.getLocalProperties().put("workdir2", SSH_BENCH_PATH_2); + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + } + + @Test + public void testGC() throws Exception { + final String userScript = "JavaBinBasicPerf -prof gc -wi 1 -i 1 -r 3 -w 3"; + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + } + + @Test + public void testGC2() throws Exception { + final String userScript = "JavaBinBasicPerf -prof gc -wi 1 -i 1 -r 3 -w 3"; + context.getLocalProperties().put("workdir", SSH_BENCH_PATH_2); + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + } + + + @Test + public void testCmpGC() throws Exception { + final String userScript = "JavaBinBasicPerf -prof gc -wi 1 -i 1 -r 3 -w 3"; + + context.getLocalProperties().put("workdir2", SSH_BENCH_PATH_2); + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + } + + @Test + public void testCmpBenchMethod() throws Exception { + final String userScript = "CloudIndexing.indexSmallDoc -wi 1 -i 1 -r 2 -w 2"; + + context.getLocalProperties().put("workdir2", SSH_BENCH_PATH_2); + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + } + + @Test + public void testCmpNumerics() throws Exception { + final String userScript = "JavaBinBasicPerf -p content=numeric -t 3 -wi 1 -i 1 -r 3 -w 3"; + context.getLocalProperties().put("workdir2", SSH_BENCH_PATH_2); + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + assertTrue("Check RESULT", resultScript.contains("ops/s")); + } + + @Test + public void testCmpVeryLargsStrings() throws Exception { + final String userScript = "JavaBinBasicPerf -p content=very_large_text_and_strings -t 3 -wi 1 -i 1 -r 3 -w 3"; + context.getLocalProperties().put("workdir2", SSH_BENCH_PATH_2); + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + assertTrue("Check RESULT", resultScript.contains("ops/s")); + } + + @Test + public void testSS() throws Exception { + final String userScript = "JavaBinBasicPerf -bm ss"; + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + } + + @Test + public void testSSCmp() throws Exception { + final String userScript = "JavaBinBasicPerf -bm ss -wi 3 -i 3"; // TODO: if we dont control the warmups/iters, they can vary and raw results won't match per row and blow our tsv - same for sample I thnk - should not put raw data in tsv + + context.getLocalProperties().put("workdir2", SSH_BENCH_PATH_2); + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + } + + @Test + public void testSSCmpWworkDir1() throws Exception { + final String userScript = "JavaBinBasicPerf JavaBinBasicPerf -t 3 -wi 1 -i 1 -r 3 -w 3"; + context.getLocalProperties().put("workdir", SSH_BENCH_PATH_1); + context.getLocalProperties().put("workdir2", SSH_BENCH_PATH_2); + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + } + + @Test + public void testNoSuchElement() throws Exception { + final String userScript = "JavaBinBasicPerf -p content=large_strings -p scale=2 -t 14 -wi 1 -i 1 -r 3 -w 3"; //-jvmArgsAppend -Xmx14G + context.getLocalProperties().put("systrace", "true"); + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + } + + + + @Test + public void testCmpMultipleThreads() throws Exception { + final String userScript = "JavaBinBasicPerf -t 3 -wi 1 -i 1 -r 3 -w 3"; + context.getLocalProperties().put("workdir2", SSH_BENCH_PATH_2); + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + assertTrue("Check RESULT", resultScript.contains("ops/s")); + } + + @Test + public void testSshCmp() throws Exception { + final String userScript = "JsonFaceting -wi 1 -i 1 -r 3 -w 3 -p scale=2"; + context.getLocalProperties().put("ssh", SSH_DEST); + context.getLocalProperties().put("workdir", SSH_BENCH_PATH_1); + context.getLocalProperties().put("workdir2", SSH_BENCH_PATH_2); + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame("Check SUCCESS: " + res.message(), Code.SUCCESS, res.code()); + + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + + log.info("result=\n {}", context.out.toString()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + } + + @Test + public void testTrace() throws Exception { + Assume.assumeTrue(!System.getProperty("os.name").startsWith("Mac OS X")); + + context.getLocalProperties().put("systrace", "true"); + + final String userScript = "JsonFaceting -wi 1 -i 1 -r 5 -w 5 -p docCount=10"; + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame( + "Check SUCCESS: " + res.message() + "\n" + context.out.toString(), + Code.SUCCESS, + res.code()); + + out.flush(); + + final String resultScript = new String(getBufferBytes()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + + final String userScript2 = "cmd=systrace epoch usr sys 1m"; + + context.out.clear(); + + final InterpreterResult res2 = interpreter.interpret(userScript2, context); + + assertSame("Check SUCCESS: " + res2.message(), Code.SUCCESS, res2.code()); + + String contextString = context.out().toString(); + System.out.println(contextString); + // assertTrue(contextString, contextString.contains("%table")); + assertTrue(res2.message().toString(), contextString.contains("epoch")); + } + + @Test + public void testTraceSsh() throws Exception { + Assume.assumeTrue(!System.getProperty("os.name").startsWith("Mac OS X")); + context.getLocalProperties().put("ssh", SSH_DEST); + context.getLocalProperties().put("workdir", SSH_BENCH_PATH_1); + context.getLocalProperties().put("systrace", "true"); + + final String userScript = "JsonFaceting -wi 1 -i 1 -r 5 -w 5 -p docCount=10"; + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame( + "Check SUCCESS: " + res.message() + "\n" + context.out.toString(), + Code.SUCCESS, + res.code()); + + out.flush(); + + + final String resultScript = new String(getBufferBytes()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + + final String userScript2 = "cmd=systrace epoch usr sys 1m"; + + context.out.clear(); + + final InterpreterResult res2 = interpreter.interpret(userScript2, context); + + assertSame("Check SUCCESS: " + res2.message(), Code.SUCCESS, res2.code()); + + String contextString = context.out().toString(); + System.out.println(contextString); + // assertTrue(contextString, contextString.contains("%table")); + assertTrue(res2.message().toString(), contextString.contains("epoch")); + } + + @Test + public void testTraceSshCmp() throws Exception { + Assume.assumeTrue(!System.getProperty("os.name").startsWith("Mac OS X")); + context.getLocalProperties().put("ssh", SSH_DEST); + context.getLocalProperties().put("workdir", SSH_BENCH_PATH_1); + context.getLocalProperties().put("workdir2", SSH_BENCH_PATH_2); + context.getLocalProperties().put("systrace", "true"); + + + final String userScript = "JsonFaceting -wi 1 -i 1 -r 5 -w 5 -p docCount=10"; + + final InterpreterResult res = interpreter.interpret(userScript, context); + + assertSame( + "Check SUCCESS: " + res.message() + "\n" + context.out.toString(), + Code.SUCCESS, + res.code()); + + out.flush(); + + + final String resultScript = new String(getBufferBytes()); + + // The script that is executed must contain the functions provided by this interpreter + assertEquals("Check SCRIPT", resultScript, resultScript); + + final String userScript2 = "cmd=systrace epoch usr sys 1m"; + + context.out.clear(); + + final InterpreterResult res2 = interpreter.interpret(userScript2, context); + + assertSame("Check SUCCESS: " + res2.message(), Code.SUCCESS, res2.code()); + + String contextString = context.out().toString(); + + System.out.println("context content:"); + System.out.println(contextString); + // assertTrue(contextString, contextString.contains("%table")); + assertTrue(res2.message().toString(), contextString.contains("epoch")); + assertTrue(res2.message().toString(), contextString.contains("Before")); + assertTrue(res2.message().toString(), contextString.contains("After")); + } + + @Test + public void testBadConf() throws InterpreterException { + props.setProperty("solrbenchmark.work.dir", "/bad/path/to/exe"); + final InterpreterResult res = interpreter.interpret("print('hello')", context); + + assertSame(Code.ERROR, res.code()); + } + + @Override + public void onUpdateAll(InterpreterOutput interpreterOutput) { + + } + + @Override + public void onAppend(int i, InterpreterResultMessageOutput interpreterResultMessageOutput, byte[] bytes) { + try { + System.out.println(interpreterResultMessageOutput.toInterpreterResultMessage().getData()); + } catch (IOException e) { + e.printStackTrace(); + } + buffer.append(bytes, 0, bytes.length); + } + + @Override + public void onUpdate(int i, InterpreterResultMessageOutput interpreterResultMessageOutput) { + try { + System.out.println(interpreterResultMessageOutput.toInterpreterResultMessage().getData()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private byte[] getBufferBytes() { + + + return buffer.toByteArray(); + } + +} diff --git a/zeppelin-jmh-interpreter/src/test/resources/log4j.properties b/zeppelin-jmh-interpreter/src/test/resources/log4j.properties new file mode 100755 index 00000000000..399e2ef90ef --- /dev/null +++ b/zeppelin-jmh-interpreter/src/test/resources/log4j.properties @@ -0,0 +1,14 @@ +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.err +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p %c{1} - %m%n + +log4j.appender.rollingFile=org.apache.log4j.RollingFileAppender +log4j.appender.rollingFile.File=logs/application.log +log4j.appender.rollingFile.layout=org.apache.log4j.PatternLayout +log4j.appender.rollingFile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %m%n +log4j.appender.rollingFile.MaxFileSize=10MB +log4j.appender.rollingFile.MaxBackupIndex=5 +log4j.appender.rollingFile.append=true + +log4j.rootLogger=ALL, stdout, rollingFile \ No newline at end of file

" + entry.getKey() + "" +entry.getValue() + "