1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
|
#include "term/term.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define BUDGET_MS_DEFAULT 0.5
/* Rows written past the history limit, so the history is full and wraps. */
#define FILL_EXTRA_ROWS 512
#define MAX_SAMPLES 4096
/*
* History sizes to measure. 4096 is the size of the recorded baseline.
* The budget applies to every size. Today the larger sizes go over it; the
* terminal redesign targets a resize cost that does not grow with history.
*/
static const int history_sizes[] = { 100, 4096, 10000 };
static double now(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec + ts.tv_nsec * 1e-9;
}
static int cmp_double(const void *a, const void *b)
{
double x = *(const double *)a, y = *(const double *)b;
return x < y ? -1 : x > y;
}
/* Content mixes, chosen so reflow cost spans no-op to worst case. */
enum content { SHORT, WRAPPED, MIXED, PROMPT, WIDE, CONTENT_COUNT };
static const char *content_names[] = {"short", "wrapped", "mixed", "prompt", "wide"};
static void emit_line(struct term *t, enum content kind, int n, int cols)
{
char line[4096];
int len = 0;
switch (kind) {
case SHORT:
len = snprintf(line, sizeof(line), "%d: short output line\r\n", n);
break;
case WRAPPED:
/* ~3.5 screen rows at 120 cols: every resize must rewrap these. */
for (int i = 0; i < 420; i++) line[len++] = 'a' + (n + i) % 26;
line[len++] = '\r';
line[len++] = '\n';
break;
case MIXED:
if (n % 4 == 0)
for (int i = 0; i < 300; i++) line[len++] = 'a' + (n + i) % 26;
else
len = snprintf(line, sizeof(line), "%d: build step ok", n);
line[len++] = '\r';
line[len++] = '\n';
break;
case PROMPT:
/* OSC 133 marked prompt rows do not wrap; output rows do. */
if (n % 3 == 0)
len = snprintf(line, sizeof(line),
"\033]133;A\033\\user@host:~/very/long/project/path $ \033]133;B\033\\"
"cargo build --release --features=all\r\n");
else
len = snprintf(line, sizeof(line), " Compiling crate v0.%d.0\r\n", n % 100);
break;
case WIDE:
for (int i = 0; i < 60; i++) len += snprintf(line + len, sizeof(line) - len, "日本語");
line[len++] = '\r';
line[len++] = '\n';
break;
default:
break;
}
(void)cols;
term_feed(t, line, (size_t)len, 0);
}
static struct term *make_filled(enum content kind, int cols, int rows, int history)
{
struct term *t = term_new(&(struct term_options){
.size = { .x = cols, .y = rows },
.scrollback = history,
});
if (!t) return NULL;
for (int n = 0; n < history + FILL_EXTRA_ROWS; n++)
emit_line(t, kind, n, cols);
return t;
}
static void resize(struct term *t, int cols, int rows)
{
if (!term_resize(t, (struct term_vector2i){ .x = cols, .y = rows })) {
fprintf(stderr, "term_resize failed: out of memory\n");
exit(EXIT_FAILURE);
}
}
static void report(const char *content, const char *scenario, double *s, int n, double budget_ms)
{
qsort(s, (size_t)n, sizeof(*s), cmp_double);
double p50 = s[n / 2];
double p99 = s[(int)((double)(n - 1) * 0.99)];
double max = s[n - 1];
printf("%-8s %-10s p50 %7.3f p99 %7.3f max %7.3f ms %s\n",
content, scenario, p50, p99, max, max <= budget_ms ? "ok" : "OVER");
}
/* One resize per freshly filled terminal: content never decays across samples. */
static double bench_pair(enum content kind, int from_cols, int to_cols, int rows,
int history, double *out, int samples)
{
double worst = 0;
for (int i = 0; i < samples; i++) {
struct term *t = make_filled(kind, from_cols, rows, history);
if (!t) exit(EXIT_FAILURE);
double start = now();
resize(t, to_cols, rows);
double ms = (now() - start) * 1e3;
term_free(t);
out[i] = ms;
if (ms > worst) worst = ms;
}
return worst;
}
/* A drag mutates progressively, so every step is timed on the same terminal. */
static int bench_drag(enum content kind, int from_cols, int to_cols, int rows,
int history, double *out, int passes)
{
int n = 0;
int step = from_cols > to_cols ? -1 : 1;
for (int p = 0; p < passes; p++) {
struct term *t = make_filled(kind, from_cols, rows, history);
if (!t) exit(EXIT_FAILURE);
for (int c = from_cols + step; c != to_cols + step; c += step) {
double start = now();
resize(t, c, rows);
out[n++] = (now() - start) * 1e3;
}
term_free(t);
}
return n;
}
int main(int argc, char **argv)
{
const char *only = argc > 1 && strcmp(argv[1], "all") ? argv[1] : NULL;
double budget = argc > 2 ? strtod(argv[2], NULL) : BUDGET_MS_DEFAULT;
int only_history = argc > 3 ? atoi(argv[3]) : 0;
if (argc > 4 || only_history < 0) {
fprintf(stderr, "usage: %s [content|all [budget-ms [history]]]\n", argv[0]);
return EXIT_FAILURE;
}
int rows = 40, samples = 32;
static const struct { const char *name; int from, to; } pairs[] = {
{"grow", 80, 120},
{"shrink", 120, 80},
{"double", 80, 160},
{"halve", 160, 80},
{"narrow", 120, 20},
{"widen", 20, 120},
};
static double s[MAX_SAMPLES];
bool matched = false, over = false;
for (size_t h = 0; h < sizeof(history_sizes) / sizeof(history_sizes[0]); h++) {
int history = history_sizes[h];
if (only_history && only_history != history) continue;
bool history_over = false;
printf("reflow budget %.3f ms, %d rows, scrollback %d\n\n", budget, rows, history);
for (int k = 0; k < CONTENT_COUNT; k++) {
if (only && strcmp(only, content_names[k])) continue;
matched = true;
for (size_t p = 0; p < sizeof(pairs) / sizeof(pairs[0]); p++) {
double worst = bench_pair((enum content)k, pairs[p].from, pairs[p].to, rows,
history, s, samples);
history_over |= worst > budget;
report(content_names[k], pairs[p].name, s, samples, budget);
}
int n = bench_drag((enum content)k, 120, 80, rows, history, s, 8);
for (int i = 0; i < n; i++) history_over |= s[i] > budget;
report(content_names[k], "drag", s, n, budget);
putchar('\n');
}
printf("scrollback %d: %s\n\n", history,
history_over ? "OVER budget" : "within budget");
over |= history_over;
}
if (!matched) {
if (only_history)
fprintf(stderr, "unknown content mix or history size: %s %d\n",
argv[1], only_history);
else
fprintf(stderr, "unknown content mix: %s\n", argv[1]);
return EXIT_FAILURE;
}
printf("%s\n", over ? "FAIL: at least one resize exceeded budget"
: "PASS: all resizes within budget");
return over ? EXIT_FAILURE : EXIT_SUCCESS;
}
|