feat(jvm): 完成资源治理与诊断增强

- 新增 JMX/Endpoint/Agent 三种 JVM 连接模式与配置归一化链路
- 支持资源浏览、变更预览、写入应用、审计记录与只读约束
- 接入 AI 结构化写入计划与诊断计划回填能力
- 新增 Agent Bridge、Arthas Tunnel、JMX Helper 诊断传输实现
- 增加诊断控制台、命令模板、输出历史与自动补全交互
- 补齐前后端契约、运行夹具与 JVM 相关回归测试
This commit is contained in:
Syngnat
2026-04-24 16:45:34 +08:00
parent d9b4c6a21b
commit 6f14e827ab
79 changed files with 18941 additions and 4349 deletions

View File

@@ -0,0 +1,46 @@
package com.gonavi.jmxhelper;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
public final class JmxHelperMain {
private JmxHelperMain() {
}
public static void main(String[] args) throws Exception {
Map<String, Object> response = new LinkedHashMap<>();
try {
Map<String, Object> request = readRequest(System.in);
response.putAll(JmxRuntime.handle(request));
response.put("ok", Boolean.TRUE);
} catch (Throwable error) {
response.clear();
response.put("ok", Boolean.FALSE);
response.put("error", error.getMessage() == null ? error.getClass().getName() : error.getMessage());
Map<String, Object> details = new LinkedHashMap<>();
details.put("exception", error.getClass().getName());
response.put("details", details);
}
System.out.print(MiniJson.stringify(response));
System.out.flush();
}
@SuppressWarnings("unchecked")
private static Map<String, Object> readRequest(InputStream inputStream) throws Exception {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
byte[] chunk = new byte[4096];
int read;
while ((read = inputStream.read(chunk)) >= 0) {
buffer.write(chunk, 0, read);
}
String payload = new String(buffer.toByteArray(), StandardCharsets.UTF_8);
Object parsed = MiniJson.parse(payload);
if (!(parsed instanceof Map<?, ?>)) {
throw new IllegalArgumentException("helper request must be a JSON object");
}
return (Map<String, Object>) parsed;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,316 @@
package com.gonavi.jmxhelper;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
final class MiniJson {
private MiniJson() {
}
static Object parse(String text) {
Parser parser = new Parser(text);
Object value = parser.parseValue();
parser.skipWhitespace();
if (!parser.isDone()) {
throw new IllegalArgumentException("JSON parsing did not consume the full payload");
}
return value;
}
static String stringify(Object value) {
StringBuilder builder = new StringBuilder();
writeValue(builder, value);
return builder.toString();
}
private static void writeValue(StringBuilder builder, Object value) {
if (value == null) {
builder.append("null");
return;
}
if (value instanceof String) {
writeString(builder, (String) value);
return;
}
if (value instanceof Number || value instanceof Boolean) {
builder.append(String.valueOf(value));
return;
}
if (value instanceof Map<?, ?>) {
builder.append('{');
Iterator<? extends Map.Entry<?, ?>> iterator = ((Map<?, ?>) value).entrySet().iterator();
boolean first = true;
while (iterator.hasNext()) {
Map.Entry<?, ?> entry = iterator.next();
if (!first) {
builder.append(',');
}
first = false;
writeString(builder, String.valueOf(entry.getKey()));
builder.append(':');
writeValue(builder, entry.getValue());
}
builder.append('}');
return;
}
if (value instanceof Iterable<?>) {
builder.append('[');
Iterator<?> iterator = ((Iterable<?>) value).iterator();
boolean first = true;
while (iterator.hasNext()) {
if (!first) {
builder.append(',');
}
first = false;
writeValue(builder, iterator.next());
}
builder.append(']');
return;
}
if (value.getClass().isArray()) {
builder.append('[');
int length = java.lang.reflect.Array.getLength(value);
for (int index = 0; index < length; index++) {
if (index > 0) {
builder.append(',');
}
writeValue(builder, java.lang.reflect.Array.get(value, index));
}
builder.append(']');
return;
}
writeString(builder, String.valueOf(value));
}
private static void writeString(StringBuilder builder, String value) {
builder.append('"');
for (int index = 0; index < value.length(); index++) {
char ch = value.charAt(index);
switch (ch) {
case '"':
builder.append("\\\"");
break;
case '\\':
builder.append("\\\\");
break;
case '\b':
builder.append("\\b");
break;
case '\f':
builder.append("\\f");
break;
case '\n':
builder.append("\\n");
break;
case '\r':
builder.append("\\r");
break;
case '\t':
builder.append("\\t");
break;
default:
if (ch < 0x20) {
builder.append(String.format("\\u%04x", (int) ch));
} else {
builder.append(ch);
}
break;
}
}
builder.append('"');
}
private static final class Parser {
private final String text;
private int index;
private Parser(String text) {
this.text = text == null ? "" : text;
this.index = 0;
}
private Object parseValue() {
skipWhitespace();
if (isDone()) {
throw new IllegalArgumentException("unexpected end of JSON input");
}
char ch = text.charAt(index);
switch (ch) {
case '{':
return parseObject();
case '[':
return parseArray();
case '"':
return parseString();
case 't':
expect("true");
return Boolean.TRUE;
case 'f':
expect("false");
return Boolean.FALSE;
case 'n':
expect("null");
return null;
default:
if (ch == '-' || Character.isDigit(ch)) {
return parseNumber();
}
throw new IllegalArgumentException("unexpected JSON token at index " + index);
}
}
private Map<String, Object> parseObject() {
expect("{");
LinkedHashMap<String, Object> result = new LinkedHashMap<>();
skipWhitespace();
if (peek('}')) {
expect("}");
return result;
}
while (true) {
String key = parseString();
skipWhitespace();
expect(":");
Object value = parseValue();
result.put(key, value);
skipWhitespace();
if (peek('}')) {
expect("}");
return result;
}
expect(",");
}
}
private List<Object> parseArray() {
expect("[");
ArrayList<Object> result = new ArrayList<>();
skipWhitespace();
if (peek(']')) {
expect("]");
return result;
}
while (true) {
result.add(parseValue());
skipWhitespace();
if (peek(']')) {
expect("]");
return result;
}
expect(",");
}
}
private String parseString() {
expect("\"");
StringBuilder builder = new StringBuilder();
while (!isDone()) {
char ch = text.charAt(index++);
if (ch == '"') {
return builder.toString();
}
if (ch != '\\') {
builder.append(ch);
continue;
}
if (isDone()) {
throw new IllegalArgumentException("unterminated escape sequence");
}
char escaped = text.charAt(index++);
switch (escaped) {
case '"':
case '\\':
case '/':
builder.append(escaped);
break;
case 'b':
builder.append('\b');
break;
case 'f':
builder.append('\f');
break;
case 'n':
builder.append('\n');
break;
case 'r':
builder.append('\r');
break;
case 't':
builder.append('\t');
break;
case 'u':
if (index + 4 > text.length()) {
throw new IllegalArgumentException("invalid unicode escape");
}
builder.append((char) Integer.parseInt(text.substring(index, index + 4), 16));
index += 4;
break;
default:
throw new IllegalArgumentException("unsupported escape sequence: \\" + escaped);
}
}
throw new IllegalArgumentException("unterminated JSON string");
}
private Number parseNumber() {
int start = index;
if (text.charAt(index) == '-') {
index++;
}
while (!isDone() && Character.isDigit(text.charAt(index))) {
index++;
}
boolean isFloat = false;
if (!isDone() && text.charAt(index) == '.') {
isFloat = true;
index++;
while (!isDone() && Character.isDigit(text.charAt(index))) {
index++;
}
}
if (!isDone() && (text.charAt(index) == 'e' || text.charAt(index) == 'E')) {
isFloat = true;
index++;
if (!isDone() && (text.charAt(index) == '+' || text.charAt(index) == '-')) {
index++;
}
while (!isDone() && Character.isDigit(text.charAt(index))) {
index++;
}
}
String raw = text.substring(start, index);
return isFloat ? Double.valueOf(raw) : Long.valueOf(raw);
}
private void expect(String expected) {
skipWhitespace();
if (!text.startsWith(expected, index)) {
throw new IllegalArgumentException("expected " + expected + " at index " + index);
}
index += expected.length();
}
private boolean peek(char ch) {
skipWhitespace();
return !isDone() && text.charAt(index) == ch;
}
private void skipWhitespace() {
while (!isDone()) {
char ch = text.charAt(index);
if (!Character.isWhitespace(ch)) {
return;
}
index++;
}
}
private boolean isDone() {
return index >= text.length();
}
}
}