// perf_json.c -- see perf_json.h. The builders that were debug_server.c's, // moved so the RRDC control port answers with the same fields. #include "perf_json.h" #include #include #include #include "glue.h" // MHZ, warp_mode #include "vera_bandwidth.h" #include "sprite_trace.h" cJSON *perf_json_stat(const perf_stat_t *s) { cJSON *o = cJSON_CreateObject(); cJSON_AddNumberToObject(o, "min", s->min); cJSON_AddNumberToObject(o, "mean", s->mean); cJSON_AddNumberToObject(o, "p50", s->p50); cJSON_AddNumberToObject(o, "p95", s->p95); cJSON_AddNumberToObject(o, "p99", s->p99); cJSON_AddNumberToObject(o, "max", s->max); return o; } void perf_json_window_name(float seconds, char *out, size_t outsz) { if (seconds <= 0.0f) snprintf(out, outsz, "session"); else snprintf(out, outsz, "%gs", (double)seconds); } // Accepts either a number of seconds or a name ("30s", "session"), so a client // can send back the same window labels it was given. float perf_json_window_seconds(const cJSON *item) { if (cJSON_IsNumber(item)) return (float)item->valuedouble; if (cJSON_IsString(item) && item->valuestring) { if (!strcmp(item->valuestring, "session")) return 0.0f; return (float)atof(item->valuestring); // "30s" -> 30 } return 0.0f; } cJSON *perf_json_budget(void) { cJSON *b = cJSON_CreateObject(); cJSON_AddBoolToObject(b, "enabled", perf_budget_is_enabled()); cJSON_AddNumberToObject(b, "targetFps", perf_budget_get_target_fps()); cJSON_AddNumberToObject(b, "vsyncHz", perf_budget_vsync_hz()); cJSON_AddNumberToObject(b, "machineKhz", (double)MHZ * 1000.0); cJSON_AddNumberToObject(b, "cyclesPerFrame", perf_budget_cycles_per_frame()); cJSON_AddNumberToObject(b, "cyclesPerScanline", perf_budget_cycles_per_scanline()); cJSON_AddNumberToObject(b, "scanlinesPerFrame", perf_budget_cycles_per_scanline() ? perf_budget_cycles_per_frame() / perf_budget_cycles_per_scanline() : 0); cJSON_AddNumberToObject(b, "budgetCycles", perf_budget_budget_cycles()); cJSON_AddNumberToObject(b, "framesPerBudget", perf_budget_frames_per_budget()); cJSON_AddNumberToObject(b, "historyFrames", perf_budget_capacity()); cJSON_AddNumberToObject(b, "totalFrames", perf_budget_total_frames()); const char *mode = "auto"; switch (perf_budget_get_idle_mode()) { case PERF_IDLE_MARKERS: mode = "markers"; break; case PERF_IDLE_NONE: mode = "none"; break; default: mode = "auto"; break; } cJSON_AddStringToObject(b, "idleMode", mode); cJSON_AddBoolToObject(b, "warpMode", warp_mode); return b; } cJSON *perf_json_frame(const perf_frame_t *f) { cJSON *o = cJSON_CreateObject(); const uint32_t budget = perf_budget_budget_cycles(); cJSON_AddNumberToObject(o, "frame", f->frame); cJSON_AddNumberToObject(o, "totalCycles", f->total); cJSON_AddNumberToObject(o, "workCycles", perf_frame_work(f)); cJSON_AddNumberToObject(o, "idleCycles", f->idle); cJSON_AddNumberToObject(o, "irqCycles", f->irq); cJSON_AddNumberToObject(o, "mainCycles", perf_frame_work(f) > f->irq ? perf_frame_work(f) - f->irq : 0); cJSON_AddNumberToObject(o, "budgetWorkCycles", f->period_work); cJSON_AddNumberToObject(o, "instructions", f->instructions); cJSON_AddNumberToObject(o, "hostUs", f->host_us); cJSON_AddNumberToObject(o, "utilizationPct", budget ? 100.0 * (double)f->period_work / (double)budget : 0.0); cJSON_AddBoolToObject(o, "overBudget", budget && f->period_work > budget); return o; } // VERA's side of the same question. Deliberately a sub-object of perfStats // rather than a request of its own: a developer asking "what is eating my // frame?" should get the CPU answer and the bus answer in one round trip, // because the interesting cases are the ones where the two disagree. // // The headline is peakLineClocks against lineClocks (800), NOT a percentage of // VERA's total bandwidth. A frame uses maybe a fifth of the chip's 100 MB/s and // reporting that would be true and useless -- the scanline is the window that // actually runs out. See src/vera_bandwidth.h. cJSON *perf_json_bandwidth(void) { cJSON *o = cJSON_CreateObject(); cJSON_AddBoolToObject(o, "enabled", vera_bandwidth_is_enabled()); cJSON_AddNumberToObject(o, "lineClocks", VERA_BW_LINE_CLOCKS); cJSON_AddNumberToObject(o, "bytesPerAccess", VERA_BW_BYTES_PER_ACCESS); cJSON_AddNumberToObject(o, "accessClocks", VERA_BW_ACCESS_CLOCKS); cJSON_AddNumberToObject(o, "scanlinesPerFrame", vera_bandwidth_scanlines_per_frame()); vera_bw_frame_t f; if (!vera_bandwidth_last_frame(&f)) { return o; } cJSON_AddNumberToObject(o, "frame", f.frame); cJSON_AddNumberToObject(o, "linesActive", f.lines_active); cJSON *layers = cJSON_CreateArray(); for (int i = 0; i < 2; i++) { cJSON *l = cJSON_CreateObject(); cJSON_AddNumberToObject(l, "fetches", f.layer_fetches[i]); cJSON_AddNumberToObject(l, "bytes", f.layer_bytes[i]); cJSON_AddNumberToObject(l, "peakLine", f.layer_peak_line[i]); cJSON_AddNumberToObject(l, "peakFetches", f.layer_peak_fetches[i]); cJSON_AddItemToArray(layers, l); } cJSON_AddItemToObject(o, "layers", layers); // Per-scanline pressure: the headline. cJSON_AddNumberToObject(o, "peakLine", f.peak_line); cJSON_AddNumberToObject(o, "peakLineClocks", f.peak_clocks); cJSON_AddNumberToObject(o, "p95LineClocks", f.p95_clocks); cJSON_AddNumberToObject(o, "meanLineClocks", f.mean_clocks); cJSON_AddNumberToObject(o, "linesOverBudget", f.lines_over); cJSON_AddNumberToObject(o, "peakLinePct", 100.0 * (double)f.peak_clocks / (double)VERA_BW_LINE_CLOCKS); cJSON_AddNumberToObject(o, "totalFetches", f.total_fetches); cJSON_AddNumberToObject(o, "totalClocks", f.total_clocks); // How much of each scanline was still free when the layers and the CPU had // taken their share. Sprites are last on the bus (vram_if.v:142-157), so // this is what they were really competing for. // // Named "bus" deliberately. Every clock figure in this object is BUS // OCCUPANCY, which is not the clock the sprite views report: sprite render // time is wall clock and runs about five times higher, so a client that // subtracted one from the other would conclude a comfortable line was // oversubscribed. Convert sprite width and colour depth to fetches first. cJSON_AddNumberToObject(o, "spriteBusHeadroomClocksAtPeak", f.peak_clocks < VERA_BW_LINE_CLOCKS ? VERA_BW_LINE_CLOCKS - f.peak_clocks : 0); cJSON_AddStringToObject(o, "clockUnits", "bus"); cJSON *port = cJSON_CreateObject(); cJSON_AddNumberToObject(port, "readBytes", f.port_reads); cJSON_AddNumberToObject(port, "writeBytes", f.port_writes); cJSON_AddNumberToObject(port, "accesses", f.port_accesses); cJSON_AddNumberToObject(port, "vramBytes", f.port_vram_bytes); cJSON_AddNumberToObject(port, "fxCacheWrites", f.fx_cache_writes); cJSON_AddNumberToObject(port, "fxAffineFetches", f.fx_affine_fetches); // Bytes that reached VRAM per byte the CPU pushed. Above 1.0 means FX is // doing work the CPU did not pay for, which is the point of using it. const uint32_t stores = f.port_reads + f.port_writes; cJSON_AddNumberToObject(port, "amplification", stores ? (double)f.port_vram_bytes / (double)stores : 0.0); cJSON_AddItemToObject(o, "port", port); return o; } cJSON *perf_json_window(float secs) { perf_window_t w; if (!perf_budget_window(secs, &w)) return NULL; char name[32]; perf_json_window_name(secs, name, sizeof name); cJSON *o = cJSON_CreateObject(); cJSON_AddStringToObject(o, "name", name); cJSON_AddNumberToObject(o, "seconds", secs); cJSON_AddNumberToObject(o, "frames", w.frames); cJSON_AddNumberToObject(o, "budgetCycles", w.budget_cycles); cJSON_AddItemToObject(o, "work", perf_json_stat(&w.work)); cJSON_AddItemToObject(o, "budgetWork", perf_json_stat(&w.budget_work)); cJSON_AddItemToObject(o, "total", perf_json_stat(&w.total)); cJSON_AddItemToObject(o, "irq", perf_json_stat(&w.irq)); cJSON_AddItemToObject(o, "hostUs", perf_json_stat(&w.host_us)); cJSON_AddNumberToObject(o, "overruns", w.overruns); cJSON_AddNumberToObject(o, "overrunPct", w.overrun_pct); cJSON_AddNumberToObject(o, "worstOverrunCycles", w.worst_overrun); cJSON_AddNumberToObject(o, "worstFrame", w.worst_frame); cJSON_AddNumberToObject(o, "meanUtilizationPct", w.mean_utilization); cJSON_AddNumberToObject(o, "p95UtilizationPct", w.p95_utilization); cJSON_AddNumberToObject(o, "maxUtilizationPct", w.max_utilization); return o; } cJSON *perf_json_bandwidth_lines(void) { uint16_t n = 0; const vera_bw_line_t *lines = vera_bandwidth_last_lines(&n); cJSON *a = cJSON_CreateArray(); for (uint16_t i = 0; lines && i < n; i++) cJSON_AddItemToArray(a, cJSON_CreateNumber(vera_bw_line_clocks(&lines[i]))); return a; } // The sprite renderer is the other half of VERA's scanline: what it spent // drawing sprites on each line against the 798 clocks it has (wall clock, not // the bus clocks perf_json_bandwidth counts -- see vera_bandwidth.h). cJSON *perf_json_sprites(bool lines) { cJSON *o = cJSON_CreateObject(); const sprite_trace_frame_t *f = sprite_trace_active ? sprite_trace_last_frame() : NULL; cJSON_AddBoolToObject(o, "enabled", sprite_trace_active); cJSON_AddNumberToObject(o, "lineBudget", SPRITE_TRACE_LINE_BUDGET); if (!f) return o; const sprite_frame_summary_t *s = &f->summary; cJSON_AddNumberToObject(o, "frame", s->frame); cJSON_AddNumberToObject(o, "peakLine", s->peak_line); cJSON_AddNumberToObject(o, "peakBudget", s->peak_budget); cJSON_AddNumberToObject(o, "peakDemand", s->peak_demand); cJSON_AddNumberToObject(o, "linesExhausted", s->lines_exhausted); cJSON_AddNumberToObject(o, "spritesDropped", s->sprites_dropped); cJSON_AddNumberToObject(o, "budgetUsedTotal", s->budget_used_total); cJSON_AddNumberToObject(o, "slotsUsed", s->slots_used); cJSON_AddNumberToObject(o, "slotsMultiplexed", s->slots_multiplexed); cJSON_AddNumberToObject(o, "effectiveSprites", s->effective_sprites); cJSON_AddNumberToObject(o, "cpuCycles", s->cpu_cycles); cJSON_AddBoolToObject(o, "multiplexing", s->multiplexing); if (lines && f->lines) { cJSON *a = cJSON_CreateArray(); for (int i = 0; i < SPRITE_TRACE_LINES; i++) cJSON_AddItemToArray(a, cJSON_CreateNumber(f->lines[i].budget_used)); cJSON_AddItemToObject(o, "lines", a); } return o; }