Login
7 branches 0 tags
Ben (X220/Parabola) Initial work on stackless funcalls 33510d0 3 years ago 650 Commits
nujel / tools / benchmark.nuj
#!/usr/bin/env nujel
#| This script generates JSON files to be used with the web-based benchmark viewer,
 | any sort of analysis is deferred to the browser, here we just measure the date
 | and capture as much data as possible. After a run a new file will be added to
 | benchmarks/results/, which should then be uploaded to the server which can then
 | use the benchmark-aggregate.nuj script to generate the single aggregate JSON
 | file for the viewer.
 |#
[def single-runs 1]
[def nujel-runs  4]
[def git-head [popen/trim "git rev-parse HEAD"]]
[def git-branch [popen/trim "git rev-parse --abbrev-ref HEAD"]]
[def uname-a [popen/trim "uname -a"]]
[def date [popen/trim "git show -s --format=%cI"]] ; Would be nice if this could be done as easily in Nujel
[def rough-date [popen/trim "git show -s --format=%cs"]]
[def hostname [popen/trim "uname -n"]]

[defn bench/get-language [filename]
  [case [lowercase [path/extension filename]]
    ["lisp" :common-lisp]
    ["lsp" :franz-lisp]
    ["c" :c]
    ["dart" :dart]
    ["erl" :erlang]
    ["jnt" :janet]
    ["js" :javascript]
    ["krk" :kuroko]
    ["lua" :lua]
    ["jl" :julia]
    ["nuj" :nujel]
    ["pl" :perl]
    ["php" :php]
    ["py" :python]
    ["rkt" :racket]
    ["rby" :ruby]
    ["scm" :scheme]
    [otherwise #nil]]]

[defn bench/get-language-runtimes [language]
  [case language
    [:c '["tcc -run"]]
    [:common-lisp '["ecl --shell" "clisp" "sbcl --script"]]
    [:franz-lisp '["pc-lisp"]]
    [:dart '["dart"]]
    [:erlang '["escript"]]
    [:janet '["janet"]]
    [:javascript '["node" "qjs" "deno run" "duk" "mujs" "js78" "js91"]]
    [:kuroko '["kuroko"]]
    [:lua '["lua" "luajit"]]
    [:julia '["julia"]]
    [:nujel '["./nujel"]]
    [:perl '["perl"]]
    [:php '["php"]]
    [:python '["python" "pypy" "tinypy"]]
    [:racket '["racket"]]
    [:ruby '["ruby" "mruby"]]
    ;[:scheme '["guile" "chibi-scheme -q" "gosh" "chez --script" "tinyscheme" "s9"]]
    [:scheme '["guile" "stklos -f" "chibi-scheme -q" "gosh" "chez --script"]]
    [otherwise '[]]]]

[defn bench/get-runtime-version [runtime]
  [case [car [split runtime " "]]
    ["clisp" [popen/trim "bash -c \"clisp -q -norc --version 2>/dev/null\""]]
    ["ecl" [popen/trim "bash -c \"ecl --version 2>&1\""]]
    ;["tinyscheme" [popen/trim "bash -c \"tinyscheme -? 2>&1\""]]
    ;["s9" [popen/trim "bash -c \"s9 -? 2>&1\""]]
    ["deno" [popen/trim "bash -c \"deno --version 2>&1\""]]
    ["qjs" [popen/trim "bash -c \"qjs -h 2>&1\""]]
    ["kuroko" [popen/trim "bash -c \"kuroko --version 2>&1\""]]
    ["escript" [popen/trim "bash -c \"erl -version 2>&1\""]]
    ["node" [popen/trim "bash -c \"node --version 2>&1\""]]
    [["lua" "luajit"] [popen/trim [fmt "bash -c \"{runtime} -v 2>&1\""]]]
    ["perl" [popen/trim "bash -c \"perl --version 2>&1\""]]
    ["php" [popen/trim "bash -c \"php --version 2>&1\""]]
    ["python" [popen/trim "bash -c \"python --version 2>&1\""]]
    ["pypy" [popen/trim "bash -c \"pypy --version 2>&1\""]]
    ["racket" [popen/trim "bash -c \"racket --version 2>&1\""]]
    ["ruby" [popen/trim "bash -c \"ruby --version 2>&1\""]]
    ["guile" [popen/trim "bash -c \"guile --version 2>&1\""]]
    ["chibi-scheme" [popen/trim "bash -c \"chibi-scheme --version 2>&1\""]]
    ["gosh" [popen/trim "bash -c \"gosh -V 2>&1\""]]
    [otherwise [fmt "{runtime}-unknown"]]]]

[defn runtime/available? [runtime]
  [def executable [car [split runtime " "]]]
  [zero? [car [popen [fmt "command -v {executable}"]]]]]

[defn get-result [testcase]
  [case testcase
    ["for" "49999995000000"]
    ["hello" "Hello"]
    ["euler1" "23333331666668"]
    [otherwise "チーズスイートホーム"]]]

[def time-template "#@[:user %U :system %S :elapsed \\\"%E\\\" :cpu \\\"%P\\\" :text %X :data %D :max-resident %M :inputs %I :outputs %O :pagefaults-major %F :pagefaults-minor %R :swaps %W]"]
[defn bench/single [filename runtime i]
  [def language [bench/get-language filename]]
  [when-not [runtime/available? runtime]
            [when [zero? i]
              [pfmt "{} {} Runtime not found"
                    [ansi-blue [string/pad-end language 14]]
                    [ansi-yellow [string/pad-end runtime 20]]]]
            [return #nil]]
  [when [zero? i]
    [pfmt "{} {} "
          [ansi-blue [string/pad-end language 14]]
          [ansi-green [string/pad-end runtime 20]]]]
  [pfmt "{i} "]
  [when-not [file/file? filename]
            [exception :io-error "Can't open the following file to benchmark it:" filename]]
  [def result-file [file/temp ""]]
  [def measurements #nil]
  [def testcase [cadr [split filename "/"]]]
  [popen "sleep 1"]
  [try [fn [err]
           [file/remove result-file]
           [display/error err]
           [throw err]]
       [def ret [popen [fmt "time -o {result-file} --format=\"{time-template}\" {runtime} {filename}"]]]
       [when-not [zero? [car ret]]
                 [exception :benchmark-error "Benchmark returned non-zero exit code" [cons filename ret]]]
       [when-not [>= [index-of [cdr ret] [get-result testcase]] 0]
                 [exception :benchmark-error "Benchmark returned wrong result" [cons filename ret]]]
       [set! measurements [tree/dup [read/single [file/read result-file]]]]
       [tree/set! measurements :stdout [cdr ret]]
       [file/remove result-file]]
  [when-not measurements [return #nil]]
  [-> measurements
      [tree/set! :language [bench/get-language filename]]
      [tree/set! :git-head git-head]
      [tree/set! :git-branch git-branch]
      [tree/set! :uname uname-a]
      [tree/set! :architecture System/Architecture]
      [tree/set! :hostname hostname]
      [tree/set! :os System/OS]
      [tree/set! :time [time]]
      [tree/set! :triplet [fmt "{testcase}-{hostname}-{System/Architecture}"]]
      [tree/set! :testcase testcase]
      [tree/set! :total [+ [ref measurements :user] [ref measurements :system]]]
      [tree/set! :date date]
      [tree/set! :runtime runtime]
      [tree/set! :runtime-version [bench/get-runtime-version runtime]]
      [tree/set! :filename filename]]]

[defn bench/file [ret filename]
  [when-not filename [return ret]]
  [def language [bench/get-language filename]]
  [and [tree/has? repl/options :only-nujel]
       [!= language :nujel]
       [return ret]]
  [def cur-runs [if [== language :nujel]
                    nujel-runs
                    single-runs]]
  [for-in [runtime [bench/get-language-runtimes language]]
          [when-not runtime [return ret]]
          [when [> cur-runs 0]
            [for [i 0 cur-runs]
              [set! ret [cons [bench/single filename runtime i] ret]]]
            [println ""]]]
  ret]

[def res-name [fmt "{rough-date}-{hostname}-{git-head}"]]
[def out-name [fmt "web/benchmark-results/{}.json" res-name]]
[directory/make "web/benchmark-results"]

[and [tree/has? repl/options :no-overwrite]
     [file/file? out-name]
     [do [pfmtln "{out-name} exists already, exiting."]
         [exit 0]]]

[-> [directory/read-recursive "benchmark"]
    [sort]
    [reduce bench/file #nil]
    [val->json]
    [file/write out-name]]