mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
feat(common): read the memory text the client sends at login
The login request's memoryStats is a run of short parts with no separators (process working set and private bytes, memory load, physical memory, commit limit, the 32-bit client's own address space, peaks). ParseMemoryStats splits it into numbers; a text cut short keeps the parts before the cut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
set(DCOMMON_SOURCES
|
||||
"AMFDeserialize.cpp"
|
||||
"ClientSysInfo.cpp"
|
||||
"AmfSerialize.cpp"
|
||||
"BinaryIO.cpp"
|
||||
"dConfig.cpp"
|
||||
|
||||
61
dCommon/ClientSysInfo.cpp
Normal file
61
dCommon/ClientSysInfo.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
#include "ClientSysInfo.h"
|
||||
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <charconv>
|
||||
|
||||
namespace {
|
||||
struct Reader {
|
||||
std::string_view text;
|
||||
size_t pos{};
|
||||
|
||||
void SkipSpace() {
|
||||
while (pos < text.size() && std::isspace(static_cast<unsigned char>(text[pos]))) pos++;
|
||||
}
|
||||
|
||||
bool Literal(std::string_view literal) {
|
||||
SkipSpace();
|
||||
if (text.substr(pos, literal.size()) != literal) return false;
|
||||
pos += literal.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<int64_t> Number() {
|
||||
SkipSpace();
|
||||
int64_t value{};
|
||||
const auto* begin = text.data() + pos;
|
||||
const auto [end, error] = std::from_chars(begin, text.data() + text.size(), value);
|
||||
if (error != std::errc{} || end == begin) return std::nullopt;
|
||||
pos += static_cast<size_t>(end - begin);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ClientSysInfo::MemoryStats ClientSysInfo::ParseMemoryStats(const std::string_view text) {
|
||||
MemoryStats stats;
|
||||
// Each part: text before the number, text after it, and where the number goes
|
||||
struct Part { std::string_view prefix; std::string_view suffix; std::optional<int64_t> MemoryStats::* field; };
|
||||
const std::array<Part, 11> parts{ {
|
||||
{ "", "p,", &MemoryStats::workingSetBytes },
|
||||
{ "", "vbytes.", &MemoryStats::pagefileUsageBytes },
|
||||
{ "", "n-use.", &MemoryStats::memoryLoadPercent },
|
||||
{ "", "TKb-pmem.", &MemoryStats::totalPhysKb },
|
||||
{ "", "FKb pmem.", &MemoryStats::availPhysKb },
|
||||
{ "", "TKb pfile.", &MemoryStats::totalPageFileKb },
|
||||
{ "", "FKb pfile.", &MemoryStats::availPageFileKb },
|
||||
{ "", "TKbytes vmem.", &MemoryStats::totalVirtualKb },
|
||||
{ "", "FKb vmem.", &MemoryStats::availVirtualKb },
|
||||
{ "P", "p,", &MemoryStats::peakWorkingSetBytes },
|
||||
{ "", "v.", &MemoryStats::peakPagefileUsageBytes },
|
||||
} };
|
||||
Reader reader{ text };
|
||||
for (const auto& part : parts) {
|
||||
if (!part.prefix.empty() && !reader.Literal(part.prefix)) return stats;
|
||||
const auto value = reader.Number();
|
||||
if (!value || !reader.Literal(part.suffix)) return stats;
|
||||
stats.*part.field = *value;
|
||||
}
|
||||
stats.complete = true;
|
||||
return stats;
|
||||
}
|
||||
39
dCommon/ClientSysInfo.h
Normal file
39
dCommon/ClientSysInfo.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
/**
|
||||
* The system description the client sends in its login request, as reported by the client. It comes from old Windows
|
||||
* calls and describes what Windows (or Wine) tells a 32-bit program without a compatibility manifest, which is not
|
||||
* necessarily the player's real hardware. docs/Dashboard.md (Client system info) says what each field means.
|
||||
*/
|
||||
namespace ClientSysInfo {
|
||||
/**
|
||||
* The login request's memoryStats text, split into numbers. The client writes these parts one after another with no
|
||||
* separator (numbers are right-aligned to 7 characters):
|
||||
* "<ws> p,<pf> vbytes." "<load> n-use." "<kb> TKb-pmem." "<kb> FKb pmem." "<kb> TKb pfile." "<kb> FKb pfile."
|
||||
* "<kb> TKbytes vmem. \n" "<kb> FKb vmem." "P <peak ws> p,<peak pf> v."
|
||||
* The client cuts the text at 255 characters, so the last parts can be missing; a part that doesn't match stops the
|
||||
* parse and leaves it and everything after it empty.
|
||||
*/
|
||||
struct MemoryStats {
|
||||
std::optional<int64_t> workingSetBytes; // the client process's working set at login (bytes)
|
||||
std::optional<int64_t> pagefileUsageBytes; // the client process's private (commit) bytes at login
|
||||
std::optional<int64_t> memoryLoadPercent; // how much of the system's memory Windows says is in use
|
||||
std::optional<int64_t> totalPhysKb; // physical memory Windows reports (KB)
|
||||
std::optional<int64_t> availPhysKb; // free physical memory at login (KB)
|
||||
std::optional<int64_t> totalPageFileKb; // commit limit: physical memory plus page files (KB)
|
||||
std::optional<int64_t> availPageFileKb; // free commit at login (KB)
|
||||
std::optional<int64_t> totalVirtualKb; // the 32-bit client's own address space, not the system's (KB)
|
||||
std::optional<int64_t> availVirtualKb; // free address space in the client (KB)
|
||||
std::optional<int64_t> peakWorkingSetBytes; // the client process's peak working set (bytes)
|
||||
std::optional<int64_t> peakPagefileUsageBytes; // the client process's peak private bytes
|
||||
|
||||
// Every part was read
|
||||
bool complete{};
|
||||
};
|
||||
|
||||
MemoryStats ParseMemoryStats(std::string_view text);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
set(DCOMMONTEST_SOURCES
|
||||
"AMFDeserializeTests.cpp"
|
||||
"ClientSysInfoTests.cpp"
|
||||
"Amf3Tests.cpp"
|
||||
"ToUnderlyingTests.cpp"
|
||||
"InstanceMigrationTests.cpp"
|
||||
|
||||
57
tests/dCommonTests/ClientSysInfoTests.cpp
Normal file
57
tests/dCommonTests/ClientSysInfoTests.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "ClientSysInfo.h"
|
||||
|
||||
using ClientSysInfo::ParseMemoryStats;
|
||||
|
||||
namespace {
|
||||
// As the client writes it: each number right-aligned to 7 characters, no separators, " \n" after the vmem total
|
||||
constexpr std::string_view FULL = "123456789 p,234567890 vbytes.45 n-use.16717048 TKb-pmem.8123456 FKb pmem.33434096 TKb pfile."
|
||||
"20000000 FKb pfile.4194176 TKbytes vmem. \n3800000 FKb vmem.P 130000000 p,240000000 v.";
|
||||
}
|
||||
|
||||
TEST(ClientSysInfoTests, ReadsEveryPart) {
|
||||
const auto s = ParseMemoryStats(FULL);
|
||||
EXPECT_TRUE(s.complete);
|
||||
EXPECT_EQ(s.workingSetBytes, 123456789);
|
||||
EXPECT_EQ(s.pagefileUsageBytes, 234567890);
|
||||
EXPECT_EQ(s.memoryLoadPercent, 45);
|
||||
EXPECT_EQ(s.totalPhysKb, 16717048);
|
||||
EXPECT_EQ(s.availPhysKb, 8123456);
|
||||
EXPECT_EQ(s.totalPageFileKb, 33434096);
|
||||
EXPECT_EQ(s.availPageFileKb, 20000000);
|
||||
EXPECT_EQ(s.totalVirtualKb, 4194176);
|
||||
EXPECT_EQ(s.availVirtualKb, 3800000);
|
||||
EXPECT_EQ(s.peakWorkingSetBytes, 130000000);
|
||||
EXPECT_EQ(s.peakPagefileUsageBytes, 240000000);
|
||||
}
|
||||
|
||||
TEST(ClientSysInfoTests, PaddedNumbers) {
|
||||
const auto s = ParseMemoryStats(" 12345 p, 67890 vbytes.7 n-use.1048576 TKb-pmem. 52428 FKb pmem.");
|
||||
EXPECT_FALSE(s.complete);
|
||||
EXPECT_EQ(s.workingSetBytes, 12345);
|
||||
EXPECT_EQ(s.pagefileUsageBytes, 67890);
|
||||
EXPECT_EQ(s.memoryLoadPercent, 7);
|
||||
EXPECT_EQ(s.totalPhysKb, 1048576);
|
||||
EXPECT_EQ(s.availPhysKb, 52428);
|
||||
EXPECT_FALSE(s.totalPageFileKb);
|
||||
}
|
||||
|
||||
TEST(ClientSysInfoTests, CutTextKeepsWhatWasRead) {
|
||||
// The client stops at 255 characters: a part cut in half is left out, with everything after it
|
||||
const std::string cut(FULL.substr(0, FULL.find("FKb vmem.") + 3));
|
||||
const auto s = ParseMemoryStats(cut);
|
||||
EXPECT_FALSE(s.complete);
|
||||
EXPECT_EQ(s.totalVirtualKb, 4194176);
|
||||
EXPECT_FALSE(s.availVirtualKb);
|
||||
EXPECT_FALSE(s.peakWorkingSetBytes);
|
||||
}
|
||||
|
||||
TEST(ClientSysInfoTests, OtherTextReadsNothing) {
|
||||
for (const auto* text : { "", "garbage", "12 q,34 vbytes.", "p, vbytes." }) {
|
||||
const auto s = ParseMemoryStats(text);
|
||||
EXPECT_FALSE(s.complete) << text;
|
||||
EXPECT_FALSE(s.workingSetBytes) << text;
|
||||
EXPECT_FALSE(s.totalPhysKb) << text;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user