in hphp/runtime/server/admin-request-handler.cpp [286:1003]
void AdminRequestHandler::handleRequest(Transport *transport) {
transport->addHeader("Content-Type", "text/plain");
std::string cmd = transport->getCommand();
do {
if (cmd == "" || cmd == "help") {
string usage =
"/stop: stop the web server\n"
" instance-id optional, if specified, instance ID has to match\n"
"/oom-kill: abort all requests whose memory usage exceed\n"
" Server.RequestMemoryOOMKillBytes\n"
"/free-mem: ask allocator to release unused memory to system\n"
"/prepare-to-stop: ask the server to prepare for stopping\n"
"/flush-profile: flush profiling counters (in -fprofile-gen builds)\n"
"/flush-logs: trigger batching log-writers to flush all content\n"
"/translate: translate hex encoded stacktrace in 'stack' param\n"
" stack required, stack trace to translate\n"
" build-id optional, if specified, build ID has to match\n"
" bare optional, whether to display frame ordinates\n"
"/build-id: returns build id that's passed in from command line"
"\n"
"/instance-id: instance id that's passed in from command line\n"
"/compiler-id: returns the compiler id that built this app\n"
"/config-id: returns the config id passed in from command line\n"
"/repo-schema: return the repo schema id used by this app\n"
"/ini-get-all: dump all settings as JSON\n"
"/check-load: how many threads are actively handling requests\n"
"/check-queued: how many http requests are queued waiting to be\n"
" handled\n"
"/check-health: return json containing basic load/usage stats\n"
"/check-ev: how many http requests are active by libevent\n"
"/check-pl-load: how many pagelet threads are actively handling\n"
" requests\n"
"/check-pl-queued: how many pagelet requests are queued waiting to\n"
" be handled\n"
"/check-sql: report SQL table statistics\n"
"/check-sat how many satellite threads are actively handling\n"
" requests and queued waiting to be handled\n"
"/status.xml: show server status in XML\n"
"/status.json: show server status in JSON\n"
"/status.html: show server status in HTML\n"
"/rqtrace-stats: show aggregate request trace stats in JSON\n"
"/memory.xml: show memory status in XML\n"
"/memory.json: show memory status in JSON\n"
"/memory.html: show memory status in HTML\n"
"/statcache-clear: clear the stat cache entries\n"
"/stats-on: main switch: enable server stats\n"
"/stats-off: main switch: disable server stats\n"
"/stats-clear: clear all server stats\n"
"/stats-web: turn on/off server page stats (CPU and gen time)\n"
"/stats-mem: turn on/off memory statistics\n"
"/stats-sql: turn on/off SQL statistics\n"
"/stats-mutex: turn on/off mutex statistics\n"
" sampling optional, default 1000\n"
"/stats.keys: list all available keys\n"
" from optional, <timestamp>, or <-n> second ago\n"
" to optional, <timestamp>, or <-n> second ago\n"
"/stats.kvp: show server stats in key-value pairs\n"
" from optional, <timestamp>, or <-n> second ago\n"
" to optional, <timestamp>, or <-n> second ago\n"
" agg optional, aggragation: *, url, code\n"
" keys optional, <key>,<key/hit>,<key/sec>,<:regex:>\n"
" url optional, only stats of this page or URL\n"
" code optional, only stats of pages returning this code\n"
"/xenon-snap: generate a Xenon snapshot, which is logged later\n"
"/hugepage: show stats about hugepage usage\n"
"/jit-des-info: show information about deserialized profile data\n"
"/static-strings: get number of static strings\n"
"/static-strings-rds: ... that correspond to defined constants\n"
"/dump-static-strings: dump static strings to /tmp/static_strings\n"
"/random-static-strings: return randomly selected static strings\n"
" count number of strings to return, default 1\n"
"/dump-apc: dump all current value in APC to /tmp/apc_dump\n"
"/dump-apc-prefix: dump a key prefix contents from APC to\n"
" /tmp/apc_dump_prefix\n"
" prefix required, the prefix to dump\n"
" count optional, the number of keys to dump, default 1\n"
"/dump-apc-info: show basic APC stats\n"
"/dump-apc-meta: dump meta information for all objects in APC to\n"
" /tmp/apc_dump_meta\n"
"/random-apc: dump the key and size of a random APC entry\n"
" count number of entries to return\n"
"/treadmill: dump treadmill information\n"
"/pcre-cache-size: get pcre cache map size\n"
"/dump-pcre-cache: dump cached pcre's to /tmp/pcre_cache\n"
"/dump-array-info: dump array tracer info to /tmp/array_tracer_dump\n"
"/invalidate-units: remove specified files from the unit cache\n"
" path absolute path of files to invalidate\n"
"/start-stacktrace-profiler: set enable_stacktrace_profiler to true\n"
"/relocate: relocate translations\n"
" random optional, default false, relocate random subset\n"
" all optional, default false, relocate all translations\n"
" time optional, default 20 (seconds)\n"
#ifdef HPHP_TRACE
"/trace-request: write trace for next request(s) to "
+ RuntimeOption::getTraceOutputFile() + "\n"
" spec module:level,... spec; see hphp/util/trace.h\n"
" count optional, total requests to trace (default: 1)\n"
" url optional, trace only if URL contains \'url\'\n"
#endif
#ifdef GOOGLE_CPU_PROFILER
"/prof-cpu-on: turn on CPU profiler\n"
"/prof-cpu-off: turn off CPU profiler\n"
#endif
#ifdef EXECUTION_PROFILER
"/prof-exe: returns sampled execution profile\n"
#endif
"/vm-tcspace: show space used by translator caches\n"
"/vm-tcaddr: show addresses of translation cache sections\n"
"/vm-dump-tc: dump translation cache to /tmp/tc_dump_a and\n"
" /tmp/tc_dump_astub\n"
"/vm-namedentities:show size of the NamedEntityTable\n"
"/proxy: set up request proxy\n"
" origin URL to proxy requests to\n"
" percentage percentage of requests to proxy\n"
"/load-factor: get or set load factor\n"
" set optional, set new load factor (default 1.0,\n"
" valid range [-1.0, 10.0])\n"
"/queue-discount: get/set how much we discount the queue-length \n"
" set optional, set discount value (default 0,\n"
" valid range [0, 10000])\n"
"/warmup-status: Describes state of JIT warmup.\n"
" Returns empty string if warmed up.\n"
;
#ifdef USE_TCMALLOC
if (MallocExtensionInstance) {
usage.append(
"/tcmalloc-stats: get internal tcmalloc stats\n"
"/tcmalloc-set-tc: set max mem tcmalloc thread-cache can use\n"
);
}
#endif
#ifdef USE_JEMALLOC
if (mallctl) {
usage.append(
"/jemalloc-stats: get internal jemalloc stats\n"
"/jemalloc-stats-print:\n"
" get comprehensive jemalloc stats in\n"
" human-readable form\n"
"/jemalloc-prof-activate:\n"
" activate heap profiling\n"
"/jemalloc-prof-deactivate:\n"
" deactivate heap profiling\n"
"/jemalloc-prof-dump:\n"
" dump heap profile\n"
" file optional, filesystem path\n"
"/jemalloc-prof-request:\n"
" dump thread-local heap profile in\n"
" the next request that runs\n"
" file optional, filesystem path\n"
);
#ifdef ENABLE_HHPROF
usage.append(
"/hhprof/start: start profiling\n"
" requestType \"all\" or \"next\"*\n"
" url profile next matching url for \"next\"\n"
" lgSample lg sample rate\n"
" profileType \"current\"* or \"cumulative\"\n"
"/hhprof/status: configuration and current dump status\n"
"/hhprof/stop: stop profiling\n"
"/pprof/cmdline: program command line\n"
"/pprof/heap: heap dump\n"
"/pprof/symbol: symbol lookup\n"
);
#endif // ENABLE_HHPROF
}
#endif // USE_JEMALLOC
AdminCommandExt::iterate([&](AdminCommandExt* ace) {
usage.append(ace->usage());
return false;
});
transport->sendString(usage);
break;
}
bool needs_password = (cmd != "build-id") && (cmd != "compiler-id") &&
(cmd != "instance-id") && (cmd != "flush-logs") &&
(cmd != "warmup-status") && (cmd != "config-id")
#if defined(ENABLE_HHPROF) && defined(USE_JEMALLOC)
&& (mallctl == nullptr || (
(cmd != "hhprof/start")
&& (cmd != "hhprof/status")
&& (cmd != "hhprof/stop")
&& (cmd != "pprof/cmdline")
&& (cmd != "pprof/heap")
&& (cmd != "pprof/symbol")))
#endif
;
// When configured, we allow read-only stats to be read without a password.
if (needs_password && !RuntimeOption::AdminServerStatsNeedPassword) {
if ((strncmp(cmd.c_str(), "memory.", 7) == 0) ||
(strncmp(cmd.c_str(), "stats.", 6) == 0) ||
(strncmp(cmd.c_str(), "check-", 6) == 0) ||
(strncmp(cmd.c_str(), "static-strings", 14) == 0) ||
cmd == "hugepage" || cmd == "pcre-cache-size" ||
cmd == "vm-tcspace" || cmd == "vm-tcaddr" ||
cmd == "vm-namedentities" || cmd == "jemalloc-stats") {
needs_password = false;
}
}
if (needs_password && !RuntimeOption::HashedAdminPasswords.empty()) {
bool matched = false;
#ifdef HAVE_CRYPTO_PWHASH_STR
const auto password = transport->getParam("auth");
for (const std::string& hash : RuntimeOption::HashedAdminPasswords) {
if (crypto_pwhash_str_verify(hash.data(),
password.data(),
password.size()) == 0) {
matched = true;
break;
}
}
#endif
if (!matched) {
transport->sendString("Unauthorized", 401);
break;
}
} else if (needs_password && !RuntimeOption::AdminPasswords.empty()) {
std::set<std::string>::const_iterator iter =
RuntimeOption::AdminPasswords.find(transport->getParam("auth"));
if (iter == RuntimeOption::AdminPasswords.end()) {
transport->sendString("Unauthorized", 401);
break;
}
} else {
if (needs_password && !RuntimeOption::AdminPassword.empty() &&
RuntimeOption::AdminPassword != transport->getParam("auth")) {
transport->sendString("Unauthorized", 401);
break;
}
}
if (cmd == "stop") {
string instanceId = transport->getParam("instance-id");
if (!instanceId.empty() && instanceId != RuntimeOption::InstanceId) {
transport->sendString("Instance ID doesn't match.", 500);
break;
}
transport->sendString("OK\n");
Logger::Info("Got admin port stop request from %s",
transport->getRemoteHost());
HttpServer::Server->stop();
break;
}
if (cmd == "oom-kill") {
Logger::Info("Invoking OOM killer upon admin port request from %s",
transport->getRemoteHost());
auto const server = HttpServer::Server->getPageServer();
RequestInfo::InvokeOOMKiller(server->getActiveWorker());
transport->sendString("OOM killer invoked");
break;
}
if (cmd == "free-mem") {
const auto before = Process::GetMemUsageMb();
std::string errStr;
if (purge_all(&errStr)) {
const auto after = Process::GetMemUsageMb();
transport->sendString(
folly::sformat("Purged {} -> {} MB RSS", before, after).c_str());
} else {
transport->sendString(errStr.c_str(), 500);
}
break;
}
if (cmd == "prepare-to-stop") {
Logger::Info("Got admin port prepare-to-stop request from %s",
transport->getRemoteHost());
MemInfo info, newInfo;
Process::GetMemoryInfo(info);
HttpServer::PrepareToStop();
// We may consider purge_all() here, too. But since requests
// are still coming in, it may not be very useful, and has some
// performance penalties.
// TODO: evaluate the effect of sync() and uncomment if
// desirable. It is blocking and can take some time, so do it
// in a separate thread.
// std::thread t{sync};
// t.detach();
transport->sendString("OK\n");
Process::GetMemoryInfo(newInfo);
Logger::FInfo("free/cached/buffer {}/{}/{} -> {}/{}/{}",
info.freeMb, info.cachedMb, info.buffersMb,
newInfo.freeMb, newInfo.cachedMb, newInfo.buffersMb);
break;
}
if (cmd == "flush-profile") {
HttpServer::ProfileFlush();
// send the response *after* flushing, so the caller knows the
// data has been updated.
transport->sendString("OK\n");
break;
}
if (cmd == "flush-logs") {
transport->sendString("OK\n");
Logger::FlushAll();
HttpRequestHandler::GetAccessLog().flushAllWriters();
AdminRequestHandler::GetAccessLog().flushAllWriters();
RPCRequestHandler::GetAccessLog().flushAllWriters();
break;
}
if (cmd == "set-log-level") {
string result("OK\n");
string level = transport->getParam("level");
if (level == "None") {
Logger::LogLevel = Logger::LogNone;
} else if (level == "Error") {
Logger::LogLevel = Logger::LogError;
} else if (level == "Warning") {
Logger::LogLevel = Logger::LogWarning;
} else if (level == "Info") {
Logger::LogLevel = Logger::LogInfo;
} else if (level == "Verbose") {
Logger::LogLevel = Logger::LogVerbose;
} else {
result = "Failed to set log level\n";
}
transport->sendString(result);
break;
}
#ifdef HPHP_TRACE
if (cmd == "trace-request") {
Trace::ensureInit(RuntimeOption::getTraceOutputFile());
// Just discard any existing task.
delete s_traceTask.exchange(
new TraceTask{transport->getParam("spec"),
transport->getParam("url"),
std::max(transport->getInt64Param("count"), 1ll)},
std::memory_order_acq_rel);
transport->sendString("OK\n");
break;
}
#endif
if (cmd == "build-id") {
transport->sendString(RuntimeOption::BuildId, 200);
break;
}
if (cmd == "instance-id") {
transport->sendString(RuntimeOption::InstanceId, 200);
break;
}
if (cmd == "compiler-id") {
transport->sendString(compilerId().begin(), 200);
break;
}
if (cmd == "config-id") {
transport->sendString(std::to_string(RuntimeOption::ConfigId), 200);
break;
}
if (cmd == "repo-schema") {
transport->sendString(repoSchemaId().begin(), 200);
break;
}
if (cmd == "translate") {
string buildId = transport->getParam("build-id");
if (!buildId.empty() && buildId != RuntimeOption::BuildId) {
transport->sendString("Build ID doesn't match.", 500);
break;
}
string translated = translate_stack(transport->getParam("stack").c_str(),
transport->getParam("bare").empty());
transport->sendString(translated);
break;
}
if (strncmp(cmd.c_str(), "check", 5) == 0 &&
handleCheckRequest(cmd, transport)) {
break;
}
if (strncmp(cmd.c_str(), "status", 6) == 0 &&
handleStatusRequest(cmd, transport)) {
break;
}
if (strncmp(cmd.c_str(),"memory", 6) == 0 &&
handleMemoryRequest(cmd, transport)) {
break;
}
if (strncmp(cmd.c_str(), "stats", 5) == 0 &&
handleStatsRequest(cmd, transport)) {
break;
}
if (strncmp(cmd.c_str(), "prof", 4) == 0 &&
handleProfileRequest(cmd, transport)) {
break;
}
if (strncmp(cmd.c_str(), "dump-apc", 8) == 0 &&
handleDumpCacheRequest(cmd, transport)) {
break;
}
if (strncmp(cmd.c_str(), "invalidate-units", 16) == 0 &&
handleInvalidateUnitRequest(cmd, transport)) {
break;
}
if (strncmp(cmd.c_str(), "xenon-snap", 10) == 0) {
static int64_t s_lastSampleTime = 0;
auto const current = TimeStamp::Current();
if (current > s_lastSampleTime) {
s_lastSampleTime = current;
Xenon::getInstance().surpriseAll();
}
transport->sendString("a Xenon sample will be collected\n", 200);
break;
}
if (strncmp(cmd.c_str(), "hugepage", 9) == 0) {
#if USE_JEMALLOC_EXTENT_HOOKS
std::string msg =
folly::sformat("{} 1G huge pages active\n", num_1g_pages());
if (auto a = alloc::lowArena()) {
msg += a->reportStats();
}
if (auto a = alloc::highArena()) {
msg += a->reportStats();
}
transport->sendString(msg, 200);
#else
transport->sendString("", 200);
#endif
break;
}
if (strncmp(cmd.c_str(), "jit-des-info", 13) == 0) {
if (isJitSerializing() || !jit::ProfData::wasDeserialized()) {
transport->sendString("", 200);
break;
}
auto msg = folly::sformat("{}:{}",
jit::ProfData::buildHost()->slice(),
jit::ProfData::buildTime());
transport->sendString(msg, 200);
}
if (strncmp(cmd.c_str(), "static-strings", 14) == 0 &&
handleStaticStringsRequest(cmd, transport)) {
break;
}
if (strncmp(cmd.c_str(), "dump-static-strings", 19) == 0) {
if (auto file = dump_file("static_strings")) {
handleDumpStaticStringsRequest(file->file);
transport->sendString(folly::sformat("dumped to {}\n", file->path));
} else {
transport->sendString("Unable to mkdir or file already exists.\n");
}
break;
}
if (strncmp(cmd.c_str(), "random-static-strings", 21) == 0) {
handleRandomStaticStringsRequest(cmd, transport);
break;
}
if (strncmp(cmd.c_str(), "vm-", 3) == 0 &&
handleVMRequest(cmd, transport)) {
break;
}
if (cmd == "proxy") {
handleProxyRequest(cmd, transport);
break;
}
if (cmd == "statcache-clear") {
StatCache::clearCache();
break;
}
if (cmd == "pcre-cache-size") {
std::ostringstream size;
size << preg_pcre_cache_size() << endl;
transport->sendString(size.str());
break;
}
if (cmd == "dump-pcre-cache") {
if (auto file = dump_file("pcre_cache")) {
pcre_dump_cache(file->file);
transport->sendString(folly::sformat("dumped to {}\n", file->path));
} else {
transport->sendString("Unable to mkdir or file already exists.\n");
}
break;
}
if (cmd == "start-stacktrace-profiler") {
enable_stacktrace_profiler = true;
transport->sendString("OK\n");
break;
}
if (cmd == "warmup-status") {
transport->sendString(jit::tc::warmupStatusString());
break;
}
if (strncmp(cmd.c_str(), "random-apc", 10) == 0 &&
handleRandomApcRequest(cmd, transport)) {
break;
}
if (cmd == "treadmill") {
transport->sendString(Treadmill::dumpTreadmillInfo());
break;
}
if (cmd == "load-factor") {
auto const factorStr = transport->getParam("set");
if (factorStr.empty()) {
std::ostringstream oss;
oss << std::fixed << std::setprecision(3)
<< HttpServer::LoadFactor.load();
transport->sendString(oss.str());
break;
}
double factor = 1.0;
if (sscanf(factorStr.c_str(), "%lf", &factor) < 1 ||
factor > 10 || factor < -1) {
transport->sendString("Invalid load factor spec: " + factorStr, 400);
break;
}
HttpServer::LoadFactor.store(factor, std::memory_order_relaxed);
transport->sendString(folly::sformat("Load factor updated to {}\n",
factor));
Logger::Info("Load factor updated to %lf", factor);
break;
}
if (cmd == "queue-discount") {
auto const discountStr = transport->getParam("set");
if (discountStr.empty()) {
transport->sendString(folly::to<string>(
HttpServer::QueueDiscount.load(std::memory_order_relaxed)));
break;
}
int queue_discount = 0;
if (sscanf(discountStr.c_str(), "%d", &queue_discount) < 1 ||
queue_discount > 10000 || queue_discount < 0) {
transport->sendString("Invalid queue discount spec: " +
discountStr, 400);
break;
}
HttpServer::QueueDiscount.store(queue_discount,
std::memory_order_relaxed);
transport->sendString(folly::sformat("Queue Discount updated to {}\n",
queue_discount));
Logger::Info("Queue Discount updated to %d", queue_discount);
break;
}
if (cmd == "ini-get-all") {
auto out = IniSetting::GetAllAsJSON();
transport->sendString(out.c_str());
break;
}
if (cmd == "numa-info") {
std::ostringstream out;
#ifdef HAVE_NUMA
out << "use_numa: " << use_numa << endl;
out << "numa_num_nodes: " << numa_num_nodes << endl;
out << "numa_node_mask: " << numa_node_mask << endl;
out << "numa_node_set: " << numa_node_set << endl;
#else
out << "HAVE_NUMA not defined" << endl;
#endif
transport->sendString(out.str());
break;
}
#ifdef USE_JEMALLOC
assertx(mallctlnametomib && mallctlbymib);
if (cmd == "jemalloc-stats") {
// jemalloc stats update is periodically triggered in the
// host-health-monitor thread.
uint32_t error = 0;
auto call_mallctl = [&](const char* statName) {
size_t value = 0;
if (mallctlRead<size_t, true>(statName, &value) != 0) {
error = 1;
}
return value;
};
size_t allocated = call_mallctl("stats.allocated");
size_t active = call_mallctl("stats.active");
size_t mapped = call_mallctl("stats.mapped");
auto const low_mapped = alloc::getLowMapped();
std::ostringstream stats;
stats << "<jemalloc-stats>" << endl;
stats << " <allocated>" << allocated << "</allocated>" << endl;
stats << " <active>" << active << "</active>" << endl;
stats << " <mapped>" << mapped << "</mapped>" << endl;
stats << " <low_mapped>" << low_mapped << "</low_mapped>" << endl;
stats << "</jemalloc-stats>" << endl;
transport->sendString(stats.str());
break;
}
if (cmd == "jemalloc-stats-print") {
malloc_write mwo;
malloc_write_init(&mwo);
malloc_stats_print(malloc_write_cb, (void *)&mwo, "");
if (mwo.oom) {
malloc_write_fini(&mwo);
transport->sendString("OOM\n");
break;
}
transport->sendString(mwo.s == nullptr ? "" : mwo.s);
malloc_write_fini(&mwo);
break;
}
if (cmd == "jemalloc-prof-activate") {
if (jemalloc_pprof_enable()) {
transport->sendString("Error in mallctl(\"prof.active\", true)\n");
} else {
transport->sendString("OK\n");
}
break;
}
if (cmd == "jemalloc-prof-deactivate") {
if (jemalloc_pprof_disable()) {
transport->sendString("Error in mallctl(\"prof.active\", false)\n");
} else {
transport->sendString("OK\n");
}
break;
}
if (cmd == "jemalloc-prof-dump") {
string f = transport->getParam("file");
if (jemalloc_pprof_dump(f, true)) {
transport->sendString("Error in mallctl(\"prof.dump\", " + f + ")\n");
} else {
transport->sendString("OK\n");
}
break;
}
if (cmd == "jemalloc-prof-request") {
auto f = transport->getParam("file");
bool success = MemoryManager::triggerProfiling(f);
if (success) {
transport->sendString("OK\n");
} else {
transport->sendString("Request profiling already triggered\n");
}
break;
}
#ifdef ENABLE_HHPROF
if (cmd == "hhprof/start") {
HHProf::HandleHHProfStart(transport);
break;
}
if (cmd == "hhprof/status") {
HHProf::HandleHHProfStatus(transport);
break;
}
if (cmd == "hhprof/stop") {
HHProf::HandleHHProfStop(transport);
break;
}
if (cmd == "pprof/cmdline") {
HHProf::HandlePProfCmdline(transport);
break;
}
if (cmd == "pprof/heap") {
HHProf::HandlePProfHeap(transport);
break;
}
if (cmd == "pprof/symbol") {
HHProf::HandlePProfSymbol(transport);
break;
}
#endif // ENABLE_HHPROF
#endif // USE_JEMALLOC
if (cmd == "rqtrace-stats") {
std::stringstream out;
bool first = true;
out << "{" << endl;
auto appendStat =
[&](folly::StringPiece name, folly::StringPiece kind, int64_t value) {
out << folly::format(
"{} \"{}_{}\":{}\n", first ? "" : ",", name, kind, value);
first = false;
};
rqtrace::visit_process_stats(
[&] (const StringData* name, rqtrace::EventStats stats) {
appendStat(name->data(), "duration", stats.total_duration);
appendStat(name->data(), "count", stats.total_count);
}
);
out << "}" << endl;
transport->sendString(out.str());
break;
}
if (AdminCommandExt::iterate([&](AdminCommandExt* ace) {
return ace->handleRequest(transport);
})) {
break;
}
transport->sendString("Unknown command: " + cmd + "\n", 404);
} while (0);
transport->onSendEnd();
}