Skip to content

Commit 9fd3f4d

Browse files
authored
Merge pull request #102 from rlaope/fix/ux-critical-fixes
fix: critical UX fixes for v1.0.0
2 parents 1584d96 + 6d2a51b commit 9fd3f4d

5 files changed

Lines changed: 97 additions & 60 deletions

File tree

β€Žargus-cli/build.gradle.ktsβ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies {
1616
tasks.jar {
1717
manifest {
1818
attributes["Main-Class"] = "io.argus.cli.ArgusCli"
19+
attributes["Implementation-Version"] = project.version.toString()
1920
}
2021
}
2122

β€Žargus-cli/src/main/java/io/argus/cli/ArgusCli.javaβ€Ž

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,15 @@
6767
*/
6868
public final class ArgusCli {
6969

70-
private static final String VERSION = "0.6.0";
70+
private static final String VERSION = resolveVersion();
71+
72+
private static String resolveVersion() {
73+
Package pkg = ArgusCli.class.getPackage();
74+
if (pkg != null && pkg.getImplementationVersion() != null) {
75+
return pkg.getImplementationVersion();
76+
}
77+
return "dev";
78+
}
7179
private static final String DEFAULT_HOST = "localhost";
7280
private static final int DEFAULT_PORT = 9202;
7381

β€Žargus-cli/src/main/java/io/argus/cli/doctor/JvmSnapshotCollector.javaβ€Ž

Lines changed: 69 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,24 @@
1313
*/
1414
public final class JvmSnapshotCollector {
1515

16+
private static final List<String> warnings = new ArrayList<>();
17+
1618
/**
1719
* Collect snapshot β€” routes to local or remote based on PID.
1820
*/
1921
public static JvmSnapshot collect(long pid) {
22+
warnings.clear();
2023
if (pid <= 0 || pid == ProcessHandle.current().pid()) {
2124
return collectLocal();
2225
}
2326
return collectRemote(pid);
2427
}
2528

26-
/**
27-
* Collect snapshot for the current JVM (in-process mode).
28-
*/
29+
/** Get warnings from the last collection (e.g., jcmd failures). */
30+
public static List<String> lastWarnings() {
31+
return List.copyOf(warnings);
32+
}
33+
2934
@SuppressWarnings("deprecation")
3035
public static JvmSnapshot collectLocal() {
3136
MemoryMXBean mem = ManagementFactory.getMemoryMXBean();
@@ -92,31 +97,51 @@ public static JvmSnapshot collectLocal() {
9297

9398
/**
9499
* Collect snapshot for a remote JVM by PID using jcmd.
95-
* Parses GC.heap_info, Thread.print, VM.version, VM.uptime, VM.flags.
100+
* Errors are collected in warnings list instead of silently swallowed.
96101
*/
97102
public static JvmSnapshot collectRemote(long pid) {
103+
int failures = 0;
104+
98105
// Heap via GC.heap_info
99106
long heapUsed = 0, heapMax = 0, heapCommitted = 0, nonHeapUsed = 0;
100107
Map<String, JvmSnapshot.PoolInfo> pools = new LinkedHashMap<>();
101108
try {
102109
String heapInfo = JcmdExecutor.execute(pid, "GC.heap_info");
103110
parseHeapInfo(heapInfo, pools);
104111
for (var p : pools.values()) {
105-
if (p.type().equalsIgnoreCase("HEAP") || p.name().toLowerCase().contains("heap")) {
106-
heapUsed += p.used();
107-
if (p.max() > 0) heapMax = Math.max(heapMax, p.max());
108-
}
109-
}
110-
// Fallback: sum all pool used values
111-
if (heapUsed == 0) {
112-
for (var p : pools.values()) heapUsed += p.used();
112+
heapUsed += p.used();
113+
if (p.max() > 0) heapMax = Math.max(heapMax, p.max());
113114
}
114-
} catch (RuntimeException ignored) {}
115+
} catch (RuntimeException e) {
116+
warnings.add("GC.heap_info failed: " + e.getMessage());
117+
failures++;
118+
}
115119

116-
// GC via jcmd (parse from VM.flags to detect GC, count from GC.heap_info not available β€” use defaults)
120+
// GC stats via jcmd β€” parse collector info
117121
List<JvmSnapshot.GcInfo> gcInfos = new ArrayList<>();
118122
long totalGcCount = 0, totalGcTime = 0;
119123
String gcAlgorithm = "unknown";
124+
try {
125+
// Use VM.info which contains GC collector stats
126+
String vmInfo = JcmdExecutor.execute(pid, "VM.info");
127+
for (String line : vmInfo.split("\n")) {
128+
String t = line.trim();
129+
// Parse: "garbage-first heap" or "PS Young Generation" etc.
130+
if (t.contains("invocations") && t.contains("ms")) {
131+
// Try to parse GC invocation lines from VM.info
132+
var matcher = Pattern.compile("(\\d+)\\s+invocations.*?(\\d+)\\s*ms").matcher(t);
133+
if (matcher.find()) {
134+
long count = Long.parseLong(matcher.group(1));
135+
long time = Long.parseLong(matcher.group(2));
136+
totalGcCount += count;
137+
totalGcTime += time;
138+
}
139+
}
140+
}
141+
} catch (RuntimeException e) {
142+
warnings.add("VM.info failed (GC stats unavailable): " + e.getMessage());
143+
failures++;
144+
}
120145

121146
// VM version
122147
String vmName = "", vmVersion = "";
@@ -127,7 +152,10 @@ public static JvmSnapshot collectRemote(long pid) {
127152
if (vmName.isEmpty() && !t.isEmpty()) vmName = t;
128153
else if (vmVersion.isEmpty() && t.contains("build")) vmVersion = t;
129154
}
130-
} catch (RuntimeException ignored) {}
155+
} catch (RuntimeException e) {
156+
warnings.add("VM.version failed: " + e.getMessage());
157+
failures++;
158+
}
131159

132160
// VM uptime
133161
long uptimeMs = 0;
@@ -141,7 +169,10 @@ public static JvmSnapshot collectRemote(long pid) {
141169
break;
142170
} catch (NumberFormatException ignored2) {}
143171
}
144-
} catch (RuntimeException ignored) {}
172+
} catch (RuntimeException e) {
173+
warnings.add("VM.uptime failed: " + e.getMessage());
174+
failures++;
175+
}
145176

146177
// VM flags β€” detect GC algorithm
147178
List<String> vmFlags = new ArrayList<>();
@@ -159,7 +190,10 @@ public static JvmSnapshot collectRemote(long pid) {
159190
}
160191
}
161192
}
162-
} catch (RuntimeException ignored) {}
193+
} catch (RuntimeException e) {
194+
warnings.add("VM.flags failed: " + e.getMessage());
195+
failures++;
196+
}
163197

164198
// Threads via Thread.print
165199
int threadCount = 0, daemonCount = 0;
@@ -177,45 +211,41 @@ public static JvmSnapshot collectRemote(long pid) {
177211
String state = t.split(":\\s*")[1].split("\\s+")[0];
178212
threadStates.merge(state, 1, Integer::sum);
179213
}
180-
if (t.contains("Found one Java-level deadlock") || t.contains("Found") && t.contains("deadlock")) {
214+
if (t.contains("Found") && t.contains("deadlock")) {
181215
deadlockCount++;
182216
}
183217
}
184-
} catch (RuntimeException ignored) {}
218+
} catch (RuntimeException e) {
219+
warnings.add("Thread.print failed: " + e.getMessage());
220+
failures++;
221+
}
185222

186-
// Class loading via jcmd VM.classloader_stats
187-
int loadedClasses = 0;
188-
long totalLoaded = 0, unloaded = 0;
189-
try {
190-
String classOut = JcmdExecutor.execute(pid, "VM.classloaders");
191-
// Count lines with class counts
192-
for (String line : classOut.split("\n")) {
193-
if (line.trim().matches(".*\\d+.*classes.*")) loadedClasses++;
223+
// Print warnings to stderr
224+
if (failures > 0) {
225+
System.err.println("[Argus] WARNING: " + failures + " jcmd call(s) failed for PID " + pid + ":");
226+
for (String w : warnings) {
227+
System.err.println(" β†’ " + w);
228+
}
229+
if (failures >= 4) {
230+
System.err.println("[Argus] Most data unavailable. Are you running as the same user as PID " + pid + "?");
231+
System.err.println("[Argus] Try: sudo argus doctor " + pid);
194232
}
195-
} catch (RuntimeException ignored) {}
233+
}
196234

197235
return new JvmSnapshot(
198236
heapUsed, heapMax, heapCommitted, nonHeapUsed,
199237
Map.copyOf(pools),
200238
List.copyOf(gcInfos), totalGcCount, totalGcTime, uptimeMs,
201239
-1, -1, Runtime.getRuntime().availableProcessors(),
202-
threadCount, daemonCount, threadCount, // peak = current for remote
240+
threadCount, daemonCount, threadCount,
203241
Map.copyOf(threadStates), deadlockCount,
204-
List.of(), // buffers not available via jcmd
205-
loadedClasses, totalLoaded, unloaded,
206-
0, // finalizer not available via jcmd
242+
List.of(),
243+
0, 0, 0, 0,
207244
vmName, vmVersion, gcAlgorithm,
208245
List.copyOf(vmFlags)
209246
);
210247
}
211248

212-
/**
213-
* Parse GC.heap_info output into pool map.
214-
* Format varies by GC but generally:
215-
* garbage-first heap total 262144K, used 45678K [...]
216-
* region size 1024K, 10 young, 2 survivors
217-
* Metaspace used 34567K, ...
218-
*/
219249
private static void parseHeapInfo(String output, Map<String, JvmSnapshot.PoolInfo> pools) {
220250
if (output == null) return;
221251
Pattern sizePattern = Pattern.compile("(\\w[\\w\\s-]*)\\s+(?:total\\s+)?(\\d+)K.*used\\s+(\\d+)K");

β€Žargus-cli/src/main/java/io/argus/cli/tui/TuiApp.javaβ€Ž

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -127,29 +127,27 @@ private void handleKey(int key, NonBlockingReader reader) throws Exception {
127127
return;
128128
}
129129

130+
if (key == 27) {
131+
// ESC β€” check for arrow key sequence first
132+
int next = reader.read(50);
133+
if (next == '[') {
134+
int arrow = reader.read(50);
135+
if (arrow == 'A') { moveUp(); return; }
136+
if (arrow == 'B') { moveDown(); return; }
137+
}
138+
// Plain ESC β€” back from output or clear search
139+
if (showOutput) showOutput = false;
140+
else if (!searchQuery.isEmpty()) { searchQuery = ""; applyFilter(); }
141+
return;
142+
}
143+
130144
switch (key) {
131145
case 'q', 'Q' -> running = false;
132146
case '/' -> { searching = true; searchQuery = ""; }
133-
case 27 -> { // Escape β€” back from output
134-
if (showOutput) showOutput = false;
135-
else if (!searchQuery.isEmpty()) { searchQuery = ""; applyFilter(); }
136-
}
137-
case 'j', 66 -> moveDown(); // j or ↓ (66 = arrow down after ESC[)
138-
case 'k', 65 -> moveUp(); // k or ↑
147+
case 'j' -> moveDown();
148+
case 'k' -> moveUp();
139149
case 10, 13 -> executeSelected(); // Enter
140-
case 'p', 'P' -> {} // PID change β€” future
141-
case '?' -> {} // Help β€” future
142-
default -> {
143-
// Handle arrow key sequences: ESC [ A/B
144-
if (key == 27) {
145-
int next = reader.read(50);
146-
if (next == '[') {
147-
int arrow = reader.read(50);
148-
if (arrow == 'A') moveUp();
149-
else if (arrow == 'B') moveDown();
150-
}
151-
}
152-
}
150+
default -> {}
153151
}
154152
}
155153

β€Žgradle.propertiesβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
argusVersion=0.8.0
1+
argusVersion=1.0.0
22
nettyVersion=4.1.104.Final
33
junitVersion=5.10.1

0 commit comments

Comments
Β (0)