All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twitter.finagle.load.LoadGenerator.scala Maven / Gradle / Ivy

package com.twitter.finagle.load

import com.twitter.finagle.{Service, SimpleFilter}
import com.twitter.util.{Duration, Future, MockTimer, Promise, Time, TimeControl, Try}
import com.twitter.conversions.time.intToTimeableNumber
import scala.collection.immutable.SortedSet

class LoadGenerator[Req, Rep](
  history: TraversableOnce[Event[Req, Rep]],
  recorder: (Duration, Future[Rep]) => Unit,
  filter: SimpleFilter[Event[Req, Rep], Rep],
  timer: MockTimer = new MockTimer()
) {
  val svc = filter andThen Service.mk[Event[Req, Rep], Rep] { evt: Event[Req, Rep] =>
    val p = Promise[Rep]()
    timer.schedule(evt.finish) {
      p.updateIfEmpty(evt())
    }
    p
  }


  def forward(dur: Duration, ctl: TimeControl) {
    ctl.advance(dur - 1.nanosecond)
    timer.tick()
    ctl.advance(1.nanosecond)
    timer.tick()
  }

  /**
   * Makes a request to a filtered service that starts at Event.start and lasts for Event.length,
   * and at the end, fulfils the response with the result of Event.fn(Event.length).
   *
   * Every time a request is made, it is recorded with the latency and passed to recorder.
   */
  def execute(): Unit = {
    var cur = Time.fromMilliseconds(0)
    var endTimes = SortedSet[Time]()

    // sets time to one nanosecond before the end of the next request
    // then sets time to the precise nanosecond of the end of the next request
    // this is done to ensure that if there is something affecting the satisfaction
    // of a promise, that it is triggered before the promise is satisfied.
    // sort of a hack
    def removeInterstices(end: Time, ctl: TimeControl) {
      val removees = endTimes.rangeImpl(None, Some(end))
      for (ts <- removees) {
        if (ts - cur > Duration.Zero) {
          forward(ts - cur, ctl)
          cur = ts
        }
      }
      endTimes --= removees
      forward(end - cur, ctl)
      cur = end
    }

    var maxSeen = cur
    Time.withTimeAt(cur) { ctl =>
      history foreach { evt =>
        val diff = (evt.start - cur)
        if (diff > Duration.Zero) {
          removeInterstices(evt.start, ctl)
        }
        cur = evt.start
        endTimes += (evt.finish)
        recorder(evt.length, svc(evt))
        maxSeen = maxSeen max (evt.finish)
      }
      removeInterstices(maxSeen, ctl)
    }
  }
}

object LoadGenerator {
  def mkHistory[Req, Rep](event: () => Event[Req, Rep], num: Int): Iterator[Event[Req, Rep]] =
    new Iterator[Event[Req, Rep]] {
      def hasNext: Boolean = true
      def next(): Event[Req, Rep] = event()
    }.take(num)

  def mk[Req, Rep](
    start: Time,
    interval: Duration,
    mkEvent: Time => Event[Req, Rep],
    num: Int
  ): Iterator[Event[Req, Rep]] = mkInGroups(start, interval, mkEvent, 1, num)

  def mkInGroups[Req, Rep](
    start: Time,
    interval: Duration,
    mkEvent: Time => Event[Req, Rep],
    groupSize: Int,
    num: Int
  ): Iterator[Event[Req, Rep]] = {
    var cur = start
    var curGroupSize = 0
    mkHistory({ () =>
      val evt = mkEvent(cur)
      curGroupSize += 1
      if (curGroupSize == groupSize) {
        curGroupSize = 0
        cur += interval
      }
      evt
    }, num)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy