Last active
April 21, 2026 06:35
-
-
Save sunng87/240f1887c823c6881d74899c1fec358a to your computer and use it in GitHub Desktop.
Benchmarking jaeger trace API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ## 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ///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