[Fix] (MG_Remote): the socket-io stats are LinkMetrics free functions, not StreamLink members

6bccf98d7 put TakeSendStats/TakeReadStats on the concrete StreamLink class, so
ClientSession.cpp and ServerLoop.cpp had to include <MG_Remote/Transport/StreamLink.h> -
which the P6.5 seam gate (scripts/ci/link_seam_purity.py, "concrete link included above
Transport") rejects, and the MGPipe generators and hygiene gates job has been red since.

The counters stay in StreamLink.cpp beside the recv/sendmsg loops that feed them; only the
struct and the two Take functions move, to LinkMetrics.h as LinkIoStats +
LinkMetricsTakeReadStats/TakeSendStats. The readers drop the concrete include; the hot
loops are untouched.
This commit is contained in:
2026-09-28 21:54:08 -04:00
parent 3bdd565dc6
commit 35dddc0c89
5 changed files with 40 additions and 27 deletions
+1 -2
View File
@@ -13,7 +13,6 @@
#include <MG_Remote/FatalFunnel.h>
#include <MG_Remote/Handshake.h>
#include <MG_Remote/Transport/LinkMetrics.h>
#include <MG_Remote/Transport/StreamLink.h>
#include "../CapsCodec.h"
#include "../Protocol/generated/protocol_generated.h"
@@ -2300,7 +2299,7 @@ namespace MobileGL::MG_Remote::Client {
const double creditMs =
static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::steady_clock::now() - paceStarted).count()) / 1e6;
const auto io = Transport::StreamLink::TakeSendStats();
const auto io = Transport::LinkMetricsTakeSendStats();
MGLOG_I("P65ClientPace present=%llu credit_wait_ms=%.1f | io: %llu B in %llu sendmsg "
"send_ms=%.1f total_ms=%.1f",
static_cast<unsigned long long>(serial), creditMs,
+2 -2
View File
@@ -10,7 +10,7 @@
#include "ServerLoop.h"
#include <MG_Remote/FatalFunnel.h>
#include <MG_Remote/Transport/StreamLink.h>
#include <MG_Remote/Transport/LinkMetrics.h>
#include <MG_Backend/MGPipe/PipeInputs.h>
#include <Config.h>
@@ -796,7 +796,7 @@ namespace MobileGL::MG_Remote::Server {
// recv - blocking included - so a frame that is 90% read_ms is a frame the
// reader spent WAITING for bytes, and one that is mostly recv calls with
// little read_ms is a reader that is copying.
const auto read = Transport::StreamLink::TakeReadStats();
const auto read = Transport::LinkMetricsTakeReadStats();
MGLOG_I("P65ServerFrame frame=%llu records=%llu apply_ms=%.1f wall_ms=%.1f "
"outside_ms=%.1f | read: %llu B in %llu recv(%llu reads) "
"recv_ms=%.1f total_ms=%.1f (%.0f%% blocked, %.1f MB/s)",
+27 -1
View File
@@ -1,4 +1,6 @@
// P6.5 transport measurements. Called only by the single client producer.
// P6.5 transport measurements. The Window state is written only by the single client
// producer; the socket-io counters drained by LinkMetricsTakeReadStats/TakeSendStats are
// atomic and cross-thread (io thread writes, apply/client thread reads) - see below.
#pragma once
#include <cstdint>
@@ -41,4 +43,28 @@ namespace MobileGL::MG_Remote::Transport {
std::uint64_t LinkMetricsRecordsFor(std::uint32_t op);
void LinkMetricsPresent();
void LinkMetricsServerPresent(std::uint64_t serial);
// P65READ: THE SERVER'S SOCKET-READ PATH, ACCOUNTED WHERE IT CAN BE SEEN. The counters are
// published from the io thread's recv loop and read by the APPLY thread, because the io
// thread's own MGLOG_ lines never reach the phone's forwarded log (only mgl-srv-apply and
// mgl-display-ser do - measured, not assumed). Taking them RESETS them, so one call is one
// frame's worth of the stage nothing else measures: the frame's time is not the client's GL
// thread (13%) and not PipeApplier::ApplyOne (11%), and this is what sits between them.
//
// Free functions, NOT StreamLink members: the P6.5 seam gate (scripts/ci/link_seam_purity.py)
// rejects a concrete-link include outside Transport, and the readers of these numbers live in
// ClientSession/ServerLoop. The counters themselves stay in StreamLink.cpp beside the recv /
// sendmsg loops that feed them.
struct LinkIoStats {
std::uint64_t bytes = 0; // payload bytes taken off the socket
std::uint64_t calls = 0; // recv() calls it took
std::uint64_t reads = 0; // Link::Read() calls (one per header, envelope or chunk)
std::uint64_t nsInRecv = 0; // wall time inside recv - blocking included
std::uint64_t nsTotal = 0; // wall time inside Read
};
LinkIoStats LinkMetricsTakeReadStats();
// The client's half: bytes handed to the io thread's sendmsg, the calls it took, and the
// wall time inside sendmsg (blocking included). nsInRecv carries the send time; reads is
// unused on this side.
LinkIoStats LinkMetricsTakeSendStats();
}
+6 -5
View File
@@ -5,6 +5,7 @@
#include "Doorbell.h"
#include "ReplySlot.h"
#include "Framing.h"
#include "LinkMetrics.h"
// P12: the declared-answer store's crossing log. Same include as LinkMetrics.cpp's, rather than
// <Includes.h>, because this file is transport-layer and pulls nothing else from the tree.
#include <MG_Util/Debug/Log.h>
@@ -28,7 +29,7 @@
namespace MobileGL::MG_Remote::Transport {
// P65Read's counters: written by the server's io thread, drained by its apply thread (see
// StreamLink::TakeReadStats).
// LinkMetricsTakeReadStats).
static std::atomic<std::uint64_t> gReadBytes{0}, gReadCalls{0}, gReadReads{0};
static std::atomic<std::uint64_t> gReadNsInRecv{0}, gReadNsTotal{0};
// P65CLIENTPACE's send half: the io thread's side of the same question. A sendmsg that blocks
@@ -479,8 +480,8 @@ namespace MobileGL::MG_Remote::Transport {
}
};
StreamLink::ReadStats StreamLink::TakeSendStats() {
ReadStats out;
LinkIoStats LinkMetricsTakeSendStats() {
LinkIoStats out;
out.bytes = gSendBytes.exchange(0, std::memory_order_relaxed);
out.calls = gSendCalls.exchange(0, std::memory_order_relaxed);
out.nsInRecv = gSendNsInSend.exchange(0, std::memory_order_relaxed);
@@ -488,8 +489,8 @@ namespace MobileGL::MG_Remote::Transport {
return out;
}
StreamLink::ReadStats StreamLink::TakeReadStats() {
ReadStats out;
LinkIoStats LinkMetricsTakeReadStats() {
LinkIoStats out;
out.bytes = gReadBytes.exchange(0, std::memory_order_relaxed);
out.calls = gReadCalls.exchange(0, std::memory_order_relaxed);
out.reads = gReadReads.exchange(0, std::memory_order_relaxed);
+4 -17
View File
@@ -11,23 +11,10 @@ namespace MobileGL::MG_Remote::Transport {
public:
StreamLink();
~StreamLink() override;
// P65READ: THE SERVER'S SOCKET-READ PATH, ACCOUNTED WHERE IT CAN BE SEEN. The counters are
// published from the io thread's recv loop and read by the APPLY thread, because the io
// thread's own MGLOG_ lines never reach the phone's forwarded log (only mgl-srv-apply and
// mgl-display-ser do - measured, not assumed). Taking them RESETS them, so one call is one
// frame's worth of the stage nothing else measures: the frame's time is not the client's GL
// thread (13%) and not PipeApplier::ApplyOne (11%), and this is what sits between them.
struct ReadStats {
std::uint64_t bytes = 0; // payload bytes taken off the socket
std::uint64_t calls = 0; // recv() calls it took
std::uint64_t reads = 0; // Link::Read() calls (one per header, envelope or chunk)
std::uint64_t nsInRecv = 0; // wall time inside recv - blocking included
std::uint64_t nsTotal = 0; // wall time inside Read
};
static ReadStats TakeReadStats();
// The client's half: bytes handed to the io thread's sendmsg, the calls it took, and the
// wall time inside sendmsg (blocking included). nsInRecv carries the send time.
static ReadStats TakeSendStats();
// The P65READ/P65CLIENTPACE socket-io stats moved to LinkMetrics.h
// (LinkMetricsTakeReadStats/TakeSendStats): the seam gate rejects this concrete
// header outside Transport, and the readers are ClientSession/ServerLoop. The
// counters themselves are still in StreamLink.cpp beside the io loops.
MobileGLResult Attach(int dataFd, SessionSegments& mirrors, TransportRoleTag role);
MobileGLResult AttachOwned(int dataFd, const struct SessionSegmentSizes& sizes, TransportRoleTag role);
// PH-7 (4), ID-P7-3. The SERVER's half of a nonce-bound data connection, in two steps.