From 222e879f3012a03cfb2ff6b0cfc4e4f975a769a6 Mon Sep 17 00:00:00 2001 From: rlaope Date: Tue, 14 Apr 2026 11:55:49 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20argus=20alert=20=E2=80=94=20metric=20mo?= =?UTF-8?q?nitoring=20with=20webhook=20notifications=20(#131)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AlertRule: configurable threshold rules with comparator support - AlertEngine: polls /prometheus, evaluates rules, deduplicates - WebhookSender: HTTP POST JSON alerts via java.net.http.HttpClient - AlertCommand: --gc-overhead, --leak, --webhook, --config flags - Config file: simple key=value format (no YAML dependency) Signed-off-by: rlaope --- .../src/main/java/io/argus/cli/ArgusCli.java | 2 + .../java/io/argus/cli/alert/AlertEngine.java | 170 +++++++++++++ .../java/io/argus/cli/alert/AlertRule.java | 30 +++ .../io/argus/cli/alert/WebhookSender.java | 95 +++++++ .../io/argus/cli/command/AlertCommand.java | 232 ++++++++++++++++++ .../src/main/resources/messages_en.properties | 1 + .../src/main/resources/messages_ja.properties | 1 + .../src/main/resources/messages_ko.properties | 1 + .../src/main/resources/messages_zh.properties | 1 + completions/argus.bash | 3 +- completions/argus.fish | 3 +- completions/argus.ps1 | 1 + completions/argus.zsh | 1 + 13 files changed, 539 insertions(+), 2 deletions(-) create mode 100644 argus-cli/src/main/java/io/argus/cli/alert/AlertEngine.java create mode 100644 argus-cli/src/main/java/io/argus/cli/alert/AlertRule.java create mode 100644 argus-cli/src/main/java/io/argus/cli/alert/WebhookSender.java create mode 100644 argus-cli/src/main/java/io/argus/cli/command/AlertCommand.java diff --git a/argus-cli/src/main/java/io/argus/cli/ArgusCli.java b/argus-cli/src/main/java/io/argus/cli/ArgusCli.java index 91e14b2..4a46d51 100644 --- a/argus-cli/src/main/java/io/argus/cli/ArgusCli.java +++ b/argus-cli/src/main/java/io/argus/cli/ArgusCli.java @@ -1,5 +1,6 @@ package io.argus.cli; +import io.argus.cli.command.AlertCommand; import io.argus.cli.command.Command; import io.argus.cli.command.CommandExitException; import io.argus.cli.command.ClusterCommand; @@ -154,6 +155,7 @@ public static void main(String[] args) { // Register all commands Map commands = new LinkedHashMap<>(); + register(commands, new AlertCommand()); register(commands, new ClusterCommand()); register(commands, new InitCommand()); register(commands, new PsCommand()); diff --git a/argus-cli/src/main/java/io/argus/cli/alert/AlertEngine.java b/argus-cli/src/main/java/io/argus/cli/alert/AlertEngine.java new file mode 100644 index 0000000..a482cc9 --- /dev/null +++ b/argus-cli/src/main/java/io/argus/cli/alert/AlertEngine.java @@ -0,0 +1,170 @@ +package io.argus.cli.alert; + +import io.argus.cli.cluster.PrometheusTextParser; +import io.argus.cli.render.AnsiStyle; +import io.argus.cli.render.RichRenderer; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Polls a Prometheus endpoint at a configurable interval, evaluates alert rules + * against current metric values, and dispatches webhook notifications on breach. + * + *

Deduplication: an alert fires once when it first breaches its threshold, and + * is suppressed until the metric returns to a non-breaching state. + */ +public final class AlertEngine { + + private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("HH:mm:ss"); + private static final int WIDTH = RichRenderer.DEFAULT_WIDTH; + + private final String target; + private final int intervalSeconds; + private final List rules; + private final boolean useColor; + private final WebhookSender webhookSender; + + /** Tracks which rule names are currently in a fired (breached) state. */ + private final Set firedAlerts = new HashSet<>(); + + public AlertEngine(String target, int intervalSeconds, List rules, boolean useColor) { + this.target = target; + this.intervalSeconds = intervalSeconds; + this.rules = rules; + this.useColor = useColor; + this.webhookSender = new WebhookSender(); + } + + /** + * Starts the polling loop. Blocks until interrupted (e.g. Ctrl+C). + */ + public void run() { + String intervalLabel = intervalSeconds + "s"; + System.out.println(RichRenderer.boxHeader(useColor, "Alert Monitor", + WIDTH, target, "checking every " + intervalLabel)); + System.out.println(RichRenderer.emptyLine(WIDTH)); + + String rulesLine = " Rules: " + rules.size() + " active"; + System.out.println(RichRenderer.boxLine( + AnsiStyle.style(useColor, AnsiStyle.BOLD) + rulesLine + + AnsiStyle.style(useColor, AnsiStyle.RESET), WIDTH)); + System.out.println(RichRenderer.emptyLine(WIDTH)); + + HttpClient client = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(5)) + .build(); + + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + System.out.println(RichRenderer.emptyLine(WIDTH)); + System.out.println(RichRenderer.boxLine( + AnsiStyle.style(useColor, AnsiStyle.DIM) + " Stopped." + + AnsiStyle.style(useColor, AnsiStyle.RESET), WIDTH)); + System.out.println(RichRenderer.boxFooter(useColor, null, WIDTH)); + }, "argus-alert-cleanup")); + + try { + while (!Thread.currentThread().isInterrupted()) { + poll(client); + Thread.sleep(intervalSeconds * 1000L); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + private void poll(HttpClient client) { + String now = LocalTime.now().format(TIME_FMT); + Map metrics = fetchMetrics(client); + + for (AlertRule rule : rules) { + Double rawValue = metrics.get(rule.metric()); + if (rawValue == null) { + // Metric not found — treat as zero for threshold evaluation + rawValue = 0.0; + } + double value = rawValue; + boolean breached = rule.isBreached(value); + + String line; + if (breached) { + String displayValue = formatValue(rule, value); + String thresholdDisplay = formatThreshold(rule); + boolean alreadyFired = firedAlerts.contains(rule.name()); + if (!alreadyFired) { + firedAlerts.add(rule.name()); + webhookSender.send(rule, value, target); + line = " [" + now + "] " + + AnsiStyle.style(useColor, AnsiStyle.YELLOW) + "\u26a0 " + + rule.name() + ": " + displayValue + " EXCEEDED \u2192 webhook sent" + + AnsiStyle.style(useColor, AnsiStyle.RESET); + } else { + line = " [" + now + "] " + + AnsiStyle.style(useColor, AnsiStyle.YELLOW) + "\u26a0 " + + rule.name() + ": " + displayValue + " EXCEEDED (ongoing)" + + AnsiStyle.style(useColor, AnsiStyle.RESET); + } + } else { + // Resolved — clear dedup state + firedAlerts.remove(rule.name()); + String displayValue = formatValue(rule, value); + line = " [" + now + "] " + + AnsiStyle.style(useColor, AnsiStyle.GREEN) + "\u2713 " + + rule.name() + ": " + displayValue + + AnsiStyle.style(useColor, AnsiStyle.RESET); + } + System.out.println(RichRenderer.boxLine(line, WIDTH)); + } + } + + private Map fetchMetrics(HttpClient client) { + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create("http://" + target + "/prometheus")) + .timeout(Duration.ofSeconds(5)) + .GET() + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + return PrometheusTextParser.parse(response.body()); + } + System.err.println("[alert] HTTP " + response.statusCode() + " from " + target); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (Exception e) { + System.err.println("[alert] Failed to reach " + target + ": " + e.getMessage()); + } + return Map.of(); + } + + /** + * Formats the metric value for display. Percentage metrics are shown as percentages, + * boolean-style metrics (threshold=1) are shown as detected/not detected. + */ + private static String formatValue(AlertRule rule, double value) { + if (rule.threshold() <= 1.0 && rule.metric().contains("suspected")) { + return value >= 1.0 ? "detected" : "not detected"; + } + // Show as percentage if metric name contains "ratio" or "overhead" + if (rule.metric().contains("ratio") || rule.metric().contains("overhead")) { + return String.format("%.1f%%", value * 100); + } + return String.format("%.1f", value); + } + + private static String formatThreshold(AlertRule rule) { + if (rule.metric().contains("ratio") || rule.metric().contains("overhead")) { + return String.format("%.0f%%", rule.threshold() * 100); + } + return String.format("%.1f", rule.threshold()); + } +} diff --git a/argus-cli/src/main/java/io/argus/cli/alert/AlertRule.java b/argus-cli/src/main/java/io/argus/cli/alert/AlertRule.java new file mode 100644 index 0000000..3b60d4e --- /dev/null +++ b/argus-cli/src/main/java/io/argus/cli/alert/AlertRule.java @@ -0,0 +1,30 @@ +package io.argus.cli.alert; + +/** + * A single alerting rule that compares a Prometheus metric value against a threshold. + * + * @param name human-readable rule name (e.g. "gc-overhead") + * @param metric Prometheus metric name (e.g. "argus_gc_overhead_ratio") + * @param threshold numeric threshold value + * @param comparator ">" or "<" — defaults to ">" when unrecognised + * @param severity "warning" or "critical" + * @param webhookUrl destination webhook URL (may be null) + */ +public record AlertRule( + String name, + String metric, + double threshold, + String comparator, + String severity, + String webhookUrl) { + + /** Returns true when the given metric value breaches this rule's threshold. */ + public boolean isBreached(double value) { + return switch (comparator) { + case "<" -> value < threshold; + case "<=" -> value <= threshold; + case ">=" -> value >= threshold; + default -> value > threshold; // ">" and fallback + }; + } +} diff --git a/argus-cli/src/main/java/io/argus/cli/alert/WebhookSender.java b/argus-cli/src/main/java/io/argus/cli/alert/WebhookSender.java new file mode 100644 index 0000000..0aaeacf --- /dev/null +++ b/argus-cli/src/main/java/io/argus/cli/alert/WebhookSender.java @@ -0,0 +1,95 @@ +package io.argus.cli.alert; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.time.Instant; + +/** + * Sends a JSON alert payload to a configured webhook URL via HTTP POST. + * + *

Payload format: + *

+ * {
+ *   "alert":     "ArgusGCOverhead",
+ *   "severity":  "warning",
+ *   "value":     12.3,
+ *   "threshold": 10.0,
+ *   "instance":  "localhost:9202",
+ *   "timestamp": "2024-01-01T14:24:01Z"
+ * }
+ * 
+ */ +public final class WebhookSender { + + private final HttpClient client; + + public WebhookSender() { + this.client = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(5)) + .build(); + } + + /** + * Posts an alert notification to the webhook URL of the given rule. + * Failures are logged to stderr but do not throw. + */ + public void send(AlertRule rule, double value, String instance) { + if (rule.webhookUrl() == null || rule.webhookUrl().isBlank()) { + return; + } + String alertName = "Argus" + toTitleCase(rule.name()); + String timestamp = Instant.now().toString(); + String json = "{" + + "\"alert\":\"" + escapeJson(alertName) + "\"," + + "\"severity\":\"" + escapeJson(rule.severity()) + "\"," + + "\"value\":" + value + "," + + "\"threshold\":" + rule.threshold() + "," + + "\"instance\":\"" + escapeJson(instance) + "\"," + + "\"timestamp\":\"" + timestamp + "\"" + + "}"; + + try { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(rule.webhookUrl())) + .timeout(Duration.ofSeconds(10)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(json)) + .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.discarding()); + if (response.statusCode() < 200 || response.statusCode() >= 300) { + System.err.println("[alert] Webhook returned HTTP " + response.statusCode() + + " for rule '" + rule.name() + "'"); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (Exception e) { + System.err.println("[alert] Webhook delivery failed for rule '" + rule.name() + + "': " + e.getMessage()); + } + } + + /** Converts "gc-overhead" → "GcOverhead" for use in alert names. */ + private static String toTitleCase(String s) { + StringBuilder sb = new StringBuilder(); + boolean nextUpper = true; + for (char c : s.toCharArray()) { + if (c == '-' || c == '_') { + nextUpper = true; + } else if (nextUpper) { + sb.append(Character.toUpperCase(c)); + nextUpper = false; + } else { + sb.append(c); + } + } + return sb.toString(); + } + + private static String escapeJson(String s) { + if (s == null) return ""; + return s.replace("\\", "\\\\").replace("\"", "\\\""); + } +} diff --git a/argus-cli/src/main/java/io/argus/cli/command/AlertCommand.java b/argus-cli/src/main/java/io/argus/cli/command/AlertCommand.java new file mode 100644 index 0000000..f31eacc --- /dev/null +++ b/argus-cli/src/main/java/io/argus/cli/command/AlertCommand.java @@ -0,0 +1,232 @@ +package io.argus.cli.command; + +import io.argus.cli.alert.AlertEngine; +import io.argus.cli.alert.AlertRule; +import io.argus.cli.config.CliConfig; +import io.argus.cli.config.Messages; +import io.argus.cli.provider.ProviderRegistry; +import io.argus.core.command.CommandGroup; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Monitors Argus instances via their /prometheus endpoint and sends webhook + * alerts when metric thresholds are breached. + * + *

Usage: + *

+ *   argus alert localhost:9202 --gc-overhead=10 --leak --webhook=https://hooks.slack.com/xxx
+ *   argus alert --config=alerts.yml
+ * 
+ * + *

Config file format (simple key=value, no YAML library required): + *

+ *   target=localhost:9202
+ *   interval=30
+ *   rule.gc-overhead.metric=argus_gc_overhead_ratio
+ *   rule.gc-overhead.threshold=0.10
+ *   rule.gc-overhead.severity=warning
+ *   rule.leak.metric=argus_gc_leak_suspected
+ *   rule.leak.threshold=1
+ *   rule.leak.severity=critical
+ *   webhook=https://hooks.slack.com/xxx
+ * 
+ */ +public final class AlertCommand implements Command { + + private static final int DEFAULT_INTERVAL = 30; + + @Override + public String name() { return "alert"; } + + @Override + public CommandGroup group() { return CommandGroup.MONITORING; } + + @Override + public CommandMode mode() { return CommandMode.WRITE; } + + @Override + public boolean supportsTui() { return false; } + + @Override + public String description(Messages messages) { + return messages.get("cmd.alert.desc"); + } + + @Override + public void execute(String[] args, CliConfig config, ProviderRegistry registry, Messages messages) { + String configFile = null; + String target = null; + String webhook = null; + int interval = DEFAULT_INTERVAL; + double gcOverhead = -1; + boolean leak = false; + + for (String arg : args) { + if (arg.startsWith("--config=")) { + configFile = arg.substring(9); + } else if (arg.startsWith("--gc-overhead=")) { + try { gcOverhead = Double.parseDouble(arg.substring(14)); } catch (NumberFormatException ignored) {} + } else if (arg.equals("--leak")) { + leak = true; + } else if (arg.startsWith("--webhook=")) { + webhook = arg.substring(10); + } else if (arg.startsWith("--interval=")) { + try { interval = Integer.parseInt(arg.substring(11)); } catch (NumberFormatException ignored) {} + } else if (!arg.startsWith("--")) { + target = arg; + } + } + + List rules; + if (configFile != null) { + ConfigResult cfg = loadConfig(configFile); + if (cfg == null) return; + target = cfg.target != null ? cfg.target : target; + interval = cfg.interval > 0 ? cfg.interval : interval; + webhook = cfg.webhook != null ? cfg.webhook : webhook; + rules = cfg.rules; + } else { + rules = buildRulesFromFlags(gcOverhead, leak, webhook); + } + + if (target == null || target.isBlank()) { + System.err.println("Error: no target specified. Provide host:port or use --config=FILE"); + printHelp(); + return; + } + + if (rules.isEmpty()) { + System.err.println("Error: no alert rules configured. Use --gc-overhead=N, --leak, or --config=FILE"); + printHelp(); + return; + } + + AlertEngine engine = new AlertEngine(target, interval, rules, config.color()); + engine.run(); + } + + // ── Rule construction from CLI flags ──────────────────────────────────── + + private List buildRulesFromFlags(double gcOverhead, boolean leak, String webhook) { + List rules = new ArrayList<>(); + if (gcOverhead >= 0) { + // User passes percentage (10 = 10%), Prometheus metric is a ratio (0.10) + rules.add(new AlertRule( + "gc-overhead", + "argus_gc_overhead_ratio", + gcOverhead / 100.0, + ">", + "warning", + webhook)); + } + if (leak) { + rules.add(new AlertRule( + "leak", + "argus_gc_leak_suspected", + 1.0, + ">=", + "critical", + webhook)); + } + return rules; + } + + // ── Config file loading ───────────────────────────────────────────────── + + private ConfigResult loadConfig(String filePath) { + List lines; + try { + lines = Files.readAllLines(Path.of(filePath)); + } catch (IOException e) { + System.err.println("Error: cannot read config file '" + filePath + "': " + e.getMessage()); + return null; + } + + String target = null; + int interval = DEFAULT_INTERVAL; + String webhook = null; + + // Collect rule sub-keys: rule..= + Map> ruleProps = new HashMap<>(); + + for (String rawLine : lines) { + String line = rawLine.trim(); + if (line.isEmpty() || line.startsWith("#")) continue; + int eq = line.indexOf('='); + if (eq < 0) continue; + String key = line.substring(0, eq).trim(); + String value = line.substring(eq + 1).trim(); + + if ("target".equals(key)) { + target = value; + } else if ("interval".equals(key)) { + try { interval = Integer.parseInt(value); } catch (NumberFormatException ignored) {} + } else if ("webhook".equals(key)) { + webhook = value; + } else if (key.startsWith("rule.")) { + // rule.. + String rest = key.substring(5); // . + int dot = rest.lastIndexOf('.'); + if (dot < 0) continue; + String ruleName = rest.substring(0, dot); + String field = rest.substring(dot + 1); + ruleProps.computeIfAbsent(ruleName, k -> new HashMap<>()).put(field, value); + } + } + + List rules = new ArrayList<>(); + for (Map.Entry> entry : ruleProps.entrySet()) { + String ruleName = entry.getKey(); + Map props = entry.getValue(); + String metric = props.get("metric"); + String thresholdStr = props.get("threshold"); + String severity = props.getOrDefault("severity", "warning"); + String comparator = props.getOrDefault("comparator", ">"); + String ruleWebhook = props.getOrDefault("webhook", webhook); + + if (metric == null || thresholdStr == null) { + System.err.println("Warning: rule '" + ruleName + "' missing metric or threshold — skipped"); + continue; + } + double threshold; + try { + threshold = Double.parseDouble(thresholdStr); + } catch (NumberFormatException e) { + System.err.println("Warning: rule '" + ruleName + "' invalid threshold '" + thresholdStr + "' — skipped"); + continue; + } + rules.add(new AlertRule(ruleName, metric, threshold, comparator, severity, ruleWebhook)); + } + + return new ConfigResult(target, interval, webhook, rules); + } + + private record ConfigResult(String target, int interval, String webhook, List rules) {} + + // ── Help ──────────────────────────────────────────────────────────────── + + private static void printHelp() { + System.out.println(); + System.out.println("Usage: argus alert [options]"); + System.out.println(" argus alert --config=FILE"); + System.out.println(); + System.out.println("Options:"); + System.out.println(" --gc-overhead=N Alert when GC overhead exceeds N% (e.g. 10)"); + System.out.println(" --leak Alert when memory leak is suspected"); + System.out.println(" --webhook=URL Webhook URL for notifications"); + System.out.println(" --interval=N Poll interval in seconds (default: 30)"); + System.out.println(" --config=FILE Load rules from config file"); + System.out.println(); + System.out.println("Examples:"); + System.out.println(" argus alert localhost:9202 --gc-overhead=10 --webhook=https://hooks.slack.com/xxx"); + System.out.println(" argus alert localhost:9202 --leak --interval=60"); + System.out.println(" argus alert --config=alerts.yml"); + } +} diff --git a/argus-cli/src/main/resources/messages_en.properties b/argus-cli/src/main/resources/messages_en.properties index 23d1391..f7dd571 100644 --- a/argus-cli/src/main/resources/messages_en.properties +++ b/argus-cli/src/main/resources/messages_en.properties @@ -1,4 +1,5 @@ # Commands +cmd.alert.desc=Monitor JVM metrics and send webhook alerts on threshold breach cmd.cluster.desc=Aggregated health view across multiple JVM instances cmd.init.desc=Initialize Argus CLI configuration cmd.ps.desc=List running JVM processes diff --git a/argus-cli/src/main/resources/messages_ja.properties b/argus-cli/src/main/resources/messages_ja.properties index 7241ac6..dbe5330 100644 --- a/argus-cli/src/main/resources/messages_ja.properties +++ b/argus-cli/src/main/resources/messages_ja.properties @@ -1,4 +1,5 @@ # Commands +cmd.alert.desc=\u30E1\u30C8\u30EA\u30AF\u30B9\u76E3\u8996\u3068\u958B\u3058\u5024\u8D85\u904E\u6642\u306EWebhook\u901A\u77E5 cmd.cluster.desc=\u8907\u6570\u306EJVM\u30A4\u30F3\u30B9\u30BF\u30F3\u30B9\u306E\u96C6\u7D04\u30D8\u30EB\u30B9\u30D3\u30E5\u30FC header.cluster=\u30AF\u30E9\u30B9\u30BF\u30FC\u30D8\u30EB\u30B9 error.cluster.no.targets=\u30BF\u30FC\u30B2\u30C3\u30C8\u304C\u6307\u5B9A\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002host:port\u5F15\u6570\u307E\u305F\u306F--file=FILE\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044\u3002 diff --git a/argus-cli/src/main/resources/messages_ko.properties b/argus-cli/src/main/resources/messages_ko.properties index 5e29988..551b68a 100644 --- a/argus-cli/src/main/resources/messages_ko.properties +++ b/argus-cli/src/main/resources/messages_ko.properties @@ -1,4 +1,5 @@ # Commands +cmd.alert.desc=\uBA54\uD2B8\uB9AD \uBAA8\uB2C8\uD130\uB9C1 \uBC0F \uC784\uACC4\uAC12 \uCD08\uACFC \uC2DC \uc6F9\ub9F9 \uc54c\ub9BC \uc804\uc1a1 cmd.cluster.desc=\uC5EC\uB7EC JVM \uC778\uC2A4\uD134\uC2A4\uC758 \uC9D1\uACC4 \uC0C1\uD0DC \uBDF0 header.cluster=\uD074\uB7EC\uC2A4\uD130 \uC0C1\uD0DC error.cluster.no.targets=\uB300\uC0C1\uC774 \uC9C0\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. host:port \uC778\uC218 \uB610\uB294 --file=FILE\uC744 \uC0AC\uC6A9\uD558\uC138\uC694. diff --git a/argus-cli/src/main/resources/messages_zh.properties b/argus-cli/src/main/resources/messages_zh.properties index 59015a0..8ef8952 100644 --- a/argus-cli/src/main/resources/messages_zh.properties +++ b/argus-cli/src/main/resources/messages_zh.properties @@ -1,4 +1,5 @@ # Commands +cmd.alert.desc=\u76D1\u63A7JVM\u6307\u6807\u5E76\u5728\u9608\u503C\u8D85\u8FC7\u65F6\u53D1\u9001Webhook\u8B66\u62A5 cmd.cluster.desc=\u591A\u4E2AJVM\u5B9E\u4F8B\u7684\u805A\u5408\u5065\u5EB7\u89C6\u56FE header.cluster=\u96C6\u7FA4\u5065\u5EB7 error.cluster.no.targets=\u672A\u6307\u5B9A\u76EE\u6807\u3002\u8BF7\u63D0\u4F9Bhost:port\u53C2\u6570\u6216--file=FILE\u3002 diff --git a/completions/argus.bash b/completions/argus.bash index d6846ed..11a0a4a 100644 --- a/completions/argus.bash +++ b/completions/argus.bash @@ -3,7 +3,7 @@ _argus_completions() { cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - commands="init ps histo threads gc gcutil heap sysprops vmflag nmt classloader profile jfr jfranalyze diff report doctor gclog gclogdiff gcprofile flame suggest watch tui info heapdump heapanalyze deadlock threaddump buffers gcrun logger events compilerqueue sc env compiler finalizer stringtable pool gccause metaspace dynlibs vmset vmlog jmx classstat gcnew symboltable top perfcounter mbean ci compare slowlog explain" + commands="alert init ps histo threads gc gcutil heap sysprops vmflag nmt classloader profile jfr jfranalyze diff report doctor gclog gclogdiff gcprofile flame suggest watch tui info heapdump heapanalyze deadlock threaddump buffers gcrun logger events compilerqueue sc env compiler finalizer stringtable pool gccause metaspace dynlibs vmset vmlog jmx classstat gcnew symboltable top perfcounter mbean ci compare slowlog explain" if [ "$COMP_CWORD" -eq 1 ]; then COMPREPLY=($(compgen -W "$commands --help --version" -- "$cur")) @@ -41,6 +41,7 @@ _argus_completions() { vmset) opts="$opts --yes" ;; jmx) opts="$opts" ;; diff) opts="$opts --top=" ;; + alert) opts="$opts --config= --gc-overhead= --leak --webhook= --interval=" ;; top) opts="$opts --host= --port= --interval=" ;; esac COMPREPLY=($(compgen -W "$opts" -- "$cur")) diff --git a/completions/argus.fish b/completions/argus.fish index c201db6..e062420 100644 --- a/completions/argus.fish +++ b/completions/argus.fish @@ -1,11 +1,12 @@ # Fish completions for argus -set -l commands init ps histo threads gc gcutil heap sysprops vmflag nmt classloader profile jfr jfranalyze diff report doctor gclog gclogdiff gcprofile flame suggest watch info heapdump deadlock threaddump buffers gcrun logger events compilerqueue sc env compiler finalizer stringtable pool gccause metaspace dynlibs vmset vmlog jmx classstat gcnew symboltable top explain +set -l commands alert init ps histo threads gc gcutil heap sysprops vmflag nmt classloader profile jfr jfranalyze diff report doctor gclog gclogdiff gcprofile flame suggest watch info heapdump deadlock threaddump buffers gcrun logger events compilerqueue sc env compiler finalizer stringtable pool gccause metaspace dynlibs vmset vmlog jmx classstat gcnew symboltable top explain # Disable file completions for argus complete -c argus -f # Commands +complete -c argus -n "not __fish_seen_subcommand_from $commands" -a alert -d 'Monitor JVM metrics and send webhook alerts on threshold breach' complete -c argus -n "not __fish_seen_subcommand_from $commands" -a init -d 'Initialize CLI configuration' complete -c argus -n "not __fish_seen_subcommand_from $commands" -a ps -d 'List running JVM processes' complete -c argus -n "not __fish_seen_subcommand_from $commands" -a histo -d 'Heap object histogram' diff --git a/completions/argus.ps1 b/completions/argus.ps1 index 390ba1f..6c57114 100644 --- a/completions/argus.ps1 +++ b/completions/argus.ps1 @@ -1,6 +1,7 @@ # PowerShell completions for argus $ArgusCommands = @( + @{ Name = 'alert'; Desc = 'Monitor JVM metrics and send webhook alerts on threshold breach' } @{ Name = 'init'; Desc = 'Initialize CLI configuration' } @{ Name = 'ps'; Desc = 'List running JVM processes' } @{ Name = 'histo'; Desc = 'Heap object histogram' } diff --git a/completions/argus.zsh b/completions/argus.zsh index bd2c1b2..1636c3a 100644 --- a/completions/argus.zsh +++ b/completions/argus.zsh @@ -3,6 +3,7 @@ _argus() { local -a commands commands=( + 'alert:Monitor JVM metrics and send webhook alerts on threshold breach' 'init:Initialize CLI configuration' 'ps:List running JVM processes' 'histo:Heap object histogram'