Refactor performance-server to make it work in IDE, and bump npm deps
diff --git a/kotlin-native/tools/performance-server/build.gradle.kts b/kotlin-native/tools/performance-server/build.gradle.kts
index 8171676..5b62254 100644
--- a/kotlin-native/tools/performance-server/build.gradle.kts
+++ b/kotlin-native/tools/performance-server/build.gradle.kts
@@ -34,26 +34,19 @@
 
 kotlin {
     sourceSets {
-        val commonMain by getting {
+        commonMain  {
             dependencies {
                 implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
             }
-            kotlin.srcDir("../benchmarks/shared/src")
         }
-        val jsMain by creating {
+        jsMain {
             dependencies {
-                implementation(npm("body-parser", "~1.20.0"))
+                implementation(npm("body-parser", "~1.20.3"))
                 implementation(npm("debug", "~4.3.4"))
                 implementation(npm("ejs", "~3.1.7"))
-                implementation(npm("express", "~4.19.2"))
-                implementation(npm("kotlin", "~1.6.20"))
+                implementation(npm("express", "~4.20.0"))
                 implementation(npm("node-fetch", "~2.6.6"))
             }
-            kotlin {
-                srcDir("src/main/kotlin")
-                srcDir("src/main/kotlin-js")
-                srcDir("shared/src/main/kotlin")
-            }
         }
     }
 
@@ -61,12 +54,6 @@
         js {
             moduleName = "app"
             nodejs()
-            compilations.all {
-                compilerOptions.configure {
-                    moduleKind.set(MODULE_COMMONJS)
-                    sourceMap.set(true)
-                }
-            }
             binaries.executable()
         }
     }
diff --git a/kotlin-native/tools/performance-server/gradle/wrapper/gradle-wrapper.properties b/kotlin-native/tools/performance-server/gradle/wrapper/gradle-wrapper.properties
index f78e028..c8a852e 100644
--- a/kotlin-native/tools/performance-server/gradle/wrapper/gradle-wrapper.properties
+++ b/kotlin-native/tools/performance-server/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
-distributionSha256Sum=7ba68c54029790ab444b39d7e293d3236b2632631fb5f2e012bb28b4ff669e4b
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
+distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
diff --git a/kotlin-native/tools/performance-server/kotlin-js-store/yarn.lock b/kotlin-native/tools/performance-server/kotlin-js-store/yarn.lock
index 7eff2fe..d67a645 100644
--- a/kotlin-native/tools/performance-server/kotlin-js-store/yarn.lock
+++ b/kotlin-native/tools/performance-server/kotlin-js-store/yarn.lock
@@ -10,10 +10,10 @@
     mime-types "~2.1.34"
     negotiator "0.6.3"
 
-ansi-colors@4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
-  integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
+ansi-colors@^4.1.3:
+  version "4.1.3"
+  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
+  integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==
 
 ansi-regex@^5.0.1:
   version "5.0.1"
@@ -46,9 +46,9 @@
   integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==
 
 async@^3.2.3:
-  version "3.2.5"
-  resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
-  integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
+  version "3.2.6"
+  resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
+  integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==
 
 balanced-match@^1.0.0:
   version "1.0.2"
@@ -60,10 +60,10 @@
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
   integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
 
-body-parser@1.20.2, body-parser@~1.20.0:
-  version "1.20.2"
-  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
-  integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
+body-parser@1.20.3, body-parser@~1.20.3:
+  version "1.20.3"
+  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6"
+  integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==
   dependencies:
     bytes "3.1.2"
     content-type "~1.0.5"
@@ -73,7 +73,7 @@
     http-errors "2.0.0"
     iconv-lite "0.4.24"
     on-finished "2.4.1"
-    qs "6.11.0"
+    qs "6.13.0"
     raw-body "2.5.2"
     type-is "~1.6.18"
     unpipe "1.0.0"
@@ -100,7 +100,7 @@
   dependencies:
     fill-range "^7.1.1"
 
-browser-stdout@1.3.1:
+browser-stdout@^1.3.1:
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60"
   integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==
@@ -139,10 +139,10 @@
     ansi-styles "^4.1.0"
     supports-color "^7.1.0"
 
-chokidar@3.5.3:
-  version "3.5.3"
-  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
-  integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
+chokidar@^3.5.3:
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
+  integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==
   dependencies:
     anymatch "~3.1.2"
     braces "~3.0.2"
@@ -209,19 +209,12 @@
   dependencies:
     ms "2.0.0"
 
-debug@4.3.4:
-  version "4.3.4"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
-  integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+debug@^4.3.5, debug@~4.3.4:
+  version "4.3.7"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
+  integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
   dependencies:
-    ms "2.1.2"
-
-debug@~4.3.4:
-  version "4.3.5"
-  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e"
-  integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==
-  dependencies:
-    ms "2.1.2"
+    ms "^2.1.3"
 
 decamelize@^4.0.0:
   version "4.0.0"
@@ -247,10 +240,10 @@
   resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
   integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
 
-diff@5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b"
-  integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==
+diff@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531"
+  integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==
 
 ee-first@1.1.1:
   version "1.1.1"
@@ -274,6 +267,11 @@
   resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
   integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==
 
+encodeurl@~2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58"
+  integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==
+
 es-define-property@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845"
@@ -287,16 +285,16 @@
   integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
 
 escalade@^3.1.1:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27"
-  integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
+  integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
 
 escape-html@~1.0.3:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
   integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
 
-escape-string-regexp@4.0.0:
+escape-string-regexp@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
   integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
@@ -306,37 +304,37 @@
   resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
   integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
 
-express@~4.19.2:
-  version "4.19.2"
-  resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465"
-  integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==
+express@~4.20.0:
+  version "4.20.0"
+  resolved "https://registry.yarnpkg.com/express/-/express-4.20.0.tgz#f1d08e591fcec770c07be4767af8eb9bcfd67c48"
+  integrity sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==
   dependencies:
     accepts "~1.3.8"
     array-flatten "1.1.1"
-    body-parser "1.20.2"
+    body-parser "1.20.3"
     content-disposition "0.5.4"
     content-type "~1.0.4"
     cookie "0.6.0"
     cookie-signature "1.0.6"
     debug "2.6.9"
     depd "2.0.0"
-    encodeurl "~1.0.2"
+    encodeurl "~2.0.0"
     escape-html "~1.0.3"
     etag "~1.8.1"
     finalhandler "1.2.0"
     fresh "0.5.2"
     http-errors "2.0.0"
-    merge-descriptors "1.0.1"
+    merge-descriptors "1.0.3"
     methods "~1.1.2"
     on-finished "2.4.1"
     parseurl "~1.3.3"
-    path-to-regexp "0.1.7"
+    path-to-regexp "0.1.10"
     proxy-addr "~2.0.7"
     qs "6.11.0"
     range-parser "~1.2.1"
     safe-buffer "5.2.1"
-    send "0.18.0"
-    serve-static "1.15.0"
+    send "0.19.0"
+    serve-static "1.16.0"
     setprototypeof "1.2.0"
     statuses "2.0.1"
     type-is "~1.6.18"
@@ -370,7 +368,7 @@
     statuses "2.0.1"
     unpipe "~1.0.0"
 
-find-up@5.0.0:
+find-up@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
   integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
@@ -436,7 +434,7 @@
   dependencies:
     is-glob "^4.0.1"
 
-glob@8.1.0:
+glob@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
   integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
@@ -483,7 +481,7 @@
   dependencies:
     function-bind "^1.1.2"
 
-he@1.2.0:
+he@^1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
   integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
@@ -564,26 +562,28 @@
   integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==
 
 jake@^10.8.5:
-  version "10.9.1"
-  resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.1.tgz#8dc96b7fcc41cb19aa502af506da4e1d56f5e62b"
-  integrity sha512-61btcOHNnLnsOdtLgA5efqQWjnSi/vow5HbI7HMdKKWqvrKR1bLK3BPlJn9gcSaP2ewuamUSMB5XEy76KUIS2w==
+  version "10.9.2"
+  resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f"
+  integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==
   dependencies:
     async "^3.2.3"
     chalk "^4.0.2"
     filelist "^1.0.4"
     minimatch "^3.1.2"
 
-js-yaml@4.1.0:
+js-yaml@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
   integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
   dependencies:
     argparse "^2.0.1"
 
-kotlin@~1.6.20:
-  version "1.6.21"
-  resolved "https://registry.yarnpkg.com/kotlin/-/kotlin-1.6.21.tgz#b7f1be888ba04c3b59807bb197909cba4d0e9e47"
-  integrity sha512-ulTTjmeZZfvlxxMFD/EjaGMlZ3pNypwOkQLK6WZjbsS7kTYqufekoiJD+mksmJQZTmc1MNW4M6bmvM5WbO1vKQ==
+kotlin-web-helpers@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/kotlin-web-helpers/-/kotlin-web-helpers-2.0.0.tgz#b112096b273c1e733e0b86560998235c09a19286"
+  integrity sha512-xkVGl60Ygn/zuLkDPx+oHj7jeLR7hCvoNF99nhwXMn8a3ApB4lLiC9pk4ol4NHPjyoCbvQctBqvzUcp8pkqyWw==
+  dependencies:
+    format-util "^1.0.5"
 
 locate-path@^6.0.0:
   version "6.0.0"
@@ -592,7 +592,7 @@
   dependencies:
     p-locate "^5.0.0"
 
-log-symbols@4.1.0:
+log-symbols@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503"
   integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==
@@ -605,10 +605,10 @@
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
   integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
 
-merge-descriptors@1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
-  integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==
+merge-descriptors@1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5"
+  integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==
 
 methods@~1.1.2:
   version "1.1.2"
@@ -632,13 +632,6 @@
   resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
   integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
 
-minimatch@5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
-  integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
-  dependencies:
-    brace-expansion "^2.0.1"
-
 minimatch@^3.1.2:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -646,50 +639,45 @@
   dependencies:
     brace-expansion "^1.1.7"
 
-minimatch@^5.0.1:
+minimatch@^5.0.1, minimatch@^5.1.6:
   version "5.1.6"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
   integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
   dependencies:
     brace-expansion "^2.0.1"
 
-mocha@10.3.0:
-  version "10.3.0"
-  resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.3.0.tgz#0e185c49e6dccf582035c05fa91084a4ff6e3fe9"
-  integrity sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==
+mocha@10.7.3:
+  version "10.7.3"
+  resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.7.3.tgz#ae32003cabbd52b59aece17846056a68eb4b0752"
+  integrity sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==
   dependencies:
-    ansi-colors "4.1.1"
-    browser-stdout "1.3.1"
-    chokidar "3.5.3"
-    debug "4.3.4"
-    diff "5.0.0"
-    escape-string-regexp "4.0.0"
-    find-up "5.0.0"
-    glob "8.1.0"
-    he "1.2.0"
-    js-yaml "4.1.0"
-    log-symbols "4.1.0"
-    minimatch "5.0.1"
-    ms "2.1.3"
-    serialize-javascript "6.0.0"
-    strip-json-comments "3.1.1"
-    supports-color "8.1.1"
-    workerpool "6.2.1"
-    yargs "16.2.0"
-    yargs-parser "20.2.4"
-    yargs-unparser "2.0.0"
+    ansi-colors "^4.1.3"
+    browser-stdout "^1.3.1"
+    chokidar "^3.5.3"
+    debug "^4.3.5"
+    diff "^5.2.0"
+    escape-string-regexp "^4.0.0"
+    find-up "^5.0.0"
+    glob "^8.1.0"
+    he "^1.2.0"
+    js-yaml "^4.1.0"
+    log-symbols "^4.1.0"
+    minimatch "^5.1.6"
+    ms "^2.1.3"
+    serialize-javascript "^6.0.2"
+    strip-json-comments "^3.1.1"
+    supports-color "^8.1.1"
+    workerpool "^6.5.1"
+    yargs "^16.2.0"
+    yargs-parser "^20.2.9"
+    yargs-unparser "^2.0.0"
 
 ms@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
   integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
 
-ms@2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
-  integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
-
-ms@2.1.3:
+ms@2.1.3, ms@^2.1.3:
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
   integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@@ -754,10 +742,10 @@
   resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
   integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
 
-path-to-regexp@0.1.7:
-  version "0.1.7"
-  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
-  integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==
+path-to-regexp@0.1.10:
+  version "0.1.10"
+  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b"
+  integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==
 
 picomatch@^2.0.4, picomatch@^2.2.1:
   version "2.3.1"
@@ -779,6 +767,13 @@
   dependencies:
     side-channel "^1.0.4"
 
+qs@6.13.0:
+  version "6.13.0"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
+  integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==
+  dependencies:
+    side-channel "^1.0.6"
+
 randombytes@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -842,17 +837,36 @@
     range-parser "~1.2.1"
     statuses "2.0.1"
 
-serialize-javascript@6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8"
-  integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==
+send@0.19.0:
+  version "0.19.0"
+  resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8"
+  integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==
+  dependencies:
+    debug "2.6.9"
+    depd "2.0.0"
+    destroy "1.2.0"
+    encodeurl "~1.0.2"
+    escape-html "~1.0.3"
+    etag "~1.8.1"
+    fresh "0.5.2"
+    http-errors "2.0.0"
+    mime "1.6.0"
+    ms "2.1.3"
+    on-finished "2.4.1"
+    range-parser "~1.2.1"
+    statuses "2.0.1"
+
+serialize-javascript@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2"
+  integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==
   dependencies:
     randombytes "^2.1.0"
 
-serve-static@1.15.0:
-  version "1.15.0"
-  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540"
-  integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==
+serve-static@1.16.0:
+  version "1.16.0"
+  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92"
+  integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==
   dependencies:
     encodeurl "~1.0.2"
     escape-html "~1.0.3"
@@ -876,7 +890,7 @@
   resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
   integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==
 
-side-channel@^1.0.4:
+side-channel@^1.0.4, side-channel@^1.0.6:
   version "1.0.6"
   resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2"
   integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==
@@ -920,18 +934,11 @@
   dependencies:
     ansi-regex "^5.0.1"
 
-strip-json-comments@3.1.1:
+strip-json-comments@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
   integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
 
-supports-color@8.1.1:
-  version "8.1.1"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
-  integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
-  dependencies:
-    has-flag "^4.0.0"
-
 supports-color@^7.1.0:
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
@@ -939,6 +946,13 @@
   dependencies:
     has-flag "^4.0.0"
 
+supports-color@^8.1.1:
+  version "8.1.1"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
+  integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+  dependencies:
+    has-flag "^4.0.0"
+
 to-regex-range@^5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@@ -964,10 +978,10 @@
     media-typer "0.3.0"
     mime-types "~2.1.24"
 
-typescript@5.4.3:
-  version "5.4.3"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff"
-  integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==
+typescript@5.5.4:
+  version "5.5.4"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba"
+  integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
 
 unpipe@1.0.0, unpipe@~1.0.0:
   version "1.0.0"
@@ -997,10 +1011,10 @@
     tr46 "~0.0.3"
     webidl-conversions "^3.0.0"
 
-workerpool@6.2.1:
-  version "6.2.1"
-  resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
-  integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
+workerpool@^6.5.1:
+  version "6.5.1"
+  resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544"
+  integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==
 
 wrap-ansi@^7.0.0:
   version "7.0.0"
@@ -1021,17 +1035,12 @@
   resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
   integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
 
-yargs-parser@20.2.4:
-  version "20.2.4"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54"
-  integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==
-
-yargs-parser@^20.2.2:
+yargs-parser@^20.2.2, yargs-parser@^20.2.9:
   version "20.2.9"
   resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
   integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
 
-yargs-unparser@2.0.0:
+yargs-unparser@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb"
   integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==
@@ -1041,7 +1050,7 @@
     flat "^5.0.2"
     is-plain-obj "^2.1.0"
 
-yargs@16.2.0:
+yargs@^16.2.0:
   version "16.2.0"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
   integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
diff --git a/kotlin-native/tools/performance-server/shared/src/main/kotlin/org/jetbrains/buildInfo/BuildInfo.kt b/kotlin-native/tools/performance-server/shared/src/main/kotlin/org/jetbrains/buildInfo/BuildInfo.kt
deleted file mode 100644
index 1ffc600..0000000
--- a/kotlin-native/tools/performance-server/shared/src/main/kotlin/org/jetbrains/buildInfo/BuildInfo.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.*
-
-@JsExport
-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)
-    }
-}
\ No newline at end of file
diff --git a/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/analyzer/Statistics.kt b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/analyzer/Statistics.kt
new file mode 100644
index 0000000..00e8079
--- /dev/null
+++ b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/analyzer/Statistics.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.jetbrains.report.BenchmarkResult
+import org.jetbrains.report.MeanVariance
+import org.jetbrains.report.MeanVarianceBenchmark
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.pow
+import kotlin.math.sqrt
+
+// Calculate difference in percentage compare to another.
+fun MeanVarianceBenchmark.calcPercentageDiff(other: MeanVarianceBenchmark): MeanVariance {
+    if (score == 0.0 && variance == 0.0 && other.score == 0.0 && other.variance == 0.0)
+        return MeanVariance(score, variance)
+    require(other.score >= 0 &&
+            other.variance >= 0 &&
+            (other.score - other.variance != 0.0 || other.score == 0.0),
+            { "Mean and variance should be positive and not equal!" })
+
+    // Analyze intervals. Calculate difference between border points.
+    val (bigValue, smallValue) = if (score > other.score) Pair(this, other) else Pair(other, this)
+    val bigValueIntervalStart = bigValue.score - bigValue.variance
+    val bigValueIntervalEnd = bigValue.score + bigValue.variance
+    val smallValueIntervalStart = smallValue.score - smallValue.variance
+    val smallValueIntervalEnd = smallValue.score + smallValue.variance
+    if (smallValueIntervalEnd > bigValueIntervalStart) {
+        // Interval intersect.
+        return MeanVariance(0.0, 0.0)
+    }
+    val mean = ((smallValueIntervalEnd - bigValueIntervalStart) / bigValueIntervalStart) *
+            (if (score > other.score) -1 else 1)
+
+    val maxValueChange = ((bigValueIntervalEnd - smallValueIntervalEnd) / bigValueIntervalEnd)
+    val minValueChange = ((bigValueIntervalStart - smallValueIntervalStart) / bigValueIntervalStart)
+    val variance = abs(abs(mean) - max(minValueChange, maxValueChange))
+    return MeanVariance(mean * 100, variance * 100)
+}
+
+// Calculate ratio value compare to another.
+fun MeanVarianceBenchmark.calcRatio(other: MeanVarianceBenchmark): MeanVariance {
+    if (other.score == 0.0 && other.variance == 0.0)
+        return MeanVariance(1.0, 0.0)
+    require(other.score >= 0 &&
+            other.variance >= 0 &&
+            (other.score - other.variance != 0.0 || other.score == 0.0),
+            { "Mean and variance should be positive and not equal!" })
+    val mean = if (other.score != 0.0) (score / other.score) else 0.0
+    val minRatio = (score - variance) / (other.score + other.variance)
+    val maxRatio = (score + variance) / (other.score - other.variance)
+    val ratioConfInt = min(abs(minRatio - mean), abs(maxRatio - mean))
+    return MeanVariance(mean, ratioConfInt)
+}
+
+fun geometricMean(values: Collection<Double>, totalNumber: Int = values.size) =
+        with(values.asSequence().filter { it != 0.0 }) {
+            if (count() == 0 || totalNumber == 0) {
+                0.0
+            } else {
+                map { it.pow(1.0 / totalNumber) }.reduce { a, b -> a * b }
+            }
+        }
+
+fun computeMeanVariance(samples: List<Double>): MeanVariance {
+    val removedBroadSamples = 0.2
+    val zStar = 1.67    // Critical point for 90% confidence of normal distribution.
+    // Skip several minimal and maximum values.
+    val filteredSamples = if (samples.size >= 1/removedBroadSamples) {
+         samples.sorted().subList((samples.size * removedBroadSamples).toInt(),
+                samples.size - (samples.size * removedBroadSamples).toInt())
+    } else {
+        samples
+    }
+
+    val mean = filteredSamples.sum() / filteredSamples.size
+    val variance = samples.indices.sumOf {
+        (samples[it] - mean) * (samples[it] - mean)
+    } / samples.size
+    val confidenceInterval = sqrt(variance / samples.size) * zStar
+    return MeanVariance(mean, confidenceInterval)
+}
+
+// Calculate average results for benchmarks (each benchmark can be run several times).
+fun collectMeanResults(benchmarks: Map<String, List<BenchmarkResult>>): BenchmarksTable {
+    return benchmarks.map { (name, resultsSet) ->
+        val repeatedSequence = IntArray(resultsSet.size)
+        var metric = BenchmarkResult.Metric.EXECUTION_TIME
+        var currentStatus = BenchmarkResult.Status.PASSED
+        var currentWarmup = -1
+
+        // Results can be already processed.
+        if (resultsSet[0] is MeanVarianceBenchmark) {
+            require(resultsSet.size == 1) { "Several MeanVarianceBenchmark instances." }
+            name to resultsSet[0] as MeanVarianceBenchmark
+        } else {
+            // Collect common benchmark values and check them.
+            resultsSet.forEachIndexed { index, result ->
+                // If there was at least one failure, summary is marked as failure.
+                if (result.status == BenchmarkResult.Status.FAILED) {
+                    currentStatus = result.status
+                }
+                repeatedSequence[index] = result.repeat
+                if (currentWarmup != -1)
+                    if (result.warmup != currentWarmup)
+                        println("Check data consistency. Warmup value for benchmark '${result.name}' differs.")
+                currentWarmup = result.warmup
+                metric = result.metric
+            }
+
+            repeatedSequence.sort()
+            // Check if there are missed loop during running benchmarks.
+            repeatedSequence.forEachIndexed { index, element ->
+                if (index != 0)
+                    if ((element - repeatedSequence[index - 1]) != 1)
+                        println("Check data consistency. For benchmark '$name' there is no run" +
+                                " between ${repeatedSequence[index - 1]} and $element.")
+            }
+
+            // Create mean and variance benchmarks result.
+            val scoreMeanVariance = computeMeanVariance(resultsSet.map { it.score })
+            val runtimeInUsMeanVariance = computeMeanVariance(resultsSet.map { it.runtimeInUs })
+            val meanBenchmark = MeanVarianceBenchmark(name, currentStatus, scoreMeanVariance.mean, metric,
+                    runtimeInUsMeanVariance.mean, repeatedSequence[resultsSet.size - 1],
+                    currentWarmup, scoreMeanVariance.variance)
+            name to meanBenchmark
+        }
+    }.toMap()
+}
+
+fun collectBenchmarksDurations(benchmarks: Map<String, List<BenchmarkResult>>): Map<String, Double> =
+        benchmarks.map { (name, resultsSet) ->
+            name to resultsSet.sumOf { it.runtimeInUs }
+        }.toMap()
\ No newline at end of file
diff --git a/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/analyzer/SummaryBenchmarksReport.kt b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/analyzer/SummaryBenchmarksReport.kt
new file mode 100644
index 0000000..c0ceb01
--- /dev/null
+++ b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/analyzer/SummaryBenchmarksReport.kt
@@ -0,0 +1,193 @@
+/*
+ * 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.jetbrains.report.BenchmarkResult
+import org.jetbrains.report.BenchmarksReport
+import org.jetbrains.report.Compiler
+import org.jetbrains.report.Environment
+import org.jetbrains.report.MeanVariance
+import org.jetbrains.report.MeanVarianceBenchmark
+import kotlin.math.abs
+
+typealias SummaryBenchmark = Pair<MeanVarianceBenchmark?, MeanVarianceBenchmark?>
+typealias BenchmarksTable = Map<String, MeanVarianceBenchmark>
+typealias ScoreChange = Pair<MeanVariance, MeanVariance>
+
+class DetailedBenchmarksReport(currentBenchmarks: Map<String, List<BenchmarkResult>>,
+                               previousBenchmarks: Map<String, List<BenchmarkResult>>? = null,
+                               val meaningfulChangesValue: Double = 0.5) {
+    // Report created by joining comparing reports.
+    val mergedReport: Map<String, SummaryBenchmark>
+
+    // Maps with changes of performance.
+    var regressions = mapOf<String, ScoreChange>()
+        private set
+    var improvements = mapOf<String, ScoreChange>()
+        private set
+
+    // Summary value of report - geometric mean.
+    val geoMeanBenchmark: SummaryBenchmark
+    var geoMeanScoreChange: ScoreChange? = null
+        private set
+
+    init {
+        // Count avarage values for each benchmark.
+        val currentBenchmarksTable = collectMeanResults(currentBenchmarks)
+        val previousBenchmarksTable = previousBenchmarks?.let {
+            collectMeanResults(previousBenchmarks)
+        }
+        mergedReport = createMergedReport(currentBenchmarksTable, previousBenchmarksTable)
+        geoMeanBenchmark = calculateGeoMeanBenchmark(currentBenchmarksTable, previousBenchmarksTable)
+
+        if (previousBenchmarks != null) {
+            // Check changes in environment and tools.
+            analyzePerformanceChanges()
+        }
+    }
+    // Analyze and collect changes in performance between same becnhmarks.
+    private fun analyzePerformanceChanges() {
+        val performanceChanges = mergedReport.asSequence().map { (name, element) ->
+            getBenchmarkPerfomanceChange(name, element)
+        }.filterNotNull().groupBy {
+            if (it.second.first.mean > 0) "regressions" else "improvements"
+        }
+
+        // Sort regressions and improvements.
+        regressions = performanceChanges["regressions"]
+                ?.sortedByDescending { it.second.first.mean }?.map { it.first to it.second }
+                ?.toMap() ?: mapOf<String, ScoreChange>()
+        improvements = performanceChanges["improvements"]
+                ?.sortedBy { it.second.first.mean }?.map { it.first to it.second }
+                ?.toMap() ?: mapOf<String, ScoreChange>()
+
+        // Calculate change for geometric mean.
+        val (current, previous) = geoMeanBenchmark
+        geoMeanScoreChange = current?.let {
+            previous?.let {
+                Pair(current.calcPercentageDiff(previous), current.calcRatio(previous))
+            }
+        }
+    }
+
+    // Merge current and compare to report.
+    private fun createMergedReport(currentBenchmarks: BenchmarksTable, previousBenchmarks: BenchmarksTable?):
+            Map<String, SummaryBenchmark> {
+        val mergedTable = mutableMapOf<String, SummaryBenchmark>()
+        mergedTable.apply {
+            currentBenchmarks.forEach { (name, current) ->
+                // Check existance of benchmark in previous results.
+                if (previousBenchmarks == null || name !in previousBenchmarks) {
+                    getOrPut(name) { SummaryBenchmark(current, null) }
+                } else {
+                    getOrPut(name) { SummaryBenchmark(current, previousBenchmarks[name]) }
+                }
+            }
+        }
+
+        // Add removed benchmarks to merged report.
+        mergedTable.apply {
+            previousBenchmarks?.filter { (key, _) -> key !in currentBenchmarks }?.forEach { (key, value) ->
+                getOrPut(key) { SummaryBenchmark(null, value) }
+            }
+        }
+
+        return mergedTable
+    }
+
+    // Calculate geometric mean.
+    private fun calculateGeoMeanBenchmark(currentBenchmarks: BenchmarksTable, previousBenchmarks: BenchmarksTable?):
+            SummaryBenchmark {
+        // Calculate geometric mean.
+        val currentGeoMean = createGeoMeanBenchmark(currentBenchmarks)
+        val previousGeoMean = previousBenchmarks?.let { createGeoMeanBenchmark(previousBenchmarks) }
+        return SummaryBenchmark(currentGeoMean, previousGeoMean)
+    }
+
+    private fun getBenchmarkPerfomanceChange(name: String, benchmark: SummaryBenchmark): Pair<String, ScoreChange>? {
+        val (current, previous) = benchmark
+        current?.let {
+            previous?.let {
+                // Calculate metrics for showing difference.
+                val percent = current.calcPercentageDiff(previous)
+                val ratio = current.calcRatio(previous)
+                if (abs(percent.mean) - percent.variance >= meaningfulChangesValue) {
+                    return Pair(name, Pair(percent, ratio))
+                }
+            }
+        }
+        return null
+    }
+
+    // Create geometric mean.
+    private fun createGeoMeanBenchmark(benchTable: BenchmarksTable): MeanVarianceBenchmark {
+        val geoMeanBenchmarkName = "Geometric mean"
+        val geoMean = geometricMean(benchTable.toList().map { (_, value) -> value.score })
+        val varianceGeoMean = geometricMean(benchTable.toList().map { (_, value) -> value.variance })
+        return MeanVarianceBenchmark(geoMeanBenchmarkName, geoMean, varianceGeoMean)
+    }
+}
+
+// Summary report with comparasion of separate benchmarks results.
+class SummaryBenchmarksReport(private val currentReport: BenchmarksReport,
+                              private val previousReport: BenchmarksReport? = null,
+                              private val meaningfulChangesValue: Double = 0.5) {
+
+    // Count avarage values for each benchmark.
+    private val detailedMetricReports: Map<BenchmarkResult.Metric, DetailedBenchmarksReport> = BenchmarkResult.Metric.entries.associateWith { metric ->
+        val currentBenchmarks = currentReport.benchmarks.map { (name, benchmarks) ->
+            name to benchmarks.filter { it.metric == metric }
+        }.filter { it.second.isNotEmpty() }.toMap()
+        val previousBenchmarks = previousReport?.benchmarks?.map { (name, benchmarks) ->
+            name to benchmarks.filter { it.metric == metric }
+        }?.filter { it.second.isNotEmpty() }?.toMap()
+        DetailedBenchmarksReport(
+                currentBenchmarks,
+                previousBenchmarks,
+                meaningfulChangesValue
+        )
+    }
+
+    private val benchmarksDurations: Map<String, Pair<Double?, Double?>>
+
+    // Environment and tools.
+    private val environments: Pair<Environment, Environment?>
+    private val compilers: Pair<Compiler, Compiler?>
+
+    private fun <T> getReducedResult(convertor: (DetailedBenchmarksReport) -> List<T>): List<T> {
+        return detailedMetricReports.values.map {
+            convertor(it)
+        }.flatten()
+    }
+
+    init {
+        benchmarksDurations = calculateBenchmarksDuration(currentReport, previousReport)
+        environments = Pair(currentReport.env, previousReport?.env)
+        compilers = Pair(currentReport.compiler, previousReport?.compiler)
+    }
+
+    // Get benchmark report.
+    fun getBenchmarksReport(takeMainReport: Boolean = true) =
+            if (takeMainReport)
+                BenchmarksReport(environments.first, getReducedResult { report ->
+                    report.mergedReport.map { (_, value) -> value.first!! }
+                }, compilers.first)
+            else
+                BenchmarksReport(environments.second!!, getReducedResult { report ->
+                    report.mergedReport.map { (_, value) -> value.second!! }
+                }, compilers.second!!)
+
+    // Generate map with summary durations of each benchmark.
+    private fun calculateBenchmarksDuration(currentReport: BenchmarksReport, previousReport: BenchmarksReport?):
+            Map<String, Pair<Double?, Double?>> {
+        val currentDurations = collectBenchmarksDurations(currentReport.benchmarks)
+        val previousDurations = previousReport?.let {
+            collectBenchmarksDurations(previousReport.benchmarks)
+        } ?: mapOf()
+        return currentDurations.keys.union(previousDurations.keys).associateWith { Pair(currentDurations[it], previousDurations[it]) }
+    }
+
+}
\ No newline at end of file
diff --git a/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/BenchmarksReport.kt b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/BenchmarksReport.kt
new file mode 100644
index 0000000..b60e001
--- /dev/null
+++ b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/BenchmarksReport.kt
@@ -0,0 +1,381 @@
+/*
+ * Copyright 2010-2019 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.report
+
+import org.jetbrains.report.json.*
+
+interface JsonSerializable {
+    fun serializeFields(): String
+
+    fun toJson(): String {
+        return """
+        {
+            ${serializeFields()}
+        }
+        """
+    }
+
+    // Convert iterable objects arrays, lists to json.
+    fun <T> arrayToJson(data: Iterable<T>): String {
+        return data.joinToString(prefix = "[", postfix = "]") {
+            if (it is JsonSerializable) it.toJson() else it.toString()
+        }
+    }
+}
+
+interface EntityFromJsonFactory<T> : ConvertedFromJson {
+    fun create(data: JsonElement): T
+}
+
+// Parse array with benchmarks to list
+fun parseBenchmarksArray(data: JsonElement): List<BenchmarkResult> {
+    if (data is JsonArray) {
+        return data.jsonArray.map {
+            if (MeanVarianceBenchmark.isMeanVarianceBenchmark(it))
+                MeanVarianceBenchmark.create(it as JsonObject)
+            else BenchmarkResult.create(it as JsonObject)
+        }
+    } else {
+        error("Benchmarks field is expected to be an array. Please, check origin files.")
+    }
+}
+
+// Class for benchmarks report with all information of run.
+open class BenchmarksReport(val env: Environment, benchmarksList: List<BenchmarkResult>, val compiler: Compiler) :
+        JsonSerializable {
+
+    companion object : EntityFromJsonFactory<BenchmarksReport> {
+        override fun create(data: JsonElement): BenchmarksReport {
+            if (data is JsonObject) {
+                val env = Environment.create(data.getRequiredField("env"))
+                val benchmarksObj = data.getRequiredField("benchmarks")
+                val compiler = Compiler.create(data.getRequiredField("kotlin"))
+                val buildNumberField = data.getOptionalField("buildNumber")
+                val benchmarksList = parseBenchmarksArray(benchmarksObj)
+                val report = BenchmarksReport(env, benchmarksList, compiler)
+                buildNumberField?.let { report.buildNumber = (it as JsonLiteral).unquoted() }
+                return report
+            } else {
+                error("Top level entity is expected to be an object. Please, check origin files.")
+            }
+        }
+
+        // Made a map of becnhmarks with name as key from list.
+        private fun structBenchmarks(benchmarksList: List<BenchmarkResult>) =
+                benchmarksList.groupBy { it.name }
+    }
+
+    val benchmarks = structBenchmarks(benchmarksList)
+
+    var buildNumber: String? = null
+
+    override fun serializeFields(): String {
+        val buildNumberField = buildNumber?.let {
+            """,
+            "buildNumber": "$buildNumber"
+            """
+        } ?: ""
+        return """
+            "env": ${env.toJson()},
+            "kotlin": ${compiler.toJson()},
+            "benchmarks": ${arrayToJson(benchmarks.flatMap { it.value })}$buildNumberField
+        """.trimIndent()
+    }
+
+    fun merge(other: BenchmarksReport): BenchmarksReport {
+        val mergedBenchmarks = HashMap(benchmarks)
+        other.benchmarks.forEach {
+            if (it.key in mergedBenchmarks) {
+                error("${it.key} already exists in report!")
+            }
+        }
+        mergedBenchmarks.putAll(other.benchmarks)
+        return BenchmarksReport(env, mergedBenchmarks.flatMap { it.value }, compiler)
+    }
+
+    // Concatenate benchmarks report if they have same environment and compiler.
+    operator fun plus(other: BenchmarksReport): BenchmarksReport {
+        if (compiler != other.compiler || env.machine != other.env.machine) {
+            error("It's impossible to concat reports from different machines!")
+        }
+        return merge(other)
+    }
+}
+
+// Class for kotlin compiler
+data class Compiler(val backend: Backend, val kotlinVersion: String) : JsonSerializable {
+
+    enum class BackendType(val type: String) {
+        JVM("jvm"),
+        NATIVE("native")
+    }
+
+    companion object : EntityFromJsonFactory<Compiler> {
+        override fun create(data: JsonElement): Compiler {
+            if (data is JsonObject) {
+                val backend = Backend.create(data.getRequiredField("backend"))
+                val kotlinVersion = elementToString(data.getRequiredField("kotlinVersion"), "kotlinVersion")
+
+                return Compiler(backend, kotlinVersion)
+            } else {
+                error("Kotlin entity is expected to be an object. Please, check origin files.")
+            }
+        }
+
+        fun backendTypeFromString(s: String): BackendType? = BackendType.values().find { it.type == s }
+    }
+
+    // Class for compiler backend
+    data class Backend(val type: BackendType, val version: String, val flags: List<String>) : JsonSerializable {
+        companion object : EntityFromJsonFactory<Backend> {
+            override fun create(data: JsonElement): Backend {
+                if (data is JsonObject) {
+                    val typeElement = data.getRequiredField("type")
+                    if (typeElement is JsonLiteral) {
+                        val type = backendTypeFromString(typeElement.unquoted())
+                                ?: error("Backend type should be 'jvm' or 'native'")
+                        val version = elementToString(data.getRequiredField("version"), "version")
+                        val flagsArray = data.getOptionalField("flags")
+                        var flags: List<String> = emptyList()
+                        if (flagsArray != null && flagsArray is JsonArray) {
+                            flags = flagsArray.jsonArray.map { it.toString() }
+                        }
+                        return Backend(type, version, flags)
+                    } else {
+                        error("Backend type should be string literal.")
+                    }
+                } else {
+                    error("Backend entity is expected to be an object. Please, check origin files.")
+                }
+            }
+        }
+
+        override fun serializeFields(): String {
+            val result = """
+                "type": "${type.type}",
+                "version": "${version}""""
+            // Don't print flags field if there is no one.
+            if (flags.isEmpty()) {
+                return """$result
+                """
+            } else {
+                return """
+                    $result,
+                "flags": ${arrayToJson(flags.map { if (it.startsWith("\"")) it else "\"$it\"" })}
+                """
+            }
+        }
+    }
+
+    override fun serializeFields(): String {
+        return """
+            "backend": ${backend.toJson()},
+            "kotlinVersion": "${kotlinVersion}"
+        """
+    }
+}
+
+// Class for description of environment of benchmarks run
+data class Environment(val machine: Machine, val jdk: JDKInstance) : JsonSerializable {
+
+    companion object : EntityFromJsonFactory<Environment> {
+        override fun create(data: JsonElement): Environment {
+            if (data is JsonObject) {
+                val machine = Machine.create(data.getRequiredField("machine"))
+                val jdk = JDKInstance.create(data.getRequiredField("jdk"))
+
+                return Environment(machine, jdk)
+            } else {
+                error("Environment entity is expected to be an object. Please, check origin files.")
+            }
+        }
+    }
+
+    // Class for description of machine used for benchmarks run.
+    data class Machine(val cpu: String, val os: String) : JsonSerializable {
+        companion object : EntityFromJsonFactory<Machine> {
+            override fun create(data: JsonElement): Machine {
+                if (data is JsonObject) {
+                    val cpu = elementToString(data.getRequiredField("cpu"), "cpu")
+                    val os = elementToString(data.getRequiredField("os"), "os")
+
+                    return Machine(cpu, os)
+                } else {
+                    error("Machine entity is expected to be an object. Please, check origin files.")
+                }
+            }
+        }
+
+        override fun serializeFields(): String {
+            return """
+                "cpu": "$cpu",
+                "os": "$os"
+            """
+        }
+    }
+
+    // Class for description of jdk used for benchmarks run.
+    data class JDKInstance(val version: String, val vendor: String) : JsonSerializable {
+        companion object : EntityFromJsonFactory<JDKInstance> {
+            override fun create(data: JsonElement): JDKInstance {
+                if (data is JsonObject) {
+                    val version = elementToString(data.getRequiredField("version"), "version")
+                    val vendor = elementToString(data.getRequiredField("vendor"), "vendor")
+
+                    return JDKInstance(version, vendor)
+                } else {
+                    error("JDK entity is expected to be an object. Please, check origin files.")
+                }
+            }
+        }
+
+        override fun serializeFields(): String {
+            return """
+                "version": "$version",
+                "vendor": "$vendor"
+            """
+        }
+    }
+
+    override fun serializeFields(): String {
+        return """
+                "machine": ${machine.toJson()},
+                "jdk": ${jdk.toJson()}
+            """
+    }
+}
+
+open class BenchmarkResult(val name: String, val status: Status,
+                           val score: Double, val metric: Metric, val runtimeInUs: Double,
+                           val repeat: Int, val warmup: Int) : JsonSerializable {
+
+    enum class Metric(val suffix: String, val value: String) {
+        EXECUTION_TIME("", "EXECUTION_TIME"),
+        CODE_SIZE(".codeSize", "CODE_SIZE"),
+        COMPILE_TIME(".compileTime", "COMPILE_TIME"),
+        BUNDLE_SIZE(".bundleSize", "BUNDLE_SIZE")
+    }
+
+    constructor(name: String, score: Double) : this(name, Status.PASSED, score, Metric.EXECUTION_TIME, 0.0, 0, 0)
+
+    companion object : EntityFromJsonFactory<BenchmarkResult> {
+
+        override fun create(data: JsonElement): BenchmarkResult {
+            if (data is JsonObject) {
+                var name = elementToString(data.getRequiredField("name"), "name")
+                val metricElement = data.getOptionalField("metric")
+                val metric = if (metricElement != null && metricElement is JsonLiteral)
+                    metricFromString(metricElement.unquoted()) ?: Metric.EXECUTION_TIME
+                else Metric.EXECUTION_TIME
+                val statusElement = data.getRequiredField("status")
+                if (statusElement is JsonLiteral) {
+                    val status = statusFromString(statusElement.unquoted())
+                            ?: error("Status should be PASSED or FAILED")
+
+                    val score = elementToDouble(data.getRequiredField("score"), "score")
+                    val runtimeInUs = elementToDouble(data.getRequiredField("runtimeInUs"), "runtimeInUs")
+                    val repeat = elementToInt(data.getRequiredField("repeat"), "repeat")
+                    val warmup = elementToInt(data.getRequiredField("warmup"), "warmup")
+
+                    return BenchmarkResult(name, status, score, metric, runtimeInUs, repeat, warmup)
+                } else {
+                    error("Status should be string literal.")
+                }
+            } else {
+                error("Benchmark entity is expected to be an object. Please, check origin files.")
+            }
+        }
+
+        fun statusFromString(s: String): Status? = Status.values().find { it.value == s }
+        fun metricFromString(s: String): Metric? = Metric.values().find { it.value == s }
+    }
+
+    enum class Status(val value: String) {
+        PASSED("PASSED"),
+        FAILED("FAILED")
+    }
+
+    override fun serializeFields(): String {
+        return """
+            "name": "${name.removeSuffix(metric.suffix)}",
+            "status": "${status.value}",
+            "score": ${score},
+            "metric": "${metric.value}",
+            "runtimeInUs": ${runtimeInUs},
+            "repeat": ${repeat},
+            "warmup": ${warmup}
+        """
+    }
+
+    val shortName: String
+        get() = name.removeSuffix(metric.suffix)
+}
+
+// Entity to describe avarage values which conssists of mean and variance values.
+data class MeanVariance(val mean: Double, val variance: Double)
+
+// Processed benchmark result with calculated mean and variance value.
+open class MeanVarianceBenchmark(name: String, status: BenchmarkResult.Status, score: Double, metric: BenchmarkResult.Metric,
+                                 runtimeInUs: Double, repeat: Int, warmup: Int, val variance: Double) :
+        BenchmarkResult(name, status, score, metric, runtimeInUs, repeat, warmup) {
+
+    constructor(name: String, score: Double, variance: Double) : this(name, BenchmarkResult.Status.PASSED, score,
+            BenchmarkResult.Metric.EXECUTION_TIME, 0.0, 0, 0, variance)
+
+    companion object : EntityFromJsonFactory<MeanVarianceBenchmark> {
+
+        fun isMeanVarianceBenchmark(data: JsonElement) = data is JsonObject && data.getOptionalField("variance") != null
+
+        override fun create(data: JsonElement): MeanVarianceBenchmark {
+            if (data is JsonObject) {
+                val baseBenchmark = BenchmarkResult.create(data)
+                val variance = elementToDouble(data.getRequiredField("variance"), "variance")
+                return MeanVarianceBenchmark(baseBenchmark.name, baseBenchmark.status, baseBenchmark.score, baseBenchmark.metric,
+                        baseBenchmark.runtimeInUs, baseBenchmark.repeat, baseBenchmark.warmup, variance)
+            } else {
+                error("Benchmark entity is expected to be an object. Please, check origin files.")
+            }
+        }
+    }
+
+    override fun serializeFields(): String {
+        return """
+            ${super.serializeFields()},
+            "variance": $variance
+            """
+    }
+}
+
+// Benchmark with set results stability state.
+open class BenchmarkWithStabilityState(name: String, status: BenchmarkResult.Status, score: Double, metric: BenchmarkResult.Metric,
+                                 runtimeInUs: Double, repeat: Int, warmup: Int, val unstable: Boolean) :
+        BenchmarkResult(name, status, score, metric, runtimeInUs, repeat, warmup) {
+
+    constructor(benchmarkResult: BenchmarkResult, unstable: Boolean) : this(benchmarkResult.name,
+            benchmarkResult.status, benchmarkResult.score, benchmarkResult.metric,
+            benchmarkResult.runtimeInUs, benchmarkResult.repeat, benchmarkResult.warmup, unstable)
+
+    override fun serializeFields(): String {
+        return """
+            ${super.serializeFields()},
+            "unstable": $unstable
+            """
+    }
+
+    companion object : EntityFromJsonFactory<BenchmarkResult> {
+        override fun create(data: JsonElement): BenchmarkWithStabilityState {
+            val parsedObject = BenchmarkResult.create(data)
+            if (data is JsonObject) {
+                val unstableElement = data.getOptionalField("unstable")
+                val unstableFlag = if (unstableElement != null && unstableElement is JsonPrimitive)
+                    unstableElement.boolean else false
+                return  BenchmarkWithStabilityState(parsedObject, unstableFlag)
+            } else {
+                error("Benchmark entity is expected to be an object. Please, check origin files.")
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/ConvertedFromJson.kt b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/ConvertedFromJson.kt
new file mode 100644
index 0000000..7f16418
--- /dev/null
+++ b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/ConvertedFromJson.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2010-2018 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.
+ */
+
+package org.jetbrains.report.json
+
+// Entity can be created from json description.
+interface ConvertedFromJson {
+    // Methods for conversion to expected type with checks of possibility of such conversions.
+    fun elementToDouble(element: JsonElement, name: String): Double =
+            if (element is JsonPrimitive)
+                element.double
+            else
+                error("Field '$name' in '$element' is expected to be a double number. Please, check origin files.")
+
+    fun elementToInt(element: JsonElement, name: String): Int =
+            if (element is JsonPrimitive)
+                element.int
+            else
+                error("Field '$name' in '$element' is expected to be an integer number. Please, check origin files.")
+
+    fun elementToString(element: JsonElement, name:String): String =
+            if (element is JsonLiteral)
+                element.unquoted()
+            else
+                error("Field '$name' in '$element' is expected to be a string. Please, check origin files.")
+
+    fun elementToStringOrNull(element: JsonElement, name:String): String? =
+            when (element) {
+                is JsonLiteral -> element.unquoted()
+                is JsonNull -> null
+                else -> error("Field '$name' in '$element' is expected to be a string. Please, check origin files.")
+            }
+}
+
+fun JsonObject.getRequiredField(fieldName: String): JsonElement {
+    return getOrNull(fieldName) ?: error("Field '$fieldName' doesn't exist in '$this'. Please, check origin files.")
+}
+
+fun JsonObject.getOptionalField(fieldName: String): JsonElement? {
+    return getOrNull(fieldName)
+}
\ No newline at end of file
diff --git a/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/JsonElement.kt b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/JsonElement.kt
new file mode 100644
index 0000000..6ca19c9
--- /dev/null
+++ b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/JsonElement.kt
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2010-2018 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.
+ */
+
+package org.jetbrains.report.json
+
+/**
+ * Class representing single JSON element.
+ * Can be [JsonPrimitive], [JsonArray] or [JsonObject].
+ *
+ * [JsonElement.toString] properly prints JSON tree as valid JSON, taking into
+ * account quoted values and primitives
+ */
+sealed class JsonElement {
+
+    /**
+     * Convenience method to get current element as [JsonPrimitive]
+     * @throws JsonElementTypeMismatchException is current element is not a [JsonPrimitive]
+     */
+    open val primitive: JsonPrimitive
+        get() = error("JsonLiteral")
+
+    /**
+     * Convenience method to get current element as [JsonObject]
+     * @throws JsonElementTypeMismatchException is current element is not a [JsonObject]
+     */
+    open val jsonObject: JsonObject
+        get() = error("JsonObject")
+
+    /**
+     * Convenience method to get current element as [JsonArray]
+     * @throws JsonElementTypeMismatchException is current element is not a [JsonArray]
+     */
+    open val jsonArray: JsonArray
+        get() = error("JsonArray")
+
+    /**
+     * Convenience method to get current element as [JsonNull]
+     * @throws JsonElementTypeMismatchException is current element is not a [JsonNull]
+     */
+    open val jsonNull: JsonNull
+        get() = error("JsonPrimitive")
+
+    /**
+     * Checks whether current element is [JsonNull]
+     */
+    val isNull: Boolean
+        get() = this === JsonNull
+
+    private fun error(element: String): Nothing =
+            throw JsonElementTypeMismatchException(this::class.toString(), element)
+}
+
+/**
+ * Class representing JSON primitive value. Can be either [JsonLiteral] or [JsonNull].
+ */
+sealed class JsonPrimitive : JsonElement() {
+
+    /**
+     * Content of given element without quotes. For [JsonNull] this methods returns `"null"`
+     */
+    abstract val content: String
+
+    /**
+     * Content of the given element without quotes or `null` if current element is [JsonNull]
+     */
+    abstract val contentOrNull: String?
+
+    @Suppress("LeakingThis")
+    final override val primitive: JsonPrimitive = this
+
+    /**
+     * Returns content of current element as int
+     * @throws NumberFormatException if current element is not a valid representation of number
+     */
+    val int: Int get() = content.toInt()
+
+    /**
+     * Returns content of current element as int or `null` if current element is not a valid representation of number
+     **/
+    val intOrNull: Int? get() = content.toIntOrNull()
+
+    /**
+     * Returns content of current element as long
+     * @throws NumberFormatException if current element is not a valid representation of number
+     */
+    val long: Long get() = content.toLong()
+
+    /**
+     * Returns content of current element as long or `null` if current element is not a valid representation of number
+     */
+    val longOrNull: Long? get() = content.toLongOrNull()
+
+    /**
+     * Returns content of current element as double
+     * @throws NumberFormatException if current element is not a valid representation of number
+     */
+    val double: Double get() = content.toDouble()
+
+    /**
+     * Returns content of current element as double or `null` if current element is not a valid representation of number
+     */
+    val doubleOrNull: Double? get() = content.toDoubleOrNull()
+
+    /**
+     * Returns content of current element as float
+     * @throws NumberFormatException if current element is not a valid representation of number
+     */
+    val float: Float get() = content.toFloat()
+
+    /**
+     * Returns content of current element as float or `null` if current element is not a valid representation of number
+     */
+    val floatOrNull: Float? get() = content.toFloatOrNull()
+
+    /**
+     * Returns content of current element as boolean
+     * @throws IllegalStateException if current element doesn't represent boolean
+     */
+    val boolean: Boolean get() = content.toBooleanStrict()
+
+    /**
+     * Returns content of current element as boolean or `null` if current element is not a valid representation of boolean
+     */
+    val booleanOrNull: Boolean? get() = content.toBooleanStrictOrNull()
+
+    override fun toString() = content
+}
+
+/**
+ * Class representing JSON literals: numbers, booleans and string.
+ * Strings are always quoted.
+ */
+data class JsonLiteral(
+        private val body: Any,
+        private val isString: Boolean
+) : JsonPrimitive() {
+
+    override val content = body.toString()
+    override val contentOrNull: String = content
+
+    /**
+     * Creates number literal
+     */
+    constructor(number: Number) : this(number, false)
+
+    /**
+     * Creates boolean literal
+     */
+    constructor(boolean: Boolean) : this(boolean, false)
+
+    /**
+     * Creates quoted string literal
+     */
+    constructor(string: String) : this(string, true)
+
+    override fun toString() =
+            if (isString) buildString { printQuoted(content) }
+            else content
+
+    fun unquoted() = content
+}
+
+/**
+ * Class representing JSON `null` value
+ */
+object JsonNull : JsonPrimitive() {
+    override val jsonNull: JsonNull = this
+    override val content: String = "null"
+    override val contentOrNull: String? = null
+}
+
+/**
+ * Class representing JSON object, consisting of name-value pairs, where value is arbitrary [JsonElement]
+ */
+data class JsonObject(val content: Map<String, JsonElement>) : JsonElement(), Map<String, JsonElement> by content {
+
+    override val jsonObject: JsonObject = this
+
+    /**
+     * Returns [JsonElement] associated with given [key]
+     * @throws NoSuchElementException if element is not present
+     */
+    override fun get(key: String): JsonElement = content[key] ?: throw NoSuchElementException("Element $key is missing")
+
+    /**
+     * Returns [JsonElement] associated with given [key] or `null` if element is not present
+     */
+    fun getOrNull(key: String): JsonElement? = content[key]
+
+    /**
+     * Returns [JsonPrimitive] associated with given [key]
+     *
+     * @throws NoSuchElementException if element is not present
+     * @throws JsonElementTypeMismatchException if element is present, but has invalid type
+     */
+    fun getPrimitive(key: String): JsonPrimitive = get(key) as? JsonPrimitive
+            ?: unexpectedJson(key, "JsonPrimitive")
+
+    /**
+     * Returns [JsonObject] associated with given [key]
+     *
+     * @throws NoSuchElementException if element is not present
+     * @throws JsonElementTypeMismatchException if element is present, but has invalid type
+     */
+    fun getObject(key: String): JsonObject = get(key) as? JsonObject
+            ?: unexpectedJson(key, "JsonObject")
+
+    /**
+     * Returns [JsonArray] associated with given [key]
+     *
+     * @throws NoSuchElementException if element is not present
+     * @throws JsonElementTypeMismatchException if element is present, but has invalid type
+     */
+    fun getArray(key: String): JsonArray = get(key) as? JsonArray
+            ?: unexpectedJson(key, "JsonArray")
+
+    /**
+     * Returns [JsonPrimitive] associated with given [key] or `null` if element
+     * is not present or has different type
+     */
+    fun getPrimitiveOrNull(key: String): JsonPrimitive? = content[key] as? JsonPrimitive
+
+    /**
+     * Returns [JsonObject] associated with given [key] or `null` if element
+     * is not present or has different type
+     */
+    fun getObjectOrNull(key: String): JsonObject? = content[key] as? JsonObject
+
+    /**
+     * Returns [JsonArray] associated with given [key] or `null` if element
+     * is not present or has different type
+     */
+    fun getArrayOrNull(key: String): JsonArray? = content[key] as? JsonArray
+
+    /**
+     * Returns [J] associated with given [key]
+     *
+     * @throws NoSuchElementException if element is not present
+     * @throws JsonElementTypeMismatchException if element is present, but has invalid type
+     */
+    inline fun <reified J : JsonElement> getAs(key: String): J = get(key) as? J
+            ?: unexpectedJson(key, J::class.toString())
+
+    /**
+     * Returns [J] associated with given [key] or `null` if element
+     * is not present or has different type
+     */
+    inline fun <reified J : JsonElement> lookup(key: String): J? = content[key] as? J
+
+    override fun toString(): String {
+        return content.entries.joinToString(
+                prefix = "{",
+                postfix = "}",
+                transform = {(k, v) -> """"$k": $v"""}
+        )
+    }
+}
+
+data class JsonArray(val content: List<JsonElement>) : JsonElement(), List<JsonElement> by content {
+
+    override val jsonArray: JsonArray = this
+
+    /**
+     * Returns [index]-th element of an array as [JsonPrimitive]
+     * @throws JsonElementTypeMismatchException if element has invalid type
+     */
+    fun getPrimitive(index: Int) = content[index] as? JsonPrimitive
+            ?: unexpectedJson("at $index", "JsonPrimitive")
+
+    /**
+     * Returns [index]-th element of an array as [JsonObject]
+     * @throws JsonElementTypeMismatchException if element has invalid type
+     */
+    fun getObject(index: Int) = content[index] as? JsonObject
+            ?: unexpectedJson("at $index", "JsonObject")
+
+    /**
+     * Returns [index]-th element of an array as [JsonArray]
+     * @throws JsonElementTypeMismatchException if element has invalid type
+     */
+    fun getArray(index: Int) = content[index] as? JsonArray
+            ?: unexpectedJson("at $index", "JsonArray")
+
+    /**
+     * Returns [index]-th element of an array as [JsonPrimitive] or `null` if element is missing or has different type
+     */
+    fun getPrimitiveOrNull(index: Int) = content.getOrNull(index) as? JsonPrimitive
+
+    /**
+     * Returns [index]-th element of an array as [JsonObject] or `null` if element is missing or has different type
+     */
+    fun getObjectOrNull(index: Int) = content.getOrNull(index) as? JsonObject
+
+    /**
+     * Returns [index]-th element of an array as [JsonArray] or `null` if element is missing or has different type
+     */
+    fun getArrayOrNull(index: Int) = content.getOrNull(index) as? JsonArray
+
+    /**
+     * Returns [index]-th element of an array as [J]
+     * @throws JsonElementTypeMismatchException if element has invalid type
+     */
+    inline fun <reified J : JsonElement> getAs(index: Int): J = content[index] as? J
+            ?: unexpectedJson("at $index", J::class.toString())
+
+    /**
+     * Returns [index]-th element of an array as [J] or `null` if element is missing or has different type
+     */
+    inline fun <reified J : JsonElement> getAsOrNull(index: Int): J? = content.getOrNull(index) as? J
+
+    override fun toString() = content.joinToString(prefix = "[", postfix = "]")
+}
+
+fun unexpectedJson(key: String, expected: String): Nothing =
+        throw JsonElementTypeMismatchException(key, expected)
\ No newline at end of file
diff --git a/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/JsonExceptions.kt b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/JsonExceptions.kt
new file mode 100644
index 0000000..4b7acaa
--- /dev/null
+++ b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/JsonExceptions.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010-2018 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.
+ */
+
+package org.jetbrains.report.json
+
+class JsonInvalidValueInStrictModeException(value: Any, valueDescription: String) : Exception(
+        "$value is not a valid $valueDescription as per JSON spec.\n" +
+                "You can disable strict mode to serialize such values"
+) {
+    constructor(floatValue: Float) : this(floatValue, "float")
+    constructor(doubleValue: Double) : this(doubleValue, "double")
+}
+
+class JsonUnknownKeyException(key: String) : Exception(
+        "Strict JSON encountered unknown key: $key\n" +
+                "You can disable strict mode to skip unknown keys"
+)
+
+class JsonParsingException(position: Int, message: String) : Exception("Invalid JSON at $position: $message")
+
+class JsonElementTypeMismatchException(key: String, expected: String) : Exception("Element $key is not a $expected")
diff --git a/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/JsonParser.kt b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/JsonParser.kt
new file mode 100644
index 0000000..bef8c80
--- /dev/null
+++ b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/JsonParser.kt
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2010-2018 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.
+ */
+
+package org.jetbrains.report.json
+
+import org.jetbrains.report.json.EscapeCharMappings.ESC2C
+// special strings
+internal const val NULL = "null"
+
+// special chars
+internal const val COMMA = ','
+internal const val COLON = ':'
+internal const val BEGIN_OBJ = '{'
+internal const val END_OBJ = '}'
+internal const val BEGIN_LIST = '['
+internal const val END_LIST = ']'
+internal const val STRING = '"'
+internal const val STRING_ESC = '\\'
+internal const val INVALID = 0.toChar()
+internal const val UNICODE_ESC = 'u'
+// token classes
+internal const val TC_OTHER: Byte = 0
+internal const val TC_STRING: Byte = 1
+internal const val TC_STRING_ESC: Byte = 2
+internal const val TC_WS: Byte = 3
+internal const val TC_COMMA: Byte = 4
+internal const val TC_COLON: Byte = 5
+internal const val TC_BEGIN_OBJ: Byte = 6
+internal const val TC_END_OBJ: Byte = 7
+internal const val TC_BEGIN_LIST: Byte = 8
+internal const val TC_END_LIST: Byte = 9
+internal const val TC_NULL: Byte = 10
+internal const val TC_INVALID: Byte = 11
+internal const val TC_EOF: Byte = 12
+// mapping from chars to token classes
+private const val CTC_MAX = 0x7e
+// mapping from escape chars real chars
+private const val C2ESC_MAX = 0x5d
+private const val ESC2C_MAX = 0x75
+
+internal val C2TC = ByteArray(CTC_MAX).apply {
+    for (i in 0..0x20)
+        initC2TC(i, TC_INVALID)
+    initC2TC(0x09, TC_WS)
+    initC2TC(0x0a, TC_WS)
+    initC2TC(0x0d, TC_WS)
+    initC2TC(0x20, TC_WS)
+    initC2TC(COMMA, TC_COMMA)
+    initC2TC(COLON, TC_COLON)
+    initC2TC(BEGIN_OBJ, TC_BEGIN_OBJ)
+    initC2TC(END_OBJ, TC_END_OBJ)
+    initC2TC(BEGIN_LIST, TC_BEGIN_LIST)
+    initC2TC(END_LIST, TC_END_LIST)
+    initC2TC(STRING, TC_STRING)
+    initC2TC(STRING_ESC, TC_STRING_ESC)
+}
+// object instead of @SharedImmutable because there is mutual initialization in [initC2ESC]
+internal object EscapeCharMappings {
+    internal val ESC2C = CharArray(ESC2C_MAX)
+    internal val C2ESC = CharArray(C2ESC_MAX).apply {
+        for (i in 0x00..0x1f)
+            initC2ESC(i, UNICODE_ESC)
+        initC2ESC(0x08, 'b')
+        initC2ESC(0x09, 't')
+        initC2ESC(0x0a, 'n')
+        initC2ESC(0x0c, 'f')
+        initC2ESC(0x0d, 'r')
+        initC2ESC('/', '/')
+        initC2ESC(STRING, STRING)
+        initC2ESC(STRING_ESC, STRING_ESC)
+    }
+    private fun CharArray.initC2ESC(c: Int, esc: Char) {
+        this[c] = esc
+        if (esc != UNICODE_ESC) ESC2C[esc.code] = c.toChar()
+    }
+    private fun CharArray.initC2ESC(c: Char, esc: Char) = initC2ESC(c.code, esc)
+}
+private fun ByteArray.initC2TC(c: Int, cl: Byte) {
+    this[c] = cl
+}
+private fun ByteArray.initC2TC(c: Char, cl: Byte) {
+    initC2TC(c.code, cl)
+}
+internal fun charToTokenClass(c: Char) = if (c.code < CTC_MAX) C2TC[c.code] else TC_OTHER
+internal fun escapeToChar(c: Int): Char = if (c < ESC2C_MAX) ESC2C[c] else INVALID
+// JSON low level parser
+internal class Parser(val source: String) {
+    var curPos: Int = 0 // position in source
+        private set
+    // updated by nextToken
+    var tokenPos: Int = 0
+        private set
+    var tc: Byte = TC_EOF
+        private set
+    // update by nextString/nextLiteral
+    private var offset = -1 // when offset >= 0 string is in source, otherwise in buf
+    private var length = 0 // length of string
+    private var buf = CharArray(16) // only used for strings with escapes
+    init {
+        nextToken()
+    }
+    internal inline fun requireTc(expected: Byte, lazyErrorMsg: () -> String) {
+        if (tc != expected)
+            fail(tokenPos, lazyErrorMsg())
+    }
+    val canBeginValue: Boolean
+        get() = when (tc) {
+            TC_BEGIN_LIST, TC_BEGIN_OBJ, TC_OTHER, TC_STRING, TC_NULL -> true
+            else -> false
+        }
+
+    @OptIn(ExperimentalStdlibApi::class)
+    fun takeStr(): String {
+        if (tc != TC_OTHER && tc != TC_STRING) fail(tokenPos, "Expected string or non-null literal")
+        val prevStr = if (offset < 0)
+            buf.concatToString(0, length) else
+            source.substring(offset, offset + length)
+        nextToken()
+        return prevStr
+    }
+    private fun append(ch: Char) {
+        if (length >= buf.size) buf = buf.copyOf(2 * buf.size)
+        buf[length++] = ch
+    }
+    // initializes buf usage upon the first encountered escaped char
+    private fun appendRange(source: String, fromIndex: Int, toIndex: Int) {
+        val addLen = toIndex - fromIndex
+        val oldLen = length
+        val newLen = oldLen + addLen
+        if (newLen > buf.size) buf = buf.copyOf(newLen.coerceAtLeast(2 * buf.size))
+        for (i in 0 until addLen) buf[oldLen + i] = source[fromIndex + i]
+        length += addLen
+    }
+    fun nextToken() {
+        val source = source
+        var curPos = curPos
+        val maxLen = source.length
+        while (true) {
+            if (curPos >= maxLen) {
+                tokenPos = curPos
+                tc = TC_EOF
+                return
+            }
+            val ch = source[curPos]
+            val tc = charToTokenClass(ch)
+            when (tc) {
+                TC_WS -> curPos++ // skip whitespace
+                TC_OTHER -> {
+                    nextLiteral(source, curPos)
+                    return
+                }
+                TC_STRING -> {
+                    nextString(source, curPos)
+                    return
+                }
+                else -> {
+                    this.tokenPos = curPos
+                    this.tc = tc
+                    this.curPos = curPos + 1
+                    return
+                }
+            }
+        }
+    }
+    private fun nextLiteral(source: String, startPos: Int) {
+        tokenPos = startPos
+        offset = startPos
+        var curPos = startPos
+        val maxLen = source.length
+        while (true) {
+            curPos++
+            if (curPos >= maxLen || charToTokenClass(source[curPos]) != TC_OTHER) break
+        }
+        this.curPos = curPos
+        length = curPos - offset
+        tc = if (rangeEquals(source, offset, length, NULL)) TC_NULL else TC_OTHER
+    }
+    private fun nextString(source: String, startPos: Int) {
+        tokenPos = startPos
+        length = 0 // in buffer
+        var curPos = startPos + 1
+        var lastPos = curPos
+        val maxLen = source.length
+        parse@ while (true) {
+            if (curPos >= maxLen) fail(curPos, "Unexpected end in string")
+            if (source[curPos] == STRING) {
+                break@parse
+            } else if (source[curPos] == STRING_ESC) {
+                appendRange(source, lastPos, curPos)
+                val newPos = appendEsc(source, curPos + 1)
+                curPos = newPos
+                lastPos = newPos
+            } else {
+                curPos++
+            }
+        }
+        if (lastPos == startPos + 1) {
+            // there was no escaped chars
+            this.offset = lastPos
+            this.length = curPos - lastPos
+        } else {
+            // some escaped chars were there
+            appendRange(source, lastPos, curPos)
+            this.offset = -1
+        }
+        this.curPos = curPos + 1
+        tc = TC_STRING
+    }
+    private fun appendEsc(source: String, startPos: Int): Int {
+        var curPos = startPos
+        require(curPos < source.length, curPos) { "Unexpected end after escape char" }
+        val curChar = source[curPos++]
+        if (curChar == UNICODE_ESC) {
+            curPos = appendHex(source, curPos)
+        } else {
+            val c = escapeToChar(curChar.code)
+            require(c != INVALID, curPos) { "Invalid escaped char '$curChar'" }
+            append(c)
+        }
+        return curPos
+    }
+    private fun appendHex(source: String, startPos: Int): Int {
+        var curPos = startPos
+        append(
+                ((fromHexChar(source, curPos++) shl 12) +
+                        (fromHexChar(source, curPos++) shl 8) +
+                        (fromHexChar(source, curPos++) shl 4) +
+                        fromHexChar(source, curPos++)).toChar()
+        )
+        return curPos
+    }
+    fun skipElement() {
+        if (tc != TC_BEGIN_OBJ && tc != TC_BEGIN_LIST) {
+            nextToken()
+            return
+        }
+        val tokenStack = mutableListOf<Byte>()
+        do {
+            when (tc) {
+                TC_BEGIN_LIST, TC_BEGIN_OBJ -> tokenStack.add(tc)
+                TC_END_LIST -> {
+                    if (tokenStack.last() != TC_BEGIN_LIST) throw JsonParsingException(curPos, "found ] instead of }")
+                    tokenStack.removeAt(tokenStack.size - 1)
+                }
+                TC_END_OBJ -> {
+                    if (tokenStack.last() != TC_BEGIN_OBJ) throw JsonParsingException(curPos, "found } instead of ]")
+                    tokenStack.removeAt(tokenStack.size - 1)
+                }
+            }
+            nextToken()
+        } while (tokenStack.isNotEmpty())
+    }
+}
+// Utility functions
+private fun fromHexChar(source: String, curPos: Int): Int {
+    require(curPos < source.length, curPos) { "Unexpected end in unicode escape" }
+    val curChar = source[curPos]
+    return when (curChar) {
+        in '0'..'9' -> curChar.code - '0'.code
+        in 'a'..'f' -> curChar.code - 'a'.code + 10
+        in 'A'..'F' -> curChar.code - 'A'.code + 10
+        else -> fail(curPos, "Invalid toHexChar char '$curChar' in unicode escape")
+    }
+}
+private fun rangeEquals(source: String, start: Int, length: Int, str: String): Boolean {
+    val n = str.length
+    if (length != n) return false
+    for (i in 0 until n) if (source[start + i] != str[i]) return false
+    return true
+}
+internal inline fun require(condition: Boolean, pos: Int, msg: () -> String) {
+    if (!condition)
+        fail(pos, msg())
+}
+@Suppress("NOTHING_TO_INLINE")
+internal inline fun fail(pos: Int, msg: String): Nothing {
+    throw JsonParsingException(pos, msg)
+}
\ No newline at end of file
diff --git a/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/JsonTreeParser.kt b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/JsonTreeParser.kt
new file mode 100644
index 0000000..23f3ccb
--- /dev/null
+++ b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/JsonTreeParser.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2010-2018 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.
+ */
+
+
+// A bit changed part of kotlinx.serialization plugin
+package org.jetbrains.report.json
+
+class JsonTreeParser internal constructor(private val p: Parser) {
+
+    companion object {
+        fun parse(input: String): JsonElement = JsonTreeParser(input).readFully()
+    }
+
+    constructor(input: String) : this(Parser(input))
+
+    private fun readObject(): JsonElement {
+        p.requireTc(TC_BEGIN_OBJ) { "Expected start of object" }
+        p.nextToken()
+        val result: MutableMap<String, JsonElement> = hashMapOf()
+        while (true) {
+            if (p.tc == TC_COMMA) p.nextToken()
+            if (!p.canBeginValue) break
+            val key = p.takeStr()
+            p.requireTc(TC_COLON) { "Expected ':'" }
+            p.nextToken()
+            val elem = read()
+            result[key] = elem
+        }
+        p.requireTc(TC_END_OBJ) { "Expected end of object" }
+        p.nextToken()
+        return JsonObject(result)
+    }
+
+    private fun readValue(isString: Boolean): JsonElement {
+        val str = p.takeStr()
+        return JsonLiteral(str, isString)
+    }
+
+    private fun readArray(): JsonElement {
+        p.requireTc(TC_BEGIN_LIST) { "Expected start of array" }
+        p.nextToken()
+        val result: MutableList<JsonElement> = arrayListOf()
+        while (true) {
+            if (p.tc == TC_COMMA) p.nextToken()
+            if (!p.canBeginValue) break
+            val elem = read()
+            result.add(elem)
+        }
+        p.requireTc(TC_END_LIST) { "Expected end of array" }
+        p.nextToken()
+        return JsonArray(result)
+    }
+
+    fun read(): JsonElement {
+        if (!p.canBeginValue) fail(p.curPos, "Can't begin reading value from here")
+        val tc = p.tc
+        return when (tc) {
+            TC_NULL -> JsonNull.also { p.nextToken() }
+            TC_STRING -> readValue(isString = true)
+            TC_OTHER -> readValue(isString = false)
+            TC_BEGIN_OBJ -> readObject()
+            TC_BEGIN_LIST -> readArray()
+            else -> fail(p.curPos, "Can't begin reading element")
+        }
+    }
+
+    fun readFully(): JsonElement {
+        val r = read()
+        p.requireTc(TC_EOF) { "Input wasn't consumed fully" }
+        return r
+    }
+}
\ No newline at end of file
diff --git a/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/StringOps.kt b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/StringOps.kt
new file mode 100644
index 0000000..1c22c66
--- /dev/null
+++ b/kotlin-native/tools/performance-server/src/commonMain/kotlin/org/jetbrains/report/json/StringOps.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2010-2018 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.
+ */
+
+package org.jetbrains.report.json
+
+private fun toHexChar(i: Int) : Char {
+    val d = i and 0xf
+    return if (d < 10) (d + '0'.code).toChar()
+    else (d - 10 + 'a'.code).toChar()
+}
+
+private val ESCAPE_CHARS: Array<String?> = arrayOfNulls<String>(128).apply {
+    for (c in 0..0x1f) {
+        val c1 = toHexChar(c shr 12)
+        val c2 = toHexChar(c shr 8)
+        val c3 = toHexChar(c shr 4)
+        val c4 = toHexChar(c)
+        this[c] = "\\u$c1$c2$c3$c4"
+    }
+    this['"'.code] = "\\\""
+    this['\\'.code] = "\\\\"
+    this['\t'.code] = "\\t"
+    this['\b'.code] = "\\b"
+    this['\n'.code] = "\\n"
+    this['\r'.code] = "\\r"
+    this[0x0c] = "\\f"
+}
+
+internal fun StringBuilder.printQuoted(value: String)  {
+    append(STRING)
+    var lastPos = 0
+    val length = value.length
+    for (i in 0 until length) {
+        val c = value[i].code
+        // Do not replace this constant with C2ESC_MAX (which is smaller than ESCAPE_CHARS size),
+        // otherwise JIT won't eliminate range check and won't vectorize this loop
+        if (c >= ESCAPE_CHARS.size) continue // no need to escape
+        val esc = ESCAPE_CHARS[c] ?: continue
+        append(value, lastPos, i) // flush prev
+        append(esc)
+        lastPos = i + 1
+    }
+    append(value, lastPos, length)
+    append(STRING)
+}
+
+/**
+ * Returns `true` if the contents of this string is equal to the word "true", ignoring case, `false` if content equals "false",
+ * and throws [IllegalStateException] otherwise.
+ */
+fun String.toBooleanStrict(): Boolean = toBooleanStrictOrNull() ?: throw IllegalStateException("$this does not represent a Boolean")
+
+/**
+ * Returns `true` if the contents of this string is equal to the word "true", ignoring case, `false` if content equals "false",
+ * and returns `null` otherwise.
+ */
+fun String.toBooleanStrictOrNull(): Boolean? = when {
+    this.equals("true", ignoreCase = true) -> true
+    this.equals("false", ignoreCase = true) -> false
+    else -> null
+}
\ No newline at end of file
diff --git a/kotlin-native/tools/performance-server/src/main/kotlin/database/BenchmarksIndexesDispatcher.kt b/kotlin-native/tools/performance-server/src/jsMain/kotlin/database/BenchmarksIndexesDispatcher.kt
similarity index 99%
rename from kotlin-native/tools/performance-server/src/main/kotlin/database/BenchmarksIndexesDispatcher.kt
rename to kotlin-native/tools/performance-server/src/jsMain/kotlin/database/BenchmarksIndexesDispatcher.kt
index d600a7b..f8e26f9 100644
--- a/kotlin-native/tools/performance-server/src/main/kotlin/database/BenchmarksIndexesDispatcher.kt
+++ b/kotlin-native/tools/performance-server/src/jsMain/kotlin/database/BenchmarksIndexesDispatcher.kt
@@ -7,7 +7,6 @@
 
 import kotlin.js.Promise
 import org.jetbrains.elastic.*
-import org.jetbrains.utils.*
 import org.jetbrains.report.json.*
 import org.jetbrains.report.*
 
diff --git a/kotlin-native/tools/performance-server/src/main/kotlin/database/DatabaseRequests.kt b/kotlin-native/tools/performance-server/src/jsMain/kotlin/database/DatabaseRequests.kt
similarity index 100%
rename from kotlin-native/tools/performance-server/src/main/kotlin/database/DatabaseRequests.kt
rename to kotlin-native/tools/performance-server/src/jsMain/kotlin/database/DatabaseRequests.kt
diff --git a/kotlin-native/tools/performance-server/src/main/kotlin/main.kt b/kotlin-native/tools/performance-server/src/jsMain/kotlin/main.kt
similarity index 100%
rename from kotlin-native/tools/performance-server/src/main/kotlin/main.kt
rename to kotlin-native/tools/performance-server/src/jsMain/kotlin/main.kt
diff --git a/kotlin-native/tools/performance-server/src/main/kotlin/network/CachableResponseDispatcher.kt b/kotlin-native/tools/performance-server/src/jsMain/kotlin/network/CachableResponseDispatcher.kt
similarity index 100%
rename from kotlin-native/tools/performance-server/src/main/kotlin/network/CachableResponseDispatcher.kt
rename to kotlin-native/tools/performance-server/src/jsMain/kotlin/network/CachableResponseDispatcher.kt
diff --git a/kotlin-native/tools/performance-server/src/jsMain/kotlin/org/jetbrains/buildInfo/BuildInfo.kt b/kotlin-native/tools/performance-server/src/jsMain/kotlin/org/jetbrains/buildInfo/BuildInfo.kt
new file mode 100644
index 0000000..8056755
--- /dev/null
+++ b/kotlin-native/tools/performance-server/src/jsMain/kotlin/org/jetbrains/buildInfo/BuildInfo.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.*
+
+@JsExport
+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.")
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/kotlin-native/tools/performance-server/shared/src/main/kotlin/org/jetbrains/elastic/ElasticSearchConnector.kt b/kotlin-native/tools/performance-server/src/jsMain/kotlin/org/jetbrains/elastic/ElasticSearchConnector.kt
similarity index 100%
rename from kotlin-native/tools/performance-server/shared/src/main/kotlin/org/jetbrains/elastic/ElasticSearchConnector.kt
rename to kotlin-native/tools/performance-server/src/jsMain/kotlin/org/jetbrains/elastic/ElasticSearchConnector.kt
diff --git a/kotlin-native/tools/performance-server/shared/src/main/kotlin/org/jetbrains/elastic/ElasticSearchIndex.kt b/kotlin-native/tools/performance-server/src/jsMain/kotlin/org/jetbrains/elastic/ElasticSearchIndex.kt
similarity index 100%
rename from kotlin-native/tools/performance-server/shared/src/main/kotlin/org/jetbrains/elastic/ElasticSearchIndex.kt
rename to kotlin-native/tools/performance-server/src/jsMain/kotlin/org/jetbrains/elastic/ElasticSearchIndex.kt
diff --git a/kotlin-native/tools/performance-server/shared/src/main/kotlin/org/jetbrains/network/NetworkConnector.kt b/kotlin-native/tools/performance-server/src/jsMain/kotlin/org/jetbrains/network/NetworkConnector.kt
similarity index 100%
rename from kotlin-native/tools/performance-server/shared/src/main/kotlin/org/jetbrains/network/NetworkConnector.kt
rename to kotlin-native/tools/performance-server/src/jsMain/kotlin/org/jetbrains/network/NetworkConnector.kt
diff --git a/kotlin-native/tools/performance-server/shared/src/main/kotlin/org/jetbrains/network/UrlNetworkConnector.kt b/kotlin-native/tools/performance-server/src/jsMain/kotlin/org/jetbrains/network/UrlNetworkConnector.kt
similarity index 100%
rename from kotlin-native/tools/performance-server/shared/src/main/kotlin/org/jetbrains/network/UrlNetworkConnector.kt
rename to kotlin-native/tools/performance-server/src/jsMain/kotlin/org/jetbrains/network/UrlNetworkConnector.kt
diff --git a/kotlin-native/tools/performance-server/src/main/kotlin/routes/route.kt b/kotlin-native/tools/performance-server/src/jsMain/kotlin/routes/route.kt
similarity index 99%
rename from kotlin-native/tools/performance-server/src/main/kotlin/routes/route.kt
rename to kotlin-native/tools/performance-server/src/jsMain/kotlin/routes/route.kt
index 6987362..fa79ede 100644
--- a/kotlin-native/tools/performance-server/src/main/kotlin/routes/route.kt
+++ b/kotlin-native/tools/performance-server/src/jsMain/kotlin/routes/route.kt
@@ -3,8 +3,6 @@
  * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
  */
 
-import org.w3c.xhr.*
-import kotlin.js.json
 import kotlin.js.Date
 import kotlin.js.Promise
 import org.jetbrains.database.*
diff --git a/kotlin-native/tools/performance-server/src/main/kotlin/utils/AsyncUtils.kt b/kotlin-native/tools/performance-server/src/jsMain/kotlin/utils/AsyncUtils.kt
similarity index 100%
rename from kotlin-native/tools/performance-server/src/main/kotlin/utils/AsyncUtils.kt
rename to kotlin-native/tools/performance-server/src/jsMain/kotlin/utils/AsyncUtils.kt
diff --git a/kotlin-native/tools/performance-server/src/main/kotlin-js/org/jetbrains/analyzer/Utils.kt b/kotlin-native/tools/performance-server/src/main/kotlin-js/org/jetbrains/analyzer/Utils.kt
deleted file mode 100644
index 06cda7f..0000000
--- a/kotlin-native/tools/performance-server/src/main/kotlin-js/org/jetbrains/analyzer/Utils.kt
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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 kotlinx.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")
-}
-
-actual fun getDefaultPerformanceServerUrl() : String? = null
\ No newline at end of file