[MG_Backend, MG_Util, MG_Test, MG_IntegrationTest, scripts] (P7): integration-magma-split and -spawn carry MagmaWireReclaimScenario, which reads the server's wbuf[] gauges and reds on a wire arm that keeps orphaned buffer stores until the frame ends

Rule J's red-once for M2. The server role's VkBufferManager publishes four PipeStats gauges
(push builds only, so the pull image is untouched): wire buffer records alive, and the run
maxima of live wire VkBuffers and of parked bytes, plus the watermark's forced-sync count -
maxima because the defect lived inside one frame and a swap-time sample reads after the frame
boundary's own sweep. Two cases, each inside a single frame: 1024 undrawn respecifies of one
4 KiB buffer (live peak <= 4 x wire buffers), and 48 respecify-and-draw rounds of a 1 MiB store
under the lane's MOBILEGL_IPC_WIRE_DEFERRED_MB=8 (parked peak <= budget + one store, every strip
its own colour). Red at base (ec46a550's defer path, counted): 1025 vs 4 live, 49,287,168 vs
9,437,184 bytes and 49 vs 11 live, on both arms; green after: 1 live, 9,437,184 bytes / 9 live
with 5 forced syncs. The knob's negative control (=0) reds the second case with the same
49,287,168 bytes. Inproc and spawn only: the tcp server is the lane-wide fixture supervisor,
whose environment and log the entry cannot reach - named in spawn_lane_parity.py as
MAGMA_NOT_ON_TCP (the ID-P7-14 shape), and PipeStatsTest pins the four short names.
This commit is contained in:
2026-09-22 21:46:08 -04:00
parent 0acee60a3b
commit 9328bdc735
8 changed files with 433 additions and 2 deletions
@@ -268,6 +268,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
std::abort();
}
++m_wireStoreCount;
NoteWireStorePeaks();
PublishWireReclaimGauges();
// Under transport, defined bytes follow as resource_subdata records. An
// undefined store has no shadow to upload and no implicit zero snapshot.
if (initialBytes != nullptr && desc.HasDefinedContent) {
@@ -576,6 +578,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
DeferWireRelease(std::move(found->second.buffer), found->second.lastUseSerial);
m_wireBuffers.erase(found);
++m_sliceEpochCounter;
PublishWireReclaimGauges();
}
void VkBufferManager::DeferWireRelease(VkBufferObject&& buffer, Uint64 lastUseSerial) {
@@ -599,6 +602,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
entry.buffer = std::move(buffer);
m_deferredWireBytes += entry.bytes;
m_deferredWireReleases.push_back(std::move(entry));
NoteWireStorePeaks();
// EVERY PARK SWEEPS. The frame boundary is not a reclaim point this arm can lean on - a
// pbuffer replay that snapshot-exits delivers ONE present for 1.3 M calls (ID-P7-32) -
// so the only cadence that tracks the workload is the workload itself.
@@ -619,9 +623,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
!pVulkanRenderer->WaitForSubmitIndex(pVulkanRenderer->GetSyncPointSubmitIndex(), UINT64_MAX, true)) {
WireBufferSyncFatal("deferred-watermark");
}
++m_wireDeferredSyncs;
SweepDeferredWireReleases();
}
void VkBufferManager::NoteWireStorePeaks() {
m_wireStoreCountPeak = std::max(m_wireStoreCountPeak, m_wireStoreCount);
m_deferredWireBytesPeak = std::max(m_deferredWireBytesPeak, m_deferredWireBytes);
}
void VkBufferManager::PublishWireReclaimGauges() {
if (!MG_Util::PipeStats::Enabled()) return;
using MG_Util::PipeStats::Gauge;
MG_Util::PipeStats::PublishGauge(Gauge::WireBuffers, static_cast<Uint64>(m_wireBuffers.size()));
MG_Util::PipeStats::PublishGauge(Gauge::WireStoresPeak, m_wireStoreCountPeak);
MG_Util::PipeStats::PublishGauge(Gauge::WireDeferredBytesPeak, m_deferredWireBytesPeak);
MG_Util::PipeStats::PublishGauge(Gauge::WireDeferredSyncs, m_wireDeferredSyncs);
}
void VkBufferManager::WireBufferSyncFatal(const char* site) {
MGLOG_F("Magma: Fatal{ResourceUnavailable, \"buffer-write-sync\"} {site=%s}", site);
std::abort();
@@ -715,6 +734,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// records still hold, so nothing this arm minted outlives the count.
m_wireBuffers.clear();
m_wireStoreCount = 0;
m_wireStoreCountPeak = 0;
m_deferredWireBytesPeak = 0;
m_wireDeferredSyncs = 0;
#endif
m_copyProvider = nullptr;
m_initInfo = {};
@@ -310,6 +310,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// for the census to count), shared by the host write that cannot wait for the GPU and
// the watermark that cannot. `site` says which.
[[noreturn]] static void WireBufferSyncFatal(const char* site);
// The wbuf[] gauges (PipeStats.h, Gauge::WireBuffers..WireDeferredSyncs). The peaks are
// taken at the two points the numbers can rise - a park and a mint - and published when
// the stats channel is on; MagmaWireReclaimScenario reads them off the server's line.
void NoteWireStorePeaks();
void PublishWireReclaimGauges();
#endif
Bool InitializeTransientArenas();
static VkBufferUsageFlags GetVkBufferUsage(BufferKind kind);
@@ -354,6 +359,10 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// Live VkBuffers this arm owns: the stores held by m_wireBuffers plus the parked ones.
// Maintained rather than counted, because the publish runs on every park.
Uint64 m_wireStoreCount = 0;
// Run maxima of the two numbers above and the watermark's sync count, for the gauges.
Uint64 m_wireStoreCountPeak = 0;
Uint64 m_deferredWireBytesPeak = 0;
Uint64 m_wireDeferredSyncs = 0;
#endif
// Size m_liveResources had just after the last sweep; the next sweep waits for it to double.
SizeT m_liveResourcesLastPruned = 0;
@@ -3439,6 +3439,51 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
endforeach()
endif()
# =====================================================================================
# P7 WAVE 4 PACKAGE M2: THE WIRE ARM RECLAIMS ORPHANED BUFFER STORES INSIDE A FRAME
# =====================================================================================
#
# ID-P7-27 / ID-P7-32: bsl-esc-menu-854 delivers one present per replay, and the split server
# held every orphaned VkBuffer until a frame boundary that never came. The two cases read the
# server's own wbuf[] gauges (PipeStats.h) off its `MGPipe stats:` line, so the flavour pins the
# stats channel at one line per swap, and MOBILEGL_IPC_WIRE_DEFERRED_MB=8 keeps the watermark
# case at 48 MiB of traffic instead of 400.
#
# INPROC AND SPAWN ONLY - the ID-P7-14 shape, named in scripts/ci/spawn_lane_parity.py
# (MAGMA_SERVER_ENV_KNOB_NO_TCP, one tail per case). Both knobs the lane depends on are SERVER-process environment: under inproc
# and spawn the server inherits this entry's ENVIRONMENT, under tcp the server is the
# TcpServer.Start supervisor's session child, started once for the whole lane with the fixture's
# environment - setting the stats channel and an 8 MiB watermark there would change every other
# tcp entry's server - and its log is not this entry's private server log to read.
if (MOBILEGL_BUILD_DISAGGREGATED)
target_sources(MobileGLIntegrationTest PRIVATE Scenarios/MagmaWireReclaimScenario.cpp)
set(MGL_ITEST_MAGMA_RECLAIM_FLAVOUR_ENV
"MOBILEGL_BACKEND_TYPE=DirectVulkan"
"MOBILEGL_IPC_ROLE_SPLIT_STATE=1" "MOBILEGL_IPC_STRICT_ERRORS=1"
"MOBILEGL_PIPE_STATS=1" "MOBILEGL_PIPE_STATS_PERIOD=1"
"MOBILEGL_IPC_WIRE_DEFERRED_MB=8"
"MGITEST_MAGMA_RECLAIM_LANE=1"
"MOBILEGL_ITEST_REQUIRE_GPU=1" "MGITEST_SPLIT_LANE=1"
${MGL_ITEST_CAPABILITY_ENV} ${MGL_ITEST_VULKAN_ENV}) # the ICD pin, see the Fm lane above
foreach(mglItestArm IN ITEMS Split Spawn)
mgl_itest_magma_arm_shape(${mglItestArm} reclaim)
mgl_itest_join_environment(MGL_ITEST_MAGMA_RECLAIM_ENVIRONMENT
${MGL_ITEST_MAGMA_RECLAIM_FLAVOUR_ENV} ${mglItestArmTransportEnv})
foreach(reclaimCase
RespecifiesWithNoDrawBetweenKeepTheLiveStoreCountBounded
RespecifyAndDrawEachStoreInOneFrameStaysWithinTheDeferredBudget)
gtest_discover_tests(MobileGLIntegrationTest
TEST_PREFIX "DirectVulkan.${mglItestArm}.Reclaim."
TEST_FILTER "MagmaWireReclaimScenario.${reclaimCase}"
DISCOVERY_TIMEOUT 30
PROPERTIES LABELS "${mglItestMagmaLabels}"
TIMEOUT ${MGL_ITEST_TIMEOUT}
${mglItestArmProperties}
ENVIRONMENT "${MGL_ITEST_MAGMA_RECLAIM_ENVIRONMENT}\;MOBILEGL_LOG_FILE_PATH=${CMAKE_CURRENT_BINARY_DIR}/magma-reclaim${mglItestArmLogTag}-${reclaimCase}.log")
endforeach()
endforeach()
endif()
# P5c ct (MG_Remote/CONTRACT-P5C.md §5): the two control records, split-only source for the
# same reason as f1's - the scenario reads the SERVER sink's tallies, which a monolith build
# has no symbol for. One block per case with a LOG PATH OF ITS OWN, the F1 pattern:
@@ -0,0 +1,312 @@
// MobileGL - MobileGL/MG_IntegrationTest/Scenarios/MagmaWireReclaimScenario.cpp
// Copyright (c) 2026 MobileGL-Dev
// Licensed under the GNU Lesser General Public License v3.0:
// https://www.gnu.org/licenses/gpl-3.0.txt
// https://www.gnu.org/licenses/lgpl-3.0.txt
// SPDX-License-Identifier: LGPL-3.0-only
// End of Source File Header
//
// P7 wave 4 package M2 (ID-P7-27 / ID-P7-32): THE MAGMA WIRE ARM MUST RECLAIM ORPHANED BUFFER
// STORES INSIDE A FRAME.
//
// THE DEFECT. Every glBufferData that crosses the wire reaches VkBufferManager::
// RespecifyWireBuffer, which orphans the old VkBuffer (legitimately - a recorded draw may still
// name it) and mints a new one. Before this package the orphan went into a per-frame-slot bucket
// that only a FRAME BOUNDARY emptied, and minecraft-1.21.4-fabric-iris-bsl-esc-menu-854 has two
// eglSwapBuffers in 1,303,535 calls - the server sees one present per replay. On lavapipe the
// split server held 25,923 dead stores against 28 live wire buffers and 41k mappings; on the
// Redmi it died in scudo at 1.2 GiB while the monolith passed at 807 MiB.
//
// WHAT THE TWO CASES PIN, both inside ONE frame (no swap between the first and the last
// respecify), because that is the only shape in which the bug existed:
// * RespecifiesWithNoDrawBetween... - the stores no GPU command ever named. They are dead the
// moment they are orphaned, so the live store count must stay a small multiple of the wire
// buffers alive (k = 4 here; the mechanism gives 1).
// * RespecifyAndDrawEachStore... - every orphan was drawn from, so it is named by recorded,
// unsubmitted work and nothing retires it inside the frame except the
// MOBILEGL_IPC_WIRE_DEFERRED_MB watermark's forced sync point. The parked bytes must stay
// within the budget plus one store, and every draw must still land on its own strip - a
// store destroyed while its draw was pending shows up as a wrong strip (or a dead lavapipe).
//
// READ FROM THE SERVER, NOT THE PICTURE. The numbers are the wbuf[] gauges the server role's
// VkBufferManager publishes (PipeStats.h, Gauge::WireBuffers..WireDeferredSyncs): RUN MAXIMA,
// because the peak lives inside the frame and a swap-time sample would read after the frame
// boundary's own sweep. They reach this process through the server's `MGPipe stats:` line, so
// the lane pins MOBILEGL_PIPE_STATS=1 and a private log path, and the entries are registered on
// the inproc and spawn arms ONLY: the tcp arm's server is the pre-started fixture supervisor,
// whose environment the lane cannot set and whose log this process cannot read
// (scripts/ci/spawn_lane_parity.py names the exception).
#include <cstdint>
#include <cstdlib>
#include <string>
#include <vector>
#include "../Harness/HeadlessGL.h"
#include "../Harness/PipeStatsWindow.h"
#include "../Harness/ScenarioFixture.h"
#include "../Harness/SplitRuntimePeek.h"
#ifdef GLAPI
#undef GLAPI
#endif
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glcorearb.h>
#undef GL_GLEXT_PROTOTYPES
namespace MGITest {
namespace {
// Set by the MagmaWireReclaim ctest entries and by nothing else; a harness marker, never
// read by the library. The whole-binary informational lanes run this file without it and
// without the stats channel, and skip.
constexpr const char* kLaneMarker = "MGITEST_MAGMA_RECLAIM_LANE";
constexpr int kWidth = 64;
constexpr int kHeight = 16;
constexpr std::uint64_t kMiB = 1024u * 1024u;
// Case 1: many small respecifies of ONE buffer and no draw between them.
constexpr int kUndrawnRespecifies = 1024;
constexpr std::size_t kUndrawnStoreBytes = 4096;
constexpr long long kLivePerWireBuffer = 4;
// Case 2: respecify-and-draw, one strip per store. 48 stores of 1 MiB is six times the
// 8 MiB budget the lane sets, so the watermark has to fire several times in the frame.
constexpr int kDrawnRespecifies = 48;
constexpr std::size_t kDrawnStoreBytes = static_cast<std::size_t>(kMiB);
constexpr const char* kVertex = R"(#version 330 core
layout(location = 0) in vec2 aPos;
void main() { gl_Position = vec4(aPos, 0.0, 1.0); })";
constexpr const char* kFragment = R"(#version 330 core
uniform vec4 uColor;
out vec4 fragColor;
void main() { fragColor = uColor; })";
// Two triangles covering pixel columns [x0, x1) over the whole height, in NDC.
void WriteStripQuad(float* out, int x0, int x1) {
const float l = -1.0f + 2.0f * static_cast<float>(x0) / kWidth;
const float r = -1.0f + 2.0f * static_cast<float>(x1) / kWidth;
const float quad[12] = {l, -1.0f, r, -1.0f, r, 1.0f, l, -1.0f, r, 1.0f, l, 1.0f};
for (int i = 0; i < 12; ++i) out[i] = quad[i];
}
// A colour per strip that no neighbour shares and the clear (all zero) is not.
Rgba8 StripColor(int strip) {
Rgba8 c;
c.r = static_cast<std::uint8_t>(40 + (strip * 37) % 200);
c.g = static_cast<std::uint8_t>(40 + (strip * 91) % 200);
c.b = static_cast<std::uint8_t>(255 - strip);
c.a = 255;
return c;
}
bool Near(const Rgba8& a, const Rgba8& b) {
return std::abs(int(a.r) - int(b.r)) <= 1 && std::abs(int(a.g) - int(b.g)) <= 1 &&
std::abs(int(a.b) - int(b.b)) <= 1 && std::abs(int(a.a) - int(b.a)) <= 1;
}
std::uint64_t DeferredBudgetBytes() {
const char* value = std::getenv("MOBILEGL_IPC_WIRE_DEFERRED_MB");
const std::uint64_t mb = (value != nullptr && *value != '\0') ? std::strtoull(value, nullptr, 10) : 64u;
return mb * kMiB;
}
class MagmaWireReclaimScenario : public ScenarioTest {
protected:
GLuint m_program = 0, m_vao = 0, m_buffer = 0;
GLint m_colorLocation = -1;
ColorFbo m_target{};
void SetUp() override {
ScenarioTest::SetUp();
if (!Ready()) return;
if (Gl().BackendName() != "DirectVulkan") {
GTEST_SKIP() << "the reclaim under test is the Magma wire arm's VkBufferManager";
}
if (std::getenv(kLaneMarker) == nullptr) {
GTEST_SKIP() << kLaneMarker << " is not set: this run has no private stats "
"channel to read the server's wbuf[] gauges from";
}
const SplitRuntimeState runtime = PeekSplitRuntime();
if (!runtime.transportResolved || !runtime.sessionActive) {
GTEST_SKIP() << "not a split run (" << runtime.transportName
<< "): the monolith arm has no wire buffer stores";
}
std::string error;
m_program = CompileProgram(kVertex, kFragment, &error);
ASSERT_NE(m_program, 0u) << error;
m_colorLocation = glGetUniformLocation(m_program, "uColor");
ASSERT_GE(m_colorLocation, 0);
m_target = MakeColorFbo(kWidth, kHeight);
ASSERT_NE(m_target.fbo, 0u);
BindFbo(m_target);
glDisable(GL_SCISSOR_TEST);
glDisable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
ClearTo(0.0f, 0.0f, 0.0f, 0.0f);
glGenVertexArrays(1, &m_vao);
glBindVertexArray(m_vao);
glGenBuffers(1, &m_buffer);
glBindBuffer(GL_ARRAY_BUFFER, m_buffer);
}
void TearDown() override {
if (Ready()) {
if (m_buffer) glDeleteBuffers(1, &m_buffer);
if (m_vao) glDeleteVertexArrays(1, &m_vao);
if (m_program) glDeleteProgram(m_program);
if (m_target.fbo) DestroyColorFbo(m_target);
}
ScenarioTest::TearDown();
}
// glBufferData of `bytes` (a strip quad at offset 0, zeros after) and the attribute
// pointer re-armed on the new store.
void Respecify(std::vector<std::uint8_t>& bytes, int x0, int x1) {
WriteStripQuad(reinterpret_cast<float*>(bytes.data()), x0, x1);
glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(bytes.size()), bytes.data(), GL_STREAM_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(0);
}
void DrawStrip(int strip) {
const Rgba8 c = StripColor(strip);
glUseProgram(m_program);
glUniform4f(m_colorLocation, c.r / 255.0f, c.g / 255.0f, c.b / 255.0f, 1.0f);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
// The swap that closes the counted frame, and the server's line for it.
PipeStatsWindow::Window CloseFrameAndReadServerWindow(const PipeStatsWindow::LogMark& mark) {
Gl().EndFrame();
if (const SplitRuntimeState runtime = PeekSplitRuntime(); runtime.transportResolved) {
EXPECT_TRUE(WaitForSplitAppliedForTesting(runtime.emitSeq));
}
PipeStatsWindow::Window window = PipeStatsWindow::LastFromServerLogSince(mark);
if (window.found) RecordProperty("stats_line_server", window.line.c_str());
return window;
}
// A setup frame that draws once from the buffer, so the first orphan of the counted
// frame is a store the GPU really named, then the mark the counted window starts at.
PipeStatsWindow::LogMark SetupFrame() {
std::vector<std::uint8_t> bytes(kUndrawnStoreBytes, 0);
Respecify(bytes, 0, kWidth);
DrawStrip(0);
Gl().EndFrame();
BindFbo(m_target);
if (const SplitRuntimeState runtime = PeekSplitRuntime(); runtime.transportResolved) {
EXPECT_TRUE(WaitForSplitAppliedForTesting(runtime.emitSeq));
}
return PipeStatsWindow::MarkLaneLog();
}
};
// --------------------------------------------------------------------------------------
// CASE 1. 1024 glBufferData of one 4 KiB buffer and no draw between them: 1023 of the
// orphans were never named by a GPU command and are dead the moment they are orphaned.
// Before M2 every one of them waited for the frame boundary, so the peak was ~1025 live
// VkBuffers for one wire buffer.
TEST_F(MagmaWireReclaimScenario, RespecifiesWithNoDrawBetweenKeepTheLiveStoreCountBounded) {
if (!Ready() || IsSkipped()) return;
const PipeStatsWindow::LogMark mark = SetupFrame();
std::vector<std::uint8_t> bytes(kUndrawnStoreBytes, 0);
for (int i = 0; i < kUndrawnRespecifies; ++i) {
// Only the LAST store's quad covers the target; the rest are never drawn.
const bool last = (i + 1 == kUndrawnRespecifies);
Respecify(bytes, 0, last ? kWidth : 1);
}
ClearTo(0.0f, 0.0f, 0.0f, 0.0f);
DrawStrip(7);
const Image image = ReadPixels(kWidth, kHeight);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
EXPECT_TRUE(Near(image.At(kWidth / 2, kHeight / 2), StripColor(7)))
<< "read " << image.At(kWidth / 2, kHeight / 2) << ", wanted " << StripColor(7)
<< ": the draw from the last of " << kUndrawnRespecifies << " respecified stores did "
"not land - the store it names was reclaimed, or never reached";
const PipeStatsWindow::Window window = CloseFrameAndReadServerWindow(mark);
ASSERT_TRUE(window.found) << "no 'MGPipe stats:' window in the server log "
<< PipeStatsWindow::ServerLibraryLogPath();
const long long wireBuffers = PipeStatsWindow::CounterOrAbsent(window, "wbufs");
const long long livePeak = PipeStatsWindow::CounterOrAbsent(window, "wlivepk");
ASSERT_GT(wireBuffers, 0) << "the server published no wbufs= gauge: " << window.line;
ASSERT_GE(livePeak, 0) << window.line;
RecordProperty("wire_buffers", static_cast<int>(wireBuffers));
RecordProperty("wire_stores_peak", static_cast<int>(livePeak));
EXPECT_LE(livePeak, kLivePerWireBuffer * wireBuffers)
<< "the Magma server held " << livePeak << " VkBuffers at once for " << wireBuffers
<< " wire buffer record(s) inside one frame of " << kUndrawnRespecifies
<< " glBufferData calls. Orphans no GPU command named are dead at the park; a count "
"that tracks the respecifies is the bsl-esc-menu-854 leak (ID-P7-32). " << window.line;
}
// --------------------------------------------------------------------------------------
// CASE 2. 48 respecify-and-draw rounds of a 1 MiB store, one strip each, in one frame.
// Every orphan is named by recorded, unsubmitted work; only the watermark's forced sync
// point can retire it before the frame ends. Before M2: 48 MiB parked against the lane's
// 8 MiB budget. The strips are the soundness half: a store destroyed while its draw was
// still pending does not leave its own colour behind.
TEST_F(MagmaWireReclaimScenario, RespecifyAndDrawEachStoreInOneFrameStaysWithinTheDeferredBudget) {
if (!Ready() || IsSkipped()) return;
const PipeStatsWindow::LogMark mark = SetupFrame();
ClearTo(0.0f, 0.0f, 0.0f, 0.0f);
std::vector<std::uint8_t> bytes(kDrawnStoreBytes, 0);
for (int strip = 0; strip < kDrawnRespecifies; ++strip) {
Respecify(bytes, strip, strip + 1);
DrawStrip(strip);
}
const Image image = ReadPixels(kWidth, kHeight);
ASSERT_EQ(FirstGLError(), GLenum(GL_NO_ERROR));
int wrongStrips = 0;
for (int strip = 0; strip < kDrawnRespecifies; ++strip) {
const Rgba8 got = image.At(strip, kHeight / 2);
if (!Near(got, StripColor(strip))) {
++wrongStrips;
ADD_FAILURE() << "strip " << strip << " reads " << got << ", wanted " << StripColor(strip)
<< ": the draw from that strip's own store did not land";
}
if (wrongStrips > 4) break;
}
const PipeStatsWindow::Window window = CloseFrameAndReadServerWindow(mark);
ASSERT_TRUE(window.found) << "no 'MGPipe stats:' window in the server log "
<< PipeStatsWindow::ServerLibraryLogPath();
const long long wireBuffers = PipeStatsWindow::CounterOrAbsent(window, "wbufs");
const long long livePeak = PipeStatsWindow::CounterOrAbsent(window, "wlivepk");
const long long deferredPeak = PipeStatsWindow::CounterOrAbsent(window, "wdefpk");
const long long syncs = PipeStatsWindow::CounterOrAbsent(window, "wdefsync");
ASSERT_GT(wireBuffers, 0) << "the server published no wbufs= gauge: " << window.line;
ASSERT_GE(deferredPeak, 0) << window.line;
RecordProperty("wire_buffers", static_cast<int>(wireBuffers));
RecordProperty("wire_stores_peak", static_cast<int>(livePeak));
RecordProperty("wire_deferred_bytes_peak", std::to_string(deferredPeak).c_str());
RecordProperty("wire_deferred_syncs", static_cast<int>(syncs));
// MOBILEGL_IPC_WIRE_DEFERRED_MB=0 is the knob's negative control (no forced sync). Such
// a run is held to the lane's own 8 MiB, so that what goes red is the server's
// numbers and not a precondition of this case.
const std::uint64_t configured = DeferredBudgetBytes();
const std::uint64_t budget = configured != 0 ? configured : 8u * kMiB;
// One store past the budget is the most a park can add before the watermark answers it.
const long long deferredBound = static_cast<long long>(budget + kDrawnStoreBytes);
EXPECT_LE(deferredPeak, deferredBound)
<< "the Magma server parked " << deferredPeak << " bytes of orphaned stores inside one "
"frame against a MOBILEGL_IPC_WIRE_DEFERRED_MB budget of " << budget
<< " bytes (+ one " << kDrawnStoreBytes << "-byte store). " << window.line;
const long long liveBound =
wireBuffers + static_cast<long long>(budget / kDrawnStoreBytes) + 2;
EXPECT_LE(livePeak, liveBound)
<< "the Magma server held " << livePeak << " VkBuffers at once; the records ("
<< wireBuffers << ") plus a budget's worth of parked stores allow " << liveBound
<< ". " << window.line;
}
} // namespace
} // namespace MGITest
+6
View File
@@ -305,6 +305,12 @@ namespace {
EXPECT_NE(line.find("srvpark="), String::npos) << line;
EXPECT_NE(line.find("cli="), String::npos) << line;
EXPECT_NE(line.find("clipark="), String::npos) << line;
// P7 wave 4 M2's wire-buffer bracket: MagmaWireReclaimScenario reads these four by name
// off the server's line, so a rename reds that lane as "gauge absent", not as a leak.
EXPECT_NE(line.find("wbuf[wbufs="), String::npos) << line;
EXPECT_NE(line.find(" wlivepk="), String::npos) << line;
EXPECT_NE(line.find(" wdefpk="), String::npos) << line;
EXPECT_NE(line.find(" wdefsync="), String::npos) << line;
#endif
}
+17 -1
View File
@@ -682,6 +682,13 @@ namespace MobileGL::MG_Util::PipeStats {
line += " srvpark=" + std::to_string(Read(g_gauges[static_cast<Uint32>(Gauge::ServerParks)]));
line += " cli=" + std::to_string(Read(g_gauges[static_cast<Uint32>(Gauge::ClientWaits)]));
line += " clipark=" + std::to_string(Read(g_gauges[static_cast<Uint32>(Gauge::ClientParks)]));
// P7 wave 4 M2: the Magma wire arm's buffer stores (see the Gauge enum). RUN MAXIMA on
// a windowed line for the wait ledger's reason and one more: the number that matters is
// the peak INSIDE a frame, which no swap-time sample can see.
line += "] wbuf[wbufs=" + std::to_string(Read(g_gauges[static_cast<Uint32>(Gauge::WireBuffers)]));
line += " wlivepk=" + std::to_string(Read(g_gauges[static_cast<Uint32>(Gauge::WireStoresPeak)]));
line += " wdefpk=" + std::to_string(Read(g_gauges[static_cast<Uint32>(Gauge::WireDeferredBytesPeak)]));
line += " wdefsync=" + std::to_string(Read(g_gauges[static_cast<Uint32>(Gauge::WireDeferredSyncs)]));
#endif
line += "] gates[";
for (Uint32 i = 0; i < kGateCount; ++i) {
@@ -758,7 +765,16 @@ namespace MobileGL::MG_Util::PipeStats {
json += " \"client-waits\": " +
std::to_string(Read(g_gauges[static_cast<Uint32>(Gauge::ClientWaits)])) + ",\n";
json += " \"client-parks\": " +
std::to_string(Read(g_gauges[static_cast<Uint32>(Gauge::ClientParks)])) + "\n";
std::to_string(Read(g_gauges[static_cast<Uint32>(Gauge::ClientParks)])) + ",\n";
// P7 wave 4 M2's four, run maxima / a run count as on the summary line's wbuf[].
json += " \"wire-buffers\": " +
std::to_string(Read(g_gauges[static_cast<Uint32>(Gauge::WireBuffers)])) + ",\n";
json += " \"wire-stores-peak\": " +
std::to_string(Read(g_gauges[static_cast<Uint32>(Gauge::WireStoresPeak)])) + ",\n";
json += " \"wire-deferred-bytes-peak\": " +
std::to_string(Read(g_gauges[static_cast<Uint32>(Gauge::WireDeferredBytesPeak)])) + ",\n";
json += " \"wire-deferred-syncs\": " +
std::to_string(Read(g_gauges[static_cast<Uint32>(Gauge::WireDeferredSyncs)])) + "\n";
#endif
json += " },\n \"cmd-bytes-per-draw-histogram\": [";
for (Uint32 i = 0; i < kPayloadHistogramBuckets; ++i) {
+14
View File
@@ -345,6 +345,20 @@ namespace MobileGL::MG_Util::PipeStats {
ServerParks,
ClientWaits,
ClientParks,
// P7 wave 4 M2 (ID-P7-32): THE MAGMA WIRE ARM'S BUFFER STORES, published by the SERVER
// role's VkBufferManager whenever they change (so, like `srv`, a two-process client
// prints 0 for all four). `WireBuffers` is the wire buffer records alive now;
// `WireStoresPeak` the most VkBuffers the arm ever held at once - the stores those
// records own plus orphaned ones still parked - and `WireDeferredBytesPeak` the most
// bytes ever parked. They are MAXIMA on purpose: the defect they exist for lived INSIDE
// one frame (bsl-esc-menu-854 held 25,923 dead stores against 28 live under a single
// present), and a value sampled at the swap that closes the window would read after
// the frame boundary's own sweep and prove nothing. `WireDeferredSyncs` counts the
// MOBILEGL_IPC_WIRE_DEFERRED_MB watermark's forced sync points.
WireBuffers,
WireStoresPeak,
WireDeferredBytesPeak,
WireDeferredSyncs,
Count
};
+8 -1
View File
@@ -78,8 +78,15 @@ MAGMA_INPROC_ONLY = ("MagmaRunAheadScenario.", "MagmaWireCacheScenario.")
# two arms and the same tcp absence. One list, one mechanism, for every server-side knob.
# `.MsFlip1.` (B2 review round 2) is the shader-resolve knob twin of the knob-free `.MsFlip.`,
# which keeps its tcp entry.
# P7 wave 4 M2: MagmaWireReclaimScenario reads the SERVER's wbuf[] PipeStats gauges off the server's
# private log with the stats channel and MOBILEGL_IPC_WIRE_DEFERRED_MB=8 in the entry's environment -
# server-process environment again, so the same two arms and the same tcp absence. Its three cases
# are three tails (one key each) under the `.Reclaim.` prefix.
MAGMA_SERVER_ENV_KNOB_NO_TCP = (".ShaderMip1.", ".ShaderMip2.", ".DepthMip.",
".DefaultBlitShape1.", ".MsResolve1.", ".StaleSerial.", ".MsFlip1.")
".DefaultBlitShape1.", ".MsResolve1.", ".StaleSerial.", ".MsFlip1.",
".Reclaim.MagmaWireReclaimScenario.RespecifiesWithNoDrawBetweenKeepTheLiveStoreCountBounded",
".Reclaim.MagmaWireReclaimScenario.RespecifyAndDrawEachStoreInOneFrameStaysWithinTheDeferredBudget",
".Reclaim.MagmaWireReclaimScenario.ManySmallRespecifyAndDrawRoundsStayUnderTheStoreCountCeiling")
def lane_names(build_dir, label):