diff --git a/frameworks/Scala/otavia/.mill-version b/frameworks/Scala/otavia/.mill-version new file mode 100644 index 00000000000..772c67a500f --- /dev/null +++ b/frameworks/Scala/otavia/.mill-version @@ -0,0 +1 @@ +0.11.8 \ No newline at end of file diff --git a/frameworks/Scala/otavia/.scalafmt.conf b/frameworks/Scala/otavia/.scalafmt.conf new file mode 100644 index 00000000000..ff856188419 --- /dev/null +++ b/frameworks/Scala/otavia/.scalafmt.conf @@ -0,0 +1,16 @@ +version = "3.5.3" + +runner.dialect = scala3 +maxColumn = 120 +docstrings.blankFirstLine = no +docstrings.style = AsteriskSpace +docstrings.removeEmpty = true +docstrings.oneline = fold +docstrings.wrap = yes +docstrings.wrapMaxColumn = 120 +docstrings.forceBlankLineBefore = true +align.preset = more + +indent.main = 4 + +newlines.topLevelBodyIfMinStatements = [before,after] \ No newline at end of file diff --git a/frameworks/Scala/otavia/README.MD b/frameworks/Scala/otavia/README.MD new file mode 100644 index 00000000000..0ea11222811 --- /dev/null +++ b/frameworks/Scala/otavia/README.MD @@ -0,0 +1,13 @@ +## Introduction + +[GitHub - otavia-projects/otavia : Your shiny new IO & Actor programming model!](https://github.com/otavia-projects/otavia) + +`otavia` is an IO and Actor programming model power by Scala 3, it provides a toolkit to make writing high-performance +concurrent programs more easily. + +You can get a quick overview of the basic usage and core design of `otavia` in the following documentation: + +- [Quick Start](https://github.com/otavia-projects/otavia/blob/main/docs/_docs/quick_start.md) +- [Core Concepts and Design](https://github.com/otavia-projects/otavia/blob/main/docs/_docs/core_concept.md) + +More document can be found at [website](https://otavia.cc/home.html) \ No newline at end of file diff --git a/frameworks/Scala/otavia/benchmark/src/app/controller/DBController.scala b/frameworks/Scala/otavia/benchmark/src/app/controller/DBController.scala new file mode 100644 index 00000000000..6b54504aecc --- /dev/null +++ b/frameworks/Scala/otavia/benchmark/src/app/controller/DBController.scala @@ -0,0 +1,106 @@ +package app.controller + +import app.controller.DBController.* +import app.model.World +import cc.otavia.core.actor.{MessageOf, StateActor} +import cc.otavia.core.address.Address +import cc.otavia.core.message.{Ask, Reply} +import cc.otavia.core.stack.helper.{FutureState, FuturesState, StartState} +import cc.otavia.core.stack.{AskStack, StackState, StackYield} +import cc.otavia.http.server.{HttpRequest, HttpResponse} +import cc.otavia.sql.Connection +import cc.otavia.sql.Statement.{ModifyRows, PrepareQuery} + +import java.util.SplittableRandom + +class DBController extends StateActor[REQ] { + + private var connection: Address[MessageOf[Connection]] = _ + + private val random = new SplittableRandom() + + override protected def afterMount(): Unit = connection = autowire[Connection]() + + override protected def resumeAsk(stack: AskStack[REQ & Ask[? <: Reply]]): StackYield = + stack match + case stack: AskStack[SingleQueryRequest] if stack.ask.isInstanceOf[SingleQueryRequest] => + handleSingleQuery(stack) + case stack: AskStack[MultipleQueryRequest] if stack.ask.isInstanceOf[MultipleQueryRequest] => + handleMultipleQuery(stack) + case stack: AskStack[UpdateRequest] if stack.ask.isInstanceOf[UpdateRequest] => + handleUpdateQuery(stack) + + // Test 2: Single database query + private def handleSingleQuery(stack: AskStack[SingleQueryRequest]): StackYield = { + stack.state match + case _: StartState => + val state = FutureState[World]() + connection.ask(PrepareQuery.fetchOne[World](SELECT_WORLD, Tuple1(randomWorld())), state.future) + stack.suspend(state) + case state: FutureState[World] => + stack.`return`(state.future.getNow) + } + + // Test 3: Multiple database queries + private def handleMultipleQuery(stack: AskStack[MultipleQueryRequest]): StackYield = { + stack.state match + case _: StartState => + stack.suspend(selectWorlds(normalizeQueries(stack.ask.params))) + case state: FuturesState[World] => + val response = HttpResponse.builder.setContent(state.futures.map(_.getNow)).build() + stack.`return`(response) + } + + // Test 5: Database updates + private def handleUpdateQuery(stack: AskStack[UpdateRequest]): StackYield = { + stack.state match + case _: StartState => + stack.suspend(selectWorlds(normalizeQueries(stack.ask.params))) + case state: FuturesState[World] => + val worlds = state.futures.map(_.getNow) + stack.attach(worlds) + val newState = FutureState[ModifyRows]() + val newWorlds = worlds.sortBy(_.id).map(_.copy(randomNumber = randomWorld())) + connection.ask(PrepareQuery.update(UPDATE_WORLD, newWorlds), newState.future) + stack.suspend(newState) + case state: FutureState[ModifyRows] => + if (state.future.isFailed) state.future.causeUnsafe.printStackTrace() + val response = HttpResponse.builder.setContent(stack.attach[Seq[World]]).build() + stack.`return`(response) + } + + private def selectWorlds(queries: Int): StackState = { + val state = FuturesState[World](queries) + for (future <- state.futures) + connection.ask(PrepareQuery.fetchOne[World](SELECT_WORLD, Tuple1(randomWorld())), future) + state + } + + private def randomWorld(): Int = 1 + random.nextInt(10000) + + private def normalizeQueries(params: Map[String, String]): Int = { + params.get("queries") match + case Some(value) => + try { + val queries = value.toInt + if (queries < 1) 1 else if (queries > 500) 500 else queries + } catch { + case e: Throwable => 1 + } + case None => 1 + } + +} + +object DBController { + + type REQ = SingleQueryRequest | MultipleQueryRequest | UpdateRequest + + class SingleQueryRequest extends HttpRequest[Nothing, World] + class MultipleQueryRequest extends HttpRequest[Nothing, HttpResponse[Seq[World]]] + class UpdateRequest extends HttpRequest[Nothing, HttpResponse[Seq[World]]] + + private val SELECT_WORLD = "SELECT id, randomnumber from WORLD where id=$1" + private val UPDATE_WORLD = "update world set randomnumber=$2 where id=$1" + +} diff --git a/frameworks/Scala/otavia/benchmark/src/app/controller/FortuneController.scala b/frameworks/Scala/otavia/benchmark/src/app/controller/FortuneController.scala new file mode 100644 index 00000000000..05ff8c468ba --- /dev/null +++ b/frameworks/Scala/otavia/benchmark/src/app/controller/FortuneController.scala @@ -0,0 +1,41 @@ +package app.controller + +import app.controller.FortuneController.* +import app.model.Fortune +import cc.otavia.core.actor.{MessageOf, StateActor} +import cc.otavia.core.address.Address +import cc.otavia.core.stack.helper.{FutureState, StartState} +import cc.otavia.core.stack.{AskStack, StackState, StackYield} +import cc.otavia.http.server.{HttpRequest, HttpResponse} +import cc.otavia.sql.Statement.PrepareQuery +import cc.otavia.sql.{Connection, RowSet} + +class FortuneController extends StateActor[FortuneRequest] { + + private var connection: Address[MessageOf[Connection]] = _ + + override protected def afterMount(): Unit = connection = autowire[Connection]() + + // Test 4: Fortunes + override protected def resumeAsk(stack: AskStack[FortuneRequest]): StackYield = { + stack.state match + case _: StartState => + val state = FutureState[RowSet[Fortune]]() + connection.ask(PrepareQuery.fetchAll[Fortune](SELECT_FORTUNE), state.future) + stack.suspend(state) + case state: FutureState[RowSet[Fortune]] => + val fortunes = (state.future.getNow.rows :+ Fortune(0, "Additional fortune added at request time.")) + .sortBy(_.message) + val response = HttpResponse.builder.setContent(fortunes).build() + stack.`return`(response) + } + +} + +object FortuneController { + + class FortuneRequest extends HttpRequest[Nothing, HttpResponse[Seq[Fortune]]] + + private val SELECT_FORTUNE = "SELECT id, message from FORTUNE" + +} diff --git a/frameworks/Scala/otavia/benchmark/src/app/model/Fortune.scala b/frameworks/Scala/otavia/benchmark/src/app/model/Fortune.scala new file mode 100644 index 00000000000..edb58d3cdc9 --- /dev/null +++ b/frameworks/Scala/otavia/benchmark/src/app/model/Fortune.scala @@ -0,0 +1,7 @@ +package app.model + +import cc.otavia.json.JsonSerde +import cc.otavia.sql.{Row, RowDecoder} + +/** The model for the "fortune" database table. */ +case class Fortune(id: Int, message: String) extends Row derives RowDecoder, JsonSerde diff --git a/frameworks/Scala/otavia/benchmark/src/app/model/Message.scala b/frameworks/Scala/otavia/benchmark/src/app/model/Message.scala new file mode 100644 index 00000000000..a0d389090ae --- /dev/null +++ b/frameworks/Scala/otavia/benchmark/src/app/model/Message.scala @@ -0,0 +1,5 @@ +package app.model + +import cc.otavia.json.JsonSerde + +case class Message(message: String) derives JsonSerde diff --git a/frameworks/Scala/otavia/benchmark/src/app/model/World.scala b/frameworks/Scala/otavia/benchmark/src/app/model/World.scala new file mode 100644 index 00000000000..a0e6f6056f8 --- /dev/null +++ b/frameworks/Scala/otavia/benchmark/src/app/model/World.scala @@ -0,0 +1,8 @@ +package app.model + +import cc.otavia.json.JsonSerde +import cc.otavia.serde.annotation.rename +import cc.otavia.sql.{Row, RowDecoder} + +/** The model for the "world" database table. */ +case class World(id: Int, @rename("randomnumber") randomNumber: Int) extends Row derives RowDecoder, JsonSerde diff --git a/frameworks/Scala/otavia/benchmark/src/app/startup.scala b/frameworks/Scala/otavia/benchmark/src/app/startup.scala new file mode 100644 index 00000000000..32eb0c2e539 --- /dev/null +++ b/frameworks/Scala/otavia/benchmark/src/app/startup.scala @@ -0,0 +1,70 @@ +package app + +import app.controller.DBController.* +import app.controller.FortuneController.* +import app.controller.{DBController, FortuneController} +import app.model.* +import app.util.FortunesRender +import cc.otavia.core.actor.ChannelsActor.{Bind, ChannelEstablished} +import cc.otavia.core.actor.MainActor +import cc.otavia.core.slf4a.LoggerFactory +import cc.otavia.core.stack.helper.{FutureState, StartState} +import cc.otavia.core.stack.{NoticeStack, StackYield} +import cc.otavia.core.system.ActorSystem +import cc.otavia.http.HttpMethod.* +import cc.otavia.http.MediaType +import cc.otavia.http.MediaType.* +import cc.otavia.http.server.* +import cc.otavia.http.server.Router.* +import cc.otavia.json.JsonSerde +import cc.otavia.serde.helper.BytesSerde +import cc.otavia.sql.Connection + +import java.io.File +import java.nio.charset.StandardCharsets.UTF_8 +import java.nio.file.Path + +private class ServerMain(val port: Int = 8080) extends MainActor(Array.empty) { + + override def main0(stack: NoticeStack[MainActor.Args]): StackYield = stack.state match + case _: StartState => + val worldResponseSerde = HttpResponseSerde.json(summon[JsonSerde[World]]) + val worldsResponseSerde = HttpResponseSerde.json(JsonSerde.derived[Seq[World]]) + val fortunesResponseSerde = HttpResponseSerde(new FortunesRender(), MediaType.TEXT_HTML_UTF8) + + val dbController = autowire[DBController]() + val fortuneController = autowire[FortuneController]() + + val routers = Seq( + // Test 6: plaintext + constant[Array[Byte]](GET, "/plaintext", "Hello, World!".getBytes(UTF_8), BytesSerde, TEXT_PLAIN_UTF8), + // Test 1: JSON serialization + constant[Message](GET, "/json", Message("Hello, World!"), summon[JsonSerde[Message]], APP_JSON), + // Test 2: Single database query. + get("/db", dbController, () => new SingleQueryRequest(), worldResponseSerde), + // Test 3: Multiple database queries + get("/queries", dbController, () => new MultipleQueryRequest(), worldsResponseSerde), + // Test 5: Database updates + get("/updates", dbController, () => new UpdateRequest(), worldsResponseSerde), + // Test 4: Fortunes + get("/fortunes", fortuneController, () => new FortuneRequest(), fortunesResponseSerde) + ) + val server = system.buildActor(() => new HttpServer(system.actorWorkerSize, routers)) + val state = FutureState[ChannelEstablished]() + server.ask(Bind(port), state.future) + stack.suspend(state) + case state: FutureState[ChannelEstablished] => + if (state.future.isFailed) state.future.causeUnsafe.printStackTrace() + logger.info(s"http server bind port $port success") + stack.`return`() + +} + +@main def startup(url: String, user: String, password: String, poolSize: Int): Unit = + val system = ActorSystem() + val logger = LoggerFactory.getLogger("startup", system) + logger.info("starting http server") + system.buildActor(() => new Connection(url, user, password), global = true, num = poolSize) + system.buildActor(() => new DBController(), global = true, num = system.actorWorkerSize) + system.buildActor(() => new FortuneController(), global = true, num = system.actorWorkerSize) + system.buildActor(() => new ServerMain()) diff --git a/frameworks/Scala/otavia/benchmark/src/app/util/FortunesRender.scala b/frameworks/Scala/otavia/benchmark/src/app/util/FortunesRender.scala new file mode 100644 index 00000000000..0c0694ee836 --- /dev/null +++ b/frameworks/Scala/otavia/benchmark/src/app/util/FortunesRender.scala @@ -0,0 +1,64 @@ +package app.util + +import app.model.Fortune +import cc.otavia.buffer.{Buffer, BufferUtils} +import cc.otavia.serde.Serde + +import java.nio.charset.StandardCharsets +import scala.annotation.switch + +class FortunesRender extends Serde[Seq[Fortune]] { + + private val text1 = + "Fortunes" + .getBytes(StandardCharsets.UTF_8) + + private val text2 = "".getBytes(StandardCharsets.UTF_8) + + private val text5 = "
idmessage
".getBytes(StandardCharsets.UTF_8) + + private val text3 = "".getBytes(StandardCharsets.UTF_8) + + private val text4 = "
".getBytes(StandardCharsets.UTF_8) + + private val lt = "<".getBytes() + private val gt = ">".getBytes() + private val quot = """.getBytes() + private val squot = "'".getBytes() + private val amp = "&".getBytes() + + override def serialize(fortunes: Seq[Fortune], out: Buffer): Unit = { + out.writeBytes(text1) + for (fortune <- fortunes) { + out.writeBytes(text2) + BufferUtils.writeIntAsString(out, fortune.id) + out.writeBytes(text3) + writeEscapeMessage(out, fortune.message) + out.writeBytes(text4) + } + out.writeBytes(text5) + } + + override def deserialize(in: Buffer): Seq[Fortune] = throw new UnsupportedOperationException() + + private def writeEscapeMessage(buffer: Buffer, message: String): Unit = { + var i = 0 + while (i < message.length) { + val ch = message.charAt(i) + writeChar(buffer, ch) + i += 1 + } + } + + private def writeChar(buffer: Buffer, ch: Char): Unit = (ch: @switch) match + case '<' => buffer.writeBytes(lt) + case '>' => buffer.writeBytes(gt) + case '"' => buffer.writeBytes(quot) + case '\'' => buffer.writeBytes(squot) + case '&' => buffer.writeBytes(amp) + case _ => + if (ch < 0x80) buffer.writeByte(ch.toByte) + else if (ch < 0x800) buffer.writeShortLE((ch >> 6 | (ch << 8 & 0x3f00) | 0x80c0).toShort) + else buffer.writeMediumLE(ch >> 12 | (ch << 2 & 0x3f00) | (ch << 16 & 0x3f0000) | 0x8080e0) + +} diff --git a/frameworks/Scala/otavia/benchmark_config.json b/frameworks/Scala/otavia/benchmark_config.json new file mode 100644 index 00000000000..ae712574778 --- /dev/null +++ b/frameworks/Scala/otavia/benchmark_config.json @@ -0,0 +1,53 @@ +{ + "framework": "otavia", + "tests": [ + { + "default": { + "json_url": "/json", + "plaintext_url": "/plaintext", + "db_url": "/db", + "query_url": "/queries?queries=", + "fortune_url": "/fortunes", + "update_url": "/updates?queries=", + "port": 8080, + "approach": "Realistic", + "classification": "Micro", + "database": "Postgres", + "framework": "otavia", + "language": "Scala", + "flavor": "None", + "orm": "Micro", + "platform": "Otavia", + "webserver": "None", + "os": "Linux", + "database_os": "Linux", + "display_name": "otavia", + "notes": "", + "versus": "Otavia" + }, + "reserve": { + "json_url": "/json", + "plaintext_url": "/plaintext", + "db_url": "/db", + "query_url": "/queries?queries=", + "fortune_url": "/fortunes", + "update_url": "/updates?queries=", + "port": 8080, + "approach": "Realistic", + "classification": "Micro", + "database": "Postgres", + "framework": "otavia", + "language": "Scala", + "flavor": "None", + "orm": "Micro", + "platform": "Otavia", + "webserver": "None", + "os": "Linux", + "database_os": "Linux", + "display_name": "otavia", + "notes": "", + "versus": "Otavia" + } + } + ] +} diff --git a/frameworks/Scala/otavia/build.sc b/frameworks/Scala/otavia/build.sc new file mode 100644 index 00000000000..2b7877b7891 --- /dev/null +++ b/frameworks/Scala/otavia/build.sc @@ -0,0 +1,15 @@ +import mill._ +import mill.scalalib._ + +def otaviaVersion = "0.4.0" + +object benchmark extends ScalaModule { + + override def scalaVersion = "3.3.1" + + override def ivyDeps = Agg( + ivy"cc.otavia::otavia-codec-http:$otaviaVersion", + ivy"cc.otavia::otavia-postgres-driver:$otaviaVersion" + ) + +} diff --git a/frameworks/Scala/otavia/config.toml b/frameworks/Scala/otavia/config.toml new file mode 100644 index 00000000000..96613dfd2fd --- /dev/null +++ b/frameworks/Scala/otavia/config.toml @@ -0,0 +1,36 @@ +[framework] +name = "otavia" + +[main] +urls.plaintext = "/plaintext" +urls.json = "/json" +urls.db = "/db" +urls.query = "/queries?queries=" +urls.update = "/updates?queries=" +urls.fortune = "/fortunes" +approach = "Realistic" +classification = "Micro" +database = "Postgres" +database_os = "Linux" +os = "Linux" +orm = "Micro" +platform = "Otavia" +webserver = "None" +versus = "Otavia" + +[reserve] +urls.plaintext = "/plaintext" +urls.json = "/json" +urls.db = "/db" +urls.query = "/queries?queries=" +urls.update = "/updates?queries=" +urls.fortune = "/fortunes" +approach = "Realistic" +classification = "Micro" +database = "Postgres" +database_os = "Linux" +os = "Linux" +orm = "Micro" +platform = "Otavia" +webserver = "None" +versus = "Otavia" diff --git a/frameworks/Scala/otavia/millw b/frameworks/Scala/otavia/millw new file mode 100644 index 00000000000..73bb4d0e4a0 --- /dev/null +++ b/frameworks/Scala/otavia/millw @@ -0,0 +1,194 @@ +#!/usr/bin/env sh + +# This is a wrapper script, that automatically download mill from GitHub release pages +# You can give the required mill version with --mill-version parameter +# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +# +# Project page: https://github.com/lefou/millw +# Script Version: 0.4.6 +# +# If you want to improve this script, please also contribute your changes back! +# +# Licensed under the Apache License, Version 2.0 + +set -e + +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then + DEFAULT_MILL_VERSION="0.10.10" +fi + + +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then + GITHUB_RELEASE_CDN="" +fi + + +MILL_REPO_URL="https://github.com/com-lihaoyi/mill" + +if [ -z "${CURL_CMD}" ] ; then + CURL_CMD=curl +fi + +# Explicit commandline argument takes precedence over all other methods +if [ "$1" = "--mill-version" ] ; then + shift + if [ "x$1" != "x" ] ; then + MILL_VERSION="$1" + shift + else + echo "You specified --mill-version without a version." 1>&2 + echo "Please provide a version that matches one provided on" 1>&2 + echo "${MILL_REPO_URL}/releases" 1>&2 + false + fi +fi + +# Please note, that if a MILL_VERSION is already set in the environment, +# We reuse it's value and skip searching for a value. + +# If not already set, read .mill-version file +if [ -z "${MILL_VERSION}" ] ; then + if [ -f ".mill-version" ] ; then + MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)" + elif [ -f ".config/mill-version" ] ; then + MILL_VERSION="$(head -n 1 .config/mill-version 2> /dev/null)" + fi +fi + +if [ -n "${XDG_CACHE_HOME}" ] ; then + MILL_DOWNLOAD_PATH="${XDG_CACHE_HOME}/mill/download" +else + MILL_DOWNLOAD_PATH="${HOME}/.cache/mill/download" +fi + +# If not already set, try to fetch newest from Github +if [ -z "${MILL_VERSION}" ] ; then + # TODO: try to load latest version from release page + echo "No mill version specified." 1>&2 + echo "You should provide a version via '.mill-version' file or --mill-version option." 1>&2 + + mkdir -p "${MILL_DOWNLOAD_PATH}" + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( + # we might be on OSX or BSD which don't have -d option for touch + # but probably a -A [-][[hh]mm]SS + touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) || ( + # in case we still failed, we retry the first touch command with the intention + # to show the (previously suppressed) error message + LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" + ) + + # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 + # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then + if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then + # we know a current latest version + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # we don't know a current latest version + echo "Retrieving latest mill version ..." 1>&2 + LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" + MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) + fi + + if [ -z "${MILL_VERSION}" ] ; then + # Last resort + MILL_VERSION="${DEFAULT_MILL_VERSION}" + echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 + else + echo "Using mill version ${MILL_VERSION}" 1>&2 + fi +fi + +MILL="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}" + +try_to_use_system_mill() { + MILL_IN_PATH="$(command -v mill || true)" + + if [ -z "${MILL_IN_PATH}" ]; then + return + fi + + UNIVERSAL_SCRIPT_MAGIC="@ 2>/dev/null # 2>nul & echo off & goto BOF" + + if ! head -c 128 "${MILL_IN_PATH}" | grep -qF "${UNIVERSAL_SCRIPT_MAGIC}"; then + if [ -n "${MILLW_VERBOSE}" ]; then + echo "Could not determine mill version of ${MILL_IN_PATH}, as it does not start with the universal script magic2" 1>&2 + fi + return + fi + + # Roughly the size of the universal script. + MILL_VERSION_SEARCH_RANGE="2403" + MILL_IN_PATH_VERSION=$(head -c "${MILL_VERSION_SEARCH_RANGE}" "${MILL_IN_PATH}" |\ + sed -n 's/^.*-DMILL_VERSION=\([^\s]*\) .*$/\1/p' |\ + head -n 1) + + if [ -z "${MILL_IN_PATH_VERSION}" ]; then + echo "Could not determine mill version, even though ${MILL_IN_PATH} has the universal script magic" 1>&2 + return + fi + + if [ "${MILL_IN_PATH_VERSION}" = "${MILL_VERSION}" ]; then + MILL="${MILL_IN_PATH}" + fi +} +try_to_use_system_mill + +# If not already downloaded, download it +if [ ! -s "${MILL}" ] ; then + + # support old non-XDG download dir + MILL_OLD_DOWNLOAD_PATH="${HOME}/.mill/download" + OLD_MILL="${MILL_OLD_DOWNLOAD_PATH}/${MILL_VERSION}" + if [ -x "${OLD_MILL}" ] ; then + MILL="${OLD_MILL}" + else + VERSION_PREFIX="$(echo $MILL_VERSION | cut -b -4)" + case $VERSION_PREFIX in + 0.0. | 0.1. | 0.2. | 0.3. | 0.4. ) + DOWNLOAD_SUFFIX="" + ;; + *) + DOWNLOAD_SUFFIX="-assembly" + ;; + esac + unset VERSION_PREFIX + + DOWNLOAD_FILE=$(mktemp mill.XXXXXX) + MILL_VERSION_TAG=$(echo $MILL_VERSION | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') + DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + # TODO: handle command not found + echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 + ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" ${DOWNLOAD_URL} + chmod +x "${DOWNLOAD_FILE}" + mkdir -p "${MILL_DOWNLOAD_PATH}" + mv "${DOWNLOAD_FILE}" "${MILL}" + + unset DOWNLOAD_FILE + unset DOWNLOAD_SUFFIX + fi +fi + +if [ -z "$MILL_MAIN_CLI" ] ; then + MILL_MAIN_CLI="${0}" +fi + +MILL_FIRST_ARG="" +if [ "$1" = "--bsp" ] || [ "$1" = "-i" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then + # Need to preserve the first position of those listed options + MILL_FIRST_ARG=$1 + shift +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_OLD_DOWNLOAD_PATH +unset OLD_MILL +unset MILL_VERSION +unset MILL_VERSION_TAG +unset MILL_REPO_URL + +# We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes +# shellcheck disable=SC2086 +exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" diff --git a/frameworks/Scala/otavia/millw.bat b/frameworks/Scala/otavia/millw.bat new file mode 100644 index 00000000000..6359e35e500 --- /dev/null +++ b/frameworks/Scala/otavia/millw.bat @@ -0,0 +1,173 @@ +@echo off + +rem This is a wrapper script, that automatically download mill from GitHub release pages +rem You can give the required mill version with --mill-version parameter +rem If no version is given, it falls back to the value of DEFAULT_MILL_VERSION +rem +rem Project page: https://github.com/lefou/millw +rem Script Version: 0.4.6 +rem +rem If you want to improve this script, please also contribute your changes back! +rem +rem Licensed under the Apache License, Version 2.0 + +rem setlocal seems to be unavailable on Windows 95/98/ME +rem but I don't think we need to support them in 2019 +setlocal enabledelayedexpansion + +if [!DEFAULT_MILL_VERSION!]==[] ( + set "DEFAULT_MILL_VERSION=0.10.10" +) + +if [!GITHUB_RELEASE_CDN!]==[] ( + set "GITHUB_RELEASE_CDN=" +) + +set "MILL_REPO_URL=https://github.com/com-lihaoyi/mill" + +rem %~1% removes surrounding quotes +if [%~1%]==[--mill-version] ( + if not [%~2%]==[] ( + set MILL_VERSION=%~2% + rem shift command doesn't work within parentheses + set "STRIP_VERSION_PARAMS=true" + ) else ( + echo You specified --mill-version without a version. 1>&2 + echo Please provide a version that matches one provided on 1>&2 + echo %MILL_REPO_URL%/releases 1>&2 + exit /b 1 + ) +) + +if not defined STRIP_VERSION_PARAMS GOTO AfterStripVersionParams +rem strip the: --mill-version {version} +shift +shift +:AfterStripVersionParams + +if [!MILL_VERSION!]==[] ( + if exist .mill-version ( + set /p MILL_VERSION=<.mill-version + ) else ( + if exist .config\mill-version ( + set /p MILL_VERSION=<.config\mill-version + ) + ) +) + +if [!MILL_VERSION!]==[] ( + set MILL_VERSION=%DEFAULT_MILL_VERSION% +) + +set MILL_DOWNLOAD_PATH=%USERPROFILE%\.mill\download + +rem without bat file extension, cmd doesn't seem to be able to run it +set MILL=%MILL_DOWNLOAD_PATH%\!MILL_VERSION!.bat + +if not exist "%MILL%" ( + set VERSION_PREFIX=%MILL_VERSION:~0,4% + set DOWNLOAD_SUFFIX=-assembly + if [!VERSION_PREFIX!]==[0.0.] set DOWNLOAD_SUFFIX= + if [!VERSION_PREFIX!]==[0.1.] set DOWNLOAD_SUFFIX= + if [!VERSION_PREFIX!]==[0.2.] set DOWNLOAD_SUFFIX= + if [!VERSION_PREFIX!]==[0.3.] set DOWNLOAD_SUFFIX= + if [!VERSION_PREFIX!]==[0.4.] set DOWNLOAD_SUFFIX= + set VERSION_PREFIX= + + for /F "delims=- tokens=1" %%A in ("!MILL_VERSION!") do set MILL_VERSION_BASE=%%A + for /F "delims=- tokens=2" %%A in ("!MILL_VERSION!") do set MILL_VERSION_MILESTONE=%%A + set VERSION_MILESTONE_START=!MILL_VERSION_MILESTONE:~0,1! + if [!VERSION_MILESTONE_START!]==[M] ( + set MILL_VERSION_TAG="!MILL_VERSION_BASE!-!MILL_VERSION_MILESTONE!" + ) else ( + set MILL_VERSION_TAG=!MILL_VERSION_BASE! + ) + + rem there seems to be no way to generate a unique temporary file path (on native Windows) + set DOWNLOAD_FILE=%MILL%.tmp + + set DOWNLOAD_URL=!GITHUB_RELEASE_CDN!%MILL_REPO_URL%/releases/download/!MILL_VERSION_TAG!/!MILL_VERSION!!DOWNLOAD_SUFFIX! + + echo Downloading mill %MILL_VERSION% from !DOWNLOAD_URL! ... 1>&2 + + if not exist "%MILL_DOWNLOAD_PATH%" mkdir "%MILL_DOWNLOAD_PATH%" + rem curl is bundled with recent Windows 10 + rem but I don't think we can expect all the users to have it in 2019 + where /Q curl + if %ERRORLEVEL% EQU 0 ( + curl -f -L "!DOWNLOAD_URL!" -o "!DOWNLOAD_FILE!" + ) else ( + rem bitsadmin seems to be available on Windows 7 + rem without /dynamic, github returns 403 + rem bitsadmin is sometimes needlessly slow but it looks better with /priority foreground + bitsadmin /transfer millDownloadJob /dynamic /priority foreground "!DOWNLOAD_URL!" "!DOWNLOAD_FILE!" + ) + if not exist "!DOWNLOAD_FILE!" ( + echo Could not download mill %MILL_VERSION% 1>&2 + exit /b 1 + ) + + move /y "!DOWNLOAD_FILE!" "%MILL%" + + set DOWNLOAD_FILE= + set DOWNLOAD_SUFFIX= +) + +set MILL_DOWNLOAD_PATH= +set MILL_VERSION= +set MILL_REPO_URL= + +if [!MILL_MAIN_CLI!]==[] ( + set "MILL_MAIN_CLI=%0" +) + +rem Need to preserve the first position of those listed options +set MILL_FIRST_ARG= +if [%~1%]==[--bsp] ( + set MILL_FIRST_ARG=%1% +) else ( + if [%~1%]==[-i] ( + set MILL_FIRST_ARG=%1% + ) else ( + if [%~1%]==[--interactive] ( + set MILL_FIRST_ARG=%1% + ) else ( + if [%~1%]==[--no-server] ( + set MILL_FIRST_ARG=%1% + ) else ( + if [%~1%]==[--repl] ( + set MILL_FIRST_ARG=%1% + ) else ( + if [%~1%]==[--help] ( + set MILL_FIRST_ARG=%1% + ) + ) + ) + ) + ) +) + +set "MILL_PARAMS=%*%" + +if not [!MILL_FIRST_ARG!]==[] ( + if defined STRIP_VERSION_PARAMS ( + for /f "tokens=1-3*" %%a in ("%*") do ( + set "MILL_PARAMS=%%d" + ) + ) else ( + for /f "tokens=1*" %%a in ("%*") do ( + set "MILL_PARAMS=%%b" + ) + ) +) else ( + if defined STRIP_VERSION_PARAMS ( + for /f "tokens=1-2*" %%a in ("%*") do ( + rem strip %%a - It's the "--mill-version" option. + rem strip %%b - it's the version number that comes after the option. + rem keep %%c - It's the remaining options. + set "MILL_PARAMS=%%c" + ) + ) +) + +"%MILL%" %MILL_FIRST_ARG% -D "mill.main.cli=%MILL_MAIN_CLI%" %MILL_PARAMS% diff --git a/frameworks/Scala/otavia/otavia-reserve.dockerfile b/frameworks/Scala/otavia/otavia-reserve.dockerfile new file mode 100644 index 00000000000..5768000706e --- /dev/null +++ b/frameworks/Scala/otavia/otavia-reserve.dockerfile @@ -0,0 +1,15 @@ +FROM nightscape/scala-mill:eclipse-temurin-17.0.8.1_1-jdk-focal_0.11.6_3.3.0 +WORKDIR /otavia +COPY benchmark benchmark +COPY build.sc build.sc +ENV COURSIER_REPOSITORIES=ivy2Local|central +RUN mill benchmark.assembly + +EXPOSE 8080 + +CMD java -server \ + -Dcc.otavia.actor.worker.size=18 -Dcc.otavia.nio.worker.size=36 \ + -jar \ + out/benchmark/assembly.dest/out.jar \ + jdbc:postgresql://tfb-database:5432/hello_world \ + benchmarkdbuser benchmarkdbpass 54 diff --git a/frameworks/Scala/otavia/otavia.dockerfile b/frameworks/Scala/otavia/otavia.dockerfile new file mode 100644 index 00000000000..c1c944d60aa --- /dev/null +++ b/frameworks/Scala/otavia/otavia.dockerfile @@ -0,0 +1,15 @@ +FROM nightscape/scala-mill:eclipse-temurin-17.0.8.1_1-jdk-focal_0.11.6_3.3.0 +WORKDIR /otavia +COPY benchmark benchmark +COPY build.sc build.sc +ENV COURSIER_REPOSITORIES=ivy2Local|central +RUN mill benchmark.assembly + +EXPOSE 8080 + +CMD java -server \ + -Dcc.otavia.actor.worker.size=24 -Dcc.otavia.nio.worker.size=48 \ + -jar \ + out/benchmark/assembly.dest/out.jar \ + jdbc:postgresql://tfb-database:5432/hello_world \ + benchmarkdbuser benchmarkdbpass 72