Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3f95ff6
feat: eliminate codeflash.toml — auto-detect Java config from build f…
misrasaurabh1 Mar 20, 2026
9d01710
fix: skip behavior instrumentation for replay test files
misrasaurabh1 Mar 20, 2026
df55e74
fix: support JUnit 4 in replay test generation
misrasaurabh1 Mar 20, 2026
9b1fc14
fix: avoid duplicate method names for overloaded Java methods in repl…
misrasaurabh1 Mar 20, 2026
721655f
test: add tests for JUnit 4 support, overload handling, instrumentati…
misrasaurabh1 Mar 20, 2026
b0d4a5e
test: add tests for JFR parser, graceful timeout, and project root re…
misrasaurabh1 Mar 20, 2026
d441bb9
fix: properly instrument replay tests instead of skipping them
misrasaurabh1 Mar 20, 2026
c087d0d
feat: smart ReplayHelper with behavior capture and performance timing
misrasaurabh1 Mar 20, 2026
0fc5bba
fix: e2e java tracer runs on all codeflash changes and validates repl…
misrasaurabh1 Mar 20, 2026
dae9b48
fix: restore correct argument order in process_pyproject_config
misrasaurabh1 Mar 20, 2026
74cbe2a
fix: Windows compatibility for Java config detection tests
misrasaurabh1 Mar 20, 2026
be616d1
fix: flush e2e test output to CI logs in real-time
misrasaurabh1 Mar 20, 2026
803fb64
fix: add missing type params for dict in _write_maven_properties and …
github-actions[bot] Mar 20, 2026
13dae81
fix: increase JFR sampling frequency and make Workload exercise funct…
misrasaurabh1 Mar 20, 2026
3c63b60
fix: preserve pom.xml formatting in config writer and align write/rem…
mashraf-222 Mar 24, 2026
5942ae9
fix: prefer closer config file over parent Java build file in monorep…
mashraf-222 Mar 24, 2026
1292144
fix: capture real line numbers in tracer and track dropped captures (…
mashraf-222 Mar 24, 2026
970c9f8
fix: use 3 package components for tracer instrumentation scope (TODO-39)
mashraf-222 Mar 24, 2026
c5b3687
refactor: remove zero-config logic from java-config-redesign branch
Mar 26, 2026
e3701d0
Fix 1. iteration_id ordering — Comparator couldn't match baseline vs…
HeshamHM28 Mar 27, 2026
5add169
Merge branch 'main' into java-config-redesign
HeshamHM28 Mar 27, 2026
f3eecac
style: remove extra blank line in cli.py
github-actions[bot] Mar 27, 2026
aeeca5c
fix(java): find mvnw in parent dirs and respect --no-pr in tracer path
HeshamHM28 Apr 1, 2026
c9c5f9d
Merge branch 'main' into java-config-redesign
Apr 1, 2026
85e8a51
docs: update Java tracer documentation to match verified behavior
HeshamHM28 Apr 1, 2026
20f76a3
fix(test): move --no-pr before optimize subcommand in e2e tracer test
HeshamHM28 Apr 1, 2026
27535c1
Revert "docs: update Java tracer documentation to match verified beha…
Apr 1, 2026
fcaa513
fix(java): use qualified names in replay test metadata for correct fu…
HeshamHM28 Apr 1, 2026
6d65dd5
Merge pull request #1939 from codeflash-ai/cf-fix-replay-test-discovery
misrasaurabh1 Apr 1, 2026
823300f
Merge branch 'main' into java-config-redesign
HeshamHM28 Apr 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions .github/workflows/e2e-java-tracer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,9 @@ name: E2E - Java Tracer
on:
pull_request:
paths:
- 'codeflash/languages/java/**'
- 'codeflash/languages/base.py'
- 'codeflash/languages/registry.py'
- 'codeflash/tracer.py'
- 'codeflash/benchmarking/function_ranker.py'
- 'codeflash/discovery/functions_to_optimize.py'
- 'codeflash/optimization/**'
- 'codeflash/verification/**'
- 'codeflash/**'
- 'codeflash-java-runtime/**'
- 'tests/test_languages/fixtures/java_tracer_e2e/**'
- 'tests/scripts/end_to_end_test_java_tracer.py'
- 'tests/**'
- '.github/workflows/e2e-java-tracer.yaml'

workflow_dispatch:
Expand Down
238 changes: 200 additions & 38 deletions codeflash-java-runtime/src/main/java/com/codeflash/ReplayHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,181 @@

public class ReplayHelper {

private final Connection db;
private final Connection traceDb;

// Codeflash instrumentation state — read from environment variables once
private final String mode; // "behavior", "performance", or null
private final int loopIndex;
private final String testIteration;
private final String outputFile; // SQLite path for behavior capture
private final int innerIterations; // for performance looping

// Behavior mode: lazily opened SQLite connection for writing results
private Connection behaviorDb;
private boolean behaviorDbInitialized;

public ReplayHelper(String traceDbPath) {
try {
this.db = DriverManager.getConnection("jdbc:sqlite:" + traceDbPath);
this.traceDb = DriverManager.getConnection("jdbc:sqlite:" + traceDbPath);
} catch (SQLException e) {
throw new RuntimeException("Failed to open trace database: " + traceDbPath, e);
}

// Read codeflash instrumentation env vars (set by the test runner)
this.mode = System.getenv("CODEFLASH_MODE");
this.loopIndex = parseIntEnv("CODEFLASH_LOOP_INDEX", 1);
this.testIteration = getEnvOrDefault("CODEFLASH_TEST_ITERATION", "0");
this.outputFile = System.getenv("CODEFLASH_OUTPUT_FILE");
this.innerIterations = parseIntEnv("CODEFLASH_INNER_ITERATIONS", 10);
}

public void replay(String className, String methodName, String descriptor, int invocationIndex) throws Exception {
// Query the function_calls table for this method at the given index
// Deserialize args and resolve method (done once, outside timing)
Object[] allArgs = loadArgs(className, methodName, descriptor, invocationIndex);
Class<?> targetClass = Class.forName(className);

Type[] paramTypes = Type.getArgumentTypes(descriptor);
Class<?>[] paramClasses = new Class<?>[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++) {
paramClasses[i] = typeToClass(paramTypes[i]);
}

Method method = targetClass.getDeclaredMethod(methodName, paramClasses);
method.setAccessible(true);
boolean isStatic = Modifier.isStatic(method.getModifiers());

Object instance = null;
if (!isStatic) {
try {
java.lang.reflect.Constructor<?> ctor = targetClass.getDeclaredConstructor();
ctor.setAccessible(true);
instance = ctor.newInstance();
} catch (NoSuchMethodException e) {
instance = new org.objenesis.ObjenesisStd().newInstance(targetClass);
}
}

// Get the calling test method name from the stack trace
String testMethodName = getCallingTestMethodName();
// Module name = the test class that called us
String testClassName = getCallingTestClassName();

if ("behavior".equals(mode)) {
replayBehavior(method, instance, allArgs, className, methodName, testClassName, testMethodName);
} else if ("performance".equals(mode)) {
replayPerformance(method, instance, allArgs, className, methodName, testClassName, testMethodName);
} else {
// No codeflash mode — just invoke (trace-only or manual testing)
method.invoke(instance, allArgs);
}
}

private void replayBehavior(Method method, Object instance, Object[] args,
String className, String methodName,
String testClassName, String testMethodName) throws Exception {
// testIteration goes at the END so the Comparator's lastUnderscore stripping
// removes it, making baseline (iteration=0) and candidate (iteration=N) keys match.
String invId = testMethodName + "_" + testIteration;

// Print start marker (same format as behavior instrumentation)
System.out.println("!$######" + testClassName + ":" + testClassName + "." + testMethodName
+ ":" + methodName + ":" + loopIndex + ":" + invId + "######$!");

long startNs = System.nanoTime();
Object result;
try {
result = method.invoke(instance, args);
} catch (java.lang.reflect.InvocationTargetException e) {
throw (Exception) e.getCause();
}
long durationNs = System.nanoTime() - startNs;

// Print end marker
System.out.println("!######" + testClassName + ":" + testClassName + "." + testMethodName
+ ":" + methodName + ":" + loopIndex + ":" + invId + ":" + durationNs + "######!");

// Write return value to SQLite for correctness comparison
if (outputFile != null && !outputFile.isEmpty()) {
writeBehaviorResult(testClassName, testMethodName, methodName, invId, durationNs, result);
}
}

private void replayPerformance(Method method, Object instance, Object[] args,
String className, String methodName,
String testClassName, String testMethodName) throws Exception {
// Performance mode: run inner loop for JIT warmup, print timing for each iteration
int maxInner = innerIterations;
for (int inner = 0; inner < maxInner; inner++) {
int loopId = (loopIndex - 1) * maxInner + inner;
String invId = testMethodName;

// Print start marker
System.out.println("!$######" + testClassName + ":" + testClassName + "." + testMethodName
+ ":" + methodName + ":" + loopId + ":" + invId + "######$!");

long startNs = System.nanoTime();
try {
method.invoke(instance, args);
} catch (java.lang.reflect.InvocationTargetException e) {
// Swallow — performance mode doesn't check correctness
}
long durationNs = System.nanoTime() - startNs;

// Print end marker
System.out.println("!######" + testClassName + ":" + testClassName + "." + testMethodName
+ ":" + methodName + ":" + loopId + ":" + invId + ":" + durationNs + "######!");
}
}

private void writeBehaviorResult(String testClassName, String testMethodName,
String functionName, String invId,
long durationNs, Object result) {
try {
ensureBehaviorDb();
String sql = "INSERT INTO test_results VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
try (PreparedStatement ps = behaviorDb.prepareStatement(sql)) {
ps.setString(1, testClassName); // test_module_path
ps.setString(2, testClassName); // test_class_name
ps.setString(3, testMethodName); // test_function_name
ps.setString(4, functionName); // function_getting_tested
ps.setInt(5, loopIndex); // loop_index
ps.setString(6, invId); // iteration_id
ps.setLong(7, durationNs); // runtime
ps.setBytes(8, serializeResult(result)); // return_value
ps.setString(9, "function_call"); // verification_type
ps.executeUpdate();
}
} catch (Exception e) {
System.err.println("ReplayHelper: SQLite behavior write error: " + e.getMessage());
}
}

private void ensureBehaviorDb() throws SQLException {
if (behaviorDbInitialized) return;
behaviorDbInitialized = true;
behaviorDb = DriverManager.getConnection("jdbc:sqlite:" + outputFile);
try (java.sql.Statement stmt = behaviorDb.createStatement()) {
stmt.execute("CREATE TABLE IF NOT EXISTS test_results (" +
"test_module_path TEXT, test_class_name TEXT, test_function_name TEXT, " +
"function_getting_tested TEXT, loop_index INTEGER, iteration_id TEXT, " +
"runtime INTEGER, return_value BLOB, verification_type TEXT)");
}
}

private byte[] serializeResult(Object result) {
if (result == null) return null;
try {
return Serializer.serialize(result);
} catch (Exception e) {
// Fall back to String.valueOf if Kryo fails
return String.valueOf(result).getBytes(java.nio.charset.StandardCharsets.UTF_8);
}
}

private Object[] loadArgs(String className, String methodName, String descriptor, int invocationIndex)
throws SQLException {
byte[] argsBlob;
try (PreparedStatement stmt = db.prepareStatement(
try (PreparedStatement stmt = traceDb.prepareStatement(
"SELECT args FROM function_calls " +
"WHERE classname = ? AND function = ? AND descriptor = ? " +
"ORDER BY time_ns LIMIT 1 OFFSET ?")) {
Expand All @@ -43,46 +204,35 @@ public void replay(String className, String methodName, String descriptor, int i
}
}

// Deserialize args
Object deserialized = Serializer.deserialize(argsBlob);
if (!(deserialized instanceof Object[])) {
throw new RuntimeException("Deserialized args is not Object[], got: "
+ (deserialized == null ? "null" : deserialized.getClass().getName()));
}
Object[] allArgs = (Object[]) deserialized;

// Load the target class
Class<?> targetClass = Class.forName(className);
return (Object[]) deserialized;
}

// Parse descriptor to find parameter types
Type[] paramTypes = Type.getArgumentTypes(descriptor);
Class<?>[] paramClasses = new Class<?>[paramTypes.length];
for (int i = 0; i < paramTypes.length; i++) {
paramClasses[i] = typeToClass(paramTypes[i]);
private static String getCallingTestMethodName() {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
// Walk up: [0]=getStackTrace, [1]=this method, [2]=replay(), [3]=calling test method
for (int i = 3; i < stack.length; i++) {
String method = stack[i].getMethodName();
if (method.startsWith("replay_")) {
return method;
}
}
return stack.length > 3 ? stack[3].getMethodName() : "unknown";
}

// Find the method
Method method = targetClass.getDeclaredMethod(methodName, paramClasses);
method.setAccessible(true);

boolean isStatic = Modifier.isStatic(method.getModifiers());

if (isStatic) {
method.invoke(null, allArgs);
} else {
// Args contain only explicit parameters (no 'this').
// Create a default instance via no-arg constructor or Kryo.
Object instance;
try {
java.lang.reflect.Constructor<?> ctor = targetClass.getDeclaredConstructor();
ctor.setAccessible(true);
instance = ctor.newInstance();
} catch (NoSuchMethodException e) {
// Fall back to Objenesis instantiation (no constructor needed)
instance = new org.objenesis.ObjenesisStd().newInstance(targetClass);
private static String getCallingTestClassName() {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
for (int i = 3; i < stack.length; i++) {
String cls = stack[i].getClassName();
if (cls.contains("ReplayTest") || cls.contains("replay")) {
return cls;
}
method.invoke(instance, allArgs);
}
return stack.length > 3 ? stack[3].getClassName() : "unknown";
}

private static Class<?> typeToClass(Type type) throws ClassNotFoundException {
Expand All @@ -106,11 +256,23 @@ private static Class<?> typeToClass(Type type) throws ClassNotFoundException {
}
}

private static int parseIntEnv(String name, int defaultValue) {
String val = System.getenv(name);
if (val == null || val.isEmpty()) return defaultValue;
try { return Integer.parseInt(val); } catch (NumberFormatException e) { return defaultValue; }
}

private static String getEnvOrDefault(String name, String defaultValue) {
String val = System.getenv(name);
return (val != null && !val.isEmpty()) ? val : defaultValue;
}

public void close() {
try {
if (db != null) db.close();
} catch (SQLException e) {
System.err.println("Error closing ReplayHelper: " + e.getMessage());
try { if (traceDb != null) traceDb.close(); } catch (SQLException e) {
System.err.println("Error closing ReplayHelper trace db: " + e.getMessage());
}
try { if (behaviorDb != null) behaviorDb.close(); } catch (SQLException e) {
System.err.println("Error closing ReplayHelper behavior db: " + e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public final class TraceRecorder {
private final TracerConfig config;
private final TraceWriter writer;
private final ConcurrentHashMap<String, AtomicInteger> functionCounts = new ConcurrentHashMap<>();
private final AtomicInteger droppedCaptures = new AtomicInteger(0);
private final int maxFunctionCount;
private final ExecutorService serializerExecutor;

Expand Down Expand Up @@ -82,11 +83,13 @@ private void onEntryImpl(String className, String methodName, String descriptor,
argsBlob = future.get(SERIALIZATION_TIMEOUT_MS, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
future.cancel(true);
droppedCaptures.incrementAndGet();
System.err.println("[codeflash-tracer] Serialization timed out for " + className + "."
+ methodName);
return;
} catch (Exception e) {
Throwable cause = e.getCause() != null ? e.getCause() : e;
droppedCaptures.incrementAndGet();
System.err.println("[codeflash-tracer] Serialization failed for " + className + "."
+ methodName + ": " + cause.getClass().getSimpleName() + ": " + cause.getMessage());
return;
Expand All @@ -113,11 +116,15 @@ public void flush() {
}
metadata.put("totalCaptures", String.valueOf(totalCaptures));

int dropped = droppedCaptures.get();
metadata.put("droppedCaptures", String.valueOf(dropped));

writer.writeMetadata(metadata);
writer.flush();
writer.close();

System.err.println("[codeflash-tracer] Captured " + totalCaptures
+ " invocations across " + functionCounts.size() + " methods");
+ " invocations across " + functionCounts.size() + " methods"
+ (dropped > 0 ? " (" + dropped + " dropped due to serialization timeout/failure)" : ""));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.util.Collections;
import java.util.Map;

public class TracingClassVisitor extends ClassVisitor {

private final String internalClassName;
private final Map<String, Integer> methodLineNumbers;
private String sourceFile;

public TracingClassVisitor(ClassVisitor classVisitor, String internalClassName) {
public TracingClassVisitor(ClassVisitor classVisitor, String internalClassName,
Map<String, Integer> methodLineNumbers) {
super(Opcodes.ASM9, classVisitor);
this.internalClassName = internalClassName;
this.methodLineNumbers = methodLineNumbers != null ? methodLineNumbers : Collections.emptyMap();
}

@Override
Expand All @@ -37,7 +43,8 @@ public MethodVisitor visitMethod(int access, String name, String descriptor,
return mv;
}

int lineNumber = methodLineNumbers.getOrDefault(name + descriptor, 0);
return new TracingMethodAdapter(mv, access, name, descriptor,
internalClassName, 0, sourceFile != null ? sourceFile : "");
internalClassName, lineNumber, sourceFile != null ? sourceFile : "");
}
}
Loading
Loading