Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b8534b9
Add APX code_hotspots integration test for CpuBurner Java workload
jaidev17 Apr 21, 2026
375c6ec
Install Java on runner and use CpuBurnerOriginal.java from repo for A…
jaidev17 Apr 22, 2026
7a89422
Fix tilde expansion in APX Java cmd - use $HOME instead of ~
jaidev17 Apr 22, 2026
5a4c32f
Use /tmp for CpuBurnerOriginal.java to avoid env var dependency in AP…
jaidev17 Apr 22, 2026
24c5a15
fix: scan all JSON lines in extract_run_id to handle Java workload ou…
jaidev17 Apr 22, 2026
d4a2b6a
fix: revert APX Java cmd to base64 approach, remove file copy steps
jaidev17 Apr 22, 2026
1e7973d
Use CpuBurnerOriginal source for APX Java test
jaidev17 Apr 28, 2026
5f523ad
Improve APX Java hotspot visibility
jaidev17 Apr 28, 2026
2626fca
clean up debug messages
jaidev17 May 4, 2026
6f6ac5d
Prepare Java CpuBurner on runner and simplify workload cmd
jaidev17 May 5, 2026
df2dc42
Increase Java APX response timeout
jaidev17 May 5, 2026
4b859ab
Timeout Java workload and extend APX response wait
jaidev17 May 6, 2026
b0b50f1
Assert CpuBurner workC appears in Java hotspots
jaidev17 May 6, 2026
dd5b2b8
Remove APX_TEST_JAVA_CMD override from workflow
jaidev17 May 6, 2026
b7c7c09
Assert if java outputs rows not found.
jaidev17 May 6, 2026
28b46b7
Parse full JSON output before line scanning for run_id
jaidev17 May 6, 2026
f53d9a8
Reduce Java CpuBurner duration to 10s
jaidev17 May 12, 2026
91754b0
Reduce Java CpuBurner duration to 1s
jaidev17 May 12, 2026
da18a93
Set Java CpuBurner duration to 3s
jaidev17 May 12, 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
13 changes: 12 additions & 1 deletion .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ jobs:
sudo visudo -cf "/etc/sudoers.d/90-${APX_SSH_USER}-nopasswd"

sudo apt-get update
sudo apt-get install -y openssh-server
sudo apt-get install -y openssh-server default-jdk-headless
sudo mkdir -p /run/sshd
sudo sed -i 's/^#\?PubkeyAuthentication .*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
sudo sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/' /etc/ssh/sshd_config
Expand Down Expand Up @@ -97,6 +97,17 @@ jobs:
echo "APX_TEST_REMOTE_IP=172.17.0.1"
} >> "${GITHUB_ENV}"

- name: Prepare Java CpuBurner workload
run: |
set -euxo pipefail
APX_SSH_USER="apxci"
CPUBURNER_DIR="/home/${APX_SSH_USER}/cpuburner"

sudo -u "${APX_SSH_USER}" mkdir -p "${CPUBURNER_DIR}"
sudo cp mcp-local/tests/CpuBurnerOriginal.java "${CPUBURNER_DIR}/CpuBurner.java"
sudo chown -R "${APX_SSH_USER}:${APX_SSH_USER}" "${CPUBURNER_DIR}"
sudo -u "${APX_SSH_USER}" javac "${CPUBURNER_DIR}/CpuBurner.java"

- name: Run integration tests
env:
APX_DEBUG_TRACE: "1"
Expand Down
199 changes: 199 additions & 0 deletions mcp-local/tests/CpuBurnerOriginal.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
// CpuBurner.java
// Orignal un-optimised version workC function is deliberately slow
Comment thread
jaidev17 marked this conversation as resolved.
// Usage: java CpuBurner <seconds>
// Example: java CpuBurner 10
//
// Runs for (approximately) the given duration, calling A/B/C the same number of times.
// A: expensive string manipulation
// B: computationally expensive memcpy (System.arraycopy over large buffers)
// C: floating point / matrix multiplications (INTENTIONALLY SLOWED DOWN)

import java.nio.charset.StandardCharsets;
import java.util.Locale;

public final class CpuBurner {

// Prevent dead-code elimination
private static volatile long SINK_LONG = 0;
private static volatile double SINK_DBL = 0.0;

// Extra sink used only to keep "wasted" math from being optimized away
private static volatile double WASTE_DBL = 0.0;

// ---- Workload B buffers ----
private static final int MEM_SIZE = 8 * 1024 * 1024; // 8 MiB
private static final byte[] SRC = new byte[MEM_SIZE];
private static final byte[] DST = new byte[MEM_SIZE];

// ---- Workload C matrices ----
private static final int N = 64;
private static final double[][] M1 = new double[N][N];
private static final double[][] M2 = new double[N][N];
private static final double[][] OUT = new double[N][N];

static {
// Deterministic init for repeatable profiling
long x = 0x9E3779B97F4A7C15L;
for (int i = 0; i < MEM_SIZE; i++) {
x ^= (x << 13); x ^= (x >>> 7); x ^= (x << 17);
SRC[i] = (byte) x;
}

long y = 0xD1B54A32D192ED03L;
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
y ^= (y << 13); y ^= (y >>> 7); y ^= (y << 17);
M1[i][j] = ((y & 0xFFFF) - 32768) / 1024.0;
y ^= (y << 13); y ^= (y >>> 7); y ^= (y << 17);
M2[i][j] = ((y & 0xFFFF) - 32768) / 1024.0;
}
}
}

public static void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: java CpuBurner <seconds>");
System.err.println("Example: java CpuBurner 10");
System.exit(2);
}

final double seconds;
try {
seconds = Double.parseDouble(args[0]);
} catch (NumberFormatException e) {
System.err.println("Invalid number: " + args[0]);
System.exit(2);
return;
}

if (seconds <= 0.0) {
System.err.println("Duration must be > 0");
System.exit(2);
}

final long durationNanos = (long) (seconds * 1_000_000_000L);
final long start = System.nanoTime();
final long deadline = start + durationNanos;

long callsA = 0, callsB = 0, callsC = 0;

// Round-robin A->B->C; only complete full cycles so counts remain equal
while (true) {
if (System.nanoTime() >= deadline) break;

// A
SINK_LONG ^= workA(callsA);
callsA++;

// Check between functions to avoid overshooting too much
if (System.nanoTime() >= deadline) { callsA--; break; }

// B
SINK_LONG ^= workB(callsB);
callsB++;

if (System.nanoTime() >= deadline) { callsA--; callsB--; break; }

// C
SINK_DBL += workC(callsC);
callsC++;

if (System.nanoTime() >= deadline) { callsA--; callsB--; callsC--; break; }
}

// Ensure exactly equal counts (drop any partial cycle if timing cut it short)
long min = Math.min(callsA, Math.min(callsB, callsC));
callsA = callsB = callsC = min;

long elapsedNanos = System.nanoTime() - start;
System.out.println("Elapsed: " + (elapsedNanos / 1_000_000) + " ms");
System.out.println("Calls: A=" + callsA + " B=" + callsB + " C=" + callsC + " (equal)");
System.out.println("Sinks: long=" + SINK_LONG + " double=" + String.format(Locale.ROOT, "%.6f", SINK_DBL));
// WASTE_DBL intentionally not printed; it's only to prevent optimization.
}

// A: expensive string manipulation
private static long workA(long iter) {
// Mix iteration to avoid identical strings each time
String base = "The_quick_brown_fox_jumps_over_the_lazy_dog_" + iter;

long h = 1469598103934665603L; // FNV-1a-ish mix
for (int round = 0; round < 500; round++) {
String s1 = new StringBuilder(base).reverse().append('_').append(round).toString();
String s2 = s1.toUpperCase(Locale.ROOT).replace('_', '-');
String s3 = s2 + "::" + Integer.toHexString(s2.hashCode());

// Byte-level churn
byte[] bytes = s3.getBytes(StandardCharsets.UTF_8);
for (byte b : bytes) {
h ^= (b & 0xFF);
h *= 1099511628211L;
}

// More string churn
String[] parts = s3.split("::");
base = parts[0] + "_" + parts[1] + "_" + (h & 0xFFFF);
}
return h;
}

// B: computationally expensive memcpy (large arraycopy + light checksum)
private static long workB(long iter) {
int chunk = 256 * 1024; // 256 KiB
int copies = 32; // 32 * 256KiB ~= 8MiB copied per call

// Offset shifts to avoid copying identical regions each call
int off = (int) ((iter * 1315423911L) & (MEM_SIZE - 1));
off = off & ~(chunk - 1); // align to chunk

for (int i = 0; i < copies; i++) {
int srcOff = (off + i * chunk) % (MEM_SIZE - chunk);
int dstOff = (srcOff ^ 0x5A5A5A) % (MEM_SIZE - chunk);
System.arraycopy(SRC, srcOff, DST, dstOff, chunk);
}

// Touch a few bytes so it isn't optimized away
long sum = 0;
for (int i = 0; i < 4096; i += 64) {
sum = (sum * 1315423911L) + (DST[(off + i) % MEM_SIZE] & 0xFF);
}
return sum;
}

// C: floating point / matrix multiplications (INTENTIONALLY SLOW)
private static double workC(long iter) {
// Naive NxN multiply repeated a few times
double total = 0.0;
int reps = 6;

for (int r = 0; r < reps; r++) {
// OUT = M1 * M2
for (int i = 0; i < N; i++) {
// (deliberately avoid caching row references to make access a bit worse)
for (int j = 0; j < N; j++) {
double acc = 0.0;
for (int k = 0; k < N; k++) {
double prod = M1[i][k] * M2[k][j];
acc += prod;

// Intentional inefficiency: extra transcendentals in the inner loop.
// WASTE_DBL is volatile so the JIT can't discard this work.
WASTE_DBL += (Math.sin(prod) + Math.cos(prod)) * 1e-12;
}
OUT[i][j] = acc;
}
}

// Fold some results into total, and slightly perturb inputs so repeats differ
int t = (int) (iter + r);
int ii = (t * 17) & (N - 1);
int jj = (t * 31) & (N - 1);
total += OUT[ii][jj];

double tweak = (total * 1e-9) + 1e-12;
M1[ii][jj] += tweak;
M2[jj][ii] -= tweak;
}
return total;
}
}
16 changes: 16 additions & 0 deletions mcp-local/tests/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,22 @@

EXPECTED_CHECK_MCA_TOOL_RESPONSE_STATUS = "ok"

CHECK_APX_CPU_HOTSPOTS_JAVA_REQUEST = {
"jsonrpc": "2.0",
"id": 9,
"method": "tools/call",
"params": {
"name": "apx_recipe_run",
"arguments": {
"cmd": "java -XX:+PreserveFramePointer -cp /home/apxci/cpuburner CpuBurner 3",
"remote_ip_addr": "localhost",
"remote_usr": "base",
Comment thread
jaidev17 marked this conversation as resolved.
"recipe": "code_hotspots",
"invocation_reason": "Run APX code hotspots recipe against the CpuBurner Java workload to identify CPU hotspots.",
},
},
}

CHECK_APX_RECIPE_RUN_REQUEST = {
"jsonrpc": "2.0",
"id": 8,
Expand Down
35 changes: 35 additions & 0 deletions mcp-local/tests/test_mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,41 @@ def _read_response(expected_id: int, timeout: float = 10.0) -> dict:
assert apx_structured.get("recipe") == "code_hotspots", "Test Failed: MCP apx_recipe_run tool failed: recipe mismatch. Expected: code_hotspots, Received: {}".format(apx_structured.get("recipe"))
assert apx_structured.get("status") in {"success"}, "Test Failed: MCP apx_recipe_run tool failed: unexpected status. Received: {}".format(apx_structured.get("status"))
print("\n***Test Passed: MCP apx_recipe_run tool call completed")

#Check APX Code Hotspots Tool Test - CpuBurner Java Workload
apx_java_request = json.loads(json.dumps(constants.CHECK_APX_CPU_HOTSPOTS_JAVA_REQUEST))
apx_java_args = apx_java_request["params"]["arguments"]
apx_java_args["remote_ip_addr"] = os.getenv("APX_TEST_REMOTE_IP", apx_java_args["remote_ip_addr"])
apx_java_args["remote_usr"] = os.getenv("APX_TEST_REMOTE_USER", apx_java_args["remote_usr"])
apx_java_args["cmd"] = os.getenv("APX_TEST_JAVA_CMD", apx_java_args["cmd"])

raw_socket.settimeout(600)
raw_socket.sendall(_encode_mcp_message(apx_java_request))
check_apx_java_response = _read_response(9, timeout=600)
raw_socket.settimeout(10)
print(
"\n***APX CPU Hotspots (Java) Raw Response: ",
json.dumps(check_apx_java_response, indent=2),
)
apx_java_structured = check_apx_java_response.get("result", {}).get("structuredContent", {})
print(
"\n***APX CPU Hotspots (Java) Structured Content: ",
json.dumps(apx_java_structured, indent=2),
)
java_rows = apx_java_structured.get("rows", [])
assert java_rows, "Test Failed: Expected non-empty APX Java hotspots rows output."
workc_found = False
for row in java_rows:
if not isinstance(row, dict):
continue
function_name = row.get("FUNCTION_NAME") or row.get("function_name") or ""
if "CpuBurner::workC" in str(function_name):
workc_found = True
break
assert workc_found, "Test Failed: Expected CpuBurner::workC in APX Java hotspots output."
assert apx_java_structured.get("recipe") == "code_hotspots", "Test Failed: MCP apx_recipe_run (Java) tool failed: recipe mismatch. Expected: code_hotspots, Received: {}".format(apx_java_structured.get("recipe"))
assert apx_java_structured.get("status") in {"success"}, "Test Failed: MCP apx_recipe_run (Java) tool failed: unexpected status. Received: {}".format(apx_java_structured.get("status"))
print("\n***Test Passed: MCP apx_recipe_run (Java CpuBurner) tool call completed")

if __name__ == "__main__":
pytest.main([__file__])
17 changes: 12 additions & 5 deletions mcp-local/utils/apx.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,11 +533,18 @@ def resolve_apx_ssh_mount_env() -> Dict[str, Any]:
def extract_run_id(output: str) -> str:
if not output:
return ""
try:
data = json.loads(output.split("\n")[1])
return data.get("data", {}).get("run_id", {})
except Exception:
return ""
raw = output.strip()
candidates = [raw] if raw else []
candidates.extend(line.strip() for line in output.splitlines() if line.strip().startswith("{"))
for candidate in candidates:
try:
data = json.loads(candidate)
run_id = data.get("data", {}).get("run_id")
if run_id:
return run_id
except Exception:
continue
return ""
Comment thread
jaidev17 marked this conversation as resolved.

def run_command(command: list, cwd: str, parse_output=None) -> tuple:
"""
Expand Down
Loading