Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 0 additions & 4 deletions code_to_optimize/java-gradle/codeflash.toml

This file was deleted.

6 changes: 0 additions & 6 deletions code_to_optimize/java/codeflash.toml

This file was deleted.

236 changes: 198 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,179 @@

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 {
String invId = testIteration + "_" + testMethodName;

// 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 +202,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 +254,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,11 +22,6 @@ public byte[] transform(ClassLoader loader, String className,
return null;
}

// Skip instrumentation if we're inside a recording call (e.g., during Kryo serialization)
if (TraceRecorder.isRecording()) {
return null;
}

// Skip internal JDK, framework, and synthetic classes
if (className.startsWith("java/")
|| className.startsWith("javax/")
Expand Down
12 changes: 8 additions & 4 deletions codeflash/cli_cmds/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,17 @@ def process_pyproject_config(args: Namespace) -> Namespace:
args.ignore_paths = normalize_ignore_paths(args.ignore_paths, base_path=args.module_root)
# If module-root is "." then all imports are relatives to it.
# in this case, the ".." becomes outside project scope, causing issues with un-importable paths
args.project_root = project_root_from_module_root(args.module_root, pyproject_file_path)
args.project_root = project_root_from_module_root(Path(args.module_root), pyproject_file_path)
args.tests_root = Path(args.tests_root).resolve()
if args.benchmarks_root:
args.benchmarks_root = Path(args.benchmarks_root).resolve()
args.test_project_root = project_root_from_module_root(args.tests_root, pyproject_file_path)

if is_java_project and pyproject_file_path.is_dir():
# For Java projects, pyproject_file_path IS the project root directory (not a file).
# Override project_root which may have resolved to a sub-module.
args.project_root = pyproject_file_path.resolve()
args.test_project_root = pyproject_file_path.resolve()
if is_LSP_enabled():
args.all = None
return args
Expand All @@ -208,8 +214,6 @@ def project_root_from_module_root(module_root: Path, pyproject_file_path: Path)
return current.resolve()
if (current / "build.gradle").exists() or (current / "build.gradle.kts").exists():
return current.resolve()
if (current / "codeflash.toml").exists():
return current.resolve()
current = current.parent

return module_root.parent.resolve()
Expand Down Expand Up @@ -370,7 +374,7 @@ def _build_parser() -> ArgumentParser:
subparsers.add_parser("vscode-install", help="Install the Codeflash VSCode extension")
subparsers.add_parser("init-actions", help="Initialize GitHub Actions workflow")

trace_optimize = subparsers.add_parser("optimize", help="Trace and optimize your project.")
trace_optimize = subparsers.add_parser("optimize", help="Trace and optimize your project.", add_help=False)

trace_optimize.add_argument(
"--max-function-count",
Expand Down
Loading
Loading