]> git.ipfire.org Git - thirdparty/mtr.git/commitdiff
feat(curses): add compact and scale controls 580/head
authorDarafei Praliaskouski <me@komzpa.net>
Sat, 9 May 2026 09:39:26 +0000 (13:39 +0400)
committerDarafei Praliaskouski <me@komzpa.net>
Mon, 25 May 2026 11:42:48 +0000 (15:42 +0400)
bash-completion/mtr
man/mtr.8.in
ui/curses.c
ui/mtr.c
ui/mtr.h

index bc60346e92783a560e63d03643861025eb1ef7ca..348b925832b6a59f291bacd1ce040c56f598886c 100644 (file)
@@ -56,7 +56,8 @@ _mtr_module()
         --interval --gracetime --tos --mpls --timeout --mark --report
         --report-wide --report-on-exit --report-cycles --json --xml --csv
         --raw --split
-        --curses --displaymode --gtk --no-dns --show-ips --order --ipinfo
+        --curses --displaymode --compact --scale --gtk
+        --no-dns --show-ips --order --ipinfo
         --aslookup --help --version
       '
       COMPREPLY=( $(compgen -W "${OPTS[*]}" -- "$cur") )
index 80bdf2309da104e73ab1e11c3ef965822ae52b4c..59c68a7bf6c97740923a3fa04103617657d6fd64 100644 (file)
@@ -32,6 +32,12 @@ mtr \- a network diagnostic tool
 .BI \--displaymode \ MODE\c
 ]
 [\c
+.B \-\-compact\c
+]
+[\c
+.BI \--scale \ SCALE\c
+]
+[\c
 .B \-\-raw\c
 ]
 [\c
@@ -253,6 +259,22 @@ buckets, and
 .B >
 marks values above the last displayed bucket.
 .TP
+.B -\-compact
+Use this option to start the curses interface in compact mode.
+.TP
+.B -\-scale \fISCALE
+Use this option to select fixed latency thresholds for the stripchart
+display modes.
+.I SCALE
+can be
+.BR fast ,
+.BR average ,
+.BR slow ,
+or nine strictly increasing millisecond thresholds separated by colons,
+for example
+.BR 5:15:25:35:45:55:101:200:400 .
+Without this option, the stripchart keeps using the dynamic scale.
+.TP
 .B \-g\fR, \fB\-\-gtk
 Use this option to force
 .B mtr
index 01abc7fc2f8ad8ea9da3a7aba113b5a5eabb9f1a..77b33b2ddb7a82dc0913dd8cf2d26b76b0fe8c0b 100644 (file)
@@ -20,6 +20,7 @@
 
 #include "mtr.h"
 
+#include <limits.h>
 #include <locale.h>
 #ifdef __CYGWIN__
 #include <windows.h>
 #include "utils.h"
 
 
-enum { NUM_FACTORS = 8 };
+enum { NUM_FACTORS = MTR_SCALE_FACTORS };
 static double factors[NUM_FACTORS];
 static int scale[NUM_FACTORS];
 static char block_map[NUM_FACTORS];
 #ifdef WITH_BRAILLE_DISPLAY
 static const wchar_t *braille_map[NUM_FACTORS] = {
-    L"⣀", L"⣀", L"⣤", L"⣤", L"⣶", L"⣶", L"⣿", L"⣿"
+    L"â£\80", L"â£\80", L"⣤", L"⣤", L"⣦", L"⣦", L"⣶", L"⣶", L"⣿", L"⣿"
 };
 #endif
 
 enum { black = 1, red, green, yellow, blue, magenta, cyan, white };
 static const int block_col[NUM_FACTORS + 1] = {
     COLOR_PAIR(red) | A_BOLD,
-    A_NORMAL,
-    COLOR_PAIR(green),
     COLOR_PAIR(green) | A_BOLD,
+    COLOR_PAIR(green) | A_BOLD,
+    COLOR_PAIR(green) | A_BOLD,
+    COLOR_PAIR(yellow) | A_BOLD,
     COLOR_PAIR(yellow) | A_BOLD,
     COLOR_PAIR(magenta) | A_BOLD,
-    COLOR_PAIR(magenta),
+    COLOR_PAIR(red) | A_BOLD,
+    COLOR_PAIR(red) | A_BOLD,
     COLOR_PAIR(red),
-    COLOR_PAIR(red) | A_BOLD
+    COLOR_PAIR(red)
 };
 
 static void pwcenter(
@@ -553,6 +556,14 @@ static void mtr_gen_scale(
     for (i = 0; i < NUM_FACTORS; i++) {
         scale[i] = 0;
     }
+    if (ctl->fixed_scale) {
+        for (i = 0; i < MTR_SCALE_THRESHOLDS; i++) {
+            scale[i] = ctl->scale[i];
+        }
+        scale[NUM_FACTORS - 1] = INT_MAX;
+        return;
+    }
+
     max = net_max(ctl);
     for (at = ctl->display_offset; at < max; at++) {
         saved = net_saved_pings(at);
@@ -586,7 +597,7 @@ static void mtr_curses_init(
     }
 
     /* Initialize block_map.  The block_split is always smaller than 9 */
-    block_split = (NUM_FACTORS - 2) / 2;
+    block_split = NUM_FACTORS / 2;
     for (i = 1; i <= block_split; i++) {
         block_map[i] = '0' + i;
     }
@@ -788,7 +799,7 @@ static void mtr_fill_graph(
             attrset(A_NORMAL);
         } else {
             if (ctl->display_mode == DisplayModeBlockmap) {
-                if (saved[i] > scale[6]) {
+                if (saved[i] > scale[NUM_FACTORS - 2]) {
                     printw("%c", block_map[NUM_FACTORS - 1]);
                 } else {
                     printw(".");
index fd97680555fb7372f8bc8335005793da0df35d6d..3214122ecce60f8f7842b0954e1603a81d760add 100644 (file)
--- a/ui/mtr.c
+++ b/ui/mtr.c
@@ -138,8 +138,10 @@ static void __attribute__ ((__noreturn__)) usage(FILE * out)
     fputs(" -p, --split                      split output\n", out);
 #ifdef HAVE_CURSES
     fputs(" -t, --curses                     use curses terminal interface\n", out);
-#endif
     fputs("     --displaymode MODE           select initial display mode\n", out);
+    fputs("     --compact                    start curses interface in compact mode\n", out);
+    fputs("     --scale SCALE                set stripchart scale thresholds\n", out);
+#endif
 #ifdef HAVE_GTK
     fputs(" -g, --gtk                        use GTK+ xwindow interface\n", out);
 #endif
@@ -405,6 +407,89 @@ static void print_version(
 }
 
 
+#ifdef HAVE_CURSES
+static void set_fixed_scale(
+    struct mtr_ctl *ctl,
+    const int *thresholds)
+{
+    int i;
+
+    for (i = 0; i < MTR_SCALE_THRESHOLDS; i++) {
+        ctl->scale[i] = thresholds[i] * 1000;
+    }
+    ctl->fixed_scale = 1;
+}
+
+
+static void parse_scale(
+    struct mtr_ctl *ctl,
+    const char *scale_arg)
+{
+    static const int fast_scale[MTR_SCALE_THRESHOLDS] = {
+        1, 2, 3, 4, 5, 10, 20, 40, 80
+    };
+    static const int average_scale[MTR_SCALE_THRESHOLDS] = {
+        5, 15, 25, 35, 45, 55, 101, 200, 400
+    };
+    static const int slow_scale[MTR_SCALE_THRESHOLDS] = {
+        50, 100, 150, 200, 300, 500, 750, 1000, 2000
+    };
+    const char *cursor = scale_arg;
+    int thresholds[MTR_SCALE_THRESHOLDS];
+    int previous = -1;
+    int i;
+
+    if (!strcmp(scale_arg, "fast")) {
+        set_fixed_scale(ctl, fast_scale);
+        return;
+    }
+    if (!strcmp(scale_arg, "average")) {
+        set_fixed_scale(ctl, average_scale);
+        return;
+    }
+    if (!strcmp(scale_arg, "slow")) {
+        set_fixed_scale(ctl, slow_scale);
+        return;
+    }
+
+    for (i = 0; i < MTR_SCALE_THRESHOLDS; i++) {
+        char *end;
+        long threshold;
+
+        errno = 0;
+        threshold = strtol(cursor, &end, 10);
+        if (cursor == end || errno || threshold < 0 ||
+            threshold > INT_MAX / 1000) {
+            error(EXIT_FAILURE, 0, "invalid scale threshold: %s", scale_arg);
+        }
+        if (threshold <= previous) {
+            error(EXIT_FAILURE, 0,
+                  "scale thresholds must be strictly increasing: %s",
+                  scale_arg);
+        }
+
+        thresholds[i] = threshold;
+        previous = threshold;
+
+        if (i == MTR_SCALE_THRESHOLDS - 1) {
+            if (*end) {
+                error(EXIT_FAILURE, 0,
+                      "scale expects %d thresholds: %s",
+                      MTR_SCALE_THRESHOLDS, scale_arg);
+            }
+        } else if (*end != ':') {
+            error(EXIT_FAILURE, 0,
+                  "scale expects %d colon-separated thresholds: %s",
+                  MTR_SCALE_THRESHOLDS, scale_arg);
+        }
+        cursor = end + 1;
+    }
+
+    set_fixed_scale(ctl, thresholds);
+}
+#endif
+
+
 static void parse_arg(
     struct mtr_ctl *ctl,
     names_t ** names,
@@ -422,12 +507,14 @@ static void parse_arg(
     enum {
         OPT_DISPLAYMODE = CHAR_MAX + 1,
         OPT_REPORT_ON_EXIT = CHAR_MAX + 2,
-        OPT_IPINFO4 = CHAR_MAX + 3,
+        OPT_SCALE = CHAR_MAX + 3,
+        OPT_IPINFO4 = CHAR_MAX + 4,
+        OPT_COMPACT = CHAR_MAX + 5,
 #ifdef ENABLE_IPV6
-        OPT_IPINFO6 = CHAR_MAX + 4,
-        OPT_CACHE = CHAR_MAX + 5,
+        OPT_IPINFO6 = CHAR_MAX + 6,
+        OPT_CACHE = CHAR_MAX + 7,
 #else
-        OPT_CACHE = CHAR_MAX + 4,
+        OPT_CACHE = CHAR_MAX + 6,
 #endif /* ifdef ENABLE_IPV6 */
     };
     static const struct option long_options[] = {
@@ -456,7 +543,11 @@ static void parse_arg(
 #ifdef HAVE_JANSSON
         {"json", 0, NULL, 'j'},
 #endif
+#ifdef HAVE_CURSES
         {"displaymode", 1, NULL, OPT_DISPLAYMODE},
+        {"compact", 0, NULL, OPT_COMPACT},
+        {"scale", 1, NULL, OPT_SCALE},
+#endif
         {"split", 0, NULL, 'p'},        /* BL */
         /* maybe above should change to -d 'x' */
 
@@ -569,6 +660,7 @@ static void parse_arg(
             ctl->DisplayMode = DisplayXML;
             break;
 
+#ifdef HAVE_CURSES
         case OPT_DISPLAYMODE:
             ctl->display_mode =
                 strtoint_or_err(optarg, "invalid argument");
@@ -576,6 +668,13 @@ static void parse_arg(
                 error(EXIT_FAILURE, 0, "value out of range (%d - %d): %s",
                       DisplayModeDefault, (DisplayModeMAX - 1), optarg);
             break;
+        case OPT_COMPACT:
+            ctl->CompactLayout = 1;
+            break;
+        case OPT_SCALE:
+            parse_scale(ctl, optarg);
+            break;
+#endif
         case 'c':
             ctl->MaxPing =
                 strtoint_or_err(optarg, "invalid argument");
index 64cdc4a68fcbd05839e5d0d98e05bbf2b377b833..f3ad796d9ed1f71c4ca3a2c574961a10f3cb5e60 100644 (file)
--- a/ui/mtr.h
+++ b/ui/mtr.h
@@ -64,6 +64,11 @@ typedef int time_t;
 #define MAXFLD 20               /* max stats fields to display */
 #define FLD_INDEX_SZ 256
 
+enum {
+    MTR_SCALE_FACTORS = 10,
+    MTR_SCALE_THRESHOLDS = MTR_SCALE_FACTORS - 1
+};
+
 /* net related definitions */
 #define SAVED_PINGS 400
 #define MAX_PATH 128
@@ -116,6 +121,7 @@ struct mtr_ctl {
     int cache_timeout;          /* seconds to skip probes for recently seen hops */
     unsigned char fld_active[2 * MAXFLD];       /* SO_MARK to set for ping packet */
     int display_mode;           /* display mode selector */
+    int scale[MTR_SCALE_THRESHOLDS];    /* fixed stripchart scale thresholds */
     int fld_index[FLD_INDEX_SZ];        /* default display field (defined by key in net.h) and order */
     char available_options[MAXFLD];
     int display_offset;         /* only used in text mode */
@@ -126,7 +132,7 @@ struct mtr_ctl {
         use_dns:1, cache:1,
         show_ips:1,
         enablempls:1, dns:1, reportwide:1, Interactive:1, DisplayMode:5,
-        CompactLayout:1, ReportOnExit:1;
+        CompactLayout:1, ReportOnExit:1, fixed_scale:1;
 #ifdef HAVE_IPINFO
 #ifdef ENABLE_IPV6
     char *ipinfo_provider6;