diff --git a/.gitignore b/.gitignore
index b91e59a..e520917 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,5 @@ project/plugins/project
project/sbt-ui.sbt
target/
credentials.sbt
+
+*.csv
diff --git a/README.md b/README.md
index b75a895..2d18bfd 100644
--- a/README.md
+++ b/README.md
@@ -5,27 +5,17 @@
[](https://en.wikipedia.org/wiki/Blue_Lines)
-`massive-attack` is a simple and configurable load generator test tool written in Scala originally to test Apache Thrift endpoints, but it can be
-used to benchmark any method that returns a Scala or Twitter `Future`.
-
Why?
+Proprietor: RMS
-Because I needed to load test a Thrift endpoint in one of my APIs, and could not find an easy to use tool after looking around for days, at least
-not one that did not require me to develop JMeter plugins, or one that has been updated in the past few years.
-
-How?
-
-So I decided to create one which is easy to use:
-
-1. It can easily be added as a dependency to your API or application
-2. It is configurable as much or as little as you need it to be
-3. It is extensible
+Load generator test tool written in Scala used to benchmark any method that returns a Scala or Twitter `Future`.
+Project forked from [massive-attack](https://github.com/delprks/massive-attack) project created and mantained by [Daniel Parks](https://github.com/delprks)
-Usage
+## Usage
1. Add it as a dependency to `build.sbt`:
-`libraryDependencies ++= Seq("com.delprks" %% "massive-attack" % "1.0.0" % "test")`
+`libraryDependencies += "bbc.rms" %% "massive-attack" % % "test")`
2. Create your test in [ScalaTest](http://www.scalatest.org) or [Specs2](https://etorreborre.github.io/specs2) (this library might change to be a testing framework in future)
@@ -58,36 +48,36 @@ And (if enabled) generate a report containing the test results:

-Test properties
+## Test properties
Following properties are available and configurable through `MassiveAttackProperties`:
-invocations
+### invocations
Specifies how many times the method should be invoked.
-threads
+### threads
You can set how many threads you want to run the load test on - beware that Thrift clients can run only on single threads.
-duration
+### duration
Specifies how long the method should be tested for in seconds - whichever comes first (duration or invocations) determines the length of the test.
-warmUp
+### warmUp
This is by default set to `true` to avoid cold start times affecting the test results - set it to false if you want to test cold starts.
-warmUpInvocations
+### warmUpInvocations
If `warmUp` is set to true, `warmUpInvocations` determines how many times the method should be invoked before the load test starts.
-spikeFactor
+### spikeFactor
This is used to decide which response times should be considered as spikes, by multiplying the average response time and `spikeFactor`. It has the default
value of `3.0`.
-verbose
+### verbose
Set this to true if you want to see invocation times when the load test is in progress.
@@ -95,10 +85,10 @@ Set this to true if you want to see invocation times when the load test is in pr
Set this to true if you want to save test results in a CSV file.
-reportName
+### reportName
If `report` is set to true, results will be saved to this file. If no `reportName` is specified, one will be generated.
# License
-`massive-attack` is open source software released under the [Apache 2 License](http://www.apache.org/licenses/LICENSE-2.0).
+original [massive-attack](https://github.com/delprks/massive-attack) is an open source software released under the [Apache 2 License](http://www.apache.org/licenses/LICENSE-2.0).
diff --git a/build.sbt b/build.sbt
index 40cb515..5bee151 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,74 +1,78 @@
-name := "massive-attack"
+import sbt.Keys.pomExtra
+import sbt.{Classpaths, Test}
-crossScalaVersions := Seq("2.12.6", "2.11.12")
-
-organization := "com.delprks"
+ThisBuild / organization := "bbc.rms"
+ThisBuild / scalaVersion := "2.13.2"
+ThisBuild / name := "massive-attack"
scalacOptions := Seq("-unchecked", "-deprecation", "-feature", "-encoding", "utf8")
javacOptions ++= Seq("-source", "1.8", "-target", "1.8")
-publishTo := {
- val isSnapshotValue = isSnapshot.value
- val nexus = "https://oss.sonatype.org/"
- if (isSnapshotValue) Some("snapshots" at nexus + "content/repositories/snapshots")
- else Some("releases" at nexus + "service/local/staging/deploy/maven2")
-}
-
-publishMavenStyle := true
-
-publishArtifact in Test := false
-
-parallelExecution in Test := false
-
-releaseCrossBuild := true
-
-sbtrelease.ReleasePlugin.autoImport.releasePublishArtifactsAction := PgpKeys.publishSigned.value
-
-sbtrelease.ReleasePlugin.autoImport.releaseCrossBuild := false
-
-SbtPgp.autoImport.useGpg := true
+resolvers ++= Seq("BBC Artifactory" at "https://artifactory.dev.bbc.co.uk/artifactory/repo/") :+ Classpaths.typesafeReleases
+
+lazy val mavenStyleSettings = Seq(
+ publishMavenStyle := true,
+ pomIncludeRepository := {
+ _ => false
+ },
+ pomExtra := {
+ https://github.com/delprks/massive-attack
+
+
+ Apache 2
+ http://www.apache.org/licenses/LICENSE-2.0
+ repo
+
+
+
+ git@github.com:delprks/massive-attack.git
+ scm:git@github.com:delprks/massive-attack.git
+
+
+
+ delprks
+ Daniel Parks
+ http://github.com/delprks
+
+
+ }
+)
SbtPgp.autoImport.useGpgAgent := true
-libraryDependencies ++= Seq(
- "com.twitter" %% "util-core" % "18.5.0",
- "com.typesafe.akka" %% "akka-actor" % "2.5.13",
- "com.typesafe.akka" %% "akka-testkit" % "2.5.13" % Test,
- "org.scalatest" %% "scalatest" % "3.0.5" % Test
+lazy val publishSettings = Seq(
+ version := scala.util.Properties.envOrElse("BUILD_VERSION", "0.1-SNAPSHOT"),
+ publishArtifact in (Test, packageBin) := true,
+ publishMavenStyle := true,
+ publishTo := Some("BBC Repository" at "https://artifactory.dev.bbc.co.uk/artifactory/int-bbc-releases"),
+ credentials += Credentials(Path.userHome / ".ivy2" / ".credentials")
)
-releaseTagComment := s"Releasing ${(version in ThisBuild).value}"
-releaseCommitMessage := s"= Setting version to ${(version in ThisBuild).value}"
-
-pomIncludeRepository := {
- _ => false
-}
-
-pomExtra := {
- https://github.com/delprks/massive-attack
-
-
- Apache 2
- http://www.apache.org/licenses/LICENSE-2.0
- repo
-
-
-
- git@github.com:delprks/massive-attack.git
- scm:git@github.com:delprks/massive-attack.git
-
-
-
- delprks
- Daniel Parks
- http://github.com/delprks
-
-
-}
-
-connectInput in run := true
+lazy val testSettings = Seq(
+ Test / publishArtifact := false,
+ Test / parallelExecution := false
+)
-fork in run := true
-lazy val root = project in file(".")
+libraryDependencies ++= Seq(
+ "com.twitter" %% "util-core" % "20.5.0",
+ "com.typesafe.akka" %% "akka-actor" % "2.6.5",
+ "com.typesafe.akka" %% "akka-testkit" % "2.6.5" % Test,
+ "org.scalatest" %% "scalatest" % "3.1.2" % Test
+) ++ (CrossVersion.partialVersion(scalaVersion.value) match {
+ case Some((2, major)) if major <= 12 =>
+ Seq()
+ case _ =>
+ Seq("org.scala-lang.modules" %% "scala-parallel-collections" % "0.2.0")
+})
+
+
+run /connectInput := true
+
+run / fork := true
+
+lazy val `massive-attack` = (project in file("."))
+ .settings(publishSettings)
+ .settings(testSettings)
+ .settings(mavenStyleSettings)
diff --git a/project/build.properties b/project/build.properties
index 16eecf5..f37b0aa 100644
--- a/project/build.properties
+++ b/project/build.properties
@@ -1 +1 @@
-sbt.version = 1.1.6
\ No newline at end of file
+sbt.version = 1.3.10
\ No newline at end of file
diff --git a/project/plugins.sbt b/project/plugins.sbt
index e5e6894..1883c16 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1,4 +1,3 @@
resolvers += Classpaths.sbtPluginReleases
-addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
-addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.7")
+addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.0.0")
diff --git a/src/main/scala/com/delprks/massiveattack/MassiveAttack.scala b/src/main/scala/bbc/rms/massiveattack/MassiveAttack.scala
similarity index 91%
rename from src/main/scala/com/delprks/massiveattack/MassiveAttack.scala
rename to src/main/scala/bbc/rms/massiveattack/MassiveAttack.scala
index 6cb8c37..a06bc6f 100644
--- a/src/main/scala/com/delprks/massiveattack/MassiveAttack.scala
+++ b/src/main/scala/bbc/rms/massiveattack/MassiveAttack.scala
@@ -1,4 +1,4 @@
-package com.delprks.massiveattack
+package bbc.rms.massiveattack
import com.twitter.util.{Future => TwitterFuture}
diff --git a/src/main/scala/bbc/rms/massiveattack/MassiveAttackProps.scala b/src/main/scala/bbc/rms/massiveattack/MassiveAttackProps.scala
new file mode 100644
index 0000000..7a61b2e
--- /dev/null
+++ b/src/main/scala/bbc/rms/massiveattack/MassiveAttackProps.scala
@@ -0,0 +1,3 @@
+package bbc.rms.massiveattack
+
+trait MassiveAttackProps
diff --git a/src/main/scala/com/delprks/massiveattack/MassiveAttackResult.scala b/src/main/scala/bbc/rms/massiveattack/MassiveAttackResult.scala
similarity index 61%
rename from src/main/scala/com/delprks/massiveattack/MassiveAttackResult.scala
rename to src/main/scala/bbc/rms/massiveattack/MassiveAttackResult.scala
index f1f2a5b..7c243ce 100644
--- a/src/main/scala/com/delprks/massiveattack/MassiveAttackResult.scala
+++ b/src/main/scala/bbc/rms/massiveattack/MassiveAttackResult.scala
@@ -1,4 +1,4 @@
-package com.delprks.massiveattack
+package bbc.rms.massiveattack
trait MassiveAttackResult {
def toString: String
diff --git a/src/main/scala/com/delprks/massiveattack/method/MethodPerformance.scala b/src/main/scala/bbc/rms/massiveattack/method/MethodPerformance.scala
similarity index 87%
rename from src/main/scala/com/delprks/massiveattack/method/MethodPerformance.scala
rename to src/main/scala/bbc/rms/massiveattack/method/MethodPerformance.scala
index 53af278..5f0d5f2 100644
--- a/src/main/scala/com/delprks/massiveattack/method/MethodPerformance.scala
+++ b/src/main/scala/bbc/rms/massiveattack/method/MethodPerformance.scala
@@ -1,19 +1,20 @@
-package com.delprks.massiveattack.method
+package bbc.rms.massiveattack.method
import java.util.concurrent.Executors
import akka.util.Timeout
-import com.delprks.massiveattack.MassiveAttack
-import com.delprks.massiveattack.method.result.{MethodDurationResult, MethodPerformanceResult}
-import com.delprks.massiveattack.method.util.{MethodOps, ResultOps}
+import bbc.rms.massiveattack.MassiveAttack
+import bbc.rms.massiveattack.method.result.{MethodDurationResult, MethodPerformanceResult}
+import bbc.rms.massiveattack.method.util.{MethodOps, ResultOps}
+import scala.collection.parallel.CollectionConverters._
+
import scala.collection.mutable.ListBuffer
-import scala.collection.parallel.ForkJoinTaskSupport
-import scala.collection.parallel.mutable.ParArray
import com.twitter.util.{Future => TwitterFuture}
+import scala.collection.parallel.ForkJoinTaskSupport
+import scala.collection.parallel.mutable.ParArray
import scala.concurrent.duration._
-import scala.concurrent.forkjoin.ForkJoinPool
import scala.concurrent.{ExecutionContext, Future => ScalaFuture}
import scala.reflect.ClassTag
@@ -37,8 +38,8 @@ class MethodPerformance(props: MethodPerformanceProps = MethodPerformanceProps()
val testStartTime = System.currentTimeMillis()
val testEndTime = testStartTime + props.duration * 1000
- val parallelInvocation: ParArray[Int] = (1 to props.invocations).toParArray
- val forkJoinPool = new ForkJoinPool(props.threads)
+ val parallelInvocation: ParArray[Int] = (1 to props.invocations).toArray.par
+ val forkJoinPool = new java.util.concurrent.ForkJoinPool(props.threads)
parallelInvocation.tasksupport = new ForkJoinTaskSupport(forkJoinPool)
@@ -62,10 +63,11 @@ class MethodPerformance(props: MethodPerformanceProps = MethodPerformanceProps()
println(Console.RED + s"Invoking method ${props.invocations} times - or maximum ${props.duration} seconds - on ${props.threads} threads" + Console.RESET)
+
val testStartTime = System.currentTimeMillis()
val testEndTime = testStartTime + props.duration * 1000
- val parallelInvocation: ParArray[Int] = (1 to props.invocations).toParArray
- val forkJoinPool = new scala.concurrent.forkjoin.ForkJoinPool(props.threads)
+ val parallelInvocation: ParArray[Int] = (1 to props.invocations).toArray.par
+ val forkJoinPool = new java.util.concurrent.ForkJoinPool(props.threads)
parallelInvocation.tasksupport = new ForkJoinTaskSupport(forkJoinPool)
diff --git a/src/main/scala/com/delprks/massiveattack/method/MethodPerformanceProps.scala b/src/main/scala/bbc/rms/massiveattack/method/MethodPerformanceProps.scala
similarity index 77%
rename from src/main/scala/com/delprks/massiveattack/method/MethodPerformanceProps.scala
rename to src/main/scala/bbc/rms/massiveattack/method/MethodPerformanceProps.scala
index 8f0f91f..734e3f2 100644
--- a/src/main/scala/com/delprks/massiveattack/method/MethodPerformanceProps.scala
+++ b/src/main/scala/bbc/rms/massiveattack/method/MethodPerformanceProps.scala
@@ -1,6 +1,6 @@
-package com.delprks.massiveattack.method
+package bbc.rms.massiveattack.method
-import com.delprks.massiveattack.MassiveAttackProps
+import bbc.rms.massiveattack.MassiveAttackProps
case class MethodPerformanceProps(
invocations: Int = 1000,
diff --git a/src/main/scala/com/delprks/massiveattack/method/result/MethodDurationResult.scala b/src/main/scala/bbc/rms/massiveattack/method/result/MethodDurationResult.scala
similarity index 57%
rename from src/main/scala/com/delprks/massiveattack/method/result/MethodDurationResult.scala
rename to src/main/scala/bbc/rms/massiveattack/method/result/MethodDurationResult.scala
index fcd1cb2..7d4d7f1 100644
--- a/src/main/scala/com/delprks/massiveattack/method/result/MethodDurationResult.scala
+++ b/src/main/scala/bbc/rms/massiveattack/method/result/MethodDurationResult.scala
@@ -1,3 +1,3 @@
-package com.delprks.massiveattack.method.result
+package bbc.rms.massiveattack.method.result
case class MethodDurationResult(duration: Long, endTime: Long)
diff --git a/src/main/scala/com/delprks/massiveattack/method/result/MethodPerformanceResult.scala b/src/main/scala/bbc/rms/massiveattack/method/result/MethodPerformanceResult.scala
similarity index 93%
rename from src/main/scala/com/delprks/massiveattack/method/result/MethodPerformanceResult.scala
rename to src/main/scala/bbc/rms/massiveattack/method/result/MethodPerformanceResult.scala
index ba5289f..d5b936e 100644
--- a/src/main/scala/com/delprks/massiveattack/method/result/MethodPerformanceResult.scala
+++ b/src/main/scala/bbc/rms/massiveattack/method/result/MethodPerformanceResult.scala
@@ -1,6 +1,6 @@
-package com.delprks.massiveattack.method.result
+package bbc.rms.massiveattack.method.result
-import com.delprks.massiveattack.MassiveAttackResult
+import bbc.rms.massiveattack.MassiveAttackResult
case class MethodPerformanceResult(
responseTimeMin: Int,
diff --git a/src/main/scala/com/delprks/massiveattack/method/result/MethodStatsRecorder.scala b/src/main/scala/bbc/rms/massiveattack/method/result/MethodStatsRecorder.scala
similarity index 88%
rename from src/main/scala/com/delprks/massiveattack/method/result/MethodStatsRecorder.scala
rename to src/main/scala/bbc/rms/massiveattack/method/result/MethodStatsRecorder.scala
index e825228..eb4d6c3 100644
--- a/src/main/scala/com/delprks/massiveattack/method/result/MethodStatsRecorder.scala
+++ b/src/main/scala/bbc/rms/massiveattack/method/result/MethodStatsRecorder.scala
@@ -1,4 +1,4 @@
-package com.delprks.massiveattack.method.result
+package bbc.rms.massiveattack.method.result
import akka.actor.Actor
diff --git a/src/main/scala/com/delprks/massiveattack/method/util/MethodOps.scala b/src/main/scala/bbc/rms/massiveattack/method/util/MethodOps.scala
similarity index 94%
rename from src/main/scala/com/delprks/massiveattack/method/util/MethodOps.scala
rename to src/main/scala/bbc/rms/massiveattack/method/util/MethodOps.scala
index 477579f..e88fe07 100644
--- a/src/main/scala/com/delprks/massiveattack/method/util/MethodOps.scala
+++ b/src/main/scala/bbc/rms/massiveattack/method/util/MethodOps.scala
@@ -1,7 +1,6 @@
-package com.delprks.massiveattack.method.util
+package bbc.rms.massiveattack.method.util
import akka.actor.{ActorSystem, Props}
-import com.delprks.massiveattack.method.result.{GetStats, MethodDurationResult, MethodStatsRecorder}
import com.twitter.util.{Future => TwitterFuture}
import scala.collection.mutable.ListBuffer
@@ -11,6 +10,7 @@ import scala.reflect.ClassTag
import akka.actor._
import akka.pattern.ask
import akka.util.Timeout
+import bbc.rms.massiveattack.method.result.{GetStats, MethodDurationResult, MethodStatsRecorder}
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration._
diff --git a/src/main/scala/com/delprks/massiveattack/method/util/ResultOps.scala b/src/main/scala/bbc/rms/massiveattack/method/util/ResultOps.scala
similarity index 88%
rename from src/main/scala/com/delprks/massiveattack/method/util/ResultOps.scala
rename to src/main/scala/bbc/rms/massiveattack/method/util/ResultOps.scala
index f3f46b7..7500422 100644
--- a/src/main/scala/com/delprks/massiveattack/method/util/ResultOps.scala
+++ b/src/main/scala/bbc/rms/massiveattack/method/util/ResultOps.scala
@@ -1,12 +1,12 @@
-package com.delprks.massiveattack.method.util
+package bbc.rms.massiveattack.method.util
import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Paths}
import java.text.SimpleDateFormat
import java.util.Calendar
-import com.delprks.massiveattack.method.MethodPerformanceProps
-import com.delprks.massiveattack.method.result.{MethodDurationResult, MethodPerformanceResult}
+import bbc.rms.massiveattack.method.MethodPerformanceProps
+import bbc.rms.massiveattack.method.result.{MethodDurationResult, MethodPerformanceResult}
import scala.concurrent.{ExecutionContext, Future}
import scala.collection.mutable.ListBuffer
@@ -15,7 +15,8 @@ class ResultOps()(implicit ec: ExecutionContext) {
def testResults(results: Future[ListBuffer[MethodDurationResult]], testProps: MethodPerformanceProps): Future[MethodPerformanceResult] = results map { response =>
val responseDuration = response.map(_.duration.toInt)
- val average = avg(responseDuration)
+ val responseDurationSeq = responseDuration.toSeq
+ val average = avg(responseDurationSeq)
val invocationSeconds = response.map(_.endTime / 1000)
val requestTimesPerSecond = invocationSeconds.groupBy(identity).map(_._2.size)
val spikeBoundary = (average * testProps.spikeFactor).toInt
@@ -27,8 +28,8 @@ class ResultOps()(implicit ec: ExecutionContext) {
val testResult = MethodPerformanceResult(
responseTimeMin = responseDuration.min,
responseTimeMax = responseDuration.max,
- responseTime95tile = percentile(95)(responseDuration),
- responseTime99tile = percentile(99)(responseDuration),
+ responseTime95tile = percentile(95)(responseDurationSeq),
+ responseTime99tile = percentile(99)(responseDurationSeq),
responseTimeAvg = average,
rpsMin = requestTimesPerSecond.min,
rpsMax = requestTimesPerSecond.max,
diff --git a/src/main/scala/com/delprks/massiveattack/MassiveAttackProps.scala b/src/main/scala/com/delprks/massiveattack/MassiveAttackProps.scala
deleted file mode 100644
index 705cc21..0000000
--- a/src/main/scala/com/delprks/massiveattack/MassiveAttackProps.scala
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.delprks.massiveattack
-
-trait MassiveAttackProps
diff --git a/src/test/scala/com/delprks/massiveattack/method/MethodPerformanceSpec.scala b/src/test/scala/bbc/rms/massiveattack/method/MethodPerformanceSpec.scala
similarity index 87%
rename from src/test/scala/com/delprks/massiveattack/method/MethodPerformanceSpec.scala
rename to src/test/scala/bbc/rms/massiveattack/method/MethodPerformanceSpec.scala
index 3dc9599..3b36944 100644
--- a/src/test/scala/com/delprks/massiveattack/method/MethodPerformanceSpec.scala
+++ b/src/test/scala/bbc/rms/massiveattack/method/MethodPerformanceSpec.scala
@@ -1,16 +1,18 @@
-package com.delprks.massiveattack.method
+package bbc.rms.massiveattack.method
-import com.delprks.massiveattack.method.result.MethodPerformanceResult
-import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
+import org.scalatest.BeforeAndAfterAll
+import org.scalatest.matchers.should.Matchers
import scala.concurrent.duration._
import scala.concurrent.{Await, Future => ScalaFuture}
import com.twitter.util.{Future => TwitterFuture}
import akka.actor.ActorSystem
import akka.testkit.{ImplicitSender, TestKit}
+import bbc.rms.massiveattack.method.result.MethodPerformanceResult
+import org.scalatest.wordspec.AnyWordSpecLike
class MethodPerformanceSpec extends TestKit(ActorSystem("MassiveAttackSpec")) with ImplicitSender
- with WordSpecLike with Matchers with BeforeAndAfterAll {
+ with AnyWordSpecLike with Matchers with BeforeAndAfterAll {
protected lazy val futureSupportTimeout: Duration = 30.seconds
diff --git a/version.sbt b/version.sbt
deleted file mode 100644
index e14138d..0000000
--- a/version.sbt
+++ /dev/null
@@ -1 +0,0 @@
-version in ThisBuild := "1.0.8-SNAPSHOT"