2

Looking at the signature of throttle in Akka Streams I see that it can take a cost function of type (Out) ⇒ Int to compute how the element passing through the stage affects the cost/per speed limit of elements sent downstream.

Is it possible to implement throttling based on external input for the stream with a built-in stage? If not, would it be easier just to implement a custom stage?

I'm expecting something like:

def throttle(cost: Int, per: FiniteDuration, maximumBurst: Int, costCalculation: ⇒ Int, mode: ThrottleMode): Repr[Out]

So that we could do something like:

throttle(75 /** 75% CPU **/, 1.second, 80 /** 80% CPU **/, getSinkCPU, ThrottleMode.Shaping) 
Samuel Heaney
  • 773
  • 1
  • 8
  • 14

1 Answers1

0

I simulate a throttle using Akka-http + Akka-stream when uploading multiple files. As far as I understood, the cost function is based on messages and not on CPU usage. maybe you can derive this to CPU usage. Here I have the code that I use a cost function based on the number of messages:

import akka.Done import akka.actor.ActorSystem import akka.http.scaladsl.Http import akka.http.scaladsl.model.{ContentTypes, HttpEntity, Multipart} import akka.http.scaladsl.server.Directives._ import akka.stream.ThrottleMode import akka.stream.scaladsl.{FileIO, Sink, Source} import akka.util.ByteString

import java.io.File import scala.concurrent.Future import scala.concurrent.duration._ import scala.util.{Failure, Success}

object UploadingFiles { def main(args: Array[String]): Unit = { implicit val system = ActorSystem("UploadingFiles")

val NO_OF_MESSAGES = 1
val filesRoutes = {
  (pathEndOrSingleSlash & get) {
    complete(
      HttpEntity(
        ContentTypes.`text/html(UTF-8)`,
        """
          |<html>
          |  <body>
          |    <form action="http://localhost:8080/upload" method="post" enctype="multipart/form-data">
          |      <input type="file" name="myFile" multiple>
          |      <button type="submit">Upload</button>
          |    </form>
          |  </body>
          |</html>
      """.stripMargin
      )
    )
  } ~ (path("upload") & post & extractLog) { log =>
    // handling uploading files using multipart/form-data
    entity(as[Multipart.FormData]) { formData =>
      // handle file payload
      val partsSource: Source[Multipart.FormData.BodyPart, Any] = formData.parts
      val filePartsSink: Sink[Multipart.FormData.BodyPart, Future[Done]] =
        Sink.foreach[Multipart.FormData.BodyPart] { bodyPart =>
          if (bodyPart.name == "myFile") {
            // create a file
            val filename = "download/" + bodyPart.filename.getOrElse("tempFile_" + System.currentTimeMillis())
            val file = new File(filename)

            log.info(s"writing to file: $filename")
            val fileContentsSource: Source[ByteString, _] = bodyPart.entity.dataBytes
            val fileContentsSink: Sink[ByteString, _] = FileIO.toPath(file.toPath)

            val publishRate = NO_OF_MESSAGES / 1
            // writing the data to the file using akka-stream graph
            // Throttling with a cost function 
            fileContentsSource
              .throttle(publishRate, 2 seconds, publishRate, ThrottleMode.shaping)
              .runWith(fileContentsSink)
          }
        }
      val writeOperationFuture = partsSource.runWith(filePartsSink)
      onComplete(writeOperationFuture) {
        case Success(value) => complete("file uploaded =)")
        case Failure(exception) => complete(s"file failed to upload: $exception")
      }
    }
  }
}

println("access the browser at: localhost:8080")
Http()
  .newServerAt("localhost", 8080)
  .bindFlow(filesRoutes)

} }

Felipe
  • 7,013
  • 8
  • 44
  • 102