blob: 34eeee23fc777a9a0ba527038a4aac4d1c253eee [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.openwhisk
import java.nio.charset.StandardCharsets
import java.util.Base64
import org.apache.openwhisk.extension.whisk.Predef._
import io.gatling.core.Predef._
import io.gatling.core.session.Expression
import io.gatling.core.util.Resource
import org.apache.commons.io.FileUtils
import scala.concurrent.duration._
class LatencySimulation extends Simulation {
// Specify parameters for the run
val host = sys.env("OPENWHISK_HOST")
// Specify authentication
val Array(uuid, key) = sys.env("API_KEY").split(":")
val pauseBetweenInvokes: Int = sys.env.getOrElse("PAUSE_BETWEEN_INVOKES", "0").toInt
val MEAN_RESPONSE_TIME = "MEAN_RESPONSE_TIME"
val MAX_MEAN_RESPONSE_TIME = "MAX_MEAN_RESPONSE_TIME"
val MAX_ERRORS_ALLOWED = "MAX_ERRORS_ALLOWED"
val MAX_ERRORS_ALLOWED_PERCENTAGE = "MAX_ERRORS_ALLOWED_PERCENTAGE"
// Specify thresholds
val meanResponseTime: Int = sys.env(MEAN_RESPONSE_TIME).toInt
val maximalMeanResponseTime: Int = sys.env.getOrElse(MAX_MEAN_RESPONSE_TIME, meanResponseTime.toString).toInt
val maxErrorsAllowed: Int = sys.env.getOrElse(MAX_ERRORS_ALLOWED, "0").toInt
val maxErrorsAllowedPercentage: Double = sys.env.getOrElse(MAX_ERRORS_ALLOWED_PERCENTAGE, "0.1").toDouble
def toKindSpecificKey(kind: String, suffix: String) = kind.split(':').head.toUpperCase + "_" + suffix
// Exclude runtimes
val excludedKinds: Seq[String] = sys.env.getOrElse("EXCLUDED_KINDS", "").split(",")
// Generate the OpenWhiskProtocol
val openWhiskProtocol = openWhisk.apiHost(host)
/**
* Generate a list of actions to execute. The list is a tuple of (kind, code, actionName, main)
* `kind` is needed to create the action
* `code` is loaded form the files located in `resources/data`
* `actionName` is the name of the action in OpenWhisk
* `main` is only needed for java. This is the name of the class where the main method is located.
*/
val actions: Seq[(String, String, String, String)] = Map(
"nodejs:default" -> (FileUtils
.readFileToString(Resource.body("nodeJSAction.js").get.file, StandardCharsets.UTF_8), "latencyTest_node", ""),
"python:default" -> (FileUtils
.readFileToString(Resource.body("pythonAction.py").get.file, StandardCharsets.UTF_8), "latencyTest_python", ""),
"swift:default" -> (FileUtils
.readFileToString(Resource.body("swiftAction.swift").get.file, StandardCharsets.UTF_8), "latencyTest_swift", ""),
"java:default" -> (Base64.getEncoder.encodeToString(
FileUtils.readFileToByteArray(Resource.body("javaAction.jar").get.file)), "latencyTest_java", "JavaAction"))
.filterNot(e => excludedKinds.contains(e._1))
.map {
case (kind, (code, name, main)) =>
(kind, code, name, main)
}
.toSeq
// Define scenario
val test = scenario("Invoke one action after each other to test latency")
.foreach(actions, "action") {
val code: Expression[String] = "${action._2}"
exec(
openWhisk("Create ${action._1} action")
.authenticate(uuid, key)
.action("${action._3}")
.create(code, "${action._1}", "${action._4}"))
.exec(openWhisk("Cold ${action._1} invocation").authenticate(uuid, key).action("${action._3}").invoke())
.repeat(100) {
// Add a pause of 100 milliseconds. Reason for this pause is, that collecting of logs runs asynchronously in
// invoker. If this is not finished before the next request arrives, a new cold-start has to be done.
pause(pauseBetweenInvokes.milliseconds)
.exec(openWhisk("Warm ${action._1} invocation").authenticate(uuid, key).action("${action._3}").invoke())
}
.exec(openWhisk("Delete ${action._1} action").authenticate(uuid, key).action("${action._3}").delete())
}
val testSetup = setUp(test.inject(atOnceUsers(1)))
.protocols(openWhiskProtocol)
actions
.map { case (kind, _, _, _) => kind }
.foldLeft(testSetup) { (agg, kind) =>
val cur = s"Warm $kind invocation"
// One failure will make the build yellow
val specificMeanResponseTime: Int =
sys.env.getOrElse(toKindSpecificKey(kind, MEAN_RESPONSE_TIME), meanResponseTime.toString).toInt
val specificMaxMeanResponseTime =
sys.env.getOrElse(toKindSpecificKey(kind, MAX_MEAN_RESPONSE_TIME), maximalMeanResponseTime.toString).toInt
val specificMaxErrorsAllowed =
sys.env.getOrElse(toKindSpecificKey(kind, MAX_ERRORS_ALLOWED), maxErrorsAllowed.toString).toInt
val specificMaxErrorsAllowedPercentage = sys.env
.getOrElse(toKindSpecificKey(kind, MAX_ERRORS_ALLOWED_PERCENTAGE), maxErrorsAllowedPercentage.toString)
.toDouble
agg
.assertions(details(cur).responseTime.mean.lte(specificMeanResponseTime))
.assertions(details(cur).responseTime.mean.lt(specificMaxMeanResponseTime))
// Mark the build yellow, if there are failed requests. And red if both conditions fail.
.assertions(details(cur).failedRequests.count.lte(specificMaxErrorsAllowed))
.assertions(details(cur).failedRequests.percent.lte(specificMaxErrorsAllowedPercentage))
}
}