Move everything under kotlin-native folder
I was forced to manually do update the following files, because otherwise they would be ignored according .gitignore settings. Probably they should be deleted from repo. Interop/.idea/compiler.xml Interop/.idea/gradle.xml Interop/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_3.xml Interop/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_3.xml Interop/.idea/modules.xml Interop/.idea/modules/Indexer/Indexer.iml Interop/.idea/modules/Runtime/Runtime.iml Interop/.idea/modules/StubGenerator/StubGenerator.iml backend.native/backend.native.iml backend.native/bc.frontend/bc.frontend.iml backend.native/cli.bc/cli.bc.iml backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt backend.native/tests/link/lib/foo.kt backend.native/tests/link/lib/foo2.kt backend.native/tests/teamcity-test.property
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
buildscript {
|
||||
ext.rootBuildDirectory = file('../..')
|
||||
|
||||
apply from: "$rootBuildDirectory/gradle/loadRootProperties.gradle"
|
||||
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin-multiplatform'
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
maven {
|
||||
url buildKotlinCompilerRepo
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir '../benchmarks/shared/src'
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion"
|
||||
implementation(npm("aws-sdk", "~2.670.0"))
|
||||
}
|
||||
kotlin.srcDir 'src/main/kotlin'
|
||||
kotlin.srcDir 'src/main/kotlin-js'
|
||||
kotlin.srcDir 'shared/src/main/kotlin'
|
||||
}
|
||||
}
|
||||
|
||||
targets {
|
||||
fromPreset(presets.js, 'js') {
|
||||
nodejs()
|
||||
compilations.main.kotlinOptions {
|
||||
outputFile = "${projectDir}/server/app.js"
|
||||
moduleKind = "commonjs"
|
||||
sourceMap = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.native.home=../../dist
|
||||
Binary file not shown.
+5
@@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
#!/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" "$@"
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@@ -0,0 +1,529 @@
|
||||
{
|
||||
"name": "performance-server",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"accepts": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
|
||||
"integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
|
||||
"requires": {
|
||||
"mime-types": "~2.1.18",
|
||||
"negotiator": "0.6.1"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
|
||||
},
|
||||
"aws-sdk": {
|
||||
"version": "2.670.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.670.0.tgz",
|
||||
"integrity": "sha512-hGRnZtp1wDUh6hZRBHO0Ki7thx/xbRlIEiTKlWes+f/0E1Nhm3KpelsBZ3L/Q6y1ragwkQd4Q720AmWEqemLyA==",
|
||||
"requires": {
|
||||
"buffer": "4.9.1",
|
||||
"events": "1.1.1",
|
||||
"ieee754": "1.1.13",
|
||||
"jmespath": "0.15.0",
|
||||
"querystring": "0.2.0",
|
||||
"sax": "1.2.1",
|
||||
"url": "0.10.3",
|
||||
"uuid": "3.3.2",
|
||||
"xml2js": "0.4.19"
|
||||
}
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
||||
"integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
|
||||
},
|
||||
"body-parser": {
|
||||
"version": "1.18.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
|
||||
"integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
|
||||
"requires": {
|
||||
"bytes": "3.0.0",
|
||||
"content-type": "~1.0.4",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"http-errors": "~1.6.3",
|
||||
"iconv-lite": "0.4.23",
|
||||
"on-finished": "~2.3.0",
|
||||
"qs": "6.5.2",
|
||||
"raw-body": "2.3.3",
|
||||
"type-is": "~1.6.16"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"buffer": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
|
||||
"integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
|
||||
"requires": {
|
||||
"base64-js": "^1.0.2",
|
||||
"ieee754": "^1.1.4",
|
||||
"isarray": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"bytes": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
|
||||
"integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
|
||||
},
|
||||
"content-disposition": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
|
||||
"integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
|
||||
},
|
||||
"content-type": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
|
||||
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
|
||||
},
|
||||
"cookie": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
|
||||
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
|
||||
},
|
||||
"cookie-signature": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
"integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
|
||||
},
|
||||
"destroy": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
|
||||
"integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"ejs": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz",
|
||||
"integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ=="
|
||||
},
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"etag": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
|
||||
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
|
||||
},
|
||||
"events": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
|
||||
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
|
||||
},
|
||||
"express": {
|
||||
"version": "4.16.4",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
|
||||
"integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
|
||||
"requires": {
|
||||
"accepts": "~1.3.5",
|
||||
"array-flatten": "1.1.1",
|
||||
"body-parser": "1.18.3",
|
||||
"content-disposition": "0.5.2",
|
||||
"content-type": "~1.0.4",
|
||||
"cookie": "0.3.1",
|
||||
"cookie-signature": "1.0.6",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"finalhandler": "1.1.1",
|
||||
"fresh": "0.5.2",
|
||||
"merge-descriptors": "1.0.1",
|
||||
"methods": "~1.1.2",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.2",
|
||||
"path-to-regexp": "0.1.7",
|
||||
"proxy-addr": "~2.0.4",
|
||||
"qs": "6.5.2",
|
||||
"range-parser": "~1.2.0",
|
||||
"safe-buffer": "5.1.2",
|
||||
"send": "0.16.2",
|
||||
"serve-static": "1.13.2",
|
||||
"setprototypeof": "1.1.0",
|
||||
"statuses": "~1.4.0",
|
||||
"type-is": "~1.6.16",
|
||||
"utils-merge": "1.0.1",
|
||||
"vary": "~1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
|
||||
"integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.2",
|
||||
"statuses": "~1.4.0",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"forwarded": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
"integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
|
||||
},
|
||||
"fresh": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||
"integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
|
||||
"integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
|
||||
"requires": {
|
||||
"depd": "~1.1.2",
|
||||
"inherits": "2.0.3",
|
||||
"setprototypeof": "1.1.0",
|
||||
"statuses": ">= 1.4.0 < 2"
|
||||
}
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.23",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
|
||||
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
|
||||
"requires": {
|
||||
"safer-buffer": ">= 2.1.2 < 3"
|
||||
}
|
||||
},
|
||||
"ieee754": {
|
||||
"version": "1.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
|
||||
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
|
||||
"integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4="
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"jmespath": {
|
||||
"version": "0.15.0",
|
||||
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
|
||||
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
|
||||
},
|
||||
"kotlin": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/kotlin/-/kotlin-1.4.0.tgz",
|
||||
"integrity": "sha512-q+Ts9Xr72eT3QkmLjHDObPAHbsKbZ/Vn0ozqrNNr7UbtpLxa26+Hn8ILORPLz1UIUTespZyfXztXJ7AO5Xl/Gg=="
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
|
||||
},
|
||||
"merge-descriptors": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
|
||||
"integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
|
||||
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.38.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz",
|
||||
"integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg=="
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.22",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz",
|
||||
"integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==",
|
||||
"requires": {
|
||||
"mime-db": "~1.38.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"negotiator": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
|
||||
"integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
|
||||
},
|
||||
"on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||
"requires": {
|
||||
"ee-first": "1.1.1"
|
||||
}
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
|
||||
"integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
|
||||
},
|
||||
"path-to-regexp": {
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
|
||||
"integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==",
|
||||
"requires": {
|
||||
"forwarded": "~0.1.2",
|
||||
"ipaddr.js": "1.8.0"
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
|
||||
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
|
||||
},
|
||||
"querystring": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
|
||||
},
|
||||
"range-parser": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
|
||||
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
|
||||
},
|
||||
"raw-body": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
|
||||
"integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
|
||||
"requires": {
|
||||
"bytes": "3.0.0",
|
||||
"http-errors": "1.6.3",
|
||||
"iconv-lite": "0.4.23",
|
||||
"unpipe": "1.0.0"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
|
||||
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
|
||||
},
|
||||
"send": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
|
||||
"integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"depd": "~1.1.2",
|
||||
"destroy": "~1.0.4",
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"etag": "~1.8.1",
|
||||
"fresh": "0.5.2",
|
||||
"http-errors": "~1.6.2",
|
||||
"mime": "1.4.1",
|
||||
"ms": "2.0.0",
|
||||
"on-finished": "~2.3.0",
|
||||
"range-parser": "~1.2.0",
|
||||
"statuses": "~1.4.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
|
||||
"integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve-static": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
|
||||
"integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
|
||||
"requires": {
|
||||
"encodeurl": "~1.0.2",
|
||||
"escape-html": "~1.0.3",
|
||||
"parseurl": "~1.3.2",
|
||||
"send": "0.16.2"
|
||||
}
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
|
||||
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
|
||||
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.16",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
|
||||
"integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
|
||||
"requires": {
|
||||
"media-typer": "0.3.0",
|
||||
"mime-types": "~2.1.18"
|
||||
}
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"url": {
|
||||
"version": "0.10.3",
|
||||
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
|
||||
"integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=",
|
||||
"requires": {
|
||||
"punycode": "1.3.2",
|
||||
"querystring": "0.2.0"
|
||||
}
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
"integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
|
||||
},
|
||||
"xml2js": {
|
||||
"version": "0.4.19",
|
||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
|
||||
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
|
||||
"requires": {
|
||||
"sax": ">=0.6.0",
|
||||
"xmlbuilder": "~9.0.1"
|
||||
}
|
||||
},
|
||||
"xmlbuilder": {
|
||||
"version": "9.0.7",
|
||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
|
||||
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "performance-server",
|
||||
"version": "1.0.0",
|
||||
"main": "server/app.js",
|
||||
"scripts": {
|
||||
"start": "node server/app.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws-sdk": "~2.670.0",
|
||||
"body-parser": "~1.18.3",
|
||||
"debug": "~4.1.1",
|
||||
"ejs": "~2.6.1",
|
||||
"express": "~4.16.4",
|
||||
"kotlin": "~1.4.0",
|
||||
"node-fetch": "~2.6.1"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.buildInfo
|
||||
|
||||
import org.jetbrains.report.*
|
||||
import org.jetbrains.report.json.*
|
||||
|
||||
data class Build(val buildNumber: String, val startTime: String, val finishTime: String, val branch: String,
|
||||
val commits: String, val failuresNumber: Int) {
|
||||
|
||||
companion object : EntityFromJsonFactory<Build> {
|
||||
override fun create(data: JsonElement): Build {
|
||||
if (data is JsonObject) {
|
||||
val buildNumber = elementToString(data.getRequiredField("buildNumber"), "buildNumber").replace("\"", "")
|
||||
val startTime = elementToString(data.getRequiredField("startTime"), "startTime").replace("\"", "")
|
||||
val finishTime = elementToString(data.getRequiredField("finishTime"), "finishTime").replace("\"", "")
|
||||
val branch = elementToString(data.getRequiredField("branch"), "branch").replace("\"", "")
|
||||
val commits = elementToString(data.getRequiredField("commits"), "commits")
|
||||
val failuresNumber = elementToInt(data.getRequiredField("failuresNumber"), "failuresNumber")
|
||||
return Build(buildNumber, startTime, finishTime, branch, commits, failuresNumber)
|
||||
} else {
|
||||
error("Top level entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatTime(time: String, targetZone: Int = 3): String {
|
||||
val matchResult = "^\\d{8}T(\\d{2})(\\d{2})\\d{2}((\\+|-)\\d{2})".toRegex().find(time)?.groupValues
|
||||
matchResult?.let {
|
||||
val timeZone = matchResult[3].toInt()
|
||||
val timeDifference = targetZone - timeZone
|
||||
var hours = (matchResult[1].toInt() + timeDifference)
|
||||
if (hours > 23) {
|
||||
hours -= 24
|
||||
}
|
||||
return "${if (hours < 10) "0$hours" else "$hours"}:${matchResult[2]}"
|
||||
} ?: error { "Wrong format of time $startTime" }
|
||||
}
|
||||
|
||||
val date: String by lazy {
|
||||
val matchResult = "^(\\d{4})(\\d{2})(\\d{2})".toRegex().find(startTime)?.groupValues
|
||||
matchResult?.let { "${matchResult[3]}/${matchResult[2]}/${matchResult[1]}" }
|
||||
?: error { "Wrong format of time $startTime" }
|
||||
}
|
||||
val formattedStartTime: String by lazy {
|
||||
formatTime(startTime)
|
||||
}
|
||||
val formattedFinishTime: String by lazy {
|
||||
formatTime(finishTime)
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.elastic
|
||||
|
||||
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||
import org.jetbrains.report.json.*
|
||||
import org.jetbrains.network.*
|
||||
|
||||
// Connector with InfluxDB.
|
||||
class ElasticSearchConnector(private val connector: NetworkConnector,
|
||||
private val user: String? = null, private val password: String? = null) {
|
||||
// Execute ElasticSearch request.
|
||||
fun request(method: RequestMethod, path: String, acceptJsonContentType: Boolean = true, body: String? = null) =
|
||||
connector.sendRequest(method, path, user, password, acceptJsonContentType, body)
|
||||
}
|
||||
+202
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.elastic
|
||||
|
||||
import org.jetbrains.report.*
|
||||
import org.jetbrains.report.json.*
|
||||
import org.jetbrains.report.MeanVarianceBenchmark
|
||||
import org.jetbrains.network.*
|
||||
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||
|
||||
data class Commit(val revision: String, val developer: String) : JsonSerializable {
|
||||
override fun toString() = "$revision by $developer"
|
||||
|
||||
override fun serializeFields() = """
|
||||
"revision": "$revision",
|
||||
"developer": "$developer"
|
||||
"""
|
||||
|
||||
companion object : EntityFromJsonFactory<Commit> {
|
||||
fun parse(description: String) = if (description != "...") {
|
||||
description.split(" by ").let {
|
||||
val (currentRevision, currentDeveloper) = it
|
||||
Commit(currentRevision, currentDeveloper)
|
||||
}
|
||||
} else {
|
||||
Commit("unknown", "unknown")
|
||||
}
|
||||
|
||||
override fun create(data: JsonElement): Commit {
|
||||
if (data is JsonObject) {
|
||||
val revision = elementToString(data.getRequiredField("revision"), "revision")
|
||||
val developer = elementToString(data.getRequiredField("developer"), "developer")
|
||||
return Commit(revision, developer)
|
||||
} else {
|
||||
error("Top level entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List of commits.
|
||||
class CommitsList : ConvertedFromJson, JsonSerializable {
|
||||
|
||||
val commits: List<Commit>
|
||||
|
||||
constructor(data: JsonElement) {
|
||||
if (data !is JsonObject) {
|
||||
error("Commits description is expected to be a JSON object!")
|
||||
}
|
||||
val changesElement = data.getOptionalField("change")
|
||||
commits = changesElement?.let {
|
||||
if (changesElement !is JsonArray) {
|
||||
error("Change field is expected to be an array. Please, check source.")
|
||||
}
|
||||
changesElement.jsonArray.map {
|
||||
with(it as JsonObject) {
|
||||
Commit(elementToString(getRequiredField("version"), "version"),
|
||||
elementToString(getRequiredField("username"), "username")
|
||||
)
|
||||
}
|
||||
}
|
||||
} ?: listOf<Commit>()
|
||||
}
|
||||
|
||||
constructor(_commits: List<Commit>) {
|
||||
commits = _commits
|
||||
}
|
||||
|
||||
override fun toString(): String =
|
||||
commits.toString()
|
||||
|
||||
companion object {
|
||||
fun parse(description: String) = CommitsList(description.split(";").filter { it.isNotEmpty() }.map {
|
||||
Commit.parse(it)
|
||||
})
|
||||
}
|
||||
|
||||
override fun serializeFields() = """
|
||||
"commits": ${arrayToJson(commits)}
|
||||
"""
|
||||
}
|
||||
|
||||
data class BuildInfo(val buildNumber: String, val startTime: String, val endTime: String, val commitsList: CommitsList,
|
||||
val branch: String,
|
||||
val agentInfo: String /* Important agent information often used in requests.*/) : JsonSerializable {
|
||||
override fun serializeFields() = """
|
||||
"buildNumber": "$buildNumber",
|
||||
"startTime": "$startTime",
|
||||
"endTime": "$endTime",
|
||||
${commitsList.serializeFields()},
|
||||
"branch": "$branch",
|
||||
"agentInfo": "$agentInfo"
|
||||
"""
|
||||
|
||||
companion object : EntityFromJsonFactory<BuildInfo> {
|
||||
override fun create(data: JsonElement): BuildInfo {
|
||||
if (data is JsonObject) {
|
||||
val buildNumber = elementToString(data.getRequiredField("buildNumber"), "buildNumber")
|
||||
val startTime = elementToString(data.getRequiredField("startTime"), "startTime")
|
||||
val endTime = elementToString(data.getRequiredField("endTime"), "endTime")
|
||||
val branch = elementToString(data.getRequiredField("branch"), "branch")
|
||||
val commitsList = data.getRequiredField("commits")
|
||||
val commits = if (commitsList is JsonArray) {
|
||||
commitsList.jsonArray.map { Commit.create(it as JsonObject) }
|
||||
} else {
|
||||
error("benchmarksSets field is expected to be an array. Please, check origin files.")
|
||||
}
|
||||
val agentInfoElement = data.getOptionalField("agentInfo")
|
||||
val agentInfo = agentInfoElement?.let {
|
||||
elementToString(agentInfoElement, "agentInfo")
|
||||
} ?: ""
|
||||
return BuildInfo(buildNumber, startTime, endTime, CommitsList(commits), branch, agentInfo)
|
||||
} else {
|
||||
error("Top level entity is expected to be an object. Please, check origin files.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class ElasticSearchType {
|
||||
TEXT, KEYWORD, DATE, LONG, DOUBLE, BOOLEAN, OBJECT, NESTED
|
||||
}
|
||||
|
||||
abstract class ElasticSearchIndex(val indexName: String, val connector: ElasticSearchConnector) {
|
||||
// Insert data.
|
||||
fun insert(data: JsonSerializable): Promise<String> {
|
||||
val description = data.toJson()
|
||||
val writePath = "$indexName/_doc/"
|
||||
return connector.request(RequestMethod.POST, writePath, body = description)
|
||||
}
|
||||
|
||||
// Delete data.
|
||||
fun delete(data: String): Promise<String> {
|
||||
val writePath = "$indexName/_delete_by_query"
|
||||
return connector.request(RequestMethod.POST, writePath, body = data)
|
||||
}
|
||||
|
||||
// Make search request.
|
||||
fun search(requestJson: String, filterPathes: List<String> = emptyList()): Promise<String> {
|
||||
val path = "$indexName/_search?pretty${if (filterPathes.isNotEmpty())
|
||||
"&filter_path=" + filterPathes.joinToString(",") else ""}"
|
||||
return connector.request(RequestMethod.POST, path, body = requestJson)
|
||||
}
|
||||
|
||||
abstract val mapping: Map<String, ElasticSearchType>
|
||||
|
||||
val mappingDescription: String
|
||||
get() = """
|
||||
{
|
||||
"mappings": {
|
||||
"properties": {
|
||||
${mapping.map { (property, type) ->
|
||||
"\"${property}\": { \"type\": \"${type.name.toLowerCase()}\"${if (type == ElasticSearchType.DATE) "," +
|
||||
"\"format\": \"basic_date_time_no_millis\"" else ""} }"
|
||||
}.joinToString()}}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
fun createMapping() =
|
||||
connector.request(RequestMethod.PUT, indexName, body = mappingDescription)
|
||||
}
|
||||
|
||||
class BenchmarksIndex(name: String, connector: ElasticSearchConnector) : ElasticSearchIndex(name, connector) {
|
||||
override val mapping: Map<String, ElasticSearchType>
|
||||
get() = mapOf("buildNumber" to ElasticSearchType.KEYWORD,
|
||||
"benchmarks" to ElasticSearchType.NESTED,
|
||||
"env" to ElasticSearchType.NESTED,
|
||||
"kotlin" to ElasticSearchType.NESTED)
|
||||
}
|
||||
|
||||
class GoldenResultsIndex(connector: ElasticSearchConnector) : ElasticSearchIndex("golden", connector) {
|
||||
override val mapping: Map<String, ElasticSearchType>
|
||||
get() = mapOf("buildNumber" to ElasticSearchType.KEYWORD,
|
||||
"benchmarks" to ElasticSearchType.NESTED,
|
||||
"env" to ElasticSearchType.NESTED,
|
||||
"kotlin" to ElasticSearchType.NESTED)
|
||||
}
|
||||
|
||||
class BuildInfoIndex(connector: ElasticSearchConnector) : ElasticSearchIndex("builds", connector) {
|
||||
override val mapping: Map<String, ElasticSearchType>
|
||||
get() = mapOf("buildNumber" to ElasticSearchType.KEYWORD,
|
||||
"startTime" to ElasticSearchType.DATE,
|
||||
"endTime" to ElasticSearchType.DATE,
|
||||
"commits" to ElasticSearchType.NESTED)
|
||||
}
|
||||
|
||||
// Processed benchmark result with calculated mean, variance and normalized reult.
|
||||
class NormalizedMeanVarianceBenchmark(name: String, status: BenchmarkResult.Status, score: Double, metric: BenchmarkResult.Metric,
|
||||
runtimeInUs: Double, repeat: Int, warmup: Int, variance: Double, val normalizedScore: Double) :
|
||||
MeanVarianceBenchmark(name, status, score, metric, runtimeInUs, repeat, warmup, variance) {
|
||||
|
||||
override fun serializeFields(): String {
|
||||
return """
|
||||
${super.serializeFields()},
|
||||
"normalizedScore": $normalizedScore
|
||||
"""
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.network
|
||||
|
||||
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||
import kotlin.js.json // TODO - migrate to multiplatform.
|
||||
|
||||
// Now implemenation for network connection only for Node.js. TODO - multiplatform.
|
||||
external fun require(module: String): dynamic
|
||||
|
||||
enum class RequestMethod {
|
||||
POST, GET, PUT
|
||||
}
|
||||
|
||||
// Abstract class for working with network.
|
||||
abstract class NetworkConnector {
|
||||
fun getAuth(user: String, password: String): String {
|
||||
val buffer = js("Buffer").from(user + ":" + password)
|
||||
val based64String = buffer.toString("base64")
|
||||
return "Basic " + based64String
|
||||
}
|
||||
|
||||
protected abstract fun <T : String?> sendBaseRequest(method: RequestMethod, path: String, user: String? = null,
|
||||
password: String? = null, acceptJsonContentType: Boolean = true,
|
||||
body: String? = null,
|
||||
errorHandler: (url: String, response: dynamic) -> Nothing?): Promise<T>
|
||||
|
||||
open fun sendRequest(method: RequestMethod, path: String, user: String? = null, password: String? = null,
|
||||
acceptJsonContentType: Boolean = true, body: String? = null): Promise<String> =
|
||||
sendBaseRequest<String>(method, path, user, password, acceptJsonContentType, body) { url, response ->
|
||||
error("Error during getting response from $url\n$response")
|
||||
}
|
||||
|
||||
open fun sendOptionalRequest(method: RequestMethod, path: String, user: String? = null, password: String? = null,
|
||||
acceptJsonContentType: Boolean = true, body: String? = null): Promise<String?> =
|
||||
sendBaseRequest<String?>(method, path, user, password, acceptJsonContentType, body) { url, response ->
|
||||
println("Error during getting response from $url\n$response")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.network
|
||||
|
||||
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||
import kotlin.js.json // TODO - migrate to multiplatform.
|
||||
|
||||
// Network connector to work with basic url requests.
|
||||
class UrlNetworkConnector(private val host: String, private val port: Int? = null) : NetworkConnector() {
|
||||
|
||||
private val url = "$host${port?.let { ":$port" } ?: ""}"
|
||||
|
||||
override fun <T : String?> sendBaseRequest(method: RequestMethod, path: String, user: String?, password: String?,
|
||||
acceptJsonContentType: Boolean, body: String?,
|
||||
errorHandler: (url: String, response: dynamic) -> Nothing?): Promise<T> {
|
||||
val fullUrl = "$url/$path"
|
||||
val request = require("node-fetch")
|
||||
val headers = mutableListOf<Pair<String, String>>()
|
||||
if (user != null && password != null) {
|
||||
headers.add("Authorization" to getAuth(user, password))
|
||||
}
|
||||
if (acceptJsonContentType) {
|
||||
headers.add("Accept" to "application/json")
|
||||
headers.add("Content-Type" to "application/json")
|
||||
}
|
||||
|
||||
return request(fullUrl,
|
||||
json(
|
||||
"method" to method.toString(),
|
||||
"headers" to json(*(headers.toTypedArray())),
|
||||
"body" to body
|
||||
)
|
||||
).then { response ->
|
||||
if (!response.ok) {
|
||||
println(JSON.stringify(response))
|
||||
errorHandler(fullUrl, response)
|
||||
} else {
|
||||
response.text()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.analyzer
|
||||
|
||||
import org.w3c.xhr.*
|
||||
import kotlin.browser.*
|
||||
import kotlin.js.*
|
||||
|
||||
actual fun readFile(fileName: String): String {
|
||||
error("Reading from local file for JS isn't supported")
|
||||
}
|
||||
|
||||
actual fun writeToFile(fileName: String, text: String) {
|
||||
error("Writing to local file for JS isn't supported")
|
||||
}
|
||||
|
||||
actual fun Double.format(decimalNumber: Int): String =
|
||||
this.asDynamic().toFixed(decimalNumber)
|
||||
|
||||
actual fun assert(value: Boolean, lazyMessage: () -> Any) {
|
||||
if (!value) error(lazyMessage)
|
||||
}
|
||||
|
||||
actual fun sendGetRequest(url: String, user: String?, password: String?, followLocation: Boolean) : String {
|
||||
error("Unsupported")
|
||||
}
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.database
|
||||
|
||||
import kotlin.js.Promise
|
||||
import org.jetbrains.elastic.*
|
||||
import org.jetbrains.utils.*
|
||||
import org.jetbrains.report.json.*
|
||||
import org.jetbrains.report.*
|
||||
|
||||
fun <T> Iterable<T>.isEmpty() = count() == 0
|
||||
fun <T> Iterable<T>.isNotEmpty() = !isEmpty()
|
||||
|
||||
inline fun <T: Any> T?.str(block: (T) -> String): String =
|
||||
if (this != null) block(this)
|
||||
else ""
|
||||
|
||||
// Dispatcher to create and control benchmarks indexes separated by some feature.
|
||||
// Feature can be choosen as often used as filtering entity in case there is no need in separate indexes.
|
||||
// Default behaviour of dispatcher is working with one index (case when separating isn't needed).
|
||||
class BenchmarksIndexesDispatcher(connector: ElasticSearchConnector, val feature: String,
|
||||
featureValues: Iterable<String> = emptyList()) {
|
||||
// Becnhmarks indexes to work with in case of existing feature values.
|
||||
private val benchmarksIndexes =
|
||||
if (featureValues.isNotEmpty())
|
||||
featureValues.map { it to BenchmarksIndex("benchmarks_${it.replace(" ", "_").toLowerCase()}", connector) }
|
||||
.toMap()
|
||||
else emptyMap()
|
||||
|
||||
// Single benchmark index.
|
||||
private val benchmarksSingleInstance =
|
||||
if (featureValues.isEmpty()) BenchmarksIndex("benchmarks", connector) else null
|
||||
|
||||
// Get right index in ES.
|
||||
private fun getIndex(featureValue: String = "") =
|
||||
benchmarksSingleInstance ?: benchmarksIndexes[featureValue]
|
||||
?: error("Used wrong feature value $featureValue. Indexes are separated using next values: ${benchmarksIndexes.keys}")
|
||||
|
||||
// Used filter to get data with needed feature value.
|
||||
var featureFilter: ((String) -> String)? = null
|
||||
|
||||
// Get benchmark reports corresponding to needed build number.
|
||||
fun getBenchmarksReports(buildNumber: String, featureValue: String): Promise<List<String>> {
|
||||
val queryDescription = """
|
||||
{
|
||||
"size": 1000,
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{ "match": { "buildNumber": "$buildNumber" } }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
return getIndex(featureValue).search(queryDescription, listOf("hits.hits._source")).then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
dbResponse.getObjectOrNull("hits")?.getArrayOrNull("hits")?.let { results ->
|
||||
results.map {
|
||||
val element = it as JsonObject
|
||||
element.getObject("_source").toString()
|
||||
}
|
||||
} ?: emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
// Get benchmarkes names corresponding to needed build number.
|
||||
fun getBenchmarksList(buildNumber: String, featureValue: String): Promise<List<String>> {
|
||||
return getBenchmarksReports(buildNumber, featureValue).then { reports ->
|
||||
reports.map {
|
||||
val dbResponse = JsonTreeParser.parse(it).jsonObject
|
||||
parseBenchmarksArray(dbResponse.getArray("benchmarks"))
|
||||
.map { it.name }
|
||||
}.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
// Delete benchmarks from database.
|
||||
fun deleteBenchmarks(featureValue: String, buildNumber: String? = null): Promise<String> {
|
||||
// Delete all or for choosen build number.
|
||||
val matchQuery = buildNumber?.let {
|
||||
""""match": { "buildNumber": "$it" }"""
|
||||
} ?: """"match_all": {}"""
|
||||
|
||||
val queryDescription = """
|
||||
{
|
||||
"query": {
|
||||
$matchQuery
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
return getIndex(featureValue).delete(queryDescription)
|
||||
}
|
||||
|
||||
// Get benchmarks values of needed metric for choosen build number.
|
||||
fun getSamples(metricName: String, featureValue: String = "", samples: List<String>, buildsCountToShow: Int,
|
||||
buildNumbers: Iterable<String>? = null,
|
||||
normalize: Boolean = false): Promise<List<Pair<String, Array<Double?>>>> {
|
||||
val queryDescription = """
|
||||
{
|
||||
"_source": ["buildNumber"],
|
||||
"size": ${samples.size * buildsCountToShow},
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
${buildNumbers.str { builds ->
|
||||
"""
|
||||
{ "terms" : { "buildNumber" : [${builds.map { "\"$it\"" }.joinToString()}] } },""" }
|
||||
}
|
||||
${featureFilter.str { "${it(featureValue)}," } }
|
||||
{"nested" : {
|
||||
"path" : "benchmarks",
|
||||
"query" : {
|
||||
"bool": {
|
||||
"must": [
|
||||
{ "match": { "benchmarks.metric": "$metricName" } },
|
||||
{ "terms": { "benchmarks.name": [${samples.map { "\"${it.toLowerCase()}\"" }.joinToString()}] }}
|
||||
]
|
||||
}
|
||||
}, "inner_hits": {
|
||||
"size": ${samples.size},
|
||||
"_source": ["benchmarks.name",
|
||||
"benchmarks.${if (normalize) "normalizedScore" else "score"}"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
||||
return getIndex(featureValue).search(queryDescription, listOf("hits.hits._source", "hits.hits.inner_hits"))
|
||||
.then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
val results = dbResponse.getObjectOrNull("hits")?.getArrayOrNull("hits")
|
||||
?: error("Wrong response:\n$responseString")
|
||||
// Get indexes for provided samples.
|
||||
val indexesMap = samples.mapIndexed { index, it -> it to index }.toMap()
|
||||
val valuesMap = buildNumbers?.map {
|
||||
it to arrayOfNulls<Double?>(samples.size)
|
||||
}?.toMap()?.toMutableMap() ?: mutableMapOf<String, Array<Double?>>()
|
||||
// Parse and save values in requested order.
|
||||
results.forEach {
|
||||
val element = it as JsonObject
|
||||
val build = element.getObject("_source").getPrimitive("buildNumber").content
|
||||
buildNumbers?.let { valuesMap.getOrPut(build) { arrayOfNulls<Double?>(samples.size) } }
|
||||
element
|
||||
.getObject("inner_hits")
|
||||
.getObject("benchmarks")
|
||||
.getObject("hits")
|
||||
.getArray("hits").forEach {
|
||||
val source = (it as JsonObject).getObject("_source")
|
||||
valuesMap[build]!![indexesMap[source.getPrimitive("name").content]!!] =
|
||||
source.getPrimitive(if (normalize) "normalizedScore" else "score").double
|
||||
}
|
||||
|
||||
}
|
||||
valuesMap.toList()
|
||||
}
|
||||
}
|
||||
|
||||
fun insert(data: JsonSerializable, featureValue: String = "") =
|
||||
getIndex(featureValue).insert(data)
|
||||
|
||||
fun delete(data: String, featureValue: String = "") =
|
||||
getIndex(featureValue).delete(data)
|
||||
|
||||
// Get failures number happned during build.
|
||||
fun getFailuresNumber(featureValue: String = "", buildNumbers: Iterable<String>? = null): Promise<Map<String, Int>> {
|
||||
val queryDescription = """
|
||||
{
|
||||
"_source": false,
|
||||
${featureFilter.str {
|
||||
"""
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [ ${it(featureValue)} ]
|
||||
}
|
||||
}, """
|
||||
} }
|
||||
${buildNumbers.str { builds ->
|
||||
"""
|
||||
"aggs" : {
|
||||
"builds": {
|
||||
"filters" : {
|
||||
"filters": {
|
||||
${builds.map { "\"$it\": { \"match\" : { \"buildNumber\" : \"$it\" }}" }
|
||||
.joinToString(",\n")}
|
||||
}
|
||||
},"""
|
||||
} }
|
||||
"aggs" : {
|
||||
"metric_build" : {
|
||||
"nested" : {
|
||||
"path" : "benchmarks"
|
||||
},
|
||||
"aggs" : {
|
||||
"metric_samples": {
|
||||
"filters" : {
|
||||
"filters": { "samples": { "match": { "benchmarks.status": "FAILED" } } }
|
||||
},
|
||||
"aggs" : {
|
||||
"failed_count": {
|
||||
"value_count": {
|
||||
"field" : "benchmarks.score"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${buildNumbers.str {
|
||||
""" }
|
||||
}"""
|
||||
} }
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
return getIndex(featureValue).search(queryDescription, listOf("aggregations")).then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
val aggregations = dbResponse.getObjectOrNull("aggregations") ?: error("Wrong response:\n$responseString")
|
||||
buildNumbers?.let {
|
||||
// Get failed number for each provided build.
|
||||
val buckets = aggregations
|
||||
.getObjectOrNull("builds")
|
||||
?.getObjectOrNull("buckets")
|
||||
?: error("Wrong response:\n$responseString")
|
||||
buildNumbers.map {
|
||||
it to buckets
|
||||
.getObject(it)
|
||||
.getObject("metric_build")
|
||||
.getObject("metric_samples")
|
||||
.getObject("buckets")
|
||||
.getObject("samples")
|
||||
.getObject("failed_count")
|
||||
.getPrimitive("value")
|
||||
.int
|
||||
}.toMap()
|
||||
} ?: listOf("golden" to aggregations
|
||||
.getObject("metric_build")
|
||||
.getObject("metric_samples")
|
||||
.getObject("buckets")
|
||||
.getObject("samples")
|
||||
.getObject("failed_count")
|
||||
.getPrimitive("value")
|
||||
.int
|
||||
).toMap()
|
||||
}
|
||||
}
|
||||
|
||||
// Get geometric mean for benchmarks values of needed metric.
|
||||
fun getGeometricMean(metricName: String, featureValue: String = "",
|
||||
buildNumbers: Iterable<String>? = null, normalize: Boolean = false,
|
||||
excludeNames: List<String> = emptyList()): Promise<List<Pair<String, List<Double?>>>> {
|
||||
// Filter only with metric or also with names.
|
||||
val filterBenchmarks = if (excludeNames.isEmpty())
|
||||
"""
|
||||
"match": { "benchmarks.metric": "$metricName" }
|
||||
"""
|
||||
else """
|
||||
"bool": {
|
||||
"must": { "match": { "benchmarks.metric": "$metricName" } },
|
||||
"must_not": { "terms" : { "benchmarks.name" : [${excludeNames.map { "\"$it\"" }.joinToString()}] } }
|
||||
}
|
||||
""".trimIndent()
|
||||
val queryDescription = """
|
||||
{
|
||||
"_source": false,
|
||||
${featureFilter.str {
|
||||
"""
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [ ${it(featureValue)} ]
|
||||
}
|
||||
}, """
|
||||
} }
|
||||
${buildNumbers.str { builds ->
|
||||
"""
|
||||
"aggs" : {
|
||||
"builds": {
|
||||
"filters" : {
|
||||
"filters": {
|
||||
${builds.map { "\"$it\": { \"match\" : { \"buildNumber\" : \"$it\" }}" }
|
||||
.joinToString(",\n")}
|
||||
}
|
||||
},"""
|
||||
} }
|
||||
"aggs" : {
|
||||
"metric_build" : {
|
||||
"nested" : {
|
||||
"path" : "benchmarks"
|
||||
},
|
||||
"aggs" : {
|
||||
"metric_samples": {
|
||||
"filters" : {
|
||||
"filters": { "samples": { $filterBenchmarks } }
|
||||
},
|
||||
"aggs" : {
|
||||
"sum_log_x": {
|
||||
"sum": {
|
||||
"field" : "benchmarks.${if (normalize) "normalizedScore" else "score"}",
|
||||
"script" : {
|
||||
"source": "if (_value == 0) { 0.0 } else { Math.log(_value) }"
|
||||
}
|
||||
}
|
||||
},
|
||||
"geom_mean": {
|
||||
"bucket_script": {
|
||||
"buckets_path": {
|
||||
"sum_log_x": "sum_log_x",
|
||||
"x_cnt": "_count"
|
||||
},
|
||||
"script": "Math.exp(params.sum_log_x/params.x_cnt)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
${buildNumbers.str {
|
||||
""" }
|
||||
}"""
|
||||
} }
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
return getIndex(featureValue).search(queryDescription, listOf("aggregations")).then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
val aggregations = dbResponse.getObjectOrNull("aggregations") ?: error("Wrong response:\n$responseString")
|
||||
buildNumbers?.let {
|
||||
val buckets = aggregations
|
||||
.getObjectOrNull("builds")
|
||||
?.getObjectOrNull("buckets")
|
||||
?: error("Wrong response:\n$responseString")
|
||||
buildNumbers.map {
|
||||
it to listOf(buckets
|
||||
.getObject(it)
|
||||
.getObject("metric_build")
|
||||
.getObject("metric_samples")
|
||||
.getObject("buckets")
|
||||
.getObject("samples")
|
||||
.getObjectOrNull("geom_mean")
|
||||
?.getPrimitive("value")
|
||||
?.double
|
||||
)
|
||||
}
|
||||
} ?: listOf("golden" to listOf(aggregations
|
||||
.getObject("metric_build")
|
||||
.getObject("metric_samples")
|
||||
.getObject("buckets")
|
||||
.getObject("samples")
|
||||
.getObjectOrNull("geom_mean")
|
||||
?.getPrimitive("value")
|
||||
?.double
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.database
|
||||
|
||||
import kotlin.js.Promise
|
||||
import org.jetbrains.elastic.*
|
||||
import org.jetbrains.utils.*
|
||||
import org.jetbrains.report.json.*
|
||||
import org.jetbrains.report.*
|
||||
|
||||
// Delete build information from ES index.
|
||||
internal fun deleteBuildInfo(agentInfo: String, buildInfoIndex: ElasticSearchIndex,
|
||||
buildNumber: String? = null): Promise<String> {
|
||||
val queryDescription = """
|
||||
{
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{ "match": { "agentInfo": "$agentInfo" } }
|
||||
${buildNumber?.let {
|
||||
""",
|
||||
{"match": { "buildNumber": "$it" }}
|
||||
"""
|
||||
} ?: ""}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
return buildInfoIndex.delete(queryDescription)
|
||||
}
|
||||
|
||||
// Get infromation about builds details from database.
|
||||
internal fun getBuildsDescription(type: String?, branch: String?, agentInfo: String, buildInfoIndex: ElasticSearchIndex,
|
||||
buildsCountToShow: Int, beforeDate: String?, afterDate: String?,
|
||||
onlyNumbers: Boolean = false): Promise<JsonArray> {
|
||||
val queryDescription = """
|
||||
{ "size": $buildsCountToShow,
|
||||
${if (onlyNumbers) """"_source": ["buildNumber"],""" else ""}
|
||||
"sort": {"startTime": "desc" },
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{ "match": { "agentInfo": "$agentInfo" } }
|
||||
${type?.let {
|
||||
""",
|
||||
{ "regexp": { "buildNumber": { "value": "${if (it == "release")
|
||||
".*eap.*|.*release.*|.*rc.*" else ".*dev.*"}" } }
|
||||
}
|
||||
"""
|
||||
} ?: ""}
|
||||
${beforeDate?.let {
|
||||
""",
|
||||
{ "range": { "startTime": { "lt": "$it" } } }
|
||||
"""
|
||||
} ?: ""}
|
||||
${afterDate?.let {
|
||||
""",
|
||||
{ "range": { "startTime": { "gt": "$it" } } }
|
||||
"""
|
||||
} ?: ""}
|
||||
${branch?.let {
|
||||
""",
|
||||
{"match": { "branch": "$it" }}
|
||||
"""
|
||||
} ?: ""}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
return buildInfoIndex.search(queryDescription, listOf("hits.hits._source")).then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
dbResponse.getObjectOrNull("hits")?.getArrayOrNull("hits") ?: error("Wrong response:\n$responseString")
|
||||
}
|
||||
}
|
||||
|
||||
// Check if current build already exists.
|
||||
suspend fun buildExists(buildInfo: BuildInfo, buildInfoIndex: ElasticSearchIndex): Boolean {
|
||||
val queryDescription = """
|
||||
{ "size": 1,
|
||||
"_source": ["buildNumber"],
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{ "match": { "buildNumber": "${buildInfo.buildNumber}" } },
|
||||
{ "match": { "agentInfo": "${buildInfo.agentInfo}" } },
|
||||
{ "match": { "branch": "${buildInfo.branch}" } }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
return buildInfoIndex.search(queryDescription, listOf("hits.total.value")).then { responseString ->
|
||||
val response = JsonTreeParser.parse(responseString).jsonObject
|
||||
val value = response.getObjectOrNull("hits")?.getObjectOrNull("total")?.getPrimitiveOrNull("value")?.content
|
||||
?: error("Error response from ElasticSearch:\n$responseString")
|
||||
value.toInt() > 0
|
||||
}.await()
|
||||
}
|
||||
|
||||
// Get builds numbers corresponding to machine and branch.
|
||||
fun getBuildsNumbers(type: String?, branch: String?, agentInfo: String, buildsCountToShow: Int,
|
||||
buildInfoIndex: ElasticSearchIndex, beforeDate: String? = null, afterDate: String? = null) =
|
||||
getBuildsDescription(type, branch, agentInfo, buildInfoIndex, buildsCountToShow, beforeDate, afterDate, true)
|
||||
.then { responseArray ->
|
||||
responseArray.map { (it as JsonObject).getObject("_source").getPrimitive("buildNumber").content }
|
||||
}
|
||||
|
||||
// Get full builds information corresponding to machine and branch.
|
||||
fun getBuildsInfo(type: String?, branch: String?, agentInfo: String, buildsCountToShow: Int,
|
||||
buildInfoIndex: ElasticSearchIndex, beforeDate: String? = null,
|
||||
afterDate: String? = null) =
|
||||
getBuildsDescription(type, branch, agentInfo, buildInfoIndex, buildsCountToShow, beforeDate, afterDate).then { responseArray ->
|
||||
responseArray.map { BuildInfo.create((it as JsonObject).getObject("_source")) }
|
||||
}
|
||||
|
||||
// Get golden results from database.
|
||||
fun getGoldenResults(goldenResultsIndex: GoldenResultsIndex): Promise<Map<String, List<BenchmarkResult>>> {
|
||||
return goldenResultsIndex.search("", listOf("hits.hits._source")).then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
dbResponse.getObjectOrNull("hits")?.getArrayOrNull("hits")?.map {
|
||||
val reportDescription = (it as JsonObject).getObject("_source")
|
||||
BenchmarksReport.create(reportDescription).benchmarks
|
||||
}?.reduce { acc, it -> acc + it } ?: error("Wrong format of response:\n $responseString")
|
||||
}
|
||||
}
|
||||
|
||||
// Get distinct values for needed field from database.
|
||||
fun distinctValues(field: String, index: ElasticSearchIndex): Promise<List<String>> {
|
||||
val queryDescription = """
|
||||
{
|
||||
"aggs": {
|
||||
"unique": {"terms": {"field": "$field", "size": 1000}}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
return index.search(queryDescription, listOf("aggregations.unique.buckets")).then { responseString ->
|
||||
val dbResponse = JsonTreeParser.parse(responseString).jsonObject
|
||||
dbResponse.getObjectOrNull("aggregations")?.getObjectOrNull("unique")?.getArrayOrNull("buckets")
|
||||
?.map { (it as JsonObject).getPrimitiveOrNull("key")?.content }?.filterNotNull()
|
||||
?: error("Wrong response:\n$responseString")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
external fun require(module: String): dynamic
|
||||
|
||||
external val process: dynamic
|
||||
external val __dirname: dynamic
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
println("Server Starting!")
|
||||
|
||||
val express = require("express")
|
||||
val app = express()
|
||||
val path = require("path")
|
||||
val bodyParser = require("body-parser")
|
||||
val http = require("http")
|
||||
// Get port from environment and store in Express.
|
||||
val port = normalizePort(process.env.PORT)
|
||||
app.use(bodyParser.json())
|
||||
app.set("port", port)
|
||||
|
||||
// View engine setup.
|
||||
app.set("views", path.join(__dirname, "../ui"))
|
||||
app.set("view engine", "ejs")
|
||||
app.use(express.static("ui"))
|
||||
|
||||
val server = http.createServer(app)
|
||||
app.listen(port, {
|
||||
println("App listening on port " + port + "!")
|
||||
})
|
||||
|
||||
app.use("/", router())
|
||||
}
|
||||
|
||||
fun normalizePort(port: Int) =
|
||||
if (port >= 0) port else 3000
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
@file:OptIn(ExperimentalTime::class)
|
||||
|
||||
package org.jetbrains.network
|
||||
|
||||
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||
import kotlin.time.*
|
||||
|
||||
// Response saved in cache.
|
||||
data class CachedResponse(val cachedResult: Any, val time: TimeMark)
|
||||
|
||||
// Dispatcher for work with cachable responses.
|
||||
object CachableResponseDispatcher {
|
||||
// Storage of cached responses.
|
||||
private val cachedResponses = mutableMapOf<String, CachedResponse>()
|
||||
|
||||
private val cacheMaxSize = 200
|
||||
|
||||
// Get response. If response isn't cached, use provided action to get response.
|
||||
fun getResponse(request: dynamic, response: dynamic,
|
||||
action: (success: (result: Any) -> Unit, reject: () -> Unit) -> Unit) {
|
||||
cachedResponses[request.url]?.let {
|
||||
// Update cache value if needed. Update only if last result was get later than 2 minutes.
|
||||
if (it.time.elapsedNow().inMinutes > 2.0) {
|
||||
println("Cache update for ${request.url}...")
|
||||
action({ result: Any ->
|
||||
cachedResponses[request.url] = CachedResponse(result, TimeSource.Monotonic.markNow())
|
||||
}, { println("Cache update for ${request.url} failed!") })
|
||||
}
|
||||
response.json(it.cachedResult)
|
||||
} ?: run {
|
||||
action({ result: Any ->
|
||||
if (cachedResponses.size >= cacheMaxSize) {
|
||||
cachedResponses[request.url] = CachedResponse(result, TimeSource.Monotonic.markNow())
|
||||
}
|
||||
response.json(result)
|
||||
}, { response.sendStatus(400) })
|
||||
}
|
||||
}
|
||||
|
||||
fun clear(): Unit {
|
||||
cachedResponses.clear()
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.network
|
||||
|
||||
import kotlin.js.Promise // TODO - migrate to multiplatform.
|
||||
import kotlin.js.json // TODO - migrate to multiplatform.
|
||||
import kotlin.js.Date
|
||||
import org.jetbrains.report.json.*
|
||||
|
||||
// Placeholder as analog for indexable type in TS.
|
||||
external interface `T$0` {
|
||||
@nativeGetter
|
||||
operator fun get(key: String): String?
|
||||
|
||||
@nativeSetter
|
||||
operator fun set(key: String, value: String)
|
||||
}
|
||||
|
||||
@JsModule("aws-sdk")
|
||||
@JsNonModule
|
||||
external object AWSInstance {
|
||||
|
||||
// Replace dynamic with some real type
|
||||
class Endpoint(domain: String)
|
||||
open class HttpRequest(endpoint: Endpoint, region: String) {
|
||||
open fun pathname(): String
|
||||
open var search: String
|
||||
open var body: String?
|
||||
open var endpoint: Endpoint
|
||||
open var headers: `T$0`
|
||||
open var method: String
|
||||
open var path: String
|
||||
}
|
||||
|
||||
class HttpClient() {
|
||||
val handleRequest: dynamic
|
||||
}
|
||||
|
||||
interface Credentials
|
||||
|
||||
class SharedIniFileCredentials(options: Map<String, String>): Credentials {
|
||||
val accessKeyId: String
|
||||
}
|
||||
|
||||
class EnvironmentCredentials(envPrefix: String): Credentials {
|
||||
val accessKeyId: String
|
||||
}
|
||||
|
||||
class Signers() {
|
||||
class V4(request: HttpRequest, subsystem: String) {
|
||||
fun addAuthorization(credentials: Credentials, date: Date)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Network connector to work with AWS resources.
|
||||
class AWSNetworkConnector : NetworkConnector() {
|
||||
val AWSDomain = "vpc-kotlin-perf-service-5e6ldakkdv526ii5hbclzcmpny.eu-west-1.es.amazonaws.com"
|
||||
val AWSRegion = "eu-west-1"
|
||||
|
||||
override fun <T : String?> sendBaseRequest(method: RequestMethod, path: String, user: String?, password: String?,
|
||||
acceptJsonContentType: Boolean, body: String?,
|
||||
errorHandler: (url: String, response: dynamic) -> Nothing?): Promise<T> {
|
||||
val useEnvironmentCredentials = true // For easy test on localhost change to false.
|
||||
val AWSEndpoint = AWSInstance.Endpoint(AWSDomain)
|
||||
var request = AWSInstance.HttpRequest(AWSEndpoint, AWSRegion)
|
||||
request.method = method.toString()
|
||||
request.path += path
|
||||
request.body = body
|
||||
|
||||
request.headers["host"] = this.AWSDomain
|
||||
|
||||
if (acceptJsonContentType) {
|
||||
request.headers["Content-Type"] = "application/json"
|
||||
request.headers["Content-Length"] = js("Buffer").byteLength(request.body)
|
||||
}
|
||||
|
||||
val credentials = if (useEnvironmentCredentials) AWSInstance.EnvironmentCredentials("AWS")
|
||||
else AWSInstance.SharedIniFileCredentials(mapOf<String, String>())
|
||||
val signer = AWSInstance.Signers.V4(request, "es")
|
||||
signer.addAuthorization(credentials, Date())
|
||||
|
||||
val client = AWSInstance.HttpClient()
|
||||
return Promise { resolve, reject ->
|
||||
client.handleRequest(request, null, { response ->
|
||||
var responseBody = ""
|
||||
response.on("data") { chunk ->
|
||||
responseBody += chunk
|
||||
chunk
|
||||
}
|
||||
response.on("end") { _ ->
|
||||
val dbResponse = JsonTreeParser.parse(responseBody).jsonObject
|
||||
// Response can fail and return 400 error for ES.
|
||||
if (dbResponse.getPrimitiveOrNull("status")?.let { it.content != "200" } ?: false) {
|
||||
println(dbResponse)
|
||||
val errorMessage = dbResponse.getObject("error").toString()
|
||||
reject(Throwable(errorMessage))
|
||||
}
|
||||
resolve(responseBody as T)
|
||||
}
|
||||
}, { error ->
|
||||
reject(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,630 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
import org.w3c.xhr.*
|
||||
import kotlin.js.json
|
||||
import kotlin.js.Date
|
||||
import kotlin.js.Promise
|
||||
import org.jetbrains.database.*
|
||||
import org.jetbrains.report.json.*
|
||||
import org.jetbrains.elastic.*
|
||||
import org.jetbrains.network.*
|
||||
import org.jetbrains.buildInfo.Build
|
||||
import org.jetbrains.analyzer.*
|
||||
import org.jetbrains.report.*
|
||||
import org.jetbrains.utils.*
|
||||
|
||||
// TODO - create DSL for ES requests?
|
||||
|
||||
const val teamCityUrl = "https://buildserver.labs.intellij.net/app/rest"
|
||||
const val artifactoryUrl = "https://repo.labs.intellij.net/kotlin-native-benchmarks"
|
||||
|
||||
operator fun <K, V> Map<K, V>?.get(key: K) = this?.get(key)
|
||||
|
||||
fun getArtifactoryHeader(artifactoryApiKey: String) = Pair("X-JFrog-Art-Api", artifactoryApiKey)
|
||||
|
||||
external fun decodeURIComponent(url: String): String
|
||||
|
||||
// Convert saved old report to expected new format.
|
||||
internal fun convertToNewFormat(data: JsonObject): List<Any> {
|
||||
val env = Environment.create(data.getRequiredField("env"))
|
||||
val benchmarksObj = data.getRequiredField("benchmarks")
|
||||
val compilerDescription = data.getRequiredField("kotlin")
|
||||
val compiler = Compiler.create(compilerDescription)
|
||||
val backend = (compilerDescription as JsonObject).getRequiredField("backend")
|
||||
val flagsArray = (backend as JsonObject).getOptionalField("flags")
|
||||
var flags: List<String> = emptyList()
|
||||
if (flagsArray != null && flagsArray is JsonArray) {
|
||||
flags = flagsArray.jsonArray.map { (it as JsonLiteral).unquoted() }
|
||||
}
|
||||
val benchmarksList = parseBenchmarksArray(benchmarksObj)
|
||||
|
||||
return listOf(env, compiler, benchmarksList, flags)
|
||||
}
|
||||
|
||||
// Convert data results to expected format.
|
||||
internal fun convert(json: String, buildNumber: String, target: String): List<BenchmarksReport> {
|
||||
val data = JsonTreeParser.parse(json)
|
||||
val reports = if (data is JsonArray) {
|
||||
data.map { convertToNewFormat(it as JsonObject) }
|
||||
} else listOf(convertToNewFormat(data as JsonObject))
|
||||
|
||||
// Restored flags for old reports.
|
||||
val knownFlags = mapOf(
|
||||
"Cinterop" to listOf("-opt"),
|
||||
"FrameworkBenchmarksAnalyzer" to listOf("-g"),
|
||||
"HelloWorld" to if (target == "Mac OS X")
|
||||
listOf("-Xcache-directory=/Users/teamcity/buildAgent/work/c104dee5223a31c5/test_dist/klib/cache/macos_x64-gSTATIC", "-g")
|
||||
else listOf("-g"),
|
||||
"Numerical" to listOf("-opt"),
|
||||
"ObjCInterop" to listOf("-opt"),
|
||||
"Ring" to listOf("-opt"),
|
||||
"Startup" to listOf("-opt"),
|
||||
"swiftInterop" to listOf("-opt"),
|
||||
"Videoplayer" to if (target == "Mac OS X")
|
||||
listOf("-Xcache-directory=/Users/teamcity/buildAgent/work/c104dee5223a31c5/test_dist/klib/cache/macos_x64-gSTATIC", "-g")
|
||||
else listOf("-g")
|
||||
)
|
||||
|
||||
return reports.map { elements ->
|
||||
val benchmarks = (elements[2] as List<BenchmarkResult>).groupBy { it.name.substringBefore('.').substringBefore(':') }
|
||||
val parsedFlags = elements[3] as List<String>
|
||||
benchmarks.map { (setName, results) ->
|
||||
val flags = if (parsedFlags.isNotEmpty() && parsedFlags[0] == "-opt") knownFlags[setName]!! else parsedFlags
|
||||
val savedCompiler = elements[1] as Compiler
|
||||
val compiler = Compiler(Compiler.Backend(savedCompiler.backend.type, savedCompiler.backend.version, flags),
|
||||
savedCompiler.kotlinVersion)
|
||||
val newReport = BenchmarksReport(elements[0] as Environment, results, compiler)
|
||||
newReport.buildNumber = buildNumber
|
||||
newReport
|
||||
}
|
||||
}.flatten()
|
||||
}
|
||||
|
||||
// Golden result value used to get normalized results.
|
||||
data class GoldenResult(val benchmarkName: String, val metric: String, val value: Double)
|
||||
data class GoldenResultsInfo(val goldenResults: Array<GoldenResult>)
|
||||
|
||||
// Convert information about golden results to benchmarks report format.
|
||||
fun GoldenResultsInfo.toBenchmarksReport(): BenchmarksReport {
|
||||
val benchmarksSamples = goldenResults.map {
|
||||
BenchmarkResult(it.benchmarkName, BenchmarkResult.Status.PASSED,
|
||||
it.value, BenchmarkResult.metricFromString(it.metric)!!, it.value, 1, 0)
|
||||
}
|
||||
val compiler = Compiler(Compiler.Backend(Compiler.BackendType.NATIVE, "golden", emptyList()), "golden")
|
||||
val environment = Environment(Environment.Machine("golden", "golden"), Environment.JDKInstance("golden", "golden"))
|
||||
return BenchmarksReport(environment,
|
||||
benchmarksSamples, compiler)
|
||||
}
|
||||
|
||||
// Build information provided from request.
|
||||
data class TCBuildInfo(val buildNumber: String, val branch: String, val startTime: String,
|
||||
val finishTime: String)
|
||||
|
||||
data class BuildRegister(val buildId: String, val teamCityUser: String, val teamCityPassword: String,
|
||||
val bundleSize: String?, val fileWithResult: String) {
|
||||
companion object {
|
||||
fun create(json: String): BuildRegister {
|
||||
val requestDetails = JSON.parse<BuildRegister>(json)
|
||||
// Parse method doesn't create real instance with all methods. So create it by hands.
|
||||
return BuildRegister(requestDetails.buildId, requestDetails.teamCityUser, requestDetails.teamCityPassword,
|
||||
requestDetails.bundleSize, requestDetails.fileWithResult)
|
||||
}
|
||||
}
|
||||
|
||||
private val teamCityBuildUrl: String by lazy { "builds/id:$buildId" }
|
||||
|
||||
val changesListUrl: String by lazy {
|
||||
"changes/?locator=build:id:$buildId"
|
||||
}
|
||||
|
||||
val teamCityArtifactsUrl: String by lazy { "builds/id:$buildId/artifacts/content/$fileWithResult" }
|
||||
|
||||
fun sendTeamCityRequest(url: String, json: Boolean = false) =
|
||||
UrlNetworkConnector(teamCityUrl).sendRequest(RequestMethod.GET, url, teamCityUser, teamCityPassword, json)
|
||||
|
||||
fun getBranchName(project: String): Promise<String> {
|
||||
val url = "builds?locator=id:$buildId&fields=build(revisions(revision(vcsBranchName,vcs-root-instance)))"
|
||||
var branch: String? = null
|
||||
return sendTeamCityRequest(url, true).then { response ->
|
||||
val data = JsonTreeParser.parse(response).jsonObject
|
||||
data.getArray("build").forEach {
|
||||
(it as JsonObject).getObject("revisions").getArray("revision").forEach {
|
||||
val currentBranch = (it as JsonObject).getPrimitive("vcsBranchName").content.removePrefix("refs/heads/")
|
||||
val currentProject = (it as JsonObject).getObject("vcs-root-instance").getPrimitive("name").content
|
||||
if (project == currentProject) {
|
||||
branch = currentBranch
|
||||
}
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
branch ?: error("No project $project can be found in build $buildId")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun format(timeValue: Int): String =
|
||||
if (timeValue < 10) "0$timeValue" else "$timeValue"
|
||||
|
||||
fun getBuildInformation(): Promise<TCBuildInfo> {
|
||||
return Promise.all(arrayOf(sendTeamCityRequest("$teamCityBuildUrl/number"),
|
||||
getBranchName("Kotlin Native"),
|
||||
sendTeamCityRequest("$teamCityBuildUrl/startDate"))).then { results ->
|
||||
val (buildNumber, branch, startTime) = results
|
||||
val currentTime = Date()
|
||||
val timeZone = currentTime.getTimezoneOffset() / -60 // Convert to hours.
|
||||
// Get finish time as current time, because buid on TeamCity isn't finished.
|
||||
val finishTime = "${format(currentTime.getUTCFullYear())}" +
|
||||
"${format(currentTime.getUTCMonth() + 1)}" +
|
||||
"${format(currentTime.getUTCDate())}" +
|
||||
"T${format(currentTime.getUTCHours())}" +
|
||||
"${format(currentTime.getUTCMinutes())}" +
|
||||
"${format(currentTime.getUTCSeconds())}" +
|
||||
"${if (timeZone > 0) "+" else "-"}${format(timeZone)}${format(0)}"
|
||||
TCBuildInfo(buildNumber, branch, startTime, finishTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get builds numbers in right order.
|
||||
internal fun <T> orderedValues(values: List<T>, buildElement: (T) -> String = { it -> it.toString() },
|
||||
skipMilestones: Boolean = false) =
|
||||
values.sortedWith(
|
||||
compareBy({ buildElement(it).substringBefore(".").toInt() },
|
||||
{ buildElement(it).substringAfter(".").substringBefore("-").toDouble() },
|
||||
{
|
||||
if (skipMilestones) 0
|
||||
else if (buildElement(it).substringAfter("-").startsWith("M"))
|
||||
buildElement(it).substringAfter("M").substringBefore("-").toInt()
|
||||
else
|
||||
Int.MAX_VALUE
|
||||
},
|
||||
{ buildElement(it).substringAfterLast("-").toDouble() }
|
||||
)
|
||||
)
|
||||
|
||||
// ElasticSearch connector for work with custom instance.
|
||||
internal val localHostElasticConnector = UrlNetworkConnector("http://localhost", 9200)
|
||||
// ElasticSearch connector for work with AWS instance.
|
||||
internal val awsElasticConnector = AWSNetworkConnector()
|
||||
internal val networkConnector = awsElasticConnector
|
||||
|
||||
fun urlParameterToBaseFormat(value: dynamic) =
|
||||
value.toString().replace("_", " ")
|
||||
|
||||
// Routing of requests to current server.
|
||||
fun router() {
|
||||
val express = require("express")
|
||||
val router = express.Router()
|
||||
val connector = ElasticSearchConnector(networkConnector)
|
||||
val benchmarksDispatcher = BenchmarksIndexesDispatcher(connector, "env.machine.os",
|
||||
listOf("Linux", "Mac OS X", "Windows 10")
|
||||
)
|
||||
val goldenIndex = GoldenResultsIndex(connector)
|
||||
val buildInfoIndex = BuildInfoIndex(connector)
|
||||
|
||||
router.get("/createMapping") { _, response ->
|
||||
buildInfoIndex.createMapping().then { _ ->
|
||||
response.sendStatus(200)
|
||||
}.catch { _ ->
|
||||
response.sendStatus(400)
|
||||
}
|
||||
}
|
||||
|
||||
// Get consistent build information in cases of rerunning the same build.
|
||||
suspend fun getConsistentBuildInfo(buildInfoInstance: BuildInfo, reports: List<BenchmarksReport>,
|
||||
rerunNumber: Int = 1): BuildInfo {
|
||||
var currentBuildInfo = buildInfoInstance
|
||||
if (buildExists(currentBuildInfo, buildInfoIndex)) {
|
||||
// Check if benchmarks aren't repeated.
|
||||
val existingBecnhmarks = benchmarksDispatcher.getBenchmarksList(currentBuildInfo.buildNumber,
|
||||
currentBuildInfo.agentInfo).await()
|
||||
val benchmarksToRegister = reports.map { it.benchmarks.keys }.flatten()
|
||||
if (existingBecnhmarks.toTypedArray().intersect(benchmarksToRegister).isNotEmpty()) {
|
||||
// Build was rerun.
|
||||
val buildNumber = "${currentBuildInfo.buildNumber}.$rerunNumber"
|
||||
currentBuildInfo = BuildInfo(buildNumber, currentBuildInfo.startTime, currentBuildInfo.endTime,
|
||||
currentBuildInfo.commitsList, currentBuildInfo.branch, currentBuildInfo.agentInfo)
|
||||
return getConsistentBuildInfo(currentBuildInfo, reports, rerunNumber + 1)
|
||||
}
|
||||
}
|
||||
return currentBuildInfo
|
||||
}
|
||||
|
||||
// Register build on Artifactory.
|
||||
router.post("/register") { request, response ->
|
||||
val register = BuildRegister.create(JSON.stringify(request.body))
|
||||
|
||||
// Get information from TeamCity.
|
||||
register.getBuildInformation().then { buildInfo ->
|
||||
register.sendTeamCityRequest(register.changesListUrl, true).then { changes ->
|
||||
val commitsList = CommitsList(JsonTreeParser.parse(changes))
|
||||
// Get artifact.
|
||||
val content = if(register.fileWithResult.contains("/"))
|
||||
UrlNetworkConnector(artifactoryUrl).sendRequest(RequestMethod.GET, register.fileWithResult)
|
||||
else register.sendTeamCityRequest(register.teamCityArtifactsUrl)
|
||||
content.then { resultsContent ->
|
||||
launch {
|
||||
val reportData = JsonTreeParser.parse(resultsContent)
|
||||
val reports = if (reportData is JsonArray) {
|
||||
reportData.map { BenchmarksReport.create(it as JsonObject) }
|
||||
} else listOf(BenchmarksReport.create(reportData as JsonObject))
|
||||
val goldenResultPromise = getGoldenResults(goldenIndex)
|
||||
val goldenResults = goldenResultPromise.await()
|
||||
// Register build information.
|
||||
var buildInfoInstance = getConsistentBuildInfo(
|
||||
BuildInfo(buildInfo.buildNumber, buildInfo.startTime, buildInfo.finishTime,
|
||||
commitsList, buildInfo.branch, reports[0].env.machine.os),
|
||||
reports
|
||||
)
|
||||
if (register.bundleSize != null) {
|
||||
// Add bundle size.
|
||||
val bundleSizeBenchmark = BenchmarkResult("KotlinNative",
|
||||
BenchmarkResult.Status.PASSED, register.bundleSize.toDouble(),
|
||||
BenchmarkResult.Metric.BUNDLE_SIZE, 0.0, 1, 0)
|
||||
val bundleSizeReport = BenchmarksReport(reports[0].env,
|
||||
listOf(bundleSizeBenchmark), reports[0].compiler)
|
||||
bundleSizeReport.buildNumber = buildInfoInstance.buildNumber
|
||||
benchmarksDispatcher.insert(bundleSizeReport, reports[0].env.machine.os).then { _ ->
|
||||
println("[BUNDLE] Success insert ${buildInfoInstance.buildNumber}")
|
||||
}.catch { errorResponse ->
|
||||
println("Failed to insert data for build")
|
||||
println(errorResponse)
|
||||
}
|
||||
}
|
||||
val insertResults = reports.map {
|
||||
val benchmarksReport = SummaryBenchmarksReport(it).getBenchmarksReport()
|
||||
.normalizeBenchmarksSet(goldenResults)
|
||||
benchmarksReport.buildNumber = buildInfoInstance.buildNumber
|
||||
// Save results in database.
|
||||
benchmarksDispatcher.insert(benchmarksReport, benchmarksReport.env.machine.os)
|
||||
}
|
||||
if (!buildExists(buildInfoInstance, buildInfoIndex)) {
|
||||
buildInfoIndex.insert(buildInfoInstance).then { _ ->
|
||||
println("Success insert build information for ${buildInfoInstance.buildNumber}")
|
||||
}.catch {
|
||||
response.sendStatus(400)
|
||||
}
|
||||
}
|
||||
Promise.all(insertResults.toTypedArray()).then { _ ->
|
||||
response.sendStatus(200)
|
||||
}.catch {
|
||||
response.sendStatus(400)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Register golden results to normalize on Artifactory.
|
||||
router.post("/registerGolden", { request, response ->
|
||||
val goldenResultsInfo: GoldenResultsInfo = JSON.parse<GoldenResultsInfo>(JSON.stringify(request.body))
|
||||
val goldenReport = goldenResultsInfo.toBenchmarksReport()
|
||||
goldenIndex.insert(goldenReport).then { _ ->
|
||||
response.sendStatus(200)
|
||||
}.catch {
|
||||
response.sendStatus(400)
|
||||
}
|
||||
})
|
||||
|
||||
// Get builds description with additional information.
|
||||
router.get("/buildsDesc/:target", { request, response ->
|
||||
CachableResponseDispatcher.getResponse(request, response) { success, reject ->
|
||||
val target = request.params.target.toString().replace('_', ' ')
|
||||
|
||||
var branch: String? = null
|
||||
var type: String? = null
|
||||
var buildsCountToShow = 200
|
||||
var beforeDate: String? = null
|
||||
var afterDate: String? = null
|
||||
if (request.query != undefined) {
|
||||
if (request.query.branch != undefined) {
|
||||
branch = request.query.branch
|
||||
}
|
||||
if (request.query.type != undefined) {
|
||||
type = request.query.type
|
||||
}
|
||||
if (request.query.count != undefined) {
|
||||
buildsCountToShow = request.query.count.toString().toInt()
|
||||
}
|
||||
if (request.query.before != undefined) {
|
||||
beforeDate = decodeURIComponent(request.query.before)
|
||||
}
|
||||
if (request.query.after != undefined) {
|
||||
afterDate = decodeURIComponent(request.query.after)
|
||||
}
|
||||
}
|
||||
|
||||
getBuildsInfo(type, branch, target, buildsCountToShow, buildInfoIndex, beforeDate, afterDate)
|
||||
.then { buildsInfo ->
|
||||
val buildNumbers = buildsInfo.map { it.buildNumber }
|
||||
// Get number of failed benchmarks for each build.
|
||||
benchmarksDispatcher.getFailuresNumber(target, buildNumbers).then { failures ->
|
||||
success(orderedValues(buildsInfo, { it -> it.buildNumber }, branch == "master").map {
|
||||
Build(it.buildNumber, it.startTime, it.endTime, it.branch,
|
||||
it.commitsList.serializeFields(), failures[it.buildNumber] ?: 0)
|
||||
})
|
||||
}.catch { errorResponse ->
|
||||
println("Error during getting failures numbers")
|
||||
println(errorResponse)
|
||||
reject()
|
||||
}
|
||||
}.catch {
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Get values of current metric.
|
||||
router.get("/metricValue/:target/:metric", { request, response ->
|
||||
CachableResponseDispatcher.getResponse(request, response) { success, reject ->
|
||||
val metric = request.params.metric
|
||||
val target = request.params.target.toString().replace('_', ' ')
|
||||
var samples: List<String> = emptyList()
|
||||
var aggregation = "geomean"
|
||||
var normalize = false
|
||||
var branch: String? = null
|
||||
var type: String? = null
|
||||
var excludeNames: List<String> = emptyList()
|
||||
var buildsCountToShow = 200
|
||||
var beforeDate: String? = null
|
||||
var afterDate: String? = null
|
||||
|
||||
// Parse parameters from request if it exists.
|
||||
if (request.query != undefined) {
|
||||
if (request.query.samples != undefined) {
|
||||
samples = request.query.samples.toString().split(",").map { it.trim() }
|
||||
}
|
||||
if (request.query.agr != undefined) {
|
||||
aggregation = request.query.agr.toString()
|
||||
}
|
||||
if (request.query.normalize != undefined) {
|
||||
normalize = true
|
||||
}
|
||||
if (request.query.branch != undefined) {
|
||||
branch = request.query.branch
|
||||
}
|
||||
if (request.query.type != undefined) {
|
||||
type = request.query.type
|
||||
}
|
||||
if (request.query.exclude != undefined) {
|
||||
excludeNames = request.query.exclude.toString().split(",").map { it.trim() }
|
||||
}
|
||||
if (request.query.count != undefined) {
|
||||
buildsCountToShow = request.query.count.toString().toInt()
|
||||
}
|
||||
if (request.query.before != undefined) {
|
||||
beforeDate = decodeURIComponent(request.query.before)
|
||||
}
|
||||
if (request.query.after != undefined) {
|
||||
afterDate = decodeURIComponent(request.query.after)
|
||||
}
|
||||
}
|
||||
|
||||
getBuildsNumbers(type, branch, target, buildsCountToShow, buildInfoIndex, beforeDate, afterDate).then { buildNumbers ->
|
||||
if (aggregation == "geomean") {
|
||||
// Get geometric mean for samples.
|
||||
benchmarksDispatcher.getGeometricMean(metric, target, buildNumbers, normalize,
|
||||
excludeNames).then { geoMeansValues ->
|
||||
success(orderedValues(geoMeansValues, { it -> it.first }, branch == "master"))
|
||||
}.catch { errorResponse ->
|
||||
println("Error during getting geometric mean")
|
||||
println(errorResponse)
|
||||
reject()
|
||||
}
|
||||
} else {
|
||||
benchmarksDispatcher.getSamples(metric, target, samples, buildsCountToShow, buildNumbers, normalize)
|
||||
.then { geoMeansValues ->
|
||||
success(orderedValues(geoMeansValues, { it -> it.first }, branch == "master"))
|
||||
}.catch {
|
||||
println("Error during getting samples")
|
||||
reject()
|
||||
}
|
||||
}
|
||||
}.catch {
|
||||
println("Error during getting builds information")
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Get branches for [target].
|
||||
router.get("/branches", { request, response ->
|
||||
CachableResponseDispatcher.getResponse(request, response) { success, reject ->
|
||||
distinctValues("branch", buildInfoIndex).then { results ->
|
||||
success(results)
|
||||
}.catch { errorMessage ->
|
||||
error(errorMessage.message ?: "Failed getting branches list.")
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Get build numbers for [target].
|
||||
router.get("/buildsNumbers/:target", { request, response ->
|
||||
CachableResponseDispatcher.getResponse(request, response) { success, reject ->
|
||||
distinctValues("buildNumber", buildInfoIndex).then { results ->
|
||||
success(results)
|
||||
}.catch { errorMessage ->
|
||||
println(errorMessage.message ?: "Failed getting branches list.")
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Conert data and migrate it from Artifactory to DB.
|
||||
router.get("/migrate/:target", { request, response ->
|
||||
val target = urlParameterToBaseFormat(request.params.target)
|
||||
val targetPathName = target.replace(" ", "")
|
||||
var buildNumber: String? = null
|
||||
if (request.query != undefined) {
|
||||
if (request.query.buildNumber != undefined) {
|
||||
buildNumber = request.query.buildNumber
|
||||
buildNumber = request.query.buildNumber
|
||||
}
|
||||
}
|
||||
getBuildsInfoFromArtifactory(targetPathName).then { buildInfo ->
|
||||
launch {
|
||||
val buildsDescription = buildInfo.lines().drop(1)
|
||||
var shouldConvert = buildNumber?.let { false } ?: true
|
||||
val goldenResultPromise = getGoldenResults(goldenIndex)
|
||||
val goldenResults = goldenResultPromise.await()
|
||||
val buildsSet = mutableSetOf<String>()
|
||||
buildsDescription.forEach {
|
||||
if (!it.isEmpty()) {
|
||||
val currentBuildNumber = it.substringBefore(',')
|
||||
if (!"\\d+(\\.\\d+)+(-M\\d)?-\\w+-\\d+(\\.\\d+)?".toRegex().matches(currentBuildNumber)) {
|
||||
error("Build number $currentBuildNumber differs from expected format. File with data for " +
|
||||
"target $target could be corrupted.")
|
||||
}
|
||||
if (!shouldConvert && buildNumber != null && buildNumber == currentBuildNumber) {
|
||||
shouldConvert = true
|
||||
}
|
||||
if (shouldConvert) {
|
||||
// Save data from Artifactory into database.
|
||||
val artifactoryUrlConnector = UrlNetworkConnector(artifactoryUrl)
|
||||
val fileName = "nativeReport.json"
|
||||
val accessFileUrl = "$targetPathName/$currentBuildNumber/$fileName"
|
||||
val extrenalFileName = if (target == "Linux") "externalReport.json" else "spaceFrameworkReport.json"
|
||||
val accessExternalFileUrl = "$targetPathName/$currentBuildNumber/$extrenalFileName"
|
||||
val infoParts = it.split(", ")
|
||||
if ((infoParts[3] == "master" || "eap" in currentBuildNumber || "release" in currentBuildNumber) &&
|
||||
currentBuildNumber !in buildsSet) {
|
||||
try {
|
||||
buildsSet.add(currentBuildNumber)
|
||||
val jsonReport = artifactoryUrlConnector.sendRequest(RequestMethod.GET, accessFileUrl).await()
|
||||
var reports = convert(jsonReport, currentBuildNumber, target)
|
||||
val buildInfoRecord = BuildInfo(currentBuildNumber, infoParts[1], infoParts[2],
|
||||
CommitsList.parse(infoParts[4]), infoParts[3], target)
|
||||
|
||||
val externalJsonReport = artifactoryUrlConnector.sendOptionalRequest(RequestMethod.GET, accessExternalFileUrl)
|
||||
.await()
|
||||
buildInfoIndex.insert(buildInfoRecord).then { _ ->
|
||||
println("[BUILD INFO] Success insert build number ${buildInfoRecord.buildNumber}")
|
||||
externalJsonReport?.let {
|
||||
var externalReports = convert(externalJsonReport.replace("circlet_iosX64", "SpaceFramework_iosX64"),
|
||||
currentBuildNumber, target)
|
||||
externalReports.forEach { externalReport ->
|
||||
val extrenalAdditionalReport = SummaryBenchmarksReport(externalReport)
|
||||
.getBenchmarksReport().normalizeBenchmarksSet(goldenResults)
|
||||
extrenalAdditionalReport.buildNumber = currentBuildNumber
|
||||
benchmarksDispatcher.insert(extrenalAdditionalReport, target).then { _ ->
|
||||
println("[External] Success insert ${buildInfoRecord.buildNumber}")
|
||||
}.catch { errorResponse ->
|
||||
println("Failed to insert data for build")
|
||||
println(errorResponse)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val bundleSize = if (infoParts[10] != "-") infoParts[10] else null
|
||||
if (bundleSize != null) {
|
||||
// Add bundle size.
|
||||
val bundleSizeBenchmark = BenchmarkResult("KotlinNative",
|
||||
BenchmarkResult.Status.PASSED, bundleSize.toDouble(),
|
||||
BenchmarkResult.Metric.BUNDLE_SIZE, 0.0, 1, 0)
|
||||
val bundleSizeReport = BenchmarksReport(reports[0].env,
|
||||
listOf(bundleSizeBenchmark), reports[0].compiler)
|
||||
bundleSizeReport.buildNumber = currentBuildNumber
|
||||
benchmarksDispatcher.insert(bundleSizeReport, target).then { _ ->
|
||||
println("[BUNDLE] Success insert ${buildInfoRecord.buildNumber}")
|
||||
}.catch { errorResponse ->
|
||||
println("Failed to insert data for build")
|
||||
println(errorResponse)
|
||||
}
|
||||
}
|
||||
|
||||
reports.forEach { report ->
|
||||
val summaryReport = SummaryBenchmarksReport(report).getBenchmarksReport()
|
||||
.normalizeBenchmarksSet(goldenResults)
|
||||
summaryReport.buildNumber = currentBuildNumber
|
||||
// Save results in database.
|
||||
benchmarksDispatcher.insert(summaryReport, target).then { _ ->
|
||||
println("Success insert ${buildInfoRecord.buildNumber}")
|
||||
}.catch { errorResponse ->
|
||||
println("Failed to insert data for build")
|
||||
println(errorResponse.message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}.catch { errorResponse ->
|
||||
println("Failed to insert data for build")
|
||||
println(errorResponse)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
println(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
response.sendStatus(200)
|
||||
}.catch {
|
||||
response.sendStatus(400)
|
||||
}
|
||||
})
|
||||
|
||||
router.get("/delete/:target", { request, response ->
|
||||
val target = urlParameterToBaseFormat(request.params.target)
|
||||
var buildNumber: String? = null
|
||||
if (request.query != undefined) {
|
||||
if (request.query.buildNumber != undefined) {
|
||||
buildNumber = request.query.buildNumber
|
||||
}
|
||||
}
|
||||
benchmarksDispatcher.deleteBenchmarks(target, buildNumber).then {
|
||||
deleteBuildInfo(target, buildInfoIndex, buildNumber).then {
|
||||
response.sendStatus(200)
|
||||
}.catch {
|
||||
response.sendStatus(400)
|
||||
}
|
||||
}.catch {
|
||||
response.sendStatus(400)
|
||||
}
|
||||
})
|
||||
|
||||
router.get("/report/:target/:buildNumber", { request, response ->
|
||||
val target = urlParameterToBaseFormat(request.params.target)
|
||||
val buildNumber = request.params.buildNumber.toString()
|
||||
benchmarksDispatcher.getBenchmarksReports(buildNumber, target).then { reports ->
|
||||
response.send(reports.joinToString(", ", "[", "]"))
|
||||
}.catch {
|
||||
response.sendStatus(400)
|
||||
}
|
||||
})
|
||||
|
||||
router.get("/clear", { _, response ->
|
||||
CachableResponseDispatcher.clear()
|
||||
response.sendStatus(200)
|
||||
})
|
||||
|
||||
// Main page.
|
||||
router.get("/", { _, response ->
|
||||
response.render("index")
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
fun getBuildsInfoFromArtifactory(target: String): Promise<String> {
|
||||
val buildsFileName = "buildsSummary.csv"
|
||||
val artifactoryBuildsDirectory = "builds"
|
||||
return UrlNetworkConnector(artifactoryUrl).sendRequest(RequestMethod.GET,
|
||||
"$artifactoryBuildsDirectory/$target/$buildsFileName")
|
||||
}
|
||||
|
||||
fun BenchmarksReport.normalizeBenchmarksSet(dataForNormalization: Map<String, List<BenchmarkResult>>): BenchmarksReport {
|
||||
val resultBenchmarksList = benchmarks.map { benchmarksList ->
|
||||
benchmarksList.value.map {
|
||||
NormalizedMeanVarianceBenchmark(it.name, it.status, it.score, it.metric,
|
||||
it.runtimeInUs, it.repeat, it.warmup, (it as MeanVarianceBenchmark).variance,
|
||||
dataForNormalization[benchmarksList.key]?.get(0)?.score?.let { golden -> it.score / golden } ?: 0.0)
|
||||
}
|
||||
}.flatten()
|
||||
return BenchmarksReport(env, resultBenchmarksList, compiler)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.utils
|
||||
|
||||
import kotlin.coroutines.*
|
||||
import kotlin.js.Promise
|
||||
|
||||
suspend fun <T> Promise<T>.await(): T = suspendCoroutine { cont ->
|
||||
then({ cont.resume(it) }, { cont.resumeWithException(it) })
|
||||
}
|
||||
|
||||
fun launch(context: CoroutineContext = EmptyCoroutineContext, block: suspend () -> Unit) =
|
||||
block.startCoroutine(Continuation(context) { result ->
|
||||
result.onFailure { exception ->
|
||||
throw exception
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,77 @@
|
||||
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
|
||||
|
||||
buildscript {
|
||||
ext.rootBuildDirectory = file('../../..')
|
||||
|
||||
apply from: "$rootBuildDirectory/gradle/loadRootProperties.gradle"
|
||||
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
maven {
|
||||
url "http://dl.bintray.com/kotlin/kotlin-eap"
|
||||
}
|
||||
maven {
|
||||
url "http://dl.bintray.com/kotlin/kotlin-dev"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://cache-redirector.jetbrains.com/jcenter'
|
||||
}
|
||||
jcenter()
|
||||
maven {
|
||||
url kotlinCompilerRepo
|
||||
}
|
||||
maven {
|
||||
url buildKotlinCompilerRepo
|
||||
}
|
||||
maven {
|
||||
url "http://dl.bintray.com/kotlin/kotlin-eap"
|
||||
}
|
||||
maven {
|
||||
url "http://dl.bintray.com/kotlin/kotlin-dev"
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'kotlin-multiplatform'
|
||||
|
||||
kotlin {
|
||||
js {
|
||||
browser {
|
||||
binaries.executable()
|
||||
distribution {
|
||||
directory = new File("$projectDir/js/")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
commonMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir '../../benchmarks/shared/src'
|
||||
}
|
||||
jsMain {
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlinVersion"
|
||||
}
|
||||
kotlin.srcDir 'src/main/kotlin'
|
||||
kotlin.srcDir '../shared/src/main/kotlin'
|
||||
kotlin.srcDir '../src/main/kotlin-js'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
.chart {
|
||||
height: 400px;
|
||||
}
|
||||
.ct-legend {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
list-style: none;
|
||||
text-align: center;
|
||||
}
|
||||
.ct-legend li {
|
||||
position: relative;
|
||||
padding-left: 23px;
|
||||
margin-right: 10px;
|
||||
margin-bottom: 3px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
.ct-legend li:before {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
content: '';
|
||||
border: 3px solid transparent;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.ct-legend li.inactive:before {
|
||||
background: transparent;
|
||||
}
|
||||
.ct-legend.ct-legend-inside {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
.ct-legend.ct-legend-inside li{
|
||||
display: block;
|
||||
margin: 0;
|
||||
}
|
||||
.ct-legend .ct-series-0:before {
|
||||
background-color: #d70206;
|
||||
border-color: #d70206;
|
||||
}
|
||||
.ct-legend .ct-series-1:before {
|
||||
background-color: gold;
|
||||
border-color: gold;
|
||||
}
|
||||
.ct-legend .ct-series-2:before {
|
||||
background-color: limegreen;
|
||||
border-color: limegreen;
|
||||
}
|
||||
|
||||
.ct-legend .ct-series-3:before {
|
||||
background-color: lightsalmon;
|
||||
border-color: lightsalmon;
|
||||
}
|
||||
|
||||
.ct-legend .ct-series-4:before {
|
||||
background-color: mediumorchid;
|
||||
border-color: mediumorchid;
|
||||
}
|
||||
.ct-legend .ct-series-5:before {
|
||||
background-color: navy;
|
||||
border-color: navy;
|
||||
}
|
||||
|
||||
.ct-legend .ct-series-6:before {
|
||||
background-color: darkcyan;
|
||||
border-color: darkcyan;
|
||||
}
|
||||
|
||||
.ct-series-b .ct-line,
|
||||
.ct-series-b .ct-point {
|
||||
/* Set the colour of this series line */
|
||||
stroke: gold;
|
||||
}
|
||||
|
||||
.ct-series-c .ct-line,
|
||||
.ct-series-c .ct-point {
|
||||
/* Set the colour of this series line */
|
||||
stroke: limegreen;
|
||||
}
|
||||
|
||||
.ct-series-d .ct-line,
|
||||
.ct-series-d .ct-point {
|
||||
/* Set the colour of this series line */
|
||||
stroke: lightsalmon;
|
||||
}
|
||||
|
||||
.ct-series-e .ct-line,
|
||||
.ct-series-e .ct-point {
|
||||
/* Set the colour of this series line */
|
||||
stroke: mediumorchid;
|
||||
}
|
||||
|
||||
.ct-series-f .ct-line,
|
||||
.ct-series-f .ct-point {
|
||||
/* Set the colour of this series line */
|
||||
stroke: navy;
|
||||
}
|
||||
|
||||
.ct-series-g .ct-line,
|
||||
.ct-series-g .ct-point {
|
||||
/* Set the colour of this series line */
|
||||
stroke: darkcyan;
|
||||
}
|
||||
.tooltip { pointer-events: none; }
|
||||
@@ -0,0 +1,115 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>
|
||||
Benchmarks report
|
||||
</title>
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/chartist/0.11.0/chartist.min.css">
|
||||
<link rel="stylesheet" href="css/style.css">
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.min.js">
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js">
|
||||
</script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js">
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header class="navbar navbar-expand navbar-dark flex-column flex-md-row bd-navbar" style="background-color:#161616;">
|
||||
<img src="https://dashboard.snapcraft.io/site_media/appmedia/2018/04/256px-kotlin-logo-svg.png" style="width:60px;height:60px;">
|
||||
|
||||
<span class="navbar-brand mb-0 h1">
|
||||
Benchmarks report
|
||||
</span>
|
||||
</header>
|
||||
<div class="container-fluid">
|
||||
<p>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<label class="input-group-text" for="inputGroupTarget">Target</label>
|
||||
</div>
|
||||
<select class="custom-select" id="inputGroupTarget">
|
||||
<option value="Linux">Linux</option>
|
||||
<option value="Mac_OS_X">Mac OS X</option>
|
||||
<option value="Windows_10">Windows 10</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<label class="input-group-text" for="inputGroupBuildType">Build type</label>
|
||||
</div>
|
||||
<select class="custom-select" id="inputGroupBuildType">
|
||||
<option value="dev">Dev builds</option>
|
||||
<option value="day">Day</option>
|
||||
<option value="release">Releases</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<label class="input-group-text" for="inputGroupBranch">Branch</label>
|
||||
</div>
|
||||
<select class="custom-select" id="inputGroupBranch">
|
||||
<option value="master">master</option>
|
||||
<option value="all">All branches</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" id="basic-addon1">Highlighted build</span>
|
||||
</div>
|
||||
<input id="highligted_build" type="text" class="form-control" placeholder="" aria-label="build" aria-describedby="basic-addon1">
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" style="font-size:25px; font-weight:bold" id="plusBtn">+</button>
|
||||
<button type="button" class="btn btn-primary" style="font-size:25px; font-weight:bold" id="minusBtn">-</button>
|
||||
<button type="button" class="btn btn-primary" style="font-size:25px; font-weight:bold" id="prevBtn"><-</button>
|
||||
<button type="button" class="btn btn-primary" style="font-size:25px; font-weight:bold" id="nextBtn">-></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>Normalized execution time</h4>
|
||||
<div id="exec_chart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 40px">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>Compile time</h4>
|
||||
<div id="compile_chart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 40px">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>Normalized code size</h4>
|
||||
<div id="codesize_chart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 40px">
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h4>Bundle size</h4>
|
||||
<div id="bundlesize_chart" class="chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 40px">
|
||||
</div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartist/0.11.0/chartist.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartist-plugin-legend/0.6.2/chartist-plugin-legend.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.devbridge-autocomplete/1.4.9/jquery.autocomplete.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartist-plugin-axistitle@0.0.4/dist/chartist-plugin-axistitle.min.js"></script>
|
||||
<script src="js/ui.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,502 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
import kotlin.browser.*
|
||||
import org.w3c.fetch.*
|
||||
import org.jetbrains.report.json.*
|
||||
import org.jetbrains.buildInfo.Build
|
||||
import kotlin.js.*
|
||||
import kotlin.math.ceil
|
||||
import org.w3c.dom.*
|
||||
|
||||
// API for interop with JS library Chartist.
|
||||
external class ChartistPlugins {
|
||||
fun legend(data: dynamic): dynamic
|
||||
fun ctAxisTitle(data: dynamic): dynamic
|
||||
}
|
||||
|
||||
external object Chartist {
|
||||
class Svg(form: String, parameters: dynamic, chartArea: String)
|
||||
|
||||
val plugins: ChartistPlugins
|
||||
val Interpolation: dynamic
|
||||
fun Line(query: String, data: dynamic, options: dynamic): dynamic
|
||||
}
|
||||
|
||||
data class Commit(val revision: String, val developer: String)
|
||||
|
||||
fun sendGetRequest(url: String) = window.fetch(url, RequestInit("GET")).then { response ->
|
||||
if (!response.ok)
|
||||
error("Error during getting response from $url\n" +
|
||||
"${response}")
|
||||
else
|
||||
response.text()
|
||||
}.then { text -> text }
|
||||
|
||||
// Get data for chart in needed format.
|
||||
fun getChartData(labels: List<String>, valuesList: Collection<List<*>>,
|
||||
classNames: Array<String>? = null): dynamic {
|
||||
val chartData: dynamic = object {}
|
||||
chartData["labels"] = labels.toTypedArray()
|
||||
chartData["series"] = valuesList.mapIndexed { index, it ->
|
||||
val series: dynamic = object {}
|
||||
series["data"] = it.toTypedArray()
|
||||
classNames?.let { series["className"] = classNames[index] }
|
||||
series
|
||||
}.toTypedArray()
|
||||
return chartData
|
||||
}
|
||||
|
||||
// Create object with options of chart.
|
||||
fun getChartOptions(samples: Array<String>, yTitle: String, classNames: Array<String>? = null): dynamic {
|
||||
val chartOptions: dynamic = object {}
|
||||
chartOptions["fullWidth"] = true
|
||||
val paddingObject: dynamic = object {}
|
||||
paddingObject["right"] = 40
|
||||
chartOptions["chartPadding"] = paddingObject
|
||||
val axisXObject: dynamic = object {}
|
||||
axisXObject["offset"] = 40
|
||||
axisXObject["labelInterpolationFnc"] = { value, index, labels ->
|
||||
val labelsCount = 20
|
||||
val skipNumber = ceil((labels.length as Int).toDouble() / labelsCount).toInt()
|
||||
if (skipNumber > 1) {
|
||||
if (index % skipNumber == 0) value else null
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
chartOptions["axisX"] = axisXObject
|
||||
val axisYObject: dynamic = object {}
|
||||
axisYObject["offset"] = 90
|
||||
chartOptions["axisY"] = axisYObject
|
||||
val legendObject: dynamic = object {}
|
||||
legendObject["legendNames"] = samples
|
||||
classNames?.let { legendObject["classNames"] = classNames.sliceArray(0 until samples.size) }
|
||||
val titleObject: dynamic = object {}
|
||||
val axisYTitle: dynamic = object {}
|
||||
axisYTitle["axisTitle"] = yTitle
|
||||
axisYTitle["axisClass"] = "ct-axis-title"
|
||||
val titleOffset: dynamic = {}
|
||||
titleOffset["x"] = 15
|
||||
titleOffset["y"] = 15
|
||||
axisYTitle["offset"] = titleOffset
|
||||
axisYTitle["textAnchor"] = "middle"
|
||||
axisYTitle["flipTitle"] = true
|
||||
titleObject["axisY"] = axisYTitle
|
||||
val interpolationObject: dynamic = {}
|
||||
interpolationObject["fillHoles"] = true
|
||||
chartOptions["lineSmooth"] = Chartist.Interpolation.simple(interpolationObject)
|
||||
chartOptions["plugins"] = arrayOf(Chartist.plugins.legend(legendObject), Chartist.plugins.ctAxisTitle(titleObject))
|
||||
return chartOptions
|
||||
}
|
||||
|
||||
fun redirect(url: String) {
|
||||
window.location.href = url
|
||||
}
|
||||
|
||||
// Set customizations rules for chart.
|
||||
fun customizeChart(chart: dynamic, chartContainer: String, jquerySelector: dynamic, builds: List<Build?>,
|
||||
parameters: Map<String, String>) {
|
||||
chart.on("draw", { data ->
|
||||
var element = data.element
|
||||
if (data.type == "point") {
|
||||
val pointSize = 12
|
||||
val currentBuild = builds.get(data.index)
|
||||
currentBuild?.let { currentBuild ->
|
||||
// Higlight builds with failures.
|
||||
if (currentBuild.failuresNumber > 0) {
|
||||
val svgParameters: dynamic = object {}
|
||||
svgParameters["d"] = arrayOf("M", data.x, data.y - pointSize,
|
||||
"L", data.x - pointSize, data.y + pointSize / 2,
|
||||
"L", data.x + pointSize, data.y + pointSize / 2, "z").joinToString(" ")
|
||||
svgParameters["style"] = "fill:rgb(255,0,0);stroke-width:0"
|
||||
val triangle = Chartist.Svg("path", svgParameters, chartContainer)
|
||||
element = data.element.replace(triangle)
|
||||
} else if (currentBuild.buildNumber == parameters["build"]) {
|
||||
// Higlight choosen build.
|
||||
val svgParameters: dynamic = object {}
|
||||
svgParameters["x"] = data.x - pointSize / 2
|
||||
svgParameters["y"] = data.y - pointSize / 2
|
||||
svgParameters["height"] = pointSize
|
||||
svgParameters["width"] = pointSize
|
||||
svgParameters["style"] = "fill:rgb(0,0,255);stroke-width:0"
|
||||
val rectangle = Chartist.Svg("rect", svgParameters, "ct-point")
|
||||
element = data.element.replace(rectangle)
|
||||
}
|
||||
// Add tooltips.
|
||||
var shift = 1
|
||||
var previousBuild: Build? = null
|
||||
while (previousBuild == null && data.index - shift >= 0) {
|
||||
previousBuild = builds.get(data.index - shift)
|
||||
shift++
|
||||
}
|
||||
val linkToDetailedInfo = "https://kotlin-native-performance.labs.jb.gg/?report=" +
|
||||
"${currentBuild.buildNumber}:${parameters["target"]}" +
|
||||
"${previousBuild?.let {
|
||||
"&compareTo=${previousBuild.buildNumber}:${parameters["target"]}"
|
||||
} ?: ""}"
|
||||
val information = buildString {
|
||||
append("<a href=\"$linkToDetailedInfo\">${currentBuild.buildNumber}</a><br>")
|
||||
append("Value: ${data.value.y.toFixed(4)}<br>")
|
||||
if (currentBuild.failuresNumber > 0) {
|
||||
append("failures: ${currentBuild.failuresNumber}<br>")
|
||||
}
|
||||
append("branch: ${currentBuild.branch}<br>")
|
||||
append("date: ${currentBuild.date}<br>")
|
||||
append("time: ${currentBuild.formattedStartTime}-${currentBuild.formattedFinishTime}<br>")
|
||||
append("Commits:<br>")
|
||||
val commitsList = (JsonTreeParser.parse("{${currentBuild.commits}}") as JsonObject).getArray("commits").map {
|
||||
Commit(
|
||||
(it as JsonObject).getPrimitive("revision").content,
|
||||
(it as JsonObject).getPrimitive("developer").content
|
||||
)
|
||||
}
|
||||
val commits = if (commitsList.size > 3) commitsList.slice(0..2) else commitsList
|
||||
commits.forEach {
|
||||
append("${it.revision.substring(0, 7)} by ${it.developer}<br>")
|
||||
}
|
||||
if (commitsList.size > 3) {
|
||||
append("...")
|
||||
}
|
||||
}
|
||||
element._node.setAttribute("title", information)
|
||||
element._node.setAttribute("data-chart-tooltip", chartContainer)
|
||||
element._node.addEventListener("click", {
|
||||
redirect(linkToDetailedInfo)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
chart.on("created", {
|
||||
val currentChart = jquerySelector
|
||||
val parameters: dynamic = object {}
|
||||
parameters["selector"] = "[data-chart-tooltip=\"$chartContainer\"]"
|
||||
parameters["container"] = "#$chartContainer"
|
||||
parameters["html"] = true
|
||||
currentChart.tooltip(parameters)
|
||||
})
|
||||
}
|
||||
|
||||
var buildsNumberToShow: Int = 200
|
||||
var beforeDate: String? = null
|
||||
var afterDate: String? = null
|
||||
|
||||
external fun decodeURIComponent(url: String): String
|
||||
external fun encodeURIComponent(url: String): String
|
||||
|
||||
fun getDatesComponents() = "${beforeDate?.let {"&before=${encodeURIComponent(it)}"} ?: ""}" +
|
||||
"${afterDate?.let {"&after=${encodeURIComponent(it)}"} ?: ""}"
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val serverUrl = "https://kotlin-native-perf-summary.labs.jb.gg"
|
||||
val zoomRatio = 2
|
||||
|
||||
// Get parameters from request.
|
||||
val url = window.location.href
|
||||
val parametersPart = url.substringAfter("?").split('&')
|
||||
val parameters = mutableMapOf("target" to "Linux", "type" to "dev", "build" to "", "branch" to "master")
|
||||
parametersPart.forEach {
|
||||
val parsedParameter = it.split("=", limit = 2)
|
||||
if (parsedParameter.size == 2) {
|
||||
val (key, value) = parsedParameter
|
||||
parameters[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
buildsNumberToShow = parameters["count"]?.toInt() ?: buildsNumberToShow
|
||||
beforeDate = parameters["before"]?.let{ decodeURIComponent(it)}
|
||||
afterDate = parameters["after"]?.let{ decodeURIComponent(it)}
|
||||
|
||||
// Get branches.
|
||||
val branchesUrl = "$serverUrl/branches"
|
||||
sendGetRequest(branchesUrl).then { response ->
|
||||
val branches: Array<String> = JSON.parse(response)
|
||||
// Add release branches to selector.
|
||||
branches.filter { it != "master" }.forEach {
|
||||
if ("v(\\d|\\.)+(-M\\d)?-fixes".toRegex().matches(it)) {
|
||||
val option = Option(it, it)
|
||||
js("$('#inputGroupBranch')").append(js("$(option)"))
|
||||
}
|
||||
}
|
||||
document.querySelector("#inputGroupBranch [value=\"${parameters["branch"]}\"]")?.setAttribute("selected", "true")
|
||||
}
|
||||
|
||||
|
||||
// Fill autocomplete list with build numbers.
|
||||
val buildsNumbersUrl = "$serverUrl/buildsNumbers/${parameters["target"]}"
|
||||
sendGetRequest(buildsNumbersUrl).then { response ->
|
||||
val buildsNumbers: Array<String> = JSON.parse(response)
|
||||
val autocompleteParameters: dynamic = object {}
|
||||
autocompleteParameters["lookup"] = buildsNumbers
|
||||
autocompleteParameters["onSelect"] = { suggestion ->
|
||||
if (suggestion.value != parameters["build"]) {
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}" +
|
||||
"${if ((suggestion.value as String).isEmpty()) "" else "&build=${suggestion.value}"}&count=$buildsNumberToShow" +
|
||||
getDatesComponents()
|
||||
window.location.href = newLink
|
||||
}
|
||||
}
|
||||
js("$( \"#highligted_build\" )").autocomplete(autocompleteParameters)
|
||||
js("$('#highligted_build')").change({ value ->
|
||||
val newValue = js("$(this).val()").toString()
|
||||
if (newValue.isEmpty() || newValue in buildsNumbers) {
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}" +
|
||||
"${if (newValue.isEmpty()) "" else "&build=$newValue"}&count=$buildsNumberToShow" +
|
||||
getDatesComponents()
|
||||
window.location.href = newLink
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Change inputs values connected with parameters and add events listeners.
|
||||
document.querySelector("#inputGroupTarget [value=\"${parameters["target"]}\"]")?.setAttribute("selected", "true")
|
||||
document.querySelector("#inputGroupBuildType [value=\"${parameters["type"]}\"]")?.setAttribute("selected", "true")
|
||||
(document.getElementById("highligted_build") as HTMLInputElement).value = parameters["build"]!!
|
||||
|
||||
// Add onChange events for fields.
|
||||
// Don't use AJAX to have opportunity to share results with simple links.
|
||||
js("$('#inputGroupTarget')").change({
|
||||
val newValue = js("$(this).val()")
|
||||
if (newValue != parameters["target"]) {
|
||||
val newLink = "http://${window.location.host}/?target=$newValue&type=${parameters["type"]}&branch=${parameters["branch"]}" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow"
|
||||
window.location.href = newLink
|
||||
}
|
||||
})
|
||||
js("$('#inputGroupBuildType')").change({
|
||||
val newValue = js("$(this).val()")
|
||||
if (newValue != parameters["type"]) {
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=$newValue&branch=${parameters["branch"]}" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow"
|
||||
window.location.href = newLink
|
||||
}
|
||||
})
|
||||
js("$('#inputGroupBranch')").change({
|
||||
val newValue = js("$(this).val()")
|
||||
if (newValue != parameters["branch"]) {
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=$newValue" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow"
|
||||
window.location.href = newLink
|
||||
}
|
||||
})
|
||||
|
||||
val platformSpecificBenchs = if (parameters["target"] == "Mac_OS_X") ",FrameworkBenchmarksAnalyzer,SpaceFramework_iosX64" else
|
||||
if (parameters["target"] == "Linux") ",kotlinx.coroutines" else ""
|
||||
|
||||
// Collect information for charts library.
|
||||
val valuesToShow = mapOf("EXECUTION_TIME" to listOf(mapOf(
|
||||
"normalize" to "true"
|
||||
)),
|
||||
"COMPILE_TIME" to listOf(mapOf(
|
||||
"samples" to "HelloWorld,Videoplayer$platformSpecificBenchs",
|
||||
"agr" to "samples"
|
||||
)),
|
||||
"CODE_SIZE" to listOf(mapOf(
|
||||
"normalize" to "true",
|
||||
"exclude" to if (parameters["target"] == "Linux")
|
||||
"kotlinx.coroutines"
|
||||
else if (parameters["target"] == "Mac_OS_X")
|
||||
"SpaceFramework_iosX64"
|
||||
else ""
|
||||
), if (platformSpecificBenchs.isNotEmpty()) mapOf(
|
||||
"normalize" to "true",
|
||||
"agr" to "samples",
|
||||
"samples" to platformSpecificBenchs.removePrefix(",")
|
||||
) else null).filterNotNull(),
|
||||
"BUNDLE_SIZE" to listOf(mapOf("samples" to "KotlinNative",
|
||||
"agr" to "samples"))
|
||||
)
|
||||
|
||||
var execData = listOf<String>() to listOf<List<Double?>>()
|
||||
var compileData = listOf<String>() to listOf<List<Double?>>()
|
||||
var codeSizeData = listOf<String>() to listOf<List<Double?>>()
|
||||
var bundleSizeData = listOf<String>() to listOf<List<Int?>>()
|
||||
|
||||
val sizeClassNames = arrayOf("ct-series-e", "ct-series-f", "ct-series-g")
|
||||
|
||||
// Draw charts.
|
||||
var execChart: dynamic = null
|
||||
var compileChart: dynamic = null
|
||||
var codeSizeChart: dynamic = null
|
||||
var bundleSizeChart: dynamic = null
|
||||
|
||||
val descriptionUrl = "$serverUrl/buildsDesc/${parameters["target"]}?type=${parameters["type"]}" +
|
||||
"${if (parameters["branch"] != "all") "&branch=${parameters["branch"]}" else ""}&count=$buildsNumberToShow" +
|
||||
getDatesComponents()
|
||||
|
||||
val metricUrl = "$serverUrl/metricValue/${parameters["target"]}/"
|
||||
|
||||
// Get builds description.
|
||||
val buildsInfoPromise = sendGetRequest(descriptionUrl).then { response ->
|
||||
val buildsInfo = response as String
|
||||
val data = JsonTreeParser.parse(buildsInfo)
|
||||
if (data !is JsonArray) {
|
||||
error("Response is expected to be an array.")
|
||||
}
|
||||
data.jsonArray.map {
|
||||
val element = it as JsonElement
|
||||
if (element.isNull) null else Build.create(element as JsonObject)
|
||||
}
|
||||
}
|
||||
|
||||
// Send requests to get all needed metric values.
|
||||
valuesToShow.map { (metric, listOfSettings) ->
|
||||
val resultValues = listOfSettings.map { settings ->
|
||||
val getParameters = with(StringBuilder()) {
|
||||
if (settings.isNotEmpty()) {
|
||||
append("?")
|
||||
}
|
||||
var prefix = ""
|
||||
settings.forEach { (key, value) ->
|
||||
if (value.isNotEmpty()) {
|
||||
append("$prefix$key=$value")
|
||||
prefix = "&"
|
||||
}
|
||||
}
|
||||
toString()
|
||||
}
|
||||
val branchParameter = if (parameters["branch"] != "all")
|
||||
(if (getParameters.isEmpty()) "?" else "&") + "branch=${parameters["branch"]}"
|
||||
else ""
|
||||
|
||||
val url = "$metricUrl$metric$getParameters$branchParameter${
|
||||
if (parameters["type"] != "all")
|
||||
(if (getParameters.isEmpty() && branchParameter.isEmpty()) "?" else "&") + "type=${parameters["type"]}"
|
||||
else ""
|
||||
}&count=$buildsNumberToShow${getDatesComponents()}"
|
||||
sendGetRequest(url)
|
||||
}.toTypedArray()
|
||||
|
||||
// Get metrics values for charts.
|
||||
Promise.all(resultValues).then { responses ->
|
||||
val valuesList = responses.map { response ->
|
||||
val results = (JsonTreeParser.parse(response) as JsonArray).map {
|
||||
(it as JsonObject).getPrimitive("first").content to
|
||||
it.getArray("second").map { (it as JsonPrimitive).doubleOrNull }
|
||||
}
|
||||
|
||||
val labels = results.map { it.first }
|
||||
val values = results[0]?.second?.size?.let { (0..it - 1).map { i -> results.map { it.second[i] } } }
|
||||
?: emptyList()
|
||||
labels to values
|
||||
}
|
||||
val labels = valuesList[0].first
|
||||
val values = valuesList.map { it.second }.reduce { acc, valuesPart -> acc + valuesPart }
|
||||
|
||||
when (metric) {
|
||||
// Update chart with gotten data.
|
||||
"COMPILE_TIME" -> {
|
||||
compileData = labels to values.map { it.map { it?.let { it / 1000 } } }
|
||||
compileChart = Chartist.Line("#compile_chart",
|
||||
getChartData(labels, compileData.second),
|
||||
getChartOptions(valuesToShow["COMPILE_TIME"]!![0]!!["samples"]!!.split(',').toTypedArray(),
|
||||
"Time, milliseconds"))
|
||||
buildsInfoPromise.then { builds ->
|
||||
customizeChart(compileChart, "compile_chart", js("$(\"#compile_chart\")"), builds, parameters)
|
||||
compileChart.update(getChartData(compileData.first, compileData.second))
|
||||
}
|
||||
}
|
||||
"EXECUTION_TIME" -> {
|
||||
execData = labels to values
|
||||
execChart = Chartist.Line("#exec_chart",
|
||||
getChartData(labels, execData.second),
|
||||
getChartOptions(arrayOf("Geometric Mean"), "Normalized time"))
|
||||
buildsInfoPromise.then { builds ->
|
||||
customizeChart(execChart, "exec_chart", js("$(\"#exec_chart\")"), builds, parameters)
|
||||
execChart.update(getChartData(execData.first, execData.second))
|
||||
}
|
||||
}
|
||||
"CODE_SIZE" -> {
|
||||
codeSizeData = labels to values
|
||||
codeSizeChart = Chartist.Line("#codesize_chart",
|
||||
getChartData(labels, codeSizeData.second),
|
||||
getChartOptions(arrayOf("Geometric Mean") + platformSpecificBenchs.split(',')
|
||||
.filter { it.isNotEmpty() },
|
||||
"Normalized size",
|
||||
arrayOf("ct-series-4", "ct-series-5", "ct-series-6")))
|
||||
buildsInfoPromise.then { builds ->
|
||||
customizeChart(codeSizeChart, "codesize_chart", js("$(\"#codesize_chart\")"), builds, parameters)
|
||||
codeSizeChart.update(getChartData(codeSizeData.first, codeSizeData.second, sizeClassNames))
|
||||
}
|
||||
}
|
||||
"BUNDLE_SIZE" -> {
|
||||
bundleSizeData = labels to values.map { it.map { it?.let { it.toInt() / 1024 / 1024 } } }
|
||||
bundleSizeChart = Chartist.Line("#bundlesize_chart",
|
||||
getChartData(labels,
|
||||
bundleSizeData.second, sizeClassNames),
|
||||
getChartOptions(arrayOf("Bundle size"), "Size, MB", arrayOf("ct-series-4")))
|
||||
buildsInfoPromise.then { builds ->
|
||||
customizeChart(bundleSizeChart, "bundlesize_chart", js("$(\"#bundlesize_chart\")"), builds, parameters)
|
||||
bundleSizeChart.update(getChartData(bundleSizeData.first, bundleSizeData.second, sizeClassNames))
|
||||
}
|
||||
}
|
||||
else -> error("No chart for metric $metric")
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// Update all charts with using same data.
|
||||
val updateAllCharts: () -> Unit = {
|
||||
execChart.update(getChartData(execData.first, execData.second))
|
||||
compileChart.update(getChartData(compileData.first, compileData.second))
|
||||
codeSizeChart.update(getChartData(codeSizeData.first, codeSizeData.second, sizeClassNames))
|
||||
bundleSizeChart.update(getChartData(bundleSizeData.first, bundleSizeData.second, sizeClassNames))
|
||||
}
|
||||
|
||||
js("$('#plusBtn')").click({
|
||||
buildsNumberToShow =
|
||||
if (buildsNumberToShow / zoomRatio > zoomRatio) {
|
||||
buildsNumberToShow / zoomRatio
|
||||
} else {
|
||||
buildsNumberToShow
|
||||
}
|
||||
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=${parameters["branch"]}" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow" +
|
||||
getDatesComponents()
|
||||
window.location.href = newLink
|
||||
Unit
|
||||
})
|
||||
|
||||
js("$('#minusBtn')").click({
|
||||
buildsNumberToShow = buildsNumberToShow * zoomRatio
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=${parameters["branch"]}" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow" +
|
||||
getDatesComponents()
|
||||
window.location.href = newLink
|
||||
Unit
|
||||
})
|
||||
|
||||
js("$('#prevBtn')").click({
|
||||
buildsInfoPromise.then { builds ->
|
||||
beforeDate = builds.firstOrNull()?.startTime
|
||||
afterDate = null
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=${parameters["branch"]}" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow" +
|
||||
"${beforeDate?.let {"&before=${encodeURIComponent(it)}"} ?: ""}"
|
||||
window.location.href = newLink
|
||||
}
|
||||
})
|
||||
|
||||
js("$('#nextBtn')").click({
|
||||
buildsInfoPromise.then { builds ->
|
||||
beforeDate = null
|
||||
afterDate = builds.lastOrNull()?.startTime
|
||||
val newLink = "http://${window.location.host}/?target=${parameters["target"]}&type=${parameters["type"]}&branch=${parameters["branch"]}" +
|
||||
"${if (parameters["build"]!!.isEmpty()) "" else "&build=${parameters["build"]}"}&count=$buildsNumberToShow" +
|
||||
"${afterDate?.let {"&after=${encodeURIComponent(it)}"} ?: ""}"
|
||||
window.location.href = newLink
|
||||
}
|
||||
})
|
||||
|
||||
// Auto reload.
|
||||
parameters["refresh"]?.let {
|
||||
// Set event.
|
||||
window.setInterval({
|
||||
window.location.reload()
|
||||
}, it.toInt() * 1000)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user