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:
Stanislav Erokhin
2020-10-27 21:00:28 +03:00
parent 91e4162dad
commit f624800b84
2830 changed files with 0 additions and 0 deletions
@@ -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
@@ -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
View File
@@ -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
View File
@@ -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
+529
View File
@@ -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"
}
}
@@ -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)
}
}
@@ -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)
}
@@ -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
"""
}
}
@@ -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
}
}
@@ -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()
}
}
}
}
@@ -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")
}
@@ -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
@@ -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()
}
}
@@ -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)
}
}