Skip to content

Instantly share code, notes, and snippets.

@sunng87
Last active April 21, 2026 06:35
Show Gist options
  • Select an option

  • Save sunng87/240f1887c823c6881d74899c1fec358a to your computer and use it in GitHub Desktop.

Select an option

Save sunng87/240f1887c823c6881d74899c1fec358a to your computer and use it in GitHub Desktop.
Benchmarking jaeger trace API
## pull image
podman pull ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:latest
## generate 7m trace spans to localhost:4000
podman run --rm telemetrygen:latest \
traces \
--otlp-endpoint host.containers.internal:4000 \
--otlp-http \
--otlp-insecure \
--otlp-http-url-path /v1/otlp/v1/traces \
--otlp-header "x-greptime-pipeline-name=\"greptime_trace_v1\"" \
--otlp-header "x-greptime-trace-table-name=\"opentelemetry_traces2\"" \
--traces 250000 \
--child-spans 6 \
--rate 1000 \
--workers 4 \
--batch-size 500
## run benchmark
jbang TraceQueryBenchmark --table opentelemetry_traces2
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS com.fasterxml.jackson.core:jackson-databind:2.17.0
//DEPS com.squareup.okhttp3:okhttp:4.12.0
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class TraceQueryBenchmark {
static final ObjectMapper MAPPER = new ObjectMapper();
public static void main(String[] args) throws Exception {
String host = "http://localhost:4000";
String tableName = "opentelemetry_traces";
int concurrency = 8;
int poolSize = 1000;
int rounds = 10;
for (int i = 0; i < args.length; i++) {
switch (args[i]) {
case "--host" -> host = args[++i];
case "--table" -> tableName = args[++i];
case "--concurrency" -> concurrency = Integer.parseInt(args[++i]);
case "--pool-size" -> poolSize = Integer.parseInt(args[++i]);
case "--rounds" -> rounds = Integer.parseInt(args[++i]);
default -> {
System.err.println("Unknown option: " + args[i]);
printUsage();
System.exit(1);
}
}
}
int totalQueries = poolSize * rounds;
final String finalHost = host;
final String finalTableName = tableName;
System.out.println("=== Trace Query Benchmark ===");
System.out.printf(" Host: %s%n", finalHost);
System.out.printf(" Table: %s%n", finalTableName);
System.out.printf(" Concurrency: %d%n", concurrency);
System.out.printf(" Trace ID pool size: %d%n", poolSize);
System.out.printf(" Rounds: %d%n", rounds);
System.out.printf(" Total queries: %d%n", totalQueries);
System.out.println();
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.connectionPool(new ConnectionPool(concurrency, 5, TimeUnit.MINUTES))
.build();
System.out.printf("Fetching %d random trace IDs...%n", poolSize);
long fetchStart = System.nanoTime();
List<String> traceIds = fetchTraceIds(client, finalHost, finalTableName, poolSize);
long fetchTime = System.nanoTime() - fetchStart;
System.out.printf("Fetched %d trace IDs in %.2f s%n%n", traceIds.size(), fetchTime / 1e9);
if (traceIds.isEmpty()) {
System.err.println("No trace IDs found. Exiting.");
System.exit(1);
}
ExecutorService executor = Executors.newFixedThreadPool(concurrency);
List<Long> latencies = Collections.synchronizedList(new ArrayList<>(totalQueries));
AtomicInteger completed = new AtomicInteger(0);
AtomicInteger failed = new AtomicInteger(0);
Random random = new Random();
System.out.printf("Warming up...%n");
int warmupQueries = concurrency * 50;
List<CompletableFuture<Void>> warmupFutures = new ArrayList<>(warmupQueries);
for (int i = 0; i < warmupQueries; i++) {
String traceId = traceIds.get(random.nextInt(traceIds.size()));
warmupFutures.add(CompletableFuture.runAsync(() -> {
try {
Request.Builder reqBuilder = new Request.Builder()
.url(finalHost + "/v1/jaeger/api/traces/" + traceId)
.get();
if (!finalTableName.equals("opentelemetry_traces")) {
reqBuilder.header("x-greptime-trace-table-name", finalTableName);
}
try (Response resp = client.newCall(reqBuilder.build()).execute()) {
resp.body().string();
}
} catch (Exception ignored) {}
}, executor));
}
CompletableFuture.allOf(warmupFutures.toArray(new CompletableFuture[0])).join();
System.out.printf("Warmup done (%d queries).%n%n", warmupQueries);
System.out.printf("Running benchmark...%n");
long benchStart = System.nanoTime();
List<CompletableFuture<Void>> futures = new ArrayList<>(totalQueries);
for (int i = 0; i < totalQueries; i++) {
String traceId = traceIds.get(random.nextInt(traceIds.size()));
futures.add(CompletableFuture.runAsync(() -> {
try {
Request.Builder reqBuilder = new Request.Builder()
.url(finalHost + "/v1/jaeger/api/traces/" + traceId)
.get();
if (!finalTableName.equals("opentelemetry_traces")) {
reqBuilder.header("x-greptime-trace-table-name", finalTableName);
}
long qStart = System.nanoTime();
try (Response resp = client.newCall(reqBuilder.build()).execute()) {
long latency = System.nanoTime() - qStart;
if (resp.isSuccessful()) {
latencies.add(latency);
} else {
failed.incrementAndGet();
}
}
} catch (Exception e) {
failed.incrementAndGet();
}
int done = completed.incrementAndGet();
if (done % 10000 == 0) {
System.out.printf(" Progress: %d / %d%n", done, totalQueries);
}
}, executor));
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
long benchTime = System.nanoTime() - benchStart;
executor.shutdown();
System.out.println();
if (latencies.isEmpty()) {
System.err.println("All queries failed.");
System.exit(1);
}
Collections.sort(latencies);
int n = latencies.size();
System.out.println("=== Results ===");
System.out.printf(" Successful: %d%n", n);
System.out.printf(" Failed: %d%n", failed.get());
System.out.printf(" Total time: %.2f s%n", benchTime / 1e9);
System.out.printf(" QPS: %.1f%n", n / (benchTime / 1e9));
System.out.println();
System.out.println(" Latency (ms):");
System.out.printf(" min: %8.2f%n", ms(latencies.get(0)));
System.out.printf(" p50: %8.2f%n", ms(latencies.get((int) (n * 0.50))));
System.out.printf(" p90: %8.2f%n", ms(latencies.get((int) (n * 0.90))));
System.out.printf(" p99: %8.2f%n", ms(latencies.get((int) (n * 0.99))));
System.out.printf(" max: %8.2f%n", ms(latencies.get(n - 1)));
System.out.printf(" avg: %8.2f%n", latencies.stream().mapToLong(l -> l).average().orElse(0) / 1e6);
}
static double ms(long nanos) {
return nanos / 1e6;
}
static List<String> fetchTraceIds(OkHttpClient client, String host, String table, int limit) throws Exception {
String sql = "SELECT DISTINCT trace_id FROM " + table + " ORDER BY RANDOM() LIMIT " + limit;
Request request = new Request.Builder()
.url(host + "/v1/sql?db=public")
.post(RequestBody.create(
"sql=" + URLEncoder.encode(sql, StandardCharsets.UTF_8),
okhttp3.MediaType.parse("application/x-www-form-urlencoded")))
.build();
try (Response response = client.newCall(request).execute()) {
JsonNode rows = MAPPER.readTree(response.body().string()).at("/output/0/records/rows");
List<String> ids = new ArrayList<>(limit);
for (JsonNode row : rows) {
ids.add(row.get(0).asText());
}
return ids.stream().distinct().collect(Collectors.toList());
}
}
static void printUsage() {
System.out.println("Usage: TraceQueryBenchmark [options]");
System.out.println(" --host <url> GreptimeDB host (default: http://localhost:4000)");
System.out.println(" --table <name> Table name (default: opentelemetry_traces)");
System.out.println(" --concurrency <n> Concurrency level (default: 8)");
System.out.println(" --pool-size <n> Trace ID pool size (default: 1000)");
System.out.println(" --rounds <n> Number of rounds (default: 10)");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment