mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 01:41:02 +00:00
The panel opens with the port version and the game update, so a screenshot in a bug report carries both. Under RAM, after a gap, two rows on the same grid: FPS with the 1% and 0.1% lows, and the frame times. perfmon.c keeps a histogram of frame times in 0.25 ms bins, one slice per second over the last ten seconds, and reads the lows from it twice a second. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
394 lines
18 KiB
C
394 lines
18 KiB
C
// Performance overlay numbers (runtime/perfmon.h). Frame times come from the game's presents (perf_present, only while
|
|
// the overlay is on); the per-frame waits from the perf counters prof_win.c keeps. The machine's numbers are sampled
|
|
// twice a second on a thread of their own (some calls take milliseconds), which runs only while the overlay is full.
|
|
// The OS calls sit behind platform.h (plat_cpu_load, plat_mem_info, plat_gpu_load). Temperature, power and clock come
|
|
// from the GPU vendor's driver library, loaded at run time: NVML on NVIDIA, ADL's PM log on AMD; on a machine without
|
|
// either their numbers stay -1.
|
|
//
|
|
// Adapted from SavageWindRecomp's runtime/perfmon.c (https://github.com/Revivedx/SavageWindRecomp @
|
|
// 575ad88e02f9a89a9a885d90039d48af33c982f4, MIT, Copyright (c) 2026 Revivedx and the Savage Wind Recomp contributors,
|
|
// Copyright (c) 2026 Peiikai): the frame ring and its one-second summary, the NVML calls, the sampler loop. The
|
|
// Windows, Linux and macOS sampling moved behind platform.h, GPU load and video memory come from the system's counters
|
|
// for every vendor, and the sampler stops when the overlay leaves full.
|
|
//
|
|
// The ADL declarations (AdapterInfo, ADLSingleSensorData, ADLPMLogDataOutput, the ADL_PMLOG_* sensor numbers and the
|
|
// ADL2_* signatures) are copied from AMD's display-library include/adl_structures.h, adl_defines.h and adl_sdk.h
|
|
// (https://github.com/GPUOpen-LibrariesAndSDKs/display-library @ 84c881cb2136f04287eb3af12de00a9e2867aa9e, MIT,
|
|
// Copyright (c) 2016 - 2022 Advanced Micro Devices, Inc.).
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "host.h"
|
|
#include "perfmon.h"
|
|
#include "platform/platform.h"
|
|
#include "sysinfo.h"
|
|
|
|
static PlatMutex g_lock = PLAT_MUTEX_INIT; // everything below
|
|
static PerfStats g_stats; // the machine numbers the sampler wrote; the rest is made per snapshot
|
|
|
|
// ---- frames
|
|
static float g_ring[PERF_HISTORY];
|
|
static uint64_t g_ring_at[PERF_HISTORY]; // when each frame ended
|
|
static int g_ring_n, g_ring_pos;
|
|
|
|
// The lows: a histogram of the frame times (perf.c's 0.25 ms bins) over the last LOW_SLICES seconds, kept as one slice
|
|
// per second so the oldest second drops out whole. Per frame: one increment. The percentiles are read twice a second.
|
|
#define LOW_BINS 1024
|
|
#define LOW_SLICES 10
|
|
static uint16_t g_low_hist[LOW_SLICES][LOW_BINS];
|
|
static int g_low_cur;
|
|
static uint64_t g_low_t0;
|
|
|
|
// The per-frame counters, summed since t0 (the overlay's lines are averages over half a second).
|
|
static struct {
|
|
uint64_t t0, frames, gpu0;
|
|
double replay, pipe, xlate, tex;
|
|
int64_t up0, up;
|
|
} g_win;
|
|
|
|
void perfmon_frame(uint64_t now, uint64_t interval_ns, const uint64_t *wait_ns, uint64_t replay_ns,
|
|
int64_t tex_uploads) {
|
|
plat_mutex_lock(&g_lock);
|
|
if (interval_ns) {
|
|
g_ring[g_ring_pos] = (float)(interval_ns / 1e6);
|
|
g_ring_at[g_ring_pos] = now;
|
|
g_ring_pos = (g_ring_pos + 1) % PERF_HISTORY;
|
|
if (g_ring_n < PERF_HISTORY) g_ring_n++;
|
|
if (now - g_low_t0 >= 1000000000ull) { // the next second: its slice starts empty
|
|
g_low_cur = (g_low_cur + 1) % LOW_SLICES;
|
|
memset(g_low_hist[g_low_cur], 0, sizeof g_low_hist[0]);
|
|
g_low_t0 = now;
|
|
}
|
|
uint64_t bin = interval_ns / 250000ull;
|
|
uint16_t *h = &g_low_hist[g_low_cur][bin < LOW_BINS ? bin : LOW_BINS - 1];
|
|
if (*h < 65535) (*h)++;
|
|
}
|
|
g_win.frames++;
|
|
g_win.replay += replay_ns / 1e6;
|
|
g_win.pipe += wait_ns[PERF_PIPE] / 1e6;
|
|
g_win.xlate += wait_ns[PERF_XLATE] / 1e6;
|
|
g_win.tex += wait_ns[PERF_TEX] / 1e6;
|
|
g_win.up = tex_uploads;
|
|
plat_mutex_unlock(&g_lock);
|
|
}
|
|
|
|
static void frames_summary(PerfStats *s, uint64_t now) { // (lock held)
|
|
double sum = 0, mx = 0;
|
|
int n = 0;
|
|
for (int i = 0; i < g_ring_n; i++) {
|
|
int k = (g_ring_pos - 1 - i + PERF_HISTORY) % PERF_HISTORY;
|
|
if (now - g_ring_at[k] > 1000000000ull) break; // the last second
|
|
sum += g_ring[k];
|
|
if (g_ring[k] > mx) mx = g_ring[k];
|
|
n++;
|
|
}
|
|
s->fps = n && sum > 0 ? (float)(n * 1000.0 / sum) : 0;
|
|
s->frame_ms_avg = n ? (float)(sum / n) : 0;
|
|
s->frame_ms_max = (float)mx;
|
|
for (int i = 0; i < PERF_HISTORY; i++) { // oldest first; frames not seen yet read 0
|
|
int age = PERF_HISTORY - 1 - i;
|
|
s->frame_ms[i] = age < g_ring_n ? g_ring[(g_ring_pos - 1 - age + 2 * PERF_HISTORY) % PERF_HISTORY] : 0;
|
|
}
|
|
}
|
|
|
|
// The fps of the slowest 1% and 0.1% of the frames in the window (the bin's upper edge, as perf.c's summary does); -1
|
|
// when the window holds too few frames. (lock held)
|
|
static void lows_summary(PerfStats *s) {
|
|
uint32_t total = 0;
|
|
for (int k = 0; k < LOW_SLICES; k++)
|
|
for (int b = 0; b < LOW_BINS; b++) total += g_low_hist[k][b];
|
|
s->low1_fps = s->low01_fps = -1;
|
|
if (total < 100) return;
|
|
uint32_t cut1 = (total + 99) / 100, cut01 = (total + 999) / 1000, acc = 0;
|
|
for (int b = LOW_BINS - 1; b >= 0 && s->low1_fps < 0; b--) {
|
|
for (int k = 0; k < LOW_SLICES; k++) acc += g_low_hist[k][b];
|
|
float fps = 1000.0f / ((b + 1) * 0.25f);
|
|
if (s->low01_fps < 0 && acc >= cut01) s->low01_fps = fps;
|
|
if (acc >= cut1) s->low1_fps = fps;
|
|
}
|
|
}
|
|
|
|
// Every half second: the window's sums as per-frame averages into g_stats, and a new window. (lock held)
|
|
static void counters_summary(uint64_t now) {
|
|
if (now - g_win.t0 < 500000000ull) return;
|
|
lows_summary(&g_stats);
|
|
uint64_t gpu = perf_gpu_busy_total();
|
|
if (g_win.t0 && g_win.frames) {
|
|
double n = (double)g_win.frames;
|
|
g_stats.replay_ms = (float)(g_win.replay / n);
|
|
g_stats.pipe_wait_ms = (float)(g_win.pipe / n);
|
|
g_stats.xlate_ms = (float)(g_win.xlate / n);
|
|
g_stats.tex_ms = (float)(g_win.tex / n);
|
|
g_stats.tex_uploads = (float)((g_win.up - g_win.up0) / n);
|
|
// no GPU time over a window of frames: no timestamps on this device (or not yet set up)
|
|
g_stats.gpu_ms = gpu > g_win.gpu0 ? (float)((gpu - g_win.gpu0) / 1e6 / n) : -1;
|
|
}
|
|
g_win.t0 = now;
|
|
g_win.frames = 0;
|
|
g_win.gpu0 = gpu;
|
|
g_win.replay = g_win.pipe = g_win.xlate = g_win.tex = 0;
|
|
g_win.up0 = g_win.up;
|
|
}
|
|
|
|
// ---- NVML (NVIDIA): loaded once on the sampler thread, kept for the process
|
|
typedef struct { unsigned gpu, memory; } NvmlUtil;
|
|
typedef struct { unsigned long long total, free, used; } NvmlMem;
|
|
static struct {
|
|
int tried, ok;
|
|
void *dev;
|
|
int (*util)(void *, NvmlUtil *);
|
|
int (*temp)(void *, int, unsigned *);
|
|
int (*mem)(void *, NvmlMem *);
|
|
int (*power)(void *, unsigned *);
|
|
int (*clock)(void *, int, unsigned *);
|
|
} nv;
|
|
|
|
static char g_gpu_name[96];
|
|
static uint8_t g_gpu_luid[8];
|
|
static int g_gpu_luid_ok;
|
|
static int g_gpu_bus = -1;
|
|
static float g_vram_total_mb = -1;
|
|
|
|
void perfmon_set_gpu(const char *name, const uint8_t *luid, int pci_bus, float vram_total_mb) {
|
|
plat_mutex_lock(&g_lock);
|
|
snprintf(g_gpu_name, sizeof g_gpu_name, "%s", name ? name : "");
|
|
g_gpu_luid_ok = luid != NULL;
|
|
if (luid) memcpy(g_gpu_luid, luid, sizeof g_gpu_luid);
|
|
g_gpu_bus = pci_bus;
|
|
g_vram_total_mb = vram_total_mb > 0 ? vram_total_mb : -1;
|
|
plat_mutex_unlock(&g_lock);
|
|
}
|
|
|
|
static void nvml_open(const char *hint) {
|
|
nv.tried = 1;
|
|
#ifdef _WIN32
|
|
static const char *const names[] = {"nvml.dll", "C:\\Program Files\\NVIDIA Corporation\\NVSMI\\nvml.dll"};
|
|
#else
|
|
static const char *const names[] = {"libnvidia-ml.so.1"};
|
|
#endif
|
|
void *lib = NULL;
|
|
for (size_t i = 0; !lib && i < sizeof names / sizeof *names; i++) lib = plat_lib_open(names[i]);
|
|
if (!lib) return;
|
|
int (*init)(void) = (int (*)(void))plat_lib_sym(lib, "nvmlInit_v2");
|
|
int (*count)(unsigned *) = (int (*)(unsigned *))plat_lib_sym(lib, "nvmlDeviceGetCount_v2");
|
|
int (*handle)(unsigned, void **) = (int (*)(unsigned, void **))plat_lib_sym(lib, "nvmlDeviceGetHandleByIndex_v2");
|
|
int (*name)(void *, char *, unsigned) = (int (*)(void *, char *, unsigned))plat_lib_sym(lib, "nvmlDeviceGetName");
|
|
nv.util = (int (*)(void *, NvmlUtil *))plat_lib_sym(lib, "nvmlDeviceGetUtilizationRates");
|
|
nv.temp = (int (*)(void *, int, unsigned *))plat_lib_sym(lib, "nvmlDeviceGetTemperature");
|
|
nv.mem = (int (*)(void *, NvmlMem *))plat_lib_sym(lib, "nvmlDeviceGetMemoryInfo");
|
|
nv.power = (int (*)(void *, unsigned *))plat_lib_sym(lib, "nvmlDeviceGetPowerUsage");
|
|
nv.clock = (int (*)(void *, int, unsigned *))plat_lib_sym(lib, "nvmlDeviceGetClockInfo");
|
|
if (!init || !count || !handle || init() != 0) return;
|
|
unsigned n = 0;
|
|
if (count(&n) != 0 || !n) return;
|
|
// the device Vulkan renders on (by name; an NVIDIA name only matches an NVIDIA GPU), else none: on a machine
|
|
// that renders on another vendor's GPU, NVML's numbers would belong to the wrong card
|
|
for (unsigned i = 0; i < n && !nv.dev; i++) {
|
|
void *d = NULL;
|
|
char nm[96] = "";
|
|
if (handle(i, &d) != 0 || !name || name(d, nm, sizeof nm) != 0 || !nm[0]) continue;
|
|
if (hint[0] && (strstr(hint, nm) || strstr(nm, hint))) nv.dev = d;
|
|
}
|
|
nv.ok = nv.dev != NULL;
|
|
rt_log("perfmon: NVML %s", nv.ok ? "found the rendering GPU" : "has no device matching the rendering GPU");
|
|
}
|
|
|
|
static void nvml_sample(PerfStats *s) {
|
|
if (!nv.ok) return;
|
|
NvmlUtil u;
|
|
NvmlMem m;
|
|
unsigned v;
|
|
if (s->gpu_pct < 0 && nv.util && nv.util(nv.dev, &u) == 0) s->gpu_pct = (float)u.gpu; // the system's counters first
|
|
if (nv.mem && nv.mem(nv.dev, &m) == 0) {
|
|
if (s->vram_used_mb < 0) s->vram_used_mb = (float)(m.used >> 20);
|
|
s->vram_total_mb = (float)(m.total >> 20);
|
|
}
|
|
if (nv.temp && nv.temp(nv.dev, 0 /* NVML_TEMPERATURE_GPU */, &v) == 0) s->gpu_temp_c = (float)v;
|
|
if (nv.power && nv.power(nv.dev, &v) == 0) s->gpu_power_w = v / 1000.0f;
|
|
if (nv.clock && nv.clock(nv.dev, 0 /* NVML_CLOCK_GRAPHICS */, &v) == 0) s->gpu_clock_mhz = (float)v;
|
|
}
|
|
|
|
// ---- ADL (AMD): loaded once on the sampler thread, kept for the process. ADL2_New_QueryPMLogData_Get reads the PM log
|
|
// the driver keeps for Radeon RX 5000 and later (the numbers Adrenalin's overlay shows); older GPUs answer with an
|
|
// error and their numbers stay -1. Windows only: the Linux driver gives these through sysfs, which this does not read.
|
|
#ifdef _WIN32
|
|
#define ADL_MAX_PATH 256
|
|
#define ADL_PMLOG_MAX_SENSORS 256
|
|
enum {
|
|
ADL_PMLOG_CLK_GFXCLK = 1,
|
|
ADL_PMLOG_TEMPERATURE_EDGE = 8,
|
|
ADL_PMLOG_INFO_ACTIVITY_GFX = 19,
|
|
ADL_PMLOG_ASIC_POWER = 23,
|
|
ADL_PMLOG_TEMPERATURE_HOTSPOT = 27,
|
|
ADL_PMLOG_BOARD_POWER = 73,
|
|
};
|
|
typedef struct { // AdapterInfo, the Windows layout
|
|
int iSize, iAdapterIndex;
|
|
char strUDID[ADL_MAX_PATH];
|
|
int iBusNumber, iDeviceNumber, iFunctionNumber, iVendorID;
|
|
char strAdapterName[ADL_MAX_PATH], strDisplayName[ADL_MAX_PATH];
|
|
int iPresent, iExist;
|
|
char strDriverPath[ADL_MAX_PATH], strDriverPathExt[ADL_MAX_PATH], strPNPString[ADL_MAX_PATH];
|
|
int iOSDisplayIndex;
|
|
} AdlAdapterInfo;
|
|
typedef struct { int supported, value; } AdlSensor;
|
|
typedef struct { int size; AdlSensor sensors[ADL_PMLOG_MAX_SENSORS]; } AdlPmLog;
|
|
typedef void *(__stdcall *AdlAlloc)(int);
|
|
|
|
static struct {
|
|
int tried, ok, index;
|
|
void *ctx;
|
|
int (*pmlog)(void *, int, AdlPmLog *);
|
|
AdlPmLog log; // the sampler thread's buffer
|
|
} adl;
|
|
|
|
static void *__stdcall adl_alloc(int n) { return malloc((size_t)n); }
|
|
|
|
// The adapter Vulkan renders on: the PCI bus when the device gave one, else the name. ADL lists one index per display
|
|
// output of a GPU; the first whose PM log answers is taken.
|
|
static void adl_open(const char *hint, int bus) {
|
|
adl.tried = 1;
|
|
void *lib = plat_lib_open("atiadlxx.dll");
|
|
if (!lib) return;
|
|
int (*create)(AdlAlloc, int, void **) = (int (*)(AdlAlloc, int, void **))plat_lib_sym(lib, "ADL2_Main_Control_Create");
|
|
int (*count)(void *, int *) = (int (*)(void *, int *))plat_lib_sym(lib, "ADL2_Adapter_NumberOfAdapters_Get");
|
|
int (*info)(void *, AdlAdapterInfo *, int) =
|
|
(int (*)(void *, AdlAdapterInfo *, int))plat_lib_sym(lib, "ADL2_Adapter_AdapterInfo_Get");
|
|
adl.pmlog = (int (*)(void *, int, AdlPmLog *))plat_lib_sym(lib, "ADL2_New_QueryPMLogData_Get");
|
|
int n = 0;
|
|
if (!create || !count || !info || !adl.pmlog || create(adl_alloc, 1, &adl.ctx) != 0 || count(adl.ctx, &n) != 0 ||
|
|
n <= 0 || n > 256)
|
|
return;
|
|
AdlAdapterInfo *a = (AdlAdapterInfo *)calloc((size_t)n, sizeof *a);
|
|
if (a) {
|
|
for (int i = 0; i < n; i++) a[i].iSize = sizeof *a;
|
|
if (info(adl.ctx, a, n * (int)sizeof *a) == 0) {
|
|
for (int i = 0; i < n && !adl.ok; i++) {
|
|
if (a[i].iVendorID != 1002) continue; // ADL writes the PCI vendor id 0x1002 as decimal 1002
|
|
const char *nm = a[i].strAdapterName;
|
|
if (bus >= 0 ? a[i].iBusNumber != bus : !hint[0] || !nm[0] || !(strstr(hint, nm) || strstr(nm, hint)))
|
|
continue;
|
|
memset(&adl.log, 0, sizeof adl.log);
|
|
adl.log.size = sizeof adl.log;
|
|
if (adl.pmlog(adl.ctx, a[i].iAdapterIndex, &adl.log) != 0) continue;
|
|
adl.index = a[i].iAdapterIndex;
|
|
adl.ok = 1;
|
|
}
|
|
}
|
|
free(a);
|
|
}
|
|
rt_log("perfmon: ADL %s", adl.ok ? "found the rendering GPU" : "has no PM log for the rendering GPU");
|
|
}
|
|
|
|
static void adl_sample(PerfStats *s) {
|
|
if (!adl.ok) return;
|
|
memset(&adl.log, 0, sizeof adl.log);
|
|
adl.log.size = sizeof adl.log;
|
|
if (adl.pmlog(adl.ctx, adl.index, &adl.log) != 0) return;
|
|
const AdlSensor *v = adl.log.sensors;
|
|
if (s->gpu_pct < 0 && v[ADL_PMLOG_INFO_ACTIVITY_GFX].supported) // the system's counters first
|
|
s->gpu_pct = (float)v[ADL_PMLOG_INFO_ACTIVITY_GFX].value;
|
|
if (v[ADL_PMLOG_TEMPERATURE_EDGE].supported) s->gpu_temp_c = (float)v[ADL_PMLOG_TEMPERATURE_EDGE].value;
|
|
else if (v[ADL_PMLOG_TEMPERATURE_HOTSPOT].supported) s->gpu_temp_c = (float)v[ADL_PMLOG_TEMPERATURE_HOTSPOT].value;
|
|
// the whole card's draw where the driver reports it (RDNA 4 reports no ASIC power), else the GPU chip's
|
|
if (v[ADL_PMLOG_BOARD_POWER].supported) s->gpu_power_w = (float)v[ADL_PMLOG_BOARD_POWER].value;
|
|
else if (v[ADL_PMLOG_ASIC_POWER].supported) s->gpu_power_w = (float)v[ADL_PMLOG_ASIC_POWER].value;
|
|
if (v[ADL_PMLOG_CLK_GFXCLK].supported) s->gpu_clock_mhz = (float)v[ADL_PMLOG_CLK_GFXCLK].value;
|
|
}
|
|
#else
|
|
static struct { int tried; } adl;
|
|
static void adl_open(const char *hint, int bus) { (void)hint, (void)bus, adl.tried = 1; }
|
|
static void adl_sample(PerfStats *s) { (void)s; }
|
|
#endif
|
|
|
|
// ---- the machine sampler: runs while want is set, twice a second
|
|
static struct {
|
|
int want, alive;
|
|
PlatCond cv;
|
|
} g_smp = {0, 0, PLAT_COND_INIT};
|
|
|
|
static void machine_clear(PerfStats *s) {
|
|
s->cpu_total_pct = s->cpu_game_pct = s->cpu_temp_c = -1;
|
|
s->ram_used_mb = s->ram_total_mb = s->ram_game_mb = -1;
|
|
s->gpu_pct = s->gpu_temp_c = s->gpu_power_w = s->gpu_clock_mhz = s->vram_used_mb = s->vram_total_mb = -1;
|
|
}
|
|
|
|
static void machine_copy(PerfStats *d, const PerfStats *s) {
|
|
memcpy(d->cpu_name, s->cpu_name, sizeof d->cpu_name);
|
|
memcpy(d->gpu_name, s->gpu_name, sizeof d->gpu_name);
|
|
d->cpu_total_pct = s->cpu_total_pct, d->cpu_game_pct = s->cpu_game_pct, d->cpu_temp_c = s->cpu_temp_c;
|
|
d->ram_used_mb = s->ram_used_mb, d->ram_total_mb = s->ram_total_mb, d->ram_game_mb = s->ram_game_mb;
|
|
d->gpu_pct = s->gpu_pct, d->gpu_temp_c = s->gpu_temp_c, d->gpu_power_w = s->gpu_power_w;
|
|
d->gpu_clock_mhz = s->gpu_clock_mhz, d->vram_used_mb = s->vram_used_mb, d->vram_total_mb = s->vram_total_mb;
|
|
}
|
|
|
|
static void sampler(void *arg) {
|
|
(void)arg;
|
|
PerfStats s;
|
|
memset(&s, 0, sizeof s);
|
|
plat_mutex_lock(&g_lock);
|
|
uint8_t luid[8];
|
|
int have_luid = g_gpu_luid_ok;
|
|
memcpy(luid, g_gpu_luid, sizeof luid);
|
|
snprintf(s.gpu_name, sizeof s.gpu_name, "%s", g_gpu_name);
|
|
int bus = g_gpu_bus;
|
|
float vram_total = g_vram_total_mb;
|
|
plat_mutex_unlock(&g_lock);
|
|
sysinfo_cpu_name(s.cpu_name, sizeof s.cpu_name);
|
|
if (!nv.tried) nvml_open(s.gpu_name);
|
|
if (!nv.ok && !adl.tried) adl_open(s.gpu_name, bus);
|
|
PlatLoad *l = plat_load_open(have_luid ? luid : NULL, bus);
|
|
plat_mutex_lock(&g_lock);
|
|
while (g_smp.want) {
|
|
plat_mutex_unlock(&g_lock);
|
|
machine_clear(&s);
|
|
plat_cpu_load(l, &s.cpu_total_pct, &s.cpu_game_pct, &s.cpu_temp_c);
|
|
plat_mem_info(&s.ram_used_mb, &s.ram_total_mb, &s.ram_game_mb);
|
|
plat_gpu_load(l, &s.gpu_pct, &s.vram_used_mb);
|
|
s.vram_total_mb = vram_total;
|
|
nvml_sample(&s);
|
|
adl_sample(&s);
|
|
plat_mutex_lock(&g_lock);
|
|
machine_copy(&g_stats, &s);
|
|
if (g_smp.want) plat_cond_wait_ns(&g_smp.cv, &g_lock, 500000000ull);
|
|
}
|
|
g_smp.alive = 0; // in the same locked section as the last want check: a new perfmon_mode(2) starts a new thread
|
|
plat_mutex_unlock(&g_lock);
|
|
plat_load_close(l);
|
|
}
|
|
|
|
void perfmon_mode(int mode) {
|
|
perf_set_overlay(mode);
|
|
plat_mutex_lock(&g_lock);
|
|
if (mode == 2) {
|
|
g_smp.want = 1;
|
|
if (!g_smp.alive) {
|
|
machine_clear(&g_stats); // "-" until the first sample, not the numbers of the last time
|
|
PlatThread *t = plat_thread_new(sampler, NULL, "perfmon");
|
|
g_smp.alive = t != NULL;
|
|
if (t) plat_thread_detach(t);
|
|
else rt_log("perfmon: cannot start the sampling thread; the machine numbers stay empty");
|
|
}
|
|
} else {
|
|
g_smp.want = 0;
|
|
plat_cond_signal(&g_smp.cv);
|
|
}
|
|
if (!mode) { // the next start begins a new graph and a new counter window
|
|
g_ring_n = g_ring_pos = 0;
|
|
memset(g_low_hist, 0, sizeof g_low_hist);
|
|
g_low_t0 = 0;
|
|
memset(&g_win, 0, sizeof g_win);
|
|
}
|
|
plat_mutex_unlock(&g_lock);
|
|
}
|
|
|
|
void perfmon_snapshot(PerfStats *out) {
|
|
uint64_t now = plat_now_ns();
|
|
plat_mutex_lock(&g_lock);
|
|
frames_summary(&g_stats, now);
|
|
counters_summary(now);
|
|
*out = g_stats;
|
|
plat_mutex_unlock(&g_lock);
|
|
}
|