Skip to content

Commit f2963eb

Browse files
committed
get ram and cpu measures for autoconfig
1 parent 3cebcb5 commit f2963eb

9 files changed

Lines changed: 609 additions & 5 deletions

File tree

js/module.d.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,29 @@ export interface IAudioTrackFactory {
977977
importLegacySettings(): void;
978978
saveLegacySettings(): void;
979979
}
980+
export interface IAutoConfigResourcePercentile {
981+
p50: number;
982+
p95: number;
983+
}
984+
export interface IAutoConfigResourceGpu {
985+
available: boolean;
986+
vramUsedMB?: IAutoConfigResourcePercentile;
987+
vramBudgetMB?: number;
988+
}
989+
export type AutoConfigResourcePhase = 'bandwidth' | 'stream_encoder' | 'recording_encoder';
990+
export interface IAutoConfigResourceUsage {
991+
phase: AutoConfigResourcePhase;
992+
sampleCount: number;
993+
durationMs: number;
994+
cpuPct: IAutoConfigResourcePercentile;
995+
procRamMB: IAutoConfigResourcePercentile;
996+
gpu: IAutoConfigResourceGpu;
997+
}
998+
export interface IAutoConfigSummary {
999+
complete: boolean;
1000+
resourceUsage: IAutoConfigResourceUsage[];
1001+
[key: string]: unknown;
1002+
}
9801003
export declare const enum VCamOutputType {
9811004
Invalid = 0,
9821005
SceneOutput = 1,

js/module.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1925,6 +1925,48 @@ export interface IAudioTrackFactory {
19251925
saveLegacySettings(): void;
19261926
}
19271927

1928+
// ---- Autoconfig resource-usage telemetry ----
1929+
//
1930+
// Shapes for the JSON payload of the autoconfig 'resource_usage' event, and
1931+
// the matching `resourceUsage` array inside GetAutoConfigSummary()'s JSON.
1932+
//
1933+
// p50 is the typical value during the phase; p95 is the sustained ceiling
1934+
// after dropping single-sample spikes from unrelated OS noise. min / max / avg
1935+
// are deliberately not exposed — max overweights one-off background activity
1936+
// and avg is hard to act on.
1937+
1938+
export interface IAutoConfigResourcePercentile {
1939+
p50: number;
1940+
p95: number;
1941+
}
1942+
1943+
export interface IAutoConfigResourceGpu {
1944+
available: boolean;
1945+
vramUsedMB?: IAutoConfigResourcePercentile;
1946+
vramBudgetMB?: number;
1947+
}
1948+
1949+
export type AutoConfigResourcePhase = 'bandwidth' | 'stream_encoder' | 'recording_encoder';
1950+
1951+
export interface IAutoConfigResourceUsage {
1952+
phase: AutoConfigResourcePhase;
1953+
sampleCount: number;
1954+
durationMs: number;
1955+
cpuPct: IAutoConfigResourcePercentile;
1956+
procRamMB: IAutoConfigResourcePercentile;
1957+
gpu: IAutoConfigResourceGpu;
1958+
}
1959+
1960+
// Parsed shape of NodeObs.GetAutoConfigSummary(). Only the fields the
1961+
// resource-usage feature consumes are typed; other historical fields
1962+
// (encoderDetection, videoDecision, bandwidthTest, selection) are present in
1963+
// the JSON but intentionally left as `unknown` — type them when you need them.
1964+
export interface IAutoConfigSummary {
1965+
complete: boolean;
1966+
resourceUsage: IAutoConfigResourceUsage[];
1967+
[key: string]: unknown;
1968+
}
1969+
19281970
export const enum VCamOutputType {
19291971
Invalid,
19301972
SceneOutput,

js/tsconfig.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
"noImplicitAny": true,
1111
"noImplicitThis": true,
1212
"removeComments": true,
13-
"suppressImplicitAnyIndexErrors": true,
1413
"allowSyntheticDefaultImports": true,
1514
"strictNullChecks": false,
1615
"emitDecoratorMetadata": true

obs-studio-server/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,8 @@ SET(osn-server_SOURCES
368368
"${PROJECT_SOURCE_DIR}/source/nodeobs_audio_encoders.h"
369369
"${PROJECT_SOURCE_DIR}/source/nodeobs_autoconfig.cpp"
370370
"${PROJECT_SOURCE_DIR}/source/nodeobs_autoconfig.h"
371+
"${PROJECT_SOURCE_DIR}/source/nodeobs_autoconfig_resource_sampler.cpp"
372+
"${PROJECT_SOURCE_DIR}/source/nodeobs_autoconfig_resource_sampler.h"
371373
"${PROJECT_SOURCE_DIR}/source/nodeobs_configManager.cpp"
372374
"${PROJECT_SOURCE_DIR}/source/nodeobs_configManager.hpp"
373375
"${PROJECT_SOURCE_DIR}/source/nodeobs_display.cpp"

obs-studio-server/source/nodeobs_autoconfig.cpp

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
******************************************************************************/
1818

1919
#include "nodeobs_autoconfig.h"
20+
#include "nodeobs_autoconfig_resource_sampler.h"
2021
#include <algorithm>
2122
#include <array>
2223
#include <future>
@@ -158,6 +159,12 @@ struct AutoconfigRun {
158159
};
159160
std::vector<SelectionDetail> selectionDetails;
160161

162+
// Per-phase resource samples (CPU%, process RAM, optionally GPU VRAM).
163+
// Captured by ResourceSampler around the bandwidth and encoder test phases;
164+
// surfaced via the resource_usage event + summary IPC. Pure telemetry — no
165+
// influence on the selection heuristics in this version.
166+
std::vector<autoConfig::ResourceWindow> resourceWindows;
167+
161168
// Per-canvas video-context decision. Captured in applyResults.
162169
struct VideoDecision {
163170
void *contextPtr = nullptr;
@@ -234,6 +241,65 @@ void autoConfig::WaitPendingTests(double timeout)
234241
}
235242
}
236243

244+
// Serialize a ResourceWindow to JSON and emit a resource_usage event. The window
245+
// is also pushed onto runContext.resourceWindows so GetAutoConfigSummary can
246+
// re-emit it later. Frontends can consume either the event stream or the summary.
247+
static std::string resourceWindowToJson(const autoConfig::ResourceWindow &w)
248+
{
249+
obs_data_t *root = obs_data_create();
250+
obs_data_set_string(root, "phase", w.phase.c_str());
251+
obs_data_set_int(root, "sampleCount", w.sampleCount);
252+
obs_data_set_int(root, "durationMs", w.durationMs);
253+
254+
// p50 is the typical value during the window; p95 is the sustained ceiling
255+
// after dropping single-sample outliers (a background process briefly using
256+
// CPU shouldn't dominate the report).
257+
auto putPct = [&](const char *key, double p50, double p95) {
258+
obs_data_t *o = obs_data_create();
259+
obs_data_set_double(o, "p50", p50);
260+
obs_data_set_double(o, "p95", p95);
261+
obs_data_set_obj(root, key, o);
262+
obs_data_release(o);
263+
};
264+
auto putPctInt = [&](obs_data_t *parent, const char *key, uint64_t p50, uint64_t p95) {
265+
obs_data_t *o = obs_data_create();
266+
obs_data_set_int(o, "p50", (long long)p50);
267+
obs_data_set_int(o, "p95", (long long)p95);
268+
obs_data_set_obj(parent, key, o);
269+
obs_data_release(o);
270+
};
271+
272+
putPct("cpuPct", w.p50Sample.cpuPct, w.p95Sample.cpuPct);
273+
putPct("procRamMB", w.p50Sample.procRamMB, w.p95Sample.procRamMB);
274+
275+
obs_data_t *gpu = obs_data_create();
276+
obs_data_set_bool(gpu, "available", w.gpuAvailable);
277+
if (w.gpuAvailable) {
278+
putPctInt(gpu, "vramUsedMB", w.p50Sample.gpuVramUsedMB, w.p95Sample.gpuVramUsedMB);
279+
// Budget is platform-driven and effectively constant across a window —
280+
// surface a single number rather than a percentile pair.
281+
obs_data_set_int(gpu, "vramBudgetMB", (long long)w.p95Sample.gpuVramBudgetMB);
282+
}
283+
obs_data_set_obj(root, "gpu", gpu);
284+
obs_data_release(gpu);
285+
286+
std::string json = obs_data_get_json(root);
287+
obs_data_release(root);
288+
return json;
289+
}
290+
291+
static void recordResourceWindow(const autoConfig::ResourceWindow &w)
292+
{
293+
if (w.sampleCount <= 0)
294+
return;
295+
296+
runContext.resourceWindows.push_back(w);
297+
298+
std::string payload = resourceWindowToJson(w);
299+
std::lock_guard<std::mutex> lock(eventsMutex);
300+
events.push(AutoConfigInfo("resource_usage", w.phase, 100, payload));
301+
}
302+
237303
void autoConfig::TestHardwareEncoding(void)
238304
{
239305
size_t idx = 0;
@@ -518,6 +584,23 @@ void autoConfig::GetAutoConfigSummary(void *data, const int64_t id, const std::v
518584
obs_data_release(sel);
519585
}
520586

587+
// resourceUsage — per-phase CPU/RAM (and Windows-only GPU VRAM) samples
588+
// captured during the bandwidth and encoder test phases. Same JSON shape
589+
// as the resource_usage event payload.
590+
{
591+
obs_data_array_t *windows = obs_data_array_create();
592+
for (auto &w : runContext.resourceWindows) {
593+
std::string s = resourceWindowToJson(w);
594+
obs_data_t *item = obs_data_create_from_json(s.c_str());
595+
if (item) {
596+
obs_data_array_push_back(windows, item);
597+
obs_data_release(item);
598+
}
599+
}
600+
obs_data_set_array(root, "resourceUsage", windows);
601+
obs_data_array_release(windows);
602+
}
603+
521604
std::string json = obs_data_get_json_pretty(root);
522605
obs_data_release(root);
523606

@@ -538,6 +621,15 @@ void autoConfig::InitializeAutoConfig(void *data, const int64_t id, const std::v
538621
runContext = AutoconfigRun{};
539622
cancel = false;
540623

624+
// Drain leftover events from a prior run. Otherwise a stopping_step queued
625+
// by an aborted bandwidth thread (e.g. after TerminateAutoConfig) leaks into
626+
// the next session's first drainUntil() and confuses callers.
627+
{
628+
std::lock_guard<std::mutex> lock(eventsMutex);
629+
while (!events.empty())
630+
events.pop();
631+
}
632+
541633
if (!args.empty()) {
542634
const std::vector<char> &bin = args[0].value_bin;
543635
size_t n = bin.size() / sizeof(uint64_t);
@@ -846,9 +938,11 @@ void autoConfig::TestBandwidthThreadV2(void)
846938
// the loop above exits as soon as signals are drained (often
847939
// < 100 ms after the RTMP connection opens) and totalBytes is 0.
848940
int dataWaitMs = 0;
941+
autoConfig::ResourceSampler sampler;
849942
if (!gotError && allConnected) {
850943
const int targetWaitMs = 5000;
851944
auto dataStart = std::chrono::steady_clock::now();
945+
sampler.start("bandwidth");
852946
while (std::chrono::steady_clock::now() - dataStart < std::chrono::milliseconds(targetWaitMs)) {
853947
std::unique_lock<std::mutex> ul(m);
854948
if (cancel) {
@@ -857,8 +951,10 @@ void autoConfig::TestBandwidthThreadV2(void)
857951
}
858952
ul.unlock();
859953
std::this_thread::sleep_for(std::chrono::milliseconds(250));
954+
sampler.sample();
860955
}
861956
dataWaitMs = (int)std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - dataStart).count();
957+
recordResourceWindow(sampler.stop());
862958
}
863959

864960
if (!gotError) {
@@ -1693,6 +1789,9 @@ void autoConfig::TestStreamEncoderThread()
16931789
events.push(AutoConfigInfo("starting_step", "runContext.streamingEncoder_test", 0));
16941790
eventsMutex.unlock();
16951791

1792+
autoConfig::ResourceSampler sampler;
1793+
sampler.start("stream_encoder", std::chrono::milliseconds(250));
1794+
16961795
TestHardwareEncoding();
16971796

16981797
if (!runContext.softwareTested) {
@@ -1738,6 +1837,8 @@ void autoConfig::TestStreamEncoderThread()
17381837
events.push(AutoConfigInfo("encoder_detection", "summary", 100, payload));
17391838
}
17401839

1840+
recordResourceWindow(sampler.stop());
1841+
17411842
eventsMutex.lock();
17421843
events.push(AutoConfigInfo("stopping_step", "runContext.streamingEncoder_test", 100));
17431844
eventsMutex.unlock();
@@ -1749,6 +1850,9 @@ void autoConfig::TestRecordingEncoderThread()
17491850
events.push(AutoConfigInfo("starting_step", "runContext.recordingEncoder_test", 0));
17501851
eventsMutex.unlock();
17511852

1853+
autoConfig::ResourceSampler sampler;
1854+
sampler.start("recording_encoder", std::chrono::milliseconds(250));
1855+
17521856
TestHardwareEncoding();
17531857

17541858
if (!runContext.hardwareEncodingAvailable && !runContext.softwareTested) {
@@ -1785,6 +1889,8 @@ void autoConfig::TestRecordingEncoderThread()
17851889
}
17861890
}
17871891

1892+
recordResourceWindow(sampler.stop());
1893+
17881894
eventsMutex.lock();
17891895
events.push(AutoConfigInfo("stopping_step", "runContext.recordingEncoder_test", 100));
17901896
eventsMutex.unlock();

0 commit comments

Comments
 (0)