From 88902046ceb2ac6c89321cf1e653a1a2a55b0eb2 Mon Sep 17 00:00:00 2001 From: Laplie Anderson Date: Thu, 7 Nov 2019 16:49:58 -0500 Subject: [PATCH] simple load generator tool --- LICENSE-3rdparty.csv | 1 + dd-java-agent/load-generator/Readme.md | 22 ++++ .../load-generator/load-generator.gradle | 18 +++ .../datadog/loadgenerator/LoadGenerator.java | 124 ++++++++++++++++++ settings.gradle | 1 + 5 files changed, 166 insertions(+) create mode 100644 dd-java-agent/load-generator/Readme.md create mode 100644 dd-java-agent/load-generator/load-generator.gradle create mode 100644 dd-java-agent/load-generator/src/main/java/datadog/loadgenerator/LoadGenerator.java diff --git a/LICENSE-3rdparty.csv b/LICENSE-3rdparty.csv index 2cd62aec64..664fef01a1 100644 --- a/LICENSE-3rdparty.csv +++ b/LICENSE-3rdparty.csv @@ -9,3 +9,4 @@ logback.xml,net.logstash.logback,Apache-2.0, import (test),org.junit,EPL-1.0,Copyright © 2002-2017 JUnit. All Rights Reserved. import (test),org.assertj,Apache-2.0,Copyright 2012-2017 the original author or authors. import (test),org.mockito,MIT,Copyright (c) 2007 Mockito contributors +import (test),info.picocli,Apache-2.0, diff --git a/dd-java-agent/load-generator/Readme.md b/dd-java-agent/load-generator/Readme.md new file mode 100644 index 0000000000..e4be2794c0 --- /dev/null +++ b/dd-java-agent/load-generator/Readme.md @@ -0,0 +1,22 @@ +Load Generator +===== + +Generates a simulated trace load. Run with + +``` +./gradlew launch --args='--rate 10' +``` + +**OR** + +``` +./gradlew :dd-java-agent:load-generator:launch --args='--rate 10' +``` + +from the root of the repo. + +To print all options, use: + +``` +--args='--help' +``` diff --git a/dd-java-agent/load-generator/load-generator.gradle b/dd-java-agent/load-generator/load-generator.gradle new file mode 100644 index 0000000000..853ec6fbad --- /dev/null +++ b/dd-java-agent/load-generator/load-generator.gradle @@ -0,0 +1,18 @@ +apply from: "${rootDir}/gradle/java.gradle" + +dependencies { + compile project(':dd-trace-api') + compile project(':dd-trace-ot') + + compile 'info.picocli:picocli:4.0.4' + compile deps.guava +} + +task launch(type: JavaExec) { + classpath = sourceSets.main.runtimeClasspath + main = 'datadog.loadgenerator.LoadGenerator' + jvmArgs = ["-javaagent:${project(':dd-java-agent').shadowJar.archivePath}", "-Ddd.service.name=loadtest"] + systemProperties System.properties + + dependsOn project(':dd-java-agent').shadowJar +} diff --git a/dd-java-agent/load-generator/src/main/java/datadog/loadgenerator/LoadGenerator.java b/dd-java-agent/load-generator/src/main/java/datadog/loadgenerator/LoadGenerator.java new file mode 100644 index 0000000000..bc52e4c11c --- /dev/null +++ b/dd-java-agent/load-generator/src/main/java/datadog/loadgenerator/LoadGenerator.java @@ -0,0 +1,124 @@ +package datadog.loadgenerator; + +import com.google.common.util.concurrent.RateLimiter; +import io.opentracing.Scope; +import io.opentracing.Span; +import io.opentracing.Tracer; +import io.opentracing.util.GlobalTracer; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import picocli.CommandLine; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +@Command( + mixinStandardHelpOptions = true, + description = "Generates traces and spans at a specified rate") +public class LoadGenerator implements Callable { + @Option(names = "--rate", required = true, description = "rate, per second, to generate traces") + private int rate; + + @Option( + names = "--threads", + defaultValue = "6", + description = "Number of trace-generating threads (default: ${DEFAULT-VALUE})") + private int threads; + + @Option( + names = "--width", + defaultValue = "2", + description = "Number of spans directly below the root (default: ${DEFAULT-VALUE})") + private int width; + + @Option( + names = "--depth", + defaultValue = "3", + description = "Total spans deep per trace, including parent (default: ${DEFAULT-VALUE})") + private int depth; + + @Option( + names = "--warmup", + defaultValue = "5", + description = "Time, in seconds, to ramp up to target rate (default: ${DEFAULT-VALUE})") + private int warmupPeriod; + + @Option( + names = "--print-interval", + defaultValue = "20", + description = "Interval, in seconds, to print statistics (default: ${DEFAULT-VALUE})") + private int printInterval; + + private RateLimiter rateLimiter; + private final AtomicLong tracesSent = new AtomicLong(); + + @Override + public Integer call() throws Exception { + rateLimiter = RateLimiter.create(rate, warmupPeriod, TimeUnit.SECONDS); + + long intervalStart = System.currentTimeMillis(); + long tracesAtLastReport = 0; + + for (int i = 0; i < threads; i++) { + final Thread workerThread = new Thread(new Worker(), "Worker-" + i); + workerThread.setDaemon(true); + workerThread.start(); + } + + while (true) { + Thread.sleep(printInterval * 1000); + + final long currentTracesSent = tracesSent.get(); + final long intervalEnd = System.currentTimeMillis(); + + final double currentRate = + (currentTracesSent - tracesAtLastReport) / ((intervalEnd - intervalStart) / 1000d); + + System.out.println( + "Total Traces Sent: " + currentTracesSent + ", Rate this interval: " + currentRate); + intervalStart = System.currentTimeMillis(); + tracesAtLastReport = currentTracesSent; + } + } + + public static void main(final String[] args) { + final int exitCode = new CommandLine(new LoadGenerator()).execute(args); + System.exit(exitCode); + } + + private class Worker implements Runnable { + + @Override + public void run() { + final Tracer tracer = GlobalTracer.get(); + + while (true) { + rateLimiter.acquire(); + final Span parent = tracer.buildSpan("parentSpan").start(); + + try (final Scope scope = tracer.activateSpan(parent)) { + for (int i = 0; i < width; i++) { + final Span widthSpan = tracer.buildSpan("span-" + i).start(); + try (final Scope widthScope = tracer.activateSpan(widthSpan)) { + for (int j = 0; j < depth - 2; j++) { + final Span depthSpan = tracer.buildSpan("span-" + i + "-" + j).start(); + try (final Scope depthScope = tracer.activateSpan(depthSpan)) { + // do nothing. Maybe sleep? but that will mean we need more threads to keep the + // effective rate + } finally { + depthSpan.finish(); + } + } + } finally { + widthSpan.finish(); + } + } + } finally { + parent.finish(); + } + + tracesSent.getAndIncrement(); + } + } + } +} diff --git a/settings.gradle b/settings.gradle index df7aa8e6a4..3e7e852bcd 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,6 +9,7 @@ include ':dd-java-agent' include ':dd-java-agent:agent-bootstrap' include ':dd-java-agent:agent-tooling' include ':dd-java-agent:agent-jmxfetch' +include ':dd-java-agent:load-generator' // misc include ':dd-java-agent:testing'