mirror of
https://github.com/mainflux/mainflux.git
synced 2025-04-26 13:48:53 +08:00
MF-134 - Evaluate system's performance (#225)
* Add initial load tests Add initial load tests for client creation and message publishing. Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com> * Make load tests executable as stand-alone application Move code from test to main. Make code runnable with sbt run command. Remove unnecessary config files. Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com> * Add native packager support Add native packager plugin. Update sbt config to support native packager. Update paths in Engine.scala. Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com> * Move files back to test folder and remove native packager support Remove nativa packager plugin. Add gatling plugin and move files to test folder where they belong. Read vars from JAVA_OPTS instead of environment variables. Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com> * Add readme file for load tests Add readme file for load tests with usage instructions. Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com> * Add number of requests per second as test parameter Add number of requests per second as parameter. Update read me according to this addition. Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com> * Add load test section in docs Create documentation skeleton for load tests. Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com> * Reformat logger config file Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com> * Update documentation skeleton Move results section to scenarios. Move test environment to intro. Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com> * Align test version with mainflux version Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com> * Update mainflux version to 0.2.2 Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
This commit is contained in:
parent
f449f8b9c8
commit
62297fedec
23
docs/load-test.md
Normal file
23
docs/load-test.md
Normal file
@ -0,0 +1,23 @@
|
||||
## Test scenarios
|
||||
|
||||
Testing environment to be determined.
|
||||
|
||||
### Message publishing
|
||||
|
||||
In this scenario, large number of requests are sent to HTTP adapter service
|
||||
every second. This test checks how much time HTTP adapter took to response to
|
||||
each request.
|
||||
|
||||
#### Results
|
||||
|
||||
TBD
|
||||
|
||||
### Create and get client
|
||||
|
||||
In this scenario, large number of requests are sent to manager service to create
|
||||
client, and than to retrieve its data. This test checks how much time manager
|
||||
service took to response to each request.
|
||||
|
||||
#### Results
|
||||
|
||||
TBD
|
8
load-test/.gitignore
vendored
Normal file
8
load-test/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
target/
|
||||
.classpath
|
||||
.cache-tests
|
||||
.cache-main
|
||||
.settings/
|
||||
.project
|
||||
*.class
|
||||
bin/
|
49
load-test/README.md
Normal file
49
load-test/README.md
Normal file
@ -0,0 +1,49 @@
|
||||
# Load Test
|
||||
|
||||
This SBT project contains load tests written for mainflux platform.
|
||||
|
||||
## Setup
|
||||
|
||||
In order to run load tests you must have [openjdk8](http://openjdk.java.net/install/) and [sbt](https://www.scala-sbt.org/1.0/docs/Setup.html) installed on your machine.
|
||||
|
||||
## Configuration
|
||||
|
||||
Tests are configured to use variables from `JAVA_OPTS` presented in the
|
||||
following table. Note that any unset variables will be replaced with their
|
||||
default values.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|------------------------------------------|-----------------------|
|
||||
| manager | Manager service URL | http://localhost:8180 |
|
||||
| http | HTTP adapter service URL | http://localhost:8182 |
|
||||
| requests | Number of requests to be sent per second | 100 |
|
||||
|
||||
## Usage
|
||||
|
||||
This project contains two simulations:
|
||||
|
||||
- `PublishSimulation`
|
||||
- `CreateAndRetrieveClientSimulation`
|
||||
|
||||
To run all tests you will have to run following commands:
|
||||
|
||||
```bash
|
||||
cd <path_to_mainflux_project>/load-test
|
||||
sbt gatling:test
|
||||
```
|
||||
|
||||
### Publish Simulation
|
||||
|
||||
`PublishSimulation` contains load tests for publishing messages. To run this test use following command:
|
||||
|
||||
```bash
|
||||
sbt "gatling:testOnly com.mainflux.loadtest.simulations.PublishSimulation"
|
||||
```
|
||||
|
||||
### Create And Retrieve Client Simulation
|
||||
|
||||
`CreateAndRetrieveClientSimulation` contains load tests for creating and retrieving clients. To run this test use following command:
|
||||
|
||||
```bash
|
||||
sbt "gatling:testOnly com.mainflux.loadtest.simulations.CreateAndRetrieveClientSimulation"
|
||||
```
|
18
load-test/build.sbt
Normal file
18
load-test/build.sbt
Normal file
@ -0,0 +1,18 @@
|
||||
enablePlugins(GatlingPlugin)
|
||||
|
||||
name := "load-test"
|
||||
version := "0.2.2"
|
||||
|
||||
scalaVersion := "2.12.4"
|
||||
|
||||
val gatlingVersion = "2.3.1"
|
||||
val circeVersion = "0.9.3"
|
||||
|
||||
libraryDependencies ++= Seq(
|
||||
"io.gatling.highcharts" % "gatling-charts-highcharts" % gatlingVersion,
|
||||
"io.gatling" % "gatling-test-framework" % gatlingVersion,
|
||||
"org.scalaj" %% "scalaj-http" % "2.3.0",
|
||||
"io.circe" %% "circe-core" % circeVersion,
|
||||
"io.circe" %% "circe-generic" % circeVersion,
|
||||
"io.circe" %% "circe-parser" % circeVersion
|
||||
)
|
1
load-test/project/build.properties
Normal file
1
load-test/project/build.properties
Normal file
@ -0,0 +1 @@
|
||||
sbt.version=1.1.2
|
1
load-test/project/plugins.sbt
Normal file
1
load-test/project/plugins.sbt
Normal file
@ -0,0 +1 @@
|
||||
addSbtPlugin("io.gatling" % "gatling-sbt" % "2.2.2")
|
15
load-test/src/test/resources/logback.xml
Normal file
15
load-test/src/test/resources/logback.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx</pattern>
|
||||
</encoder>
|
||||
<immediateFlush>false</immediateFlush>
|
||||
</appender>
|
||||
<appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</appender>
|
||||
<root level="WARN">
|
||||
<appender-ref ref="ASYNC" />
|
||||
</root>
|
||||
</configuration>
|
@ -0,0 +1,7 @@
|
||||
package com.mainflux.loadtest.simulations
|
||||
|
||||
object Constants {
|
||||
val ManagerUrl = System.getProperty("manager", "http://localhost:8180")
|
||||
val HttpAdapterUrl = System.getProperty("http", "http://localhost:8182")
|
||||
val RequestsPerSecond = Integer.getInteger("requests", 100)
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package com.mainflux.loadtest.simulations
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scalaj.http.Http
|
||||
import io.gatling.core.Predef._
|
||||
import io.gatling.http.Predef._
|
||||
import io.gatling.jdbc.Predef._
|
||||
import io.circe._
|
||||
import io.circe.generic.auto._
|
||||
import io.circe.parser._
|
||||
import io.circe.syntax._
|
||||
import CreateAndRetrieveClientSimulation._
|
||||
import io.gatling.http.protocol.HttpProtocolBuilder.toHttpProtocol
|
||||
import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder
|
||||
import com.mainflux.loadtest.simulations.Constants._
|
||||
|
||||
class CreateAndRetrieveClientSimulation extends Simulation {
|
||||
|
||||
// Register user
|
||||
Http(s"${ManagerUrl}/users")
|
||||
.postData(User)
|
||||
.header(HttpHeaderNames.ContentType, ContentType)
|
||||
.asString
|
||||
|
||||
// Login user
|
||||
val tokenRes = Http(s"${ManagerUrl}/tokens")
|
||||
.postData(User)
|
||||
.header(HttpHeaderNames.ContentType, ContentType)
|
||||
.asString
|
||||
.body
|
||||
|
||||
val tokenCursor = parse(tokenRes).getOrElse(Json.Null).hcursor
|
||||
val token = tokenCursor.downField("token").as[String].getOrElse("")
|
||||
|
||||
// Prepare testing scenario
|
||||
val httpProtocol = http
|
||||
.baseURL(ManagerUrl)
|
||||
.inferHtmlResources()
|
||||
.acceptHeader("*/*")
|
||||
.contentTypeHeader(ContentType)
|
||||
.userAgentHeader("curl/7.54.0")
|
||||
|
||||
val scn = scenario("CreateAndGetClient")
|
||||
.exec(http("CreateClientRequest")
|
||||
.post("/clients")
|
||||
.header(HttpHeaderNames.ContentType, ContentType)
|
||||
.header(HttpHeaderNames.Authorization, token)
|
||||
.body(StringBody(Client))
|
||||
.check(status.is(201))
|
||||
.check(headerRegex(HttpHeaderNames.Location, "(.*)").saveAs("location")))
|
||||
.exec(http("GetClientRequest")
|
||||
.get("${location}")
|
||||
.header(HttpHeaderNames.Authorization, token)
|
||||
.check(status.is(200)))
|
||||
|
||||
setUp(
|
||||
scn.inject(
|
||||
constantUsersPerSec(RequestsPerSecond.toDouble) during (15 second))).protocols(httpProtocol)
|
||||
}
|
||||
|
||||
object CreateAndRetrieveClientSimulation {
|
||||
val ContentType = "application/json; charset=utf-8"
|
||||
val User = """{"email":"john.doe@email.com", "password":"123"}"""
|
||||
val Client = """{"type":"device", "name":"weio"}"""
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
package com.mainflux.loadtest.simulations
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scalaj.http.Http
|
||||
import io.gatling.core.Predef._
|
||||
import io.gatling.http.Predef._
|
||||
import io.gatling.jdbc.Predef._
|
||||
import io.circe._
|
||||
import io.circe.generic.auto._
|
||||
import io.circe.parser._
|
||||
import io.circe.syntax._
|
||||
import PublishSimulation._
|
||||
import io.gatling.http.protocol.HttpProtocolBuilder.toHttpProtocol
|
||||
import io.gatling.http.request.builder.HttpRequestBuilder.toActionBuilder
|
||||
import com.mainflux.loadtest.simulations.Constants._
|
||||
|
||||
class PublishSimulation extends Simulation {
|
||||
|
||||
// Register user
|
||||
Http(s"${ManagerUrl}/users")
|
||||
.postData(User)
|
||||
.header(HttpHeaderNames.ContentType, ContentType)
|
||||
.asString
|
||||
|
||||
// Login user
|
||||
val tokenRes = Http(s"${ManagerUrl}/tokens")
|
||||
.postData(User)
|
||||
.header(HttpHeaderNames.ContentType, ContentType)
|
||||
.asString
|
||||
.body
|
||||
|
||||
val tokenCursor = parse(tokenRes).getOrElse(Json.Null).hcursor
|
||||
val token = tokenCursor.downField("token").as[String].getOrElse("")
|
||||
|
||||
// Register client
|
||||
val clientLocation = Http(s"${ManagerUrl}/clients")
|
||||
.postData(Client)
|
||||
.header(HttpHeaderNames.Authorization, token)
|
||||
.header(HttpHeaderNames.ContentType, ContentType)
|
||||
.asString
|
||||
.headers.get("Location").get(0)
|
||||
|
||||
val clientId = clientLocation.split("/")(2)
|
||||
|
||||
// Get client key
|
||||
val clientRes = Http(s"${ManagerUrl}/clients/${clientId}")
|
||||
.header(HttpHeaderNames.Authorization, token)
|
||||
.header(HttpHeaderNames.ContentType, ContentType)
|
||||
.asString
|
||||
.body
|
||||
|
||||
val clientCursor = parse(clientRes).getOrElse(Json.Null).hcursor
|
||||
val clientKey = clientCursor.downField("key").as[String].getOrElse("")
|
||||
|
||||
// Register channel
|
||||
val chanLocation = Http(s"${ManagerUrl}/channels")
|
||||
.postData(Channel)
|
||||
.header(HttpHeaderNames.Authorization, token)
|
||||
.header(HttpHeaderNames.ContentType, ContentType)
|
||||
.asString
|
||||
.headers.get("Location").get(0)
|
||||
|
||||
val chanId = chanLocation.split("/")(2)
|
||||
|
||||
// Connect client to channel
|
||||
Http(s"${ManagerUrl}/channels/${chanId}/clients/${clientId}")
|
||||
.method("PUT")
|
||||
.header(HttpHeaderNames.Authorization, token)
|
||||
.asString
|
||||
|
||||
// Prepare testing scenario
|
||||
val httpProtocol = http
|
||||
.baseURL(HttpAdapterUrl)
|
||||
.inferHtmlResources()
|
||||
.acceptHeader("*/*")
|
||||
.contentTypeHeader("application/json; charset=utf-8")
|
||||
.userAgentHeader("curl/7.54.0")
|
||||
|
||||
val scn = scenario("PublishMessage")
|
||||
.exec(http("PublishMessageRequest")
|
||||
.post(s"/channels/${chanId}/messages")
|
||||
.header(HttpHeaderNames.ContentType, "application/senml+json")
|
||||
.header(HttpHeaderNames.Authorization, clientKey)
|
||||
.body(StringBody(Message))
|
||||
.check(status.is(202)))
|
||||
|
||||
setUp(
|
||||
scn.inject(
|
||||
constantUsersPerSec(RequestsPerSecond.toDouble) during (15 second))).protocols(httpProtocol)
|
||||
}
|
||||
|
||||
object PublishSimulation {
|
||||
val ContentType = "application/json; charset=utf-8"
|
||||
val User = """{"email":"john.doe@email.com", "password":"123"}"""
|
||||
val Client = """{"type":"device", "name":"weio"}"""
|
||||
val Channel = """{"name":"mychan"}"""
|
||||
val Message = """[{"bn":"some-base-name:","bt":1.276020076001e+09, "bu":"A","bver":5, "n":"voltage","u":"V","v":120.1}, {"n":"current","t":-5,"v":1.2}, {"n":"current","t":-4,"v":1.3}]"""
|
||||
}
|
@ -41,3 +41,4 @@ pages:
|
||||
- License: LICENSE.txt
|
||||
- Architecture: architecture.md
|
||||
- Getting started: getting-started.md
|
||||
- Load test: load-test.md
|
||||
|
@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
const version string = "0.2.1"
|
||||
const version string = "0.2.2"
|
||||
|
||||
type response struct {
|
||||
Version string
|
||||
|
Loading…
x
Reference in New Issue
Block a user