Iago

一个为工程师建造的负载生成器。「A load generator, built for engineers」

Github stars Tracking Chart

Iago, A Load Generator

Build Status

  • Iago Quick Start
    • Iago Prerequisites
    • Preparing Your Test
    • Executing Your Test
  • Iago Overview
    • Supported Services
    • Transaction Requirements
    • Sources of Transactions
  • Iago Architecture Overview
  • Implementing Your Test
    • Scala Example
    • Scala Thrift Example
    • Java Example
    • Java Thrift Example
    • Code Annotations for the Examples
  • Configuring Your Test
    • Specifying Victims
    • Extension Point Parameters
    • Sending Large Messages
  • Weighted Requests
  • Metrics
  • Tracing
  • What Files Are Created?
  • ChangeLog
  • Contributing to Iago

Iago Quick Start

Please join iago-users@googlegroups.com for updates and to ask questions.

If you are already familiar with the Iago Load Generation tool, follow these steps to get started; otherwise, start with the Iago Overview and perhaps Iago Philosophy, also known as "Why Iago?". For questions, please contact iago-users@googlegroups.com.

Iago Prerequisites

  1. Download and unpack the Iago distribution.
    We support Scala 2.10 and recommend you clone the latest master: master.

  2. Read the documentation.

Preparing Your Test

  1. Identify your transaction source; see Transaction Requirements and Sources of Transactions for more information.
  2. In Scala, extend the Iago server's RecordProcessor or ThriftRecordProcessor class, or in Java, extend LoadTest or ThriftLoadTest; see Implementing Your Test for more information.
  3. Create a launcher.scala file in your Iago config directory with the appropriate settings; see Configuring Your Test for more information.

Executing Your Test

Launch Iago from the distribution with java -jar iago_jar -f your_config. This will create the Iago processes for you and configure it to use your transactions. To kill a running job, add -k to your launch parameters: java -jar iago_jar -f your_config -k.

If you launch your Iago job on your local machine and an old Iago job is still running, it probably won't get far: it will attempt to re-use a port and fail. You want to kill the running job, as described above.

If you build via Maven, then you might wonder "How do I launch Iago 'from the distribution'?" The steps are:

Don't assume that you can skip the package/unzip steps if you're just changing a config file. You need to re-package and unzip again.

If you are using Iago as a library, for example, in the case of testing over the Thrift protocol or building more complex tests with HTTP or Memcached/Kestrel, you should instead add a task to your project's configuration. See Configuring Your Test for more information.

Top

Iago Overview

Iago is a load generation tool that replays production or synthetic traffic against a given target. Among other things, it differs from other load generation tools in that it attempts to hold constant the transaction rate. For example, if you want to test your service at 100K requests per minute, Iago attempts to achieve that rate.

Because Iago replays traffic, you must specify the source of the traffic. You use a transaction log as the source of traffic, in which each transaction generates a request to your service that your service processes.

Replaying transactions at a fixed rate enables you to study the behavior of your service under an anticipated load. Iago also allows you to identify bottlenecks or other issues that may not be easily observable in a production environment in which your maximum anticipated load occurs only rarely.

Top

Supported Services

Iago can generate service requests that travel the net in different ways and are in different formats. The code that does this is in a Transport, a class that extends ParrotTransport. Iago comes with several Transports already defined. When you configure your test, you will need to set some parameters; to understand which of those parameters are used and how they are used, you probably want to look at the source code for your test's Transport class.

  • HTTP: Use FinagleTransport
  • Thrift: Use ThriftTransport
  • Memcached: Use MemcacheTransport
  • Kestrel: Use KestrelTransport
  • UDP: Use ParrotUdpTransport

Your service is typically an HTTP or Thrift service written in either Scala or Java.

Top

Transaction Requirements

For replay, Iago recommends you scrub your logs to only include requests which meet the following requirements:

  • Idempotent, meaning that re-execution of a transaction any number of times yields the same result as the initial execution.
  • Commutative, meaning that transaction order is not important. Although transactions are initiated in replay order, Iago's internal behavior may change the actual execution order to guarantee the transaction rate. Also, transactions that implement Future responses are executed asynchronously. You can achieve ordering, if required, by using Iago as a library and initiating new requests in response to previous ones. Examples of this are available.

Top

Sources of Transactions

Transactions typically come from logs, such as the following:

  • Web server logs capture HTTP transactions.
  • Proxy server logs can capture transactions coming through a server. You can place a proxy server in your stack to capture either HTTP or Thrift transactions.
  • Network sniffers can capture transactions as they come across a physical wire. You can program the sniffer to create a log of transactions you identify for capture.

In some cases, transactions do not exist. For example, transactions for your service may not yet exist because they are part of a new service, or you are obligated not to use transactions that contain sensitive information. In such cases, you can provide synthetic transactions, which are transactions that you create to model the operating environment for your service. When you create synthetic transactions, you must statistically distribute your transactions to match the distribution you expect when your service goes live.

Top

Iago Architecture Overview

Iago consists of feeders and servers. A feeder reads your transaction source. A server formats and delivers requests to the service you want to test. The feeder contains a Poller object, which is responsible for guaranteeing cachedSeconds worth of transactions in the pipeline to the Iago servers.

Metrics are available in logs and in graphs as described in Metrics.

The Iago servers generate requests to your service. Together, all Iago servers generate the specified number of requests per minute. A Iago server's RecordProcessor object executes your service and maps the transaction to the format required by your service.

The feeder polls its servers to see how much data they need to maintain cachedSeconds worth of data. That is how we can have many feeders that need not coordinate with each other.

Ensuring that we go through every last message is important when we are writing traffic summaries in the record processor, especially when the data set is small. The parrot feeder shuts down due to running out of time, running out of data, or both. When the feeder runs out of data we

  • make sure that all the data in parrot feeder's internal queues are sent to the parrot server
  • make sure all the data held in the parrot servers cache is sent
  • wait until we get a response for all pending messages or until the reads time out

When the parrot feeder runs out of time (the duration configuration) the data in the feeder's internal queues are ignored, otherwise the same process as above occurs.

Top

Implementing Your Test

The following sections show examples of implementing your test in both Scala and Java. See Code Annotations for the Examples for information about either example.

Top

Scala Example

package com.twitter.example

import org.apache.thrift.protocol.TBinaryProtocol

import com.twitter.parrot.processor.RecordProcessor                                     // 1
import com.twitter.parrot.thrift.ParrotJob                                              // 2
import com.twitter.parrot.server.{ParrotRequest,ParrotService}                          // 3
import com.twitter.logging.Logger
import org.jboss.netty.handler.codec.http.HttpResponse

import thrift.EchoService

class EchoLoadTest(parrotService: ParrotService[ParrotRequest, HttpResponse]) extends RecordProcessor {
  val client = new EchoService.ServiceToClient(service, new TBinaryProtocol.Factory())  // 4
  val log = Logger.get(getClass)

  def processLines(job: ParrotJob, lines: Seq[String]) {                                // 5
    lines map { line =>
      client.echo(line) respond { rep =>
        if (rep == "hello") {
          client.echo("IT'S TALKING TO US")                                             // 6
        }
        log.info("response: " + rep)                                                    // 7
      }
    }
  }
}

Top

Scala Thrift Example

package com.twitter.example

import org.apache.thrift.protocol.TBinaryProtocol

import com.twitter.parrot.processor.ThriftRecordProcessor                               // 1
import com.twitter.parrot.thrift.ParrotJob                                              // 2
import com.twitter.parrot.server.{ParrotRequest,ParrotService}                          // 3
import com.twitter.logging.Logger

import thrift.EchoService

class EchoLoadTest(parrotService: ParrotService[ParrotRequest, Array[Byte]]) extends ThriftRecordProcessor(parrotService) {
  val client = new EchoService.ServiceToClient(service, new TBinaryProtocol.Factory())  // 4
  val log = Logger.get(getClass)

  def processLines(job: ParrotJob, lines: Seq[String]) {                                // 5
    lines map { line =>
      client.echo(line) respond { rep =>
        if (rep == "hello") {
          client.echo("IT'S TALKING TO US")                                             // 6
        }
        log.info("response: " + rep)                                                    // 7
      }
    }
  }
}

Top

Java Example

package com.twitter.jexample;

import com.twitter.example.thrift.EchoService;
import com.twitter.parrot.processor.LoadTest;                                           // 1
import com.twitter.parrot.thrift.ParrotJob;                                             // 2
import com.twitter.parrot.server.ParrotRequest;                                         // 3

import com.twitter.parrot.server.ParrotService;                                         // 3
import com.twitter.util.Future;
import com.twitter.util.FutureEventListener;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.jboss.netty.handler.codec.http.HttpResponse;

import java.util.List;

public class EchoLoadTest extends LoadTest {
  EchoService.ServiceToClient client = null;

  public EchoLoadTest(ParrotService<ParrotRequest, HttpResponse> parrotService) {
    super(parrotService);
    client = new EchoService.ServiceToClient(service(), new TBinaryProtocol.Factory()); // 4
  }

  public void processLines(ParrotJob job, List<String> lines) {                         // 5
    for(String line: lines) {
      Future<String> future = client.echo(line);
      future.addEventListener(new FutureEventListener<String>() {
        public void onSuccess(String msg) {
          System.out.println("response: " + msg);
        }

      public void onFailure(Throwable cause) {
        System.out.println("Error: " + cause);
      }
     });
    }
  }
}

Top

Java Thrift Example

package com.twitter.jexample;

import com.twitter.example.thrift.EchoService;
import com.twitter.parrot.processor.ThriftLoadTest;                                     // 1
import com.twitter.parrot.thrift.ParrotJob;                                             // 2
import com.twitter.parrot.server.ParrotRequest;                                         // 3
import com.twitter.parrot.server.ParrotService;                                         // 3
import com.twitter.util.Future;
import com.twitter.util.FutureEventListener;
import org.apache.thrift.protocol.TBinaryProtocol;

import java.util.List;

public class EchoLoadTest extends ThriftLoadTest {
  EchoService.ServiceToClient client = null;

  public EchoLoadTest(ParrotService<ParrotRequest, byte[]> parrotService) {
    super(parrotService);
    client = new EchoService.ServiceToClient(service(), new TBinaryProtocol.Factory()); // 4
  }

  public void processLines(ParrotJob job, List<String> lines) {                         // 5
    for(String line: lines) {
      Future<String> future = client.echo(line);
      future.addEventListener(new FutureEventListener<String>() {
        public void onSuccess(String msg) {
          System.out.println("response: " + msg);
        }

      public void onFailure(Throwable cause) {
        System.out.println("Error: " + cause);
      }
     });
    }
  }
}

Top

Code Annotations for the Examples

You define your Iago subclass to execute your service and map transactions to requests for your service:

  1. Import com.twitter.parrot.processor.RecordProcessor (Scala) or LoadTest (Java), whose instance will be executed by a Iago server.
  2. Import com.twitter.parrot.thrift.ParrotJob, which contains the Iago server class.
  3. Import com.twitter.parrot.server.ParrotService and com.twitter.parrot.server.ParrotRequest
  4. Create an instance of your service to be placed under test. Your service is a client of the Iago service.
  5. Define a processLines method to format the request and and execute your service.
  6. Optionally, you can initiate a new request based on the response to a previous one.
  7. Optionally, do something with the response. In this example, the response is logged.

Top

Configuring Your Test

To configure your test, create a launcher.scala file that that creates a ParrotLauncherConfig instance with the configuration parameters you want to set.

There are several parameters to set. A good one to figure out early is transport; that will in turn help you to find out what, e.g., responseType you need.

The following example shows parameters for testing a Thrift service:

import com.twitter.parrot.config.ParrotLauncherConfig

new ParrotLauncherConfig {
  distDir = "."
  jobName = "load_echo"
  port = 8080
  victims = "localhost"
  log = "logs/yesterday.log"
  requestRate = 1
  numInstances = 1
  duration = 5
  timeUnit = "MINUTES" // affects duration; does not affect requestRate

  imports = "import com.twitter.example.EchoLoadTest"
  responseType = "Array[Byte]"
  transport = "ThriftTransportFactory(this)"
  loadTest = "new EchoLoadTest(service.get)"
}

Note: For a sample configuration file, see config/launcher.scala within the Iago distribution.

You can specify any of the following parameters:

new ParrotLauncherConfig {
...
loggers = new LoggerFactory(
level = Level.DEBUG,
handlers = new ConsoleHandlerConfig()
)
}
Nil

[Specifying Victims]

The point of Iago is to load-test a service. Iago calls these "victims".

Victims may be a

  1. single host:port pair
  2. list of host:port pairs
  3. a zookeeper serverset

Note that ParrotUdpTransport can only handle a single host:port pair. The other transports that come with Iago, being Finagle based, do not have this limitation.

[Extension Point Parameters]

Top

[Sending Large Messages]

By default, the parrot feeder sends a thousand messages at a time to each connected parrot server until the parrot server has twenty seconds worth of data. This is a good strategy when messages are small (less than a kilobyte). When messages are large, the parrot server will run out of memory. Consider an average message size of 100k, then the feeder will be maintaining an output queue for each connected parrot server of 100 million bytes. For the parrot server, consider a request rate of 2000, then 2000 * 20 * 100k = 4 gigabytes (at least). The following parameters help with large messages:

Top

[Weighted Requests]

Some applications must make bulk requests to their service. In other words, a single meta-request in the input log may result in several requests being satisfied by the victim. A weight field to ParrotRequest was added so that the RecordProcessor can set and use that weight to control the send rate in the RequestConsumer. For example, a request for 17 messages would be given a weight of 17 which would cause the RequestConsumer to sample the request distribution 17 times yielding a consistent distribution of load on the victim.

Top

[Metrics]

Iago uses Ostrich to record its metrics. Iago is configured so that a simple graph server is available as long as the parrot server is running. If you are using localMode=true, then the default place for this is

  http://localhost:9994/graph/

One metric of particular interest is

  http://localhost:9994/graph/?g=metric:client/request_latency_ms

Request latency is the time it takes to queue the request for sending until the response is received. See the Finagle User Guide for more about the individual metrics.

Other metrics of interest:

[Raggiana]

Raggiana is a simple standalone Finagle stats viewer.

You can use Raggiana to view the stats log, parrot-server-stats.log, generated by Iago.

You can clone it from

https://github.com/twitter/raggiana

or, just use it directly at

http://twitter.github.io/raggiana

Top

[Tracing]

Parrot works with Zipkin, a distributed tracing system.

Top

[What Files Are Created?]

The Iago launcher creates the following files

config/target/parrot-feeder.scala
config/target/parrot-server.scala
scripts/common.sh
scripts/parrot-feeder.sh
scripts/parrot-server.sh

The Iago feeder creates

parrot-feeder.log
gc-feeder.log

The Iago server creates

parrot-server.log
parrot-server-stats.log
gc-server.log 

The logs are rotated by size. Each individual log can be up to 100 megabytes before being rotated. There are 6 rotations maintained.

The stats log, parrot-server-stats.log, is a minute-by-minute dump of all the statistics (or Metrics) maintained by the Iago server. Each entry is for the time period since
the previous one. That is, all entries in parrot-server-stats.log need to be accumulated to match
the final values reported by http://localhost:9994/stats.txt.

Top

Using Iago as a Library

While Iago provides everything you need to target your API with a large distributed loadtest with just a small log processor,
it also exposes a library of classes for log processing, traffic replay, & load generation. These can be used in your Iago configuration or incorporated in your application as a library.

parrot/server:

  • ParrotRequest: Parrot's internal representation of a request
  • ParrotTransport (FinagleTransport, KestrelTransport, MemcacheTransport, ParrotUdpTransport, ThriftTransport): Interchangeable transport layer for requests to be sent. Parrot contains transport implementations for the following protocols: HTTP (FinagleTransport), Kestrel, Memcache, raw UDP and Thrift.
  • RequestConsumer: Queues ParrotRequests and sends them out on a ParrotTransport at a rate determined by RequestDistribution
  • RequestQueue: A wrapper/control layer for RequestConsumer
  • ParrotService (ParrotThriftService): Enqueues ParrotRequests to a RequestQueue. ParrotThriftService implements finagle's Service interface for use with finagle thrift clients.

parrot/util:

  • RequestDistribution: A function specifying the time to arrival of the next request, used to control the request rate. Instances include
    • UniformDistribution: Sends requests at a uniform rate
    • PoissonProcess: Sends requests at approximatly constant rate randomly varying using a poisson process. This is the default.
    • SinusoidalPoissonProcess: Like PoissonProcess but varying the rate sinusoidally.
    • SlowStartPoissonProcess: Same as PoissonProcess but starting with a gradual ramp from initial rate to final rate. It will then hold steady at the final rate until time runs out.
    • InfiniteRampPoissonProcess: a two staged ramped distribution. Ideal for services that need a warm-up period before ramping up. The rate continues to increase until time runs out.

You may also find the LogSource and RequestProcessor interfaces discussed earlier useful.

Examples:

Top

[ChangeLog]

2013-06-25 release 0.6.7

  • graceful shutdown for small log sources
  • dropped vestigial parser config
  • weighted parrot requests
  • supporting large requests (BlobStore): new configurations cachedSeconds & mesosRamInMb
  • launcher changes: configurable proxy, create config directory if needed, and handle errors better (don't hang)
  • serversets as victims
  • make local logs work with non-local distribution directories
  • kestrel transport transactional get support
  • check generated config files before launch
  • LzoFileLogSource for iago
  • Thrift over TLS
  • traceLevel config

Top

[Contributing to Iago]

Iago is open source, hosted on Github here.
If you have a contribution to make, please fork the repo and submit a pull request.

Main metrics

Overview
Name With Ownertwitter-archive/iago
Primary LanguageScala
Program languageScala (Language Count: 4)
PlatformLinux, Mac, Windows
License:Apache License 2.0
所有者活动
Created At2012-06-22 18:44:42
Pushed At2020-02-11 16:03:57
Last Commit At2015-07-14 11:08:11
Release Count0
用户参与
Stargazers Count1.3k
Watchers Count174
Fork Count140
Commits Count123
Has Issues Enabled
Issues Count13
Issue Open Count3
Pull Requests Count28
Pull Requests Open Count3
Pull Requests Close Count4
项目设置
Has Wiki Enabled
Is Archived
Is Fork
Is Locked
Is Mirror
Is Private