OXYGENHUB
◆ main enhanced-x16-emulator / src / perf_json.c
10.8 KiB · C · last changed feat: the RRDC control port (-controlport), contract 0.6: GET/POST /perf from the Performance panel's measurements, built by perf_json.c with DAP's x16/perfStats; a guest marker region named in advance; a framed guest's regions carried across the frame boundary and an interrupt after $F1 counted as work (#1) 25 days ago source blame history raw
2980b7b6 Andy McCall ↶ 1
// perf_json.c -- see perf_json.h. The builders that were debug_server.c's,
2
// moved so the RRDC control port answers with the same fields.
3
4
#include "perf_json.h"
5
6
#include <stdio.h>
7
#include <stdlib.h>
8
#include <string.h>
9
10
#include "glue.h"            // MHZ, warp_mode
11
#include "vera_bandwidth.h"
12
#include "sprite_trace.h"
13
14
cJSON *perf_json_stat(const perf_stat_t *s) {
15
    cJSON *o = cJSON_CreateObject();
16
    cJSON_AddNumberToObject(o, "min", s->min);
17
    cJSON_AddNumberToObject(o, "mean", s->mean);
18
    cJSON_AddNumberToObject(o, "p50", s->p50);
19
    cJSON_AddNumberToObject(o, "p95", s->p95);
20
    cJSON_AddNumberToObject(o, "p99", s->p99);
21
    cJSON_AddNumberToObject(o, "max", s->max);
22
    return o;
23
}
24
25
void perf_json_window_name(float seconds, char *out, size_t outsz) {
26
    if (seconds <= 0.0f) snprintf(out, outsz, "session");
27
    else                 snprintf(out, outsz, "%gs", (double)seconds);
28
}
29
30
// Accepts either a number of seconds or a name ("30s", "session"), so a client
31
// can send back the same window labels it was given.
32
float perf_json_window_seconds(const cJSON *item) {
33
    if (cJSON_IsNumber(item)) return (float)item->valuedouble;
34
    if (cJSON_IsString(item) && item->valuestring) {
35
        if (!strcmp(item->valuestring, "session")) return 0.0f;
36
        return (float)atof(item->valuestring); // "30s" -> 30
37
    }
38
    return 0.0f;
39
}
40
41
cJSON *perf_json_budget(void) {
42
    cJSON *b = cJSON_CreateObject();
43
    cJSON_AddBoolToObject(b, "enabled", perf_budget_is_enabled());
44
    cJSON_AddNumberToObject(b, "targetFps", perf_budget_get_target_fps());
45
    cJSON_AddNumberToObject(b, "vsyncHz", perf_budget_vsync_hz());
46
    cJSON_AddNumberToObject(b, "machineKhz", (double)MHZ * 1000.0);
47
    cJSON_AddNumberToObject(b, "cyclesPerFrame", perf_budget_cycles_per_frame());
48
    cJSON_AddNumberToObject(b, "cyclesPerScanline", perf_budget_cycles_per_scanline());
49
    cJSON_AddNumberToObject(b, "scanlinesPerFrame",
50
                            perf_budget_cycles_per_scanline()
51
                                ? perf_budget_cycles_per_frame() / perf_budget_cycles_per_scanline() : 0);
52
    cJSON_AddNumberToObject(b, "budgetCycles", perf_budget_budget_cycles());
53
    cJSON_AddNumberToObject(b, "framesPerBudget", perf_budget_frames_per_budget());
54
    cJSON_AddNumberToObject(b, "historyFrames", perf_budget_capacity());
55
    cJSON_AddNumberToObject(b, "totalFrames", perf_budget_total_frames());
56
    const char *mode = "auto";
57
    switch (perf_budget_get_idle_mode()) {
58
        case PERF_IDLE_MARKERS: mode = "markers"; break;
59
        case PERF_IDLE_NONE:    mode = "none";    break;
60
        default:                mode = "auto";    break;
61
    }
62
    cJSON_AddStringToObject(b, "idleMode", mode);
63
    cJSON_AddBoolToObject(b, "warpMode", warp_mode);
64
    return b;
65
}
66
67
cJSON *perf_json_frame(const perf_frame_t *f) {
68
    cJSON *o = cJSON_CreateObject();
69
    const uint32_t budget = perf_budget_budget_cycles();
70
    cJSON_AddNumberToObject(o, "frame", f->frame);
71
    cJSON_AddNumberToObject(o, "totalCycles", f->total);
72
    cJSON_AddNumberToObject(o, "workCycles", perf_frame_work(f));
73
    cJSON_AddNumberToObject(o, "idleCycles", f->idle);
74
    cJSON_AddNumberToObject(o, "irqCycles", f->irq);
75
    cJSON_AddNumberToObject(o, "mainCycles",
76
                            perf_frame_work(f) > f->irq ? perf_frame_work(f) - f->irq : 0);
77
    cJSON_AddNumberToObject(o, "budgetWorkCycles", f->period_work);
78
    cJSON_AddNumberToObject(o, "instructions", f->instructions);
79
    cJSON_AddNumberToObject(o, "hostUs", f->host_us);
80
    cJSON_AddNumberToObject(o, "utilizationPct",
81
                            budget ? 100.0 * (double)f->period_work / (double)budget : 0.0);
82
    cJSON_AddBoolToObject(o, "overBudget", budget && f->period_work > budget);
83
    return o;
84
}
85
86
// VERA's side of the same question. Deliberately a sub-object of perfStats
87
// rather than a request of its own: a developer asking "what is eating my
88
// frame?" should get the CPU answer and the bus answer in one round trip,
89
// because the interesting cases are the ones where the two disagree.
90
//
91
// The headline is peakLineClocks against lineClocks (800), NOT a percentage of
92
// VERA's total bandwidth. A frame uses maybe a fifth of the chip's 100 MB/s and
93
// reporting that would be true and useless -- the scanline is the window that
94
// actually runs out. See src/vera_bandwidth.h.
95
cJSON *perf_json_bandwidth(void) {
96
    cJSON *o = cJSON_CreateObject();
97
    cJSON_AddBoolToObject(o, "enabled", vera_bandwidth_is_enabled());
98
    cJSON_AddNumberToObject(o, "lineClocks", VERA_BW_LINE_CLOCKS);
99
    cJSON_AddNumberToObject(o, "bytesPerAccess", VERA_BW_BYTES_PER_ACCESS);
100
    cJSON_AddNumberToObject(o, "accessClocks", VERA_BW_ACCESS_CLOCKS);
101
    cJSON_AddNumberToObject(o, "scanlinesPerFrame", vera_bandwidth_scanlines_per_frame());
102
103
    vera_bw_frame_t f;
104
    if (!vera_bandwidth_last_frame(&f)) {
105
        return o;
106
    }
107
108
    cJSON_AddNumberToObject(o, "frame", f.frame);
109
    cJSON_AddNumberToObject(o, "linesActive", f.lines_active);
110
111
    cJSON *layers = cJSON_CreateArray();
112
    for (int i = 0; i < 2; i++) {
113
        cJSON *l = cJSON_CreateObject();
114
        cJSON_AddNumberToObject(l, "fetches", f.layer_fetches[i]);
115
        cJSON_AddNumberToObject(l, "bytes", f.layer_bytes[i]);
116
        cJSON_AddNumberToObject(l, "peakLine", f.layer_peak_line[i]);
117
        cJSON_AddNumberToObject(l, "peakFetches", f.layer_peak_fetches[i]);
118
        cJSON_AddItemToArray(layers, l);
119
    }
120
    cJSON_AddItemToObject(o, "layers", layers);
121
122
    // Per-scanline pressure: the headline.
123
    cJSON_AddNumberToObject(o, "peakLine", f.peak_line);
124
    cJSON_AddNumberToObject(o, "peakLineClocks", f.peak_clocks);
125
    cJSON_AddNumberToObject(o, "p95LineClocks", f.p95_clocks);
126
    cJSON_AddNumberToObject(o, "meanLineClocks", f.mean_clocks);
127
    cJSON_AddNumberToObject(o, "linesOverBudget", f.lines_over);
128
    cJSON_AddNumberToObject(o, "peakLinePct",
129
                            100.0 * (double)f.peak_clocks / (double)VERA_BW_LINE_CLOCKS);
130
    cJSON_AddNumberToObject(o, "totalFetches", f.total_fetches);
131
    cJSON_AddNumberToObject(o, "totalClocks", f.total_clocks);
132
133
    // How much of each scanline was still free when the layers and the CPU had
134
    // taken their share. Sprites are last on the bus (vram_if.v:142-157), so
135
    // this is what they were really competing for.
136
    //
137
    // Named "bus" deliberately. Every clock figure in this object is BUS
138
    // OCCUPANCY, which is not the clock the sprite views report: sprite render
139
    // time is wall clock and runs about five times higher, so a client that
140
    // subtracted one from the other would conclude a comfortable line was
141
    // oversubscribed. Convert sprite width and colour depth to fetches first.
142
    cJSON_AddNumberToObject(o, "spriteBusHeadroomClocksAtPeak",
143
                            f.peak_clocks < VERA_BW_LINE_CLOCKS
144
                                ? VERA_BW_LINE_CLOCKS - f.peak_clocks : 0);
145
    cJSON_AddStringToObject(o, "clockUnits", "bus");
146
147
    cJSON *port = cJSON_CreateObject();
148
    cJSON_AddNumberToObject(port, "readBytes", f.port_reads);
149
    cJSON_AddNumberToObject(port, "writeBytes", f.port_writes);
150
    cJSON_AddNumberToObject(port, "accesses", f.port_accesses);
151
    cJSON_AddNumberToObject(port, "vramBytes", f.port_vram_bytes);
152
    cJSON_AddNumberToObject(port, "fxCacheWrites", f.fx_cache_writes);
153
    cJSON_AddNumberToObject(port, "fxAffineFetches", f.fx_affine_fetches);
154
    // Bytes that reached VRAM per byte the CPU pushed. Above 1.0 means FX is
155
    // doing work the CPU did not pay for, which is the point of using it.
156
    const uint32_t stores = f.port_reads + f.port_writes;
157
    cJSON_AddNumberToObject(port, "amplification",
158
                            stores ? (double)f.port_vram_bytes / (double)stores : 0.0);
159
    cJSON_AddItemToObject(o, "port", port);
160
161
    return o;
162
}
163
164
165
cJSON *perf_json_window(float secs) {
166
    perf_window_t w;
167
    if (!perf_budget_window(secs, &w)) return NULL;
168
    char name[32];
169
    perf_json_window_name(secs, name, sizeof name);
170
    cJSON *o = cJSON_CreateObject();
171
    cJSON_AddStringToObject(o, "name", name);
172
    cJSON_AddNumberToObject(o, "seconds", secs);
173
    cJSON_AddNumberToObject(o, "frames", w.frames);
174
    cJSON_AddNumberToObject(o, "budgetCycles", w.budget_cycles);
175
    cJSON_AddItemToObject(o, "work", perf_json_stat(&w.work));
176
    cJSON_AddItemToObject(o, "budgetWork", perf_json_stat(&w.budget_work));
177
    cJSON_AddItemToObject(o, "total", perf_json_stat(&w.total));
178
    cJSON_AddItemToObject(o, "irq", perf_json_stat(&w.irq));
179
    cJSON_AddItemToObject(o, "hostUs", perf_json_stat(&w.host_us));
180
    cJSON_AddNumberToObject(o, "overruns", w.overruns);
181
    cJSON_AddNumberToObject(o, "overrunPct", w.overrun_pct);
182
    cJSON_AddNumberToObject(o, "worstOverrunCycles", w.worst_overrun);
183
    cJSON_AddNumberToObject(o, "worstFrame", w.worst_frame);
184
    cJSON_AddNumberToObject(o, "meanUtilizationPct", w.mean_utilization);
185
    cJSON_AddNumberToObject(o, "p95UtilizationPct", w.p95_utilization);
186
    cJSON_AddNumberToObject(o, "maxUtilizationPct", w.max_utilization);
187
    return o;
188
}
189
190
cJSON *perf_json_bandwidth_lines(void) {
191
    uint16_t n = 0;
192
    const vera_bw_line_t *lines = vera_bandwidth_last_lines(&n);
193
    cJSON *a = cJSON_CreateArray();
194
    for (uint16_t i = 0; lines && i < n; i++)
195
        cJSON_AddItemToArray(a, cJSON_CreateNumber(vera_bw_line_clocks(&lines[i])));
196
    return a;
197
}
198
199
// The sprite renderer is the other half of VERA's scanline: what it spent
200
// drawing sprites on each line against the 798 clocks it has (wall clock, not
201
// the bus clocks perf_json_bandwidth counts -- see vera_bandwidth.h).
202
cJSON *perf_json_sprites(bool lines) {
203
    cJSON *o = cJSON_CreateObject();
204
    const sprite_trace_frame_t *f = sprite_trace_active ? sprite_trace_last_frame() : NULL;
205
    cJSON_AddBoolToObject(o, "enabled", sprite_trace_active);
206
    cJSON_AddNumberToObject(o, "lineBudget", SPRITE_TRACE_LINE_BUDGET);
207
    if (!f) return o;
208
    const sprite_frame_summary_t *s = &f->summary;
209
    cJSON_AddNumberToObject(o, "frame", s->frame);
210
    cJSON_AddNumberToObject(o, "peakLine", s->peak_line);
211
    cJSON_AddNumberToObject(o, "peakBudget", s->peak_budget);
212
    cJSON_AddNumberToObject(o, "peakDemand", s->peak_demand);
213
    cJSON_AddNumberToObject(o, "linesExhausted", s->lines_exhausted);
214
    cJSON_AddNumberToObject(o, "spritesDropped", s->sprites_dropped);
215
    cJSON_AddNumberToObject(o, "budgetUsedTotal", s->budget_used_total);
216
    cJSON_AddNumberToObject(o, "slotsUsed", s->slots_used);
217
    cJSON_AddNumberToObject(o, "slotsMultiplexed", s->slots_multiplexed);
218
    cJSON_AddNumberToObject(o, "effectiveSprites", s->effective_sprites);
219
    cJSON_AddNumberToObject(o, "cpuCycles", s->cpu_cycles);
220
    cJSON_AddBoolToObject(o, "multiplexing", s->multiplexing);
221
    if (lines && f->lines) {
222
        cJSON *a = cJSON_CreateArray();
223
        for (int i = 0; i < SPRITE_TRACE_LINES; i++)
224
            cJSON_AddItemToArray(a, cJSON_CreateNumber(f->lines[i].budget_used));
225
        cJSON_AddItemToObject(o, "lines", a);
226
    }
227
    return o;
228
}