This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch master
in repository terminology.

View the commit online.

commit 77303ff89e52d32416ddb6ddac09f1698c8efd75
Author: Boris Faure <[email protected]>
AuthorDate: Sat Aug 22 23:36:50 2026 +0200

    tybench: benchmark the pty intake path
---
 meson.build         |   7 ++
 meson_options.txt   |   4 +
 src/bin/meson.build |  28 ++++++
 src/bin/tybench.c   | 285 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 324 insertions(+)

diff --git a/meson.build b/meson.build
index f248aba3..94da1bc5 100644
--- a/meson.build
+++ b/meson.build
@@ -141,6 +141,13 @@ else
   message('Tests are disabled')
 endif
 
+benchmarks = get_option('benchmarks')
+if benchmarks
+  message('Benchmarks are enabled')
+else
+  message('Benchmarks are disabled')
+endif
+
 message('edje_cc set to:' + edje_cc)
 
 sed = find_program('sed')
diff --git a/meson_options.txt b/meson_options.txt
index be2b29da..601d215a 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -12,6 +12,10 @@ option('tests',
        type: 'boolean',
        value: false,
        description: 'Enable generating tytest, used to run tests. (default=false)')
+option('benchmarks',
+       type: 'boolean',
+       value: false,
+       description: 'Enable generating tybench, used to benchmark the pty intake path. (default=false)')
 option('nls',
        type: 'boolean',
        value: true,
diff --git a/src/bin/meson.build b/src/bin/meson.build
index 53a67c3e..0972c6d7 100644
--- a/src/bin/meson.build
+++ b/src/bin/meson.build
@@ -85,6 +85,23 @@ tytest_sources = ['termptyesc.c', 'termptyesc.h',
                   'unit_tests.h',
                   'tytest_common.c', 'tytest_common.h',
                   'tytest.c', 'tytest.h']
+tybench_sources = ['termptyesc.c', 'termptyesc.h',
+                  'backlog.c', 'backlog.h',
+                  'termptyops.c', 'termptyops.h',
+                  'termptydbl.c', 'termptydbl.h',
+                  'termptyext.c', 'termptyext.h',
+                  'termptygfx.c', 'termptygfx.h',
+                  'termpty.c', 'termpty.h',
+                  'termiointernals.c', 'termiointernals.h',
+                  'termiolink.c', 'termiolink.h',
+                  'config.c', 'config.h',
+                  'colors.c', 'colors.h',
+                  'sb.c', 'sb.h',
+                  'theme.h',
+                  'utils.c', 'utils.h',
+                  'utf8.c', 'utf8.h',
+                  'tytest_common.c', 'tytest_common.h',
+                  'tybench.c']
 
 executable('terminology',
            terminology_sources,
@@ -144,3 +161,14 @@ if tests
              c_args: '-DBINARY_TYTEST=1',
              dependencies: terminology_dependencies)
 endif
+
+if benchmarks
+  # BINARY_TYFUZZ for the headless setup, BINARY_TYBENCH to keep eina logging
+  # compiled in so the numbers include what it costs.
+  tybench = executable('tybench',
+             tybench_sources,
+             install: false,
+             include_directories: config_dir,
+             c_args: ['-DBINARY_TYFUZZ=1', '-DBINARY_TYBENCH=1'],
+             dependencies: terminology_dependencies)
+endif
diff --git a/src/bin/tybench.c b/src/bin/tybench.c
new file mode 100644
index 00000000..e857e371
--- /dev/null
+++ b/src/bin/tybench.c
@@ -0,0 +1,285 @@
+/* Throughput benchmark for the pty intake path.
+ *
+ * Feeds a corpus through exactly the code the shipping binary runs when bytes
+ * arrive from the pty -- UTF-8 decode plus termpty_handle_buf() -- and reports
+ * how fast it goes. The read() syscall itself is deliberately left out: it is
+ * measured by changing the chunk size rather than by timing the kernel.
+ *
+ * Built without EINA_LOG_LEVEL_MAXIMUM (see private.h) so that logging calls
+ * cost here what they cost in production.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <time.h>
+#include <sys/stat.h>
+
+#include "private.h"
+#include <Elementary.h>
+#include "config.h"
+#include "termpty.h"
+#include "tytest_common.h"
+
+int _log_domain = -1;
+
+#define DEFAULT_ITERATIONS 10
+#define DEFAULT_WARMUP      2
+#define DEFAULT_CHUNK    4096
+
+typedef struct tag_Corpus
+{
+   const char *name;
+   char       *data;
+   long        len;
+} Corpus;
+
+static double
+_now(void)
+{
+   struct timespec ts;
+
+   clock_gettime(CLOCK_MONOTONIC, &ts);
+   return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
+}
+
+static const char *
+_basename(const char *path)
+{
+   const char *slash = strrchr(path, '/');
+
+   return slash ? slash + 1 : path;
+}
+
+static Eina_Bool
+_corpus_load(Corpus *c, const char *path)
+{
+   struct stat st;
+   long got = 0;
+   int fd;
+
+   fd = open(path, O_RDONLY);
+   if (fd < 0)
+     {
+        fprintf(stderr, "tybench: cannot open %s\n", path);
+        return EINA_FALSE;
+     }
+   if (fstat(fd, &st) < 0 || st.st_size <= 0)
+     {
+        fprintf(stderr, "tybench: cannot size %s\n", path);
+        close(fd);
+        return EINA_FALSE;
+     }
+
+   c->len = (long)st.st_size;
+   c->data = ""
+   if (!c->data)
+     {
+        fprintf(stderr, "tybench: out of memory for %s\n", path);
+        close(fd);
+        return EINA_FALSE;
+     }
+
+   while (got < c->len)
+     {
+        ssize_t n = read(fd, c->data + got, c->len - got);
+
+        if (n <= 0) break;
+        got += n;
+     }
+   close(fd);
+
+   if (got != c->len)
+     {
+        fprintf(stderr, "tybench: short read on %s\n", path);
+        free(c->data);
+        c->data = ""
+        return EINA_FALSE;
+     }
+
+   c->name = _basename(path);
+   return EINA_TRUE;
+}
+
+/* One pass over the corpus, split into chunk-sized pieces so the benchmark can
+ * show what read() sizing is worth. */
+static void
+_feed_pass(const Corpus *c, int chunk)
+{
+   long off;
+
+   for (off = 0; off < c->len; off += chunk)
+     {
+        int n = (c->len - off < chunk) ? (int)(c->len - off) : chunk;
+
+        tytest_common_feed(c->data + off, n);
+     }
+}
+
+/* Put the terminal back to a known state between corpora, so one corpus cannot
+ * leave modes set that change how the next one parses. */
+static void
+_reset_terminal(void)
+{
+   static const char ris[] = "\033c";
+
+   tytest_common_feed(ris, sizeof(ris) - 1);
+}
+
+static void
+_usage(const char *argv0)
+{
+   fprintf(stderr,
+           "usage: %s [options] <corpus>...\n"
+           "\n"
+           "  -i, --iterations=N  timed passes over each corpus (default %d)\n"
+           "  -w, --warmup=N      untimed passes before timing (default %d)\n"
+           "  -c, --chunk=N       bytes per simulated read() (default %d)\n"
+           "  -t, --tsv           tab-separated output for scripting\n"
+           "  -h, --help          this message\n",
+           argv0, DEFAULT_ITERATIONS, DEFAULT_WARMUP, DEFAULT_CHUNK);
+}
+
+static int
+_int_opt(const char *arg, const char *longform, int *out)
+{
+   size_t n = strlen(longform);
+
+   if (strncmp(arg, longform, n) != 0) return 0;
+   if (arg[n] != '=') return 0;
+   *out = atoi(arg + n + 1);
+   return 1;
+}
+
+int
+main(int argc, char **argv)
+{
+   int iterations = DEFAULT_ITERATIONS;
+   int warmup = DEFAULT_WARMUP;
+   int chunk = DEFAULT_CHUNK;
+   Eina_Bool tsv = EINA_FALSE;
+   int i, ncorpus = 0;
+   Corpus *corpus;
+
+   corpus = calloc(argc, sizeof(Corpus));
+   if (!corpus) return 1;
+
+   for (i = 1; i < argc; i++)
+     {
+        const char *a = argv[i];
+
+        if (!strcmp(a, "-h") || !strcmp(a, "--help"))
+          {
+             _usage(argv[0]);
+             free(corpus);
+             return 0;
+          }
+        else if (!strcmp(a, "-t") || !strcmp(a, "--tsv"))
+          tsv = EINA_TRUE;
+        else if (!strcmp(a, "-i") && i + 1 < argc)
+          iterations = atoi(argv[++i]);
+        else if (!strcmp(a, "-w") && i + 1 < argc)
+          warmup = atoi(argv[++i]);
+        else if (!strcmp(a, "-c") && i + 1 < argc)
+          chunk = atoi(argv[++i]);
+        else if (_int_opt(a, "--iterations", &iterations))
+          continue;
+        else if (_int_opt(a, "--warmup", &warmup))
+          continue;
+        else if (_int_opt(a, "--chunk", &chunk))
+          continue;
+        else if (a[0] == '-')
+          {
+             fprintf(stderr, "tybench: unknown option %s\n", a);
+             _usage(argv[0]);
+             free(corpus);
+             return 1;
+          }
+        else
+          {
+             if (!_corpus_load(&corpus[ncorpus], a))
+               {
+                  free(corpus);
+                  return 1;
+               }
+             ncorpus++;
+          }
+     }
+
+   if (ncorpus == 0)
+     {
+        _usage(argv[0]);
+        free(corpus);
+        return 1;
+     }
+   if (iterations < 1) iterations = 1;
+   if (warmup < 0) warmup = 0;
+   if (chunk < 1) chunk = 1;
+
+   eina_init();
+   _log_domain = eina_log_domain_register("tybench", NULL);
+   /* The parser logs to its own domain. Registering it matters here in a way it
+    * does not for tytest/tyfuzz: those compile logging out entirely, whereas
+    * this binary keeps it, and an unregistered domain sends every DBG down
+    * eina's "unknown domain" complaint path instead of the cheap level check
+    * that production takes. */
+   termpty_init();
+   tytest_common_init();
+
+   if (!tsv)
+     {
+        printf("chunk=%d iterations=%d warmup=%d\n\n", chunk, iterations, warmup);
+        printf("%-16s %10s %8s %10s %10s\n",
+               "corpus", "bytes", "MB/s", "ns/byte", "best MB/s");
+        printf("%-16s %10s %8s %10s %10s\n",
+               "----------------", "----------", "--------",
+               "----------", "----------");
+     }
+
+   for (i = 0; i < ncorpus; i++)
+     {
+        double total, best_pass = 0.0, mbs, best_mbs;
+        int pass;
+
+        _reset_terminal();
+        for (pass = 0; pass < warmup; pass++)
+          _feed_pass(&corpus[i], chunk);
+
+        total = 0.0;
+        for (pass = 0; pass < iterations; pass++)
+          {
+             double t0, dt;
+
+             t0 = _now();
+             _feed_pass(&corpus[i], chunk);
+             dt = _now() - t0;
+
+             total += dt;
+             if (pass == 0 || dt < best_pass) best_pass = dt;
+          }
+
+        mbs = ((double)corpus[i].len * iterations) / total / 1e6;
+        best_mbs = (double)corpus[i].len / best_pass / 1e6;
+
+        if (tsv)
+          printf("%s\t%ld\t%d\t%d\t%.6f\t%.3f\t%.3f\n",
+                 corpus[i].name, corpus[i].len, chunk, iterations,
+                 total, mbs, best_mbs);
+        else
+          printf("%-16s %10ld %8.2f %10.3f %10.2f\n",
+                 corpus[i].name, corpus[i].len, mbs,
+                 total * 1e9 / ((double)corpus[i].len * iterations), best_mbs);
+
+        fflush(stdout);
+     }
+
+   for (i = 0; i < ncorpus; i++)
+     free(corpus[i].data);
+   free(corpus);
+
+   tytest_common_shutdown();
+   eina_shutdown();
+
+   return 0;
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to