diff --git a/ledger-service/http-json-perf/BUILD.bazel b/ledger-service/http-json-perf/BUILD.bazel index 4b44049bbeb3..c6d50bb2dbff 100644 --- a/ledger-service/http-json-perf/BUILD.bazel +++ b/ledger-service/http-json-perf/BUILD.bazel @@ -13,6 +13,13 @@ scalacopts = lf_scalacopts + [ "-P:wartremover:traverser:org.wartremover.warts.NonUnitStatements", ] +perf_runtime_deps = { + "ce": [], + "ee": [ + "@maven//:com_oracle_database_jdbc_ojdbc8", + ], +} + [ da_scala_library( name = "http-json-perf-{}".format(edition), @@ -45,9 +52,11 @@ scalacopts = lf_scalacopts + [ "//ledger-service/http-json-testing:{}".format(edition), "//ledger-service/jwt", "//libs-scala/gatling-utils", + "//libs-scala/oracle-testing", "//libs-scala/ports", "//libs-scala/postgresql-testing", "//libs-scala/scala-utils", + "//runtime-components/jdbc-drivers:jdbc-drivers-{}".format(edition), "@maven//:com_fasterxml_jackson_core_jackson_core", "@maven//:com_fasterxml_jackson_core_jackson_databind", "@maven//:io_gatling_gatling_app", @@ -92,7 +101,7 @@ scalacopts = lf_scalacopts + [ visibility = ["//visibility:public"], runtime_deps = [ "@maven//:ch_qos_logback_logback_classic", - ], + ] + perf_runtime_deps.get(edition), deps = [ ":http-json-perf-{}".format(edition), "//language-support/scala/bindings-akka", diff --git a/ledger-service/http-json-perf/src/main/scala/com/daml/http/perf/Config.scala b/ledger-service/http-json-perf/src/main/scala/com/daml/http/perf/Config.scala index 482218e1fddb..ffd05f731167 100644 --- a/ledger-service/http-json-perf/src/main/scala/com/daml/http/perf/Config.scala +++ b/ledger-service/http-json-perf/src/main/scala/com/daml/http/perf/Config.scala @@ -10,6 +10,10 @@ import com.daml.jwt.domain.Jwt import scalaz.{Applicative, Traverse} import scopt.RenderingMode +import Config.QueryStoreIndex +import com.daml.http.dbbackend.ContractDao.supportedJdbcDriverNames +import com.daml.runtime.JdbcDrivers.availableJdbcDriverNames + import scala.concurrent.duration.{Duration, FiniteDuration} private[perf] final case class Config[+S]( @@ -18,7 +22,7 @@ private[perf] final case class Config[+S]( jwt: Jwt, reportsDir: File, maxDuration: Option[FiniteDuration], - queryStoreIndex: Boolean, + queryStoreIndex: QueryStoreIndex, ) { override def toString: String = s"Config(" + @@ -27,7 +31,7 @@ private[perf] final case class Config[+S]( s", jwt=..." + // don't print the JWT s", reportsDir=${reportsDir: File}" + s", maxDuration=${this.maxDuration: Option[FiniteDuration]}" + - s", queryStoreIndex=${this.queryStoreIndex: Boolean}" + + s", queryStoreIndex=${this.queryStoreIndex: QueryStoreIndex}" + ")" } @@ -39,7 +43,7 @@ private[perf] object Config { jwt = Jwt(""), reportsDir = new File(""), maxDuration = None, - queryStoreIndex = false, + queryStoreIndex = QueryStoreIndex.No, ) implicit val configInstance: Traverse[Config] = new Traverse[Config] { @@ -47,7 +51,7 @@ private[perf] object Config { fa: Config[A] )(f: A => G[B]): G[Config[B]] = { import scalaz.syntax.functor._ - f(fa.scenario).map(b => Empty.copy(scenario = b)) + f(fa.scenario).map(b => fa.copy(scenario = b)) } } @@ -79,10 +83,12 @@ private[perf] object Config { .validate(validateJwt) .text("JWT token to use when connecting to JSON API.") - opt[Boolean]("query-store-index") + opt[QueryStoreIndex]("query-store-index") .action((x, c) => c.copy(queryStoreIndex = x)) .optional() - .text("Enables JSON API query store index. Default is false, disabled.") + .text( + s"Enables JSON API query store index ${QueryStoreIndex.allowedHelp}. Default is no, disabled." + ) opt[File]("reports-dir") .action((x, c) => c.copy(reportsDir = x)) @@ -108,4 +114,28 @@ private[perf] object Config { ) .toEither } + + sealed abstract class QueryStoreIndex extends Product with Serializable + object QueryStoreIndex { + case object No extends QueryStoreIndex + case object Postgres extends QueryStoreIndex + case object Oracle extends QueryStoreIndex + + val names: Map[String, QueryStoreIndex] = Map("no" -> No, "postgres" -> Postgres) ++ ( + if (supportedJdbcDriverNames(availableJdbcDriverNames)("oracle.jdbc.OracleDriver")) + Seq("oracle" -> Oracle) + else Seq.empty + ) + + private[Config] val allowedHelp = names.keys.mkString("{", ", ", "}") + + implicit val scoptRead: scopt.Read[QueryStoreIndex] = scopt.Read.reads { s => + names.getOrElse( + s.toLowerCase(java.util.Locale.ROOT), + throw new IllegalArgumentException( + s"$s is not a query store index; only $allowedHelp allowed" + ), + ) + } + } } diff --git a/ledger-service/http-json-perf/src/main/scala/com/daml/http/perf/Main.scala b/ledger-service/http-json-perf/src/main/scala/com/daml/http/perf/Main.scala index f2574f7f304e..94038eef58c4 100644 --- a/ledger-service/http-json-perf/src/main/scala/com/daml/http/perf/Main.scala +++ b/ledger-service/http-json-perf/src/main/scala/com/daml/http/perf/Main.scala @@ -24,9 +24,11 @@ import scalaz.std.string._ import scalaz.syntax.tag._ import scalaz.{-\/, EitherT, \/, \/-} +import Config.QueryStoreIndex + import scala.concurrent.duration.{Duration, _} import scala.concurrent.{Await, ExecutionContext, Future, TimeoutException} -import scala.util.{Failure, Success} +import scala.util.{Failure, Success, Try} object Main extends StrictLogging { @@ -137,25 +139,70 @@ object Main extends StrictLogging { } } - private def withJsonApiJdbcConfig[A](jsonApiQueryStoreEnabled: Boolean)( + private def withJsonApiJdbcConfig[A](jsonApiQueryStoreEnabled: QueryStoreIndex)( fn: Option[JdbcConfig] => Future[A] )(implicit ec: ExecutionContext - ): Future[A] = - if (jsonApiQueryStoreEnabled) { + ): Future[A] = QueryStoreBracket lookup jsonApiQueryStoreEnabled match { + case Some(b: QueryStoreBracket[s, d]) => + import b._ for { - dbInstance <- Future.successful(new PostgresRunner()) - dbConfig <- toFuture(dbInstance.start()) - jsonApiDbConfig <- Future.successful(jsonApiJdbcConfig(dbConfig)) + dbInstance <- Future.successful(state()) + dbConfig <- toFuture(start(dbInstance)) + jsonApiDbConfig <- Future.successful(config(dbInstance, dbConfig)) a <- fn(Some(jsonApiDbConfig)) _ <- Future.successful( - dbInstance.stop() + stop(dbInstance, dbConfig) // XXX ignores resulting Try ) // TODO: use something like `lf.data.TryOps.Bracket.bracket` } yield a - } else { - fn(None) + + case None => fn(None) + } + + private[this] final case class QueryStoreBracket[S, D]( + state: () => S, + start: S => Try[D], + config: (S, D) => JdbcConfig, + stop: (S, D) => Try[Unit], + ) + private[this] object QueryStoreBracket { + type T = QueryStoreBracket[_, _] + val Postgres: T = QueryStoreBracket[PostgresRunner, PostgresDatabase]( + () => new PostgresRunner(), + _.start(), + (_, d) => jsonApiJdbcConfig(d), + (r, _) => r.stop(), + ) + + import com.daml.testing.oracle, oracle.OracleAround + val Oracle: T = QueryStoreBracket[OracleRunner, oracle.User]( + () => new OracleRunner, + _.start(), + _ jdbcConfig _, + _.stop(_), + ) + + private[this] final class OracleRunner extends OracleAround { + type St = oracle.User + + def start() = Try { + connectToOracle() + createNewRandomUser(): St + } + + def jdbcConfig(user: St) = + JdbcConfig("oracle.jdbc.OracleDriver", oracleJdbcUrl, user.name, user.pwd) + + def stop(user: St) = Try(dropUser(user.name)) } + def lookup(q: QueryStoreIndex): Option[T] = q match { + case QueryStoreIndex.No => None + case QueryStoreIndex.Postgres => Some(Postgres) + case QueryStoreIndex.Oracle => Some(Oracle) + } + } + private def jsonApiJdbcConfig(c: PostgresDatabase): JdbcConfig = JdbcConfig( driver = "org.postgresql.Driver", diff --git a/libs-scala/oracle-testing/src/main/scala/testing/oracle/OracleAround.scala b/libs-scala/oracle-testing/src/main/scala/testing/oracle/OracleAround.scala index 72fefed43c6a..9dcb23d68db8 100644 --- a/libs-scala/oracle-testing/src/main/scala/testing/oracle/OracleAround.scala +++ b/libs-scala/oracle-testing/src/main/scala/testing/oracle/OracleAround.scala @@ -7,7 +7,7 @@ import com.daml.ports._ import java.sql._ import scala.util.{Random, Using} -private[oracle] final case class User(name: String, pwd: String) +private[daml] final case class User(name: String, pwd: String) trait OracleAround { @volatile