Name: iago
Owner: Twitter, Inc.
Description: A load generator, built for engineers
Created: 2012-06-22 18:44:42.0
Updated: 2018-01-17 13:07:53.0
Pushed: 2016-08-01 06:40:07.0
Homepage: http://twitter.github.com/iago/
Size: 1537
Language: Scala
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
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.
Download and unpack the Iago distribution. We support Scala 2.10 and recommend you clone the latest master: master.
Read the documentation.
RecordProcessor
or ThriftRecordProcessor
class, or in Java, extend LoadTest
or ThriftLoadTest
; see Implementing Your Test for more information.launcher.scala
file in your Iago config
directory with the appropriate settings; see Configuring Your Test for more information.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:
% mvn package -DskipTests % mkdir tmp; cd tmp % unzip ../target/iago-version-package-dist.zip % java -jar iago-version.jar -f config/my_config.scala
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.
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.
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.
Your service is typically an HTTP or Thrift service written in either Scala or Java.
For replay, Iago recommends you scrub your logs to only include requests which meet the following requirements:
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.Transactions typically come from logs, such as the following:
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.
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
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.
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.
To implement a load test in Scala, you must extend the Iago server's RecordProcessor
class to specify how to map transactions into the requests that the Iago server delivers to your service. The following example shows a RecordProcessor
subclass that implements a load test on an EchoService
HTTP service:
age com.twitter.example
rt org.apache.thrift.protocol.TBinaryProtocol
rt com.twitter.parrot.processor.RecordProcessor // 1
rt com.twitter.parrot.thrift.ParrotJob // 2
rt com.twitter.parrot.server.{ParrotRequest,ParrotService} // 3
rt com.twitter.logging.Logger
rt org.jboss.netty.handler.codec.http.HttpResponse
rt thrift.EchoService
s EchoLoadTest(parrotService: ParrotService[ParrotRequest, HttpResponse]) extends RecordProcessor {
l client = new EchoService.ServiceToClient(service, new TBinaryProtocol.Factory()) // 4
l log = Logger.get(getClass)
f 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
}
}
To implement a Thrift load test in Scala, you must extend the Iago server's Thrift RecordProcessor
class to specify how to map transactions into the requests that the Iago server delivers to your service. The following example shows a ThriftRecordProcessor
subclass that implements a load test on an EchoService
Thrift service:
age com.twitter.example
rt org.apache.thrift.protocol.TBinaryProtocol
rt com.twitter.parrot.processor.ThriftRecordProcessor // 1
rt com.twitter.parrot.thrift.ParrotJob // 2
rt com.twitter.parrot.server.{ParrotRequest,ParrotService} // 3
rt com.twitter.logging.Logger
rt thrift.EchoService
s EchoLoadTest(parrotService: ParrotService[ParrotRequest, Array[Byte]]) extends ThriftRecordProcessor(parrotService) {
l client = new EchoService.ServiceToClient(service, new TBinaryProtocol.Factory()) // 4
l log = Logger.get(getClass)
f 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
}
}
To implement a load test in Java, you must extend the Iago server's LoadTest
class to specify how to map transactions into the requests that the Iago server delivers to your service. The LoadTest
class provides Java-friendly type mappings for the underlying Scala internals. The following example shows a LoadTest
subclass that implements a load test on an EchoService
HTTP service:
age com.twitter.jexample;
rt com.twitter.example.thrift.EchoService;
rt com.twitter.parrot.processor.LoadTest; // 1
rt com.twitter.parrot.thrift.ParrotJob; // 2
rt com.twitter.parrot.server.ParrotRequest; // 3
rt com.twitter.parrot.server.ParrotService; // 3
rt com.twitter.util.Future;
rt com.twitter.util.FutureEventListener;
rt org.apache.thrift.protocol.TBinaryProtocol;
rt org.jboss.netty.handler.codec.http.HttpResponse;
rt java.util.List;
ic class EchoLoadTest extends LoadTest {
hoService.ServiceToClient client = null;
blic EchoLoadTest(ParrotService<ParrotRequest, HttpResponse> parrotService) {
super(parrotService);
client = new EchoService.ServiceToClient(service(), new TBinaryProtocol.Factory()); // 4
blic 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);
}
});
}
To implement a Thrift load test in Java, you must extend the Iago server's ThriftLoadTest
class to specify how to map transactions into the requests that the Iago server delivers to your service. The ThriftLoadTest
class provides Java-friendly type mappings for the underlying Scala internals. The following example shows a ThriftLoadTest
subclass that implements a load test on an EchoService
Thrift service:
age com.twitter.jexample;
rt com.twitter.example.thrift.EchoService;
rt com.twitter.parrot.processor.ThriftLoadTest; // 1
rt com.twitter.parrot.thrift.ParrotJob; // 2
rt com.twitter.parrot.server.ParrotRequest; // 3
rt com.twitter.parrot.server.ParrotService; // 3
rt com.twitter.util.Future;
rt com.twitter.util.FutureEventListener;
rt org.apache.thrift.protocol.TBinaryProtocol;
rt java.util.List;
ic class EchoLoadTest extends ThriftLoadTest {
hoService.ServiceToClient client = null;
blic EchoLoadTest(ParrotService<ParrotRequest, byte[]> parrotService) {
super(parrotService);
client = new EchoService.ServiceToClient(service(), new TBinaryProtocol.Factory()); // 4
blic 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);
}
});
}
You define your Iago subclass to execute your service and map transactions to requests for your service:
com.twitter.parrot.processor.RecordProcessor
(Scala) or LoadTest
(Java), whose instance will be executed by a Iago server.com.twitter.parrot.thrift.ParrotJob
, which contains the Iago server class.com.twitter.parrot.server.ParrotService
and com.twitter.parrot.server.ParrotRequest
processLines
method to format the request and and execute your service.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:
rt com.twitter.parrot.config.ParrotLauncherConfig
ParrotLauncherConfig {
stDir = "."
bName = "load_echo"
rt = 8080
ctims = "localhost"
g = "logs/yesterday.log"
questRate = 1
mInstances = 1
ration = 5
meUnit = "MINUTES" // affects duration; does not affect requestRate
ports = "import com.twitter.example.EchoLoadTest"
sponseType = "Array[Byte]"
ansport = "ThriftTransportFactory(this)"
adTest = "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:
Parameter | Description | Required or Default Value |
---|---|---|
customLogSource | A string with Scala code that will be put into the
if(inputLog.endsWith(“.lzo”)) {
}““”
| |
jobName |
A string value that specifies the the name of your test. This is used for two things:
| |
requestTimeoutInMs |
|
30000 // 30 seconds |
The point of Iago is to load-test a service. Iago calls these “victims”.
Victims may be a
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.
Parameter | Description | Required or Default Value |
---|---|---|
victims |
A list of host:port pairs: victims=“example.com:80 example2.com:80”
A zookeeper server set: victims=“/some/zookeeper/path”
|
Required |
victimClusterType |
When victimClusterType is "static", we set victims and port. victims can be a single host name, a host:port pair, or a list of host:port pairs separated with commas or spaces. When victimClusterType is "sdzk" (which stands for "service discovery zookeeper") the victim is considered to be a server set, referenced with victims, victimZk, and victimZkPort. |
Default: “static” |
victimZk |
the host name of the zookeeper where your serverset is registered |
Default is |
victimZkPort |
The port of the zookeeper where your serverset is registered |
Default: |
Alternative Use: You can specify the following extension point parameters to configure projects in which Iago is used as both a feeder and server. The Iago feeder provides the log lines to your project, which uses these log lines to form requests that the Iago server then handles:
Parameter | Description | Required or Default Value |
---|---|---|
imports |
Imports from this project to Iago Example: If |
import org.jboss.netty.handler.codec.http.HttpResponse |
requestType |
The request type of requests from Iago. Examples:
|
ParrotRequest |
responseType |
The response type of responses from Iago. Examples:
|
HttpResponse |
transport |
The kind of transport to the server, which matches the Example: The Thrift Transport will send your request and give back |
FinagleTransport |
loadTest |
Your processor for the Iago feeder's lines, which converts the lines into requests and sends them to the Iago server. Example: |
new LoadTestStub(service.get) |
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:
Parameter | Description | Required or Default Value |
---|---|---|
batchSize |
how many messages the parrot feeder sends at one time to the parrot server. For large messages, setting this to 1 is recommended. |
Default: 1000 |
cachedSeconds |
How many seconds worth of data the parrot server will attempt to cache. Setting this to 1 for large messages is recommended. The consequence is that, if the parrot feeder garbage-collects, there will be a corresponding pause in traffic to your service unless cachedSeconds is set to a value larger than a typical feeder gc. This author has never observed a feeder gc exceeding a fraction of a second. |
Default is |
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.
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
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:
Statistic | Description |
---|---|
connection_duration |
duration of a connection from established to closed? |
connection_received_bytes |
bytes received per connection |
connection_requests |
Number of connection requests that your client did, ie. you can have a pool of 1 connection and the connection can be closed 3 times, so the "connection_requests" would be 4 (even if connections = 1) |
connection_sent_bytes |
bytes send per connection |
connections |
is the current number of connections between client and server |
handletime_us |
time to process the response from the server (ie. execute all the chained map/flatMap) |
pending |
Number of pending requests (ie. requests without responses) |
request_concurrency |
is the current number of connections being processed by finagle |
request_latency_ms |
the time of everything between request/response. |
request_queue_size |
Number of requests waiting to be handled by the server |
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
Parrot works with Zipkin, a distributed tracing system.
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.
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:
parrot/util:
You may also find the LogSource and RequestProcessor interfaces discussed earlier useful.
Examples:
// Make 1000 HTTP requests at a roughly constant rate of 10/sec // construct the transport and queue val client = ClientBuilder() .codec(http()) .hosts("twitter.com:80") .build() val transport = new FinagleTransport(FinagleService(client)) val consumer = new RequestConsumer(() => new PoissionProcess(10) // add 1000 requests to the queue for (i <- (1 to 1000)) { consumer.offer(new ParrotRequest(uri= Uri("/jack/status/20", Nil)) } // start sending transport.start() consumer.start() // wait for the comsumer to exhaust the queue while(consumer.size > 0) { Thread.sleep(100) } // shutdown consumer.shutdown() transport.close()
// Call a thrift service with a sinusoidally varying rate // Configure cluster for the service using zookeeper val zk = "zookeeper.example.com" val zkPort = 2181 val path = "my/env/role/service" val zookeeperClient = new ZooKeeperClient(Amount.of(1, Time.SECONDS), Seq(InetSocketAddress.createUnresolved(zk, zkPort)).asJava) val serverSet = new ServerSetImpl(zookeeperClient, path) val cluster = new ZookeeperServerSetCluster(serverSet) // create transport and queue val client = ClientBuilder() .codec(ThriftClientFramedCodec) .cluster(cluster) .build() val transport = new ThriftTransport(client) val createDistribution = () => new SinusoidalPoisionProccess(10, 20, 60.seconds) val queue = new RequestQueue(new RequestConsumer(createDistribution, transport), transport) // create the service and processor val service = transport.createService(queue) val processor = new EchoLoadTest(service) // start sending transport.start() consumer.start() // Fill the queue from a logfile val source = new LogSourceImpl("some_file.txt") while (source.hasNext) { processor.processLines(Seq(source.next)) } // wait for the comsumer to exhaust the queue while(consumer.size > 0) { Thread.sleep(100) } // shutdown consumer.shutdown() transport.close()
2013-06-25 release 0.6.7
Iago is open source, hosted on Github here. If you have a contribution to make, please fork the repo and submit a pull request.