Skip to content

Commit 42cd6d1

Browse files
nt591meta-codesync[bot]
authored andcommitted
add RDS logging by category on OOMs and via admin endpoint
Summary: TSIA: expose a little more to allow userland perf work. Reviewed By: paulbiss Differential Revision: D95246808 fbshipit-source-id: 6c1ef1b0f6b8a0860ec55f2da617f19fe23d4176
1 parent e0d24a9 commit 42cd6d1

File tree

6 files changed

+212
-1
lines changed

6 files changed

+212
-1
lines changed

hphp/doc/configs.specification

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,12 @@ The format can be found in hphp/tools/configs/generate_configs.rs
910910

911911
- double Eval.RDSReorderThreshold = 0.0005, UNKNOWN
912912

913+
- bool Eval.LogRDSOnOOM = false, nzthomas
914+
915+
When enabled, log a per-category RDS usage breakdown before crashing on RDS
916+
OOM. Iterates the RDS symbol table under lock, so should not be enabled in
917+
production unless actively debugging RDS exhaustion.
918+
913919
- uint32_t Eval.ProfileGlobalsLimit = 200, UNKNOWN
914920

915921
- double Eval.ProfileGlobalsSlowExitThreshold = 0.98, UNKNOWN

hphp/runtime/base/rds.cpp

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
#include "hphp/runtime/base/rds.h"
1717

18+
#include <algorithm>
1819
#include <atomic>
1920
#include <cassert>
2021
#include <cstdio>
@@ -153,10 +154,29 @@ std::string profilingKeyForSymbol(const Symbol& s) {
153154
return symbol_kind(s) + "->" + symbol_rep(s);
154155
}
155156

157+
/*
158+
* Cache of per-category ServiceData counters to avoid repeated lookups.
159+
*/
160+
folly_concurrent_hash_map_simd<std::string, ServiceData::ExportedCounter*>
161+
s_categoryCounters;
162+
163+
ServiceData::ExportedCounter* getCategoryCounter(const std::string& kind) {
164+
auto it = s_categoryCounters.find(kind);
165+
if (it != s_categoryCounters.end()) return it->second;
166+
auto counter = ServiceData::createCounter("admin.rds_usage." + kind);
167+
s_categoryCounters.insert(kind, counter);
168+
return counter;
169+
}
170+
171+
static auto s_anonymousCounter =
172+
ServiceData::createCounter("admin.rds_usage.Anonymous");
173+
156174
}
157175

158176
//////////////////////////////////////////////////////////////////////
159177

178+
namespace { void logRDSUsageBreakdown(); }
179+
160180
namespace detail {
161181

162182
static auto byte_counter = ServiceData::createCounter("admin.vm-tcspace.RDS");
@@ -348,6 +368,9 @@ Handle alloc(Mode mode, size_t numBytes,
348368
s_normal_frontier - oldFrontier
349369
);
350370
}
371+
if (UNLIKELY(s_normal_frontier >= s_local_frontier)) {
372+
if (Cfg::Eval::LogRDSOnOOM) logRDSUsageBreakdown();
373+
}
351374
always_assert_flog(
352375
s_normal_frontier < s_local_frontier,
353376
"Ran out of RDS space (mode=Normal)"
@@ -397,6 +420,7 @@ Handle alloc(Mode mode, size_t numBytes,
397420
// We reserved plenty of space in s_persistent_free_lists in the beginning
398421
// of the process, but maybe it is time to increase the size in the
399422
// config.
423+
if (Cfg::Eval::LogRDSOnOOM) logRDSUsageBreakdown();
400424
always_assert_flog(
401425
false,
402426
"Ran out of RDS space (mode=Persistent)"
@@ -416,6 +440,9 @@ Handle alloc(Mode mode, size_t numBytes,
416440
frontier -= numBytes;
417441
frontier &= ~(align - 1);
418442

443+
if (UNLIKELY(frontier < s_normal_frontier)) {
444+
if (Cfg::Eval::LogRDSOnOOM) logRDSUsageBreakdown();
445+
}
419446
always_assert_flog(
420447
frontier >= s_normal_frontier,
421448
"Ran out of RDS space (mode=Local)"
@@ -443,7 +470,11 @@ Handle allocUnlocked(Mode mode, size_t numBytes,
443470
size_t align, type_scan::Index tyIndex,
444471
const Symbol* symbol) {
445472
Guard g(s_allocMutex);
446-
return alloc(mode, numBytes, align, tyIndex, symbol);
473+
auto const handle = alloc(mode, numBytes, align, tyIndex, symbol);
474+
if (!symbol) {
475+
s_anonymousCounter->addValue(numBytes);
476+
}
477+
return handle;
447478
}
448479

449480
Handle bindImpl(Symbol key, Mode mode, size_t sizeBytes,
@@ -456,6 +487,7 @@ Handle bindImpl(Symbol key, Mode mode, size_t sizeBytes,
456487

457488
auto const handle = alloc(mode, sizeBytes, align, tyIndex, &key);
458489
recordRds(handle, sizeBytes, key);
490+
getCategoryCounter(symbol_kind(key))->addValue(sizeBytes);
459491

460492
if (shouldProfileAccesses() && !std::visit(IsProfile(), key)) {
461493
// Allocate an integer in the local section to profile this
@@ -504,6 +536,7 @@ void bindOnLinkImpl(std::atomic<Handle>& handle,
504536
// we flipped it from kUninitHandle, so we get to fill in the value.
505537
auto const h = allocUnlocked(mode, size, align, tsi, &sym);
506538
recordRds(h, size, sym);
539+
getCategoryCounter(symbol_kind(sym))->addValue(size);
507540

508541
if (shouldProfileAccesses() && !std::visit(IsProfile(), sym)) {
509542
// Allocate an integer in the local section to profile this
@@ -733,6 +766,98 @@ size_t usedPersistentBytes() {
733766
return s_persistent_usage;
734767
}
735768

769+
namespace {
770+
771+
std::vector<CategoryUsage> usageByCategoryLocked() {
772+
struct Agg {
773+
size_t bytes{0};
774+
size_t count{0};
775+
};
776+
using Key = std::pair<std::string, Mode>;
777+
std::map<Key, Agg> catMap;
778+
779+
size_t normalAttributed = 0;
780+
size_t localAttributed = 0;
781+
size_t persistentAttributed = 0;
782+
783+
s_linkTable.rehash();
784+
for (auto const& entry : s_linkTable) {
785+
auto const& sym = entry.first;
786+
auto const h = entry.second.handle;
787+
auto const sz = entry.second.size;
788+
auto const kind = symbol_kind(sym);
789+
790+
Mode mode;
791+
if (isPersistentHandle(h)) {
792+
mode = Mode::Persistent;
793+
persistentAttributed += sz;
794+
} else if (isNormalHandle(h)) {
795+
mode = Mode::Normal;
796+
normalAttributed += sz;
797+
} else {
798+
mode = Mode::Local;
799+
localAttributed += sz;
800+
}
801+
802+
auto& agg = catMap[{kind, mode}];
803+
agg.bytes += sz;
804+
agg.count++;
805+
}
806+
807+
auto const normalTotal = usedBytes();
808+
auto const localTotal = usedLocalBytes();
809+
auto const persistentTotal = usedPersistentBytes();
810+
811+
if (normalTotal > normalAttributed) {
812+
catMap[{"Anonymous", Mode::Normal}].bytes += normalTotal - normalAttributed;
813+
}
814+
if (localTotal > localAttributed) {
815+
catMap[{"Anonymous", Mode::Local}].bytes += localTotal - localAttributed;
816+
}
817+
if (persistentTotal > persistentAttributed) {
818+
catMap[{"Anonymous", Mode::Persistent}].bytes +=
819+
persistentTotal - persistentAttributed;
820+
}
821+
822+
std::vector<CategoryUsage> result;
823+
result.reserve(catMap.size());
824+
for (auto& [key, agg] : catMap) {
825+
result.push_back({key.first, agg.bytes, agg.count, key.second});
826+
}
827+
std::sort(result.begin(), result.end(),
828+
[](const CategoryUsage& a, const CategoryUsage& b) {
829+
return a.bytes > b.bytes;
830+
});
831+
return result;
832+
}
833+
834+
void logRDSUsageBreakdown() {
835+
auto const usage = usageByCategoryLocked();
836+
Logger::FError("RDS usage breakdown:");
837+
Logger::FError(" Normal: {} bytes used", usedBytes());
838+
Logger::FError(" Local: {} bytes used", usedLocalBytes());
839+
Logger::FError(" Persistent: {} bytes used", usedPersistentBytes());
840+
for (auto const& cat : usage) {
841+
auto const modeName = [&] {
842+
switch (cat.mode) {
843+
case Mode::Normal: return "Normal";
844+
case Mode::Local: return "Local";
845+
case Mode::Persistent: return "Persistent";
846+
default: return "Unknown";
847+
}
848+
}();
849+
Logger::FError(" {:>30s} ({:10s}): {:>10} bytes, {:>6} entries",
850+
cat.category, modeName, cat.bytes, cat.count);
851+
}
852+
}
853+
854+
} // namespace
855+
856+
std::vector<CategoryUsage> usageByCategory() {
857+
Guard g(s_allocMutex);
858+
return usageByCategoryLocked();
859+
}
860+
736861
folly::Range<const char*> normalSection() {
737862
return {(const char*)tl_base, usedBytes()};
738863
}

hphp/runtime/base/rds.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include <cstdlib>
3030
#include <string>
3131
#include <type_traits>
32+
#include <vector>
3233

3334
#ifndef RDS_FIXED_PERSISTENT_BASE
3435
// If RDS_FIXED_PERSISTENT_BASE is defined from compiler command line, don't
@@ -192,6 +193,17 @@ constexpr bool pure(Mode mask) {
192193
return !(static_cast<ModeU>(mask) & (static_cast<ModeU>(mask) - 1));
193194
}
194195

196+
/*
197+
* Per-category RDS usage breakdown.
198+
*/
199+
struct CategoryUsage {
200+
std::string category;
201+
size_t bytes;
202+
size_t count;
203+
Mode mode;
204+
};
205+
std::vector<CategoryUsage> usageByCategory();
206+
195207
/*
196208
* Handles into Request Data Segment.
197209
*

hphp/runtime/server/admin-request-handler.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,7 @@ void AdminRequestHandler::handleRequest(Transport *transport) {
431431
#endif
432432
"/vm-tcspace: show space used by translator caches\n"
433433
"/vm-tcaddr: show addresses of translation cache sections\n"
434+
"/vm-rds-usage: show RDS usage breakdown by category (JSON)\n"
434435
"/vm-dump-tc: dump translation cache to /tmp/tc_dump_a and\n"
435436
" /tmp/tc_dump_astub\n"
436437
"/vm-namedentities:show combined size of the NamedType and\n"
@@ -519,6 +520,7 @@ void AdminRequestHandler::handleRequest(Transport *transport) {
519520
(strncmp(cmd.c_str(), "static-strings", 14) == 0) ||
520521
cmd == "hugepage" || cmd == "pcre-cache-size" ||
521522
cmd == "vm-tcspace" || cmd == "vm-tcaddr" ||
523+
cmd == "vm-rds-usage" ||
522524
cmd == "vm-namedentities" || cmd == "jemalloc-stats") {
523525
needs_password = false;
524526
}
@@ -1467,6 +1469,11 @@ bool AdminRequestHandler::handleVMRequest(const std::string &cmd,
14671469
transport->sendString(jit::tc::getTCSpace());
14681470
return true;
14691471
}
1472+
if (cmd == "vm-rds-usage") {
1473+
transport->replaceHeader("Content-Type", "application/json");
1474+
transport->sendString(jit::tc::getRDSUsageJSON());
1475+
return true;
1476+
}
14701477
if (cmd == "vm-tcaddr") {
14711478
transport->sendString(jit::tc::getTCAddrs());
14721479
return true;

hphp/runtime/vm/jit/tc-info.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "hphp/runtime/vm/jit/tc.h"
1818
#include "hphp/runtime/vm/jit/tc-internal.h"
1919

20+
#include "hphp/runtime/base/rds.h"
2021
#include "hphp/runtime/vm/runtime-compiler.h"
2122
#include "hphp/runtime/vm/jit/code-cache.h"
2223
#include "hphp/runtime/vm/jit/mcgen.h"
@@ -28,6 +29,8 @@
2829
#include "hphp/util/build-info.h"
2930

3031
#include <folly/Format.h>
32+
#include <folly/json/dynamic.h>
33+
#include <folly/json/json.h>
3134

3235
#include <algorithm>
3336
#include <string>
@@ -260,4 +263,57 @@ std::vector<TCMemInfo> getTCMemoryUsage() {
260263
return ret;
261264
}
262265

266+
std::string getRDSUsageJSON() {
267+
auto const normalCap = Cfg::Eval::RDSSize * 3 / 4;
268+
auto const localCap = normalCap;
269+
auto const persistentCap = Cfg::Eval::RDSSize / 4;
270+
271+
folly::dynamic summary = folly::dynamic::object
272+
("normal", folly::dynamic::object
273+
("used", rds::usedBytes())
274+
("capacity", normalCap))
275+
("local", folly::dynamic::object
276+
("used", rds::usedLocalBytes())
277+
("capacity", localCap))
278+
("persistent", folly::dynamic::object
279+
("used", rds::usedPersistentBytes())
280+
("capacity", persistentCap));
281+
282+
auto const categories = rds::usageByCategory();
283+
folly::dynamic catArray = folly::dynamic::array;
284+
for (auto const& cat : categories) {
285+
auto const modeName = [&] {
286+
switch (cat.mode) {
287+
case rds::Mode::Normal: return "Normal";
288+
case rds::Mode::Local: return "Local";
289+
case rds::Mode::Persistent: return "Persistent";
290+
default: return "Unknown";
291+
}
292+
}();
293+
294+
size_t regionUsed = 0;
295+
switch (cat.mode) {
296+
case rds::Mode::Normal: regionUsed = rds::usedBytes(); break;
297+
case rds::Mode::Local: regionUsed = rds::usedLocalBytes(); break;
298+
case rds::Mode::Persistent: regionUsed = rds::usedPersistentBytes(); break;
299+
default: break;
300+
}
301+
double pct = regionUsed > 0
302+
? 100.0 * cat.bytes / regionUsed
303+
: 0.0;
304+
305+
catArray.push_back(folly::dynamic::object
306+
("category", cat.category)
307+
("mode", modeName)
308+
("bytes", cat.bytes)
309+
("count", cat.count)
310+
("pct_of_region", pct));
311+
}
312+
313+
folly::dynamic result = folly::dynamic::object
314+
("summary", std::move(summary))
315+
("categories", std::move(catArray));
316+
return folly::toJson(result);
317+
}
318+
263319
}

hphp/runtime/vm/jit/tc.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,11 @@ std::vector<UsageInfo> getUsageInfo();
450450
*/
451451
std::string getTCSpace();
452452

453+
/*
454+
* Return RDS usage breakdown as a JSON string, categorized by symbol type.
455+
*/
456+
std::string getRDSUsageJSON();
457+
453458
/*
454459
* Return a string containing the names and start addresses of all TC code
455460
* sections.

0 commit comments

Comments
 (0)