d432f19ae1
Only in RepoAuthoritative mode, where units can't be deallocated. I have a semi-reproducable bug where a unit's m_bc region is getting corrupted (only occasionally in perflab). Presumably moving it out of the malloc'd heap will make the bug corrupt something else, so this probably doesn't really help much, but it seemed like having a separate region for some cold, read-only, process-lifetime metadata might make sense.
1068 linhas
35 KiB
C++
1068 linhas
35 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
|
|
#include "runtime/eval/runtime/file_repository.h"
|
|
#include "runtime/base/server/admin_request_handler.h"
|
|
#include "runtime/base/server/http_server.h"
|
|
#include "runtime/base/server/pagelet_server.h"
|
|
#include "runtime/base/util/http_client.h"
|
|
#include "runtime/base/server/server_stats.h"
|
|
#include "runtime/base/runtime_option.h"
|
|
#include "runtime/base/preg.h"
|
|
#include "util/process.h"
|
|
#include "util/logger.h"
|
|
#include "util/util.h"
|
|
#include "util/mutex.h"
|
|
#include "runtime/base/time/datetime.h"
|
|
#include "runtime/base/memory/memory_manager.h"
|
|
#include "runtime/base/program_functions.h"
|
|
#include "runtime/base/shared/shared_store_base.h"
|
|
#include "runtime/base/memory/leak_detectable.h"
|
|
#include "runtime/ext/mysql_stats.h"
|
|
#include "runtime/base/shared/shared_store_stats.h"
|
|
#include "runtime/vm/repo.h"
|
|
#include "runtime/vm/translator/translator.h"
|
|
#include "runtime/vm/translator/translator-deps.h"
|
|
#include "runtime/vm/translator/translator-x64.h"
|
|
#include "util/alloc.h"
|
|
#include <util/timer.h>
|
|
#include "util/repo_schema.h"
|
|
#include "runtime/ext/ext_fb.h"
|
|
#include "runtime/ext/ext_apc.h"
|
|
|
|
#ifdef GOOGLE_CPU_PROFILER
|
|
#include <google/profiler.h>
|
|
#endif
|
|
#ifdef GOOGLE_HEAP_PROFILER
|
|
#include <google/heap-profiler.h>
|
|
#endif
|
|
|
|
using std::endl;
|
|
|
|
namespace HPHP {
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
IMPLEMENT_THREAD_LOCAL(AccessLog::ThreadData,
|
|
AdminRequestHandler::s_accessLogThreadData);
|
|
|
|
AccessLog AdminRequestHandler::s_accessLog(
|
|
&(AdminRequestHandler::getAccessLogThreadData));
|
|
|
|
AdminRequestHandler::AdminRequestHandler() {
|
|
}
|
|
|
|
// Helper machinery for jemalloc-stats-print command.
|
|
#ifdef USE_JEMALLOC
|
|
struct malloc_write {
|
|
char *s;
|
|
size_t slen;
|
|
size_t smax;
|
|
bool oom;
|
|
};
|
|
|
|
void malloc_write_init(malloc_write *mw) {
|
|
mw->s = nullptr;
|
|
mw->slen = 0;
|
|
mw->smax = 0;
|
|
mw->oom = false;
|
|
}
|
|
|
|
void malloc_write_fini(malloc_write *mw) {
|
|
if (mw->s != nullptr) {
|
|
free(mw->s);
|
|
malloc_write_init(mw);
|
|
}
|
|
}
|
|
|
|
static void malloc_write_cb(void *cbopaque, const char *s) {
|
|
malloc_write* mw = (malloc_write*)cbopaque;
|
|
size_t slen = strlen(s);
|
|
|
|
if (mw->oom) {
|
|
return;
|
|
}
|
|
|
|
if (mw->slen + slen+1 >= mw->smax) {
|
|
assert(mw->slen + slen > 0);
|
|
char* ts = (char*)realloc(mw->s, (mw->slen + slen) << 1);
|
|
if (ts == nullptr) {
|
|
mw->oom = true;
|
|
return;
|
|
}
|
|
mw->s = ts;
|
|
mw->smax = (mw->slen + slen) << 1;
|
|
}
|
|
memcpy(&mw->s[mw->slen], s, slen+1);
|
|
mw->slen += slen;
|
|
}
|
|
#endif
|
|
|
|
void AdminRequestHandler::handleRequest(Transport *transport) {
|
|
GetAccessLog().onNewRequest();
|
|
transport->addHeader("Content-Type", "text/plain");
|
|
string cmd = transport->getCommand();
|
|
|
|
do {
|
|
if (cmd == "" || cmd == "help") {
|
|
string usage =
|
|
"/stop: stop the web server\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"
|
|
"/compiler-id: returns the compiler id that built this app\n"
|
|
"/repo-schema: return the repo schema id used by this app\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-mem: report memory quick statistics in log file\n"
|
|
"/check-sql: report SQL table statistics\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"
|
|
|
|
"/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-apc: turn on/off APC statistics\n"
|
|
"/stats-apc-key: turn on/off APC key statistics\n"
|
|
"/stats-mcc: turn on/off memcache 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.xml: show server stats in XML\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"
|
|
"/stats.json: show server stats in JSON\n"
|
|
" (same as /stats.xml)\n"
|
|
"/stats.kvp: show server stats in key-value pairs\n"
|
|
" (same as /stats.xml)\n"
|
|
"/stats.html: show server stats in HTML\n"
|
|
" (same as /stats.xml)\n"
|
|
|
|
"/apc-ss: get apc size stats\n"
|
|
"/apc-ss-flat: get apc size stats in flat format\n"
|
|
"/apc-ss-keys: get apc size break-down on keys\n"
|
|
"/apc-ss-dump: dump the size info on each key to /tmp/APC_details\n"
|
|
" only valid when EnableAPCSizeDetail is true\n"
|
|
" keysample optional, only dump keys that belongs to the same\n"
|
|
" group as <keysample>\n"
|
|
"/const-ss: get const_map_size\n"
|
|
"/static-strings: get number of static strings\n"
|
|
"/dump-apc: dump all current value in APC to /tmp/apc_dump\n"
|
|
"/dump-const: dump all constant value in constant map to\n"
|
|
" /tmp/const_map_dump\n"
|
|
"/dump-file-repo: dump file repository to /tmp/file_repo_dump\n"
|
|
|
|
"/pcre-cache-size: get pcre cache map size\n"
|
|
|
|
#ifdef GOOGLE_CPU_PROFILER
|
|
"/prof-cpu-on: turn on CPU profiler\n"
|
|
"/prof-cpu-off: turn off CPU profiler\n"
|
|
#endif
|
|
#ifdef GOOGLE_HEAP_PROFILER
|
|
"/prof-heap-on: turn on heap profiler\n"
|
|
"/prof-heap-dump: take one snapshot of the heap\n"
|
|
"/prof-heap-off: turn off heap profiler\n"
|
|
"/stats-malloc: turn on/off malloc statistics\n"
|
|
"/leak-on: start leak detection\n"
|
|
" sampling required, frequency\n"
|
|
"/leak-off: end leak detection and report leaking\n"
|
|
" cutoff optional, default 20 seconds, ignore newer allocs\n"
|
|
#endif
|
|
#ifdef EXECUTION_PROFILER
|
|
"/prof-exe: returns sampled execution profile\n"
|
|
#endif
|
|
"/vm-tcspace: show space used by translator caches\n"
|
|
"/vm-dump-tc: dump translation cache to /tmp/tc_dump_a and\n"
|
|
" /tmp/tc_dump_astub\n"
|
|
"/vm-preconsts: show information about preconsts\n"
|
|
"/vm-tcreset: throw away translations and start over\n"
|
|
;
|
|
#ifdef USE_TCMALLOC
|
|
if (MallocExtensionInstance) {
|
|
usage.append(
|
|
"/free-mem: ask tcmalloc to release memory to system\n"
|
|
"/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"
|
|
);
|
|
}
|
|
#endif
|
|
|
|
transport->sendString(usage);
|
|
break;
|
|
}
|
|
|
|
if (!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 (!RuntimeOption::AdminPassword.empty() &&
|
|
RuntimeOption::AdminPassword != transport->getParam("auth")) {
|
|
transport->sendString("Unauthorized", 401);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cmd == "stop") {
|
|
transport->sendString("OK\n");
|
|
Logger::Info("Got admin port stop request from %s",
|
|
transport->getRemoteHost());
|
|
HttpServer::Server->stop();
|
|
break;
|
|
}
|
|
if (cmd == "build-id") {
|
|
transport->sendString(RuntimeOption::BuildId, 200);
|
|
break;
|
|
}
|
|
if (cmd == "compiler-id") {
|
|
transport->sendString(kCompilerId, 200);
|
|
break;
|
|
}
|
|
if (cmd == "repo-schema") {
|
|
transport->sendString(kRepoSchemaId, 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(), "stats", 5) == 0 &&
|
|
handleStatsRequest(cmd, transport)) {
|
|
break;
|
|
}
|
|
if (strncmp(cmd.c_str(), "prof", 4) == 0 &&
|
|
handleProfileRequest(cmd, transport)) {
|
|
break;
|
|
}
|
|
if (strncmp(cmd.c_str(), "leak", 4) == 0 &&
|
|
handleLeakRequest(cmd, transport)) {
|
|
break;
|
|
}
|
|
if (strncmp(cmd.c_str(), "apc-ss", 6) == 0 &&
|
|
handleAPCSizeRequest(cmd, transport)) {
|
|
break;
|
|
}
|
|
if (strncmp(cmd.c_str(), "dump", 4) == 0 &&
|
|
handleDumpCacheRequest(cmd, transport)) {
|
|
break;
|
|
}
|
|
if (strncmp(cmd.c_str(), "const-ss", 8) == 0 &&
|
|
handleConstSizeRequest(cmd, transport)) {
|
|
break;
|
|
}
|
|
if (strcmp(cmd.c_str(), "static-strings") == 0 &&
|
|
handleStaticStringsRequest(cmd, transport)) {
|
|
break;
|
|
}
|
|
if (strncmp(cmd.c_str(), "vm-", 3) == 0 &&
|
|
handleVMRequest(cmd, transport)) {
|
|
break;
|
|
}
|
|
|
|
if (cmd == "pcre-cache-size") {
|
|
std::ostringstream size;
|
|
size << preg_pcre_cache_size() << endl;
|
|
transport->sendString(size.str());
|
|
break;
|
|
}
|
|
|
|
#ifdef USE_TCMALLOC
|
|
if (MallocExtensionInstance) {
|
|
if (cmd == "free-mem") {
|
|
MallocExtensionInstance()->ReleaseFreeMemory();
|
|
transport->sendString("OK\n");
|
|
break;
|
|
}
|
|
if (cmd == "tcmalloc-stats") {
|
|
std::ostringstream stats;
|
|
size_t user_allocated, heap_size, slack_bytes;
|
|
size_t pageheap_free, pageheap_unmapped;
|
|
size_t tc_max, tc_allocated;
|
|
|
|
MallocExtensionInstance()->
|
|
GetNumericProperty("generic.current_allocated_bytes",
|
|
&user_allocated);
|
|
MallocExtensionInstance()->
|
|
GetNumericProperty("generic.heap_size", &heap_size);
|
|
MallocExtensionInstance()->
|
|
GetNumericProperty("tcmalloc.slack_bytes", &slack_bytes);
|
|
MallocExtensionInstance()->
|
|
GetNumericProperty("tcmalloc.pageheap_free_bytes", &pageheap_free);
|
|
MallocExtensionInstance()->
|
|
GetNumericProperty("tcmalloc.pageheap_unmapped_bytes",
|
|
&pageheap_unmapped);
|
|
MallocExtensionInstance()->
|
|
GetNumericProperty("tcmalloc.max_total_thread_cache_bytes",
|
|
&tc_max);
|
|
MallocExtensionInstance()->
|
|
GetNumericProperty("tcmalloc.current_total_thread_cache_bytes",
|
|
&tc_allocated);
|
|
stats << "<tcmalloc-stats>" << endl;
|
|
stats << " <user_allocated>" << user_allocated << "</user_allocated>"
|
|
<< endl;
|
|
stats << " <heap_size>" << heap_size << "</heap_size>" << endl;
|
|
stats << " <slack_bytes>" << slack_bytes << "</slack_bytes>" << endl;
|
|
stats << " <pageheap_free>" << pageheap_free
|
|
<< "</pageheap_free>" << endl;
|
|
stats << " <pageheap_unmapped>" << pageheap_unmapped
|
|
<< "</pageheap_unmapped>" << endl;
|
|
stats << " <thread_cache_max>" << tc_max
|
|
<< "</thread_cache_max>" << endl;
|
|
stats << " <thread_cache_allocated>" << tc_allocated
|
|
<< "</thread_cache_allocated>" << endl;
|
|
stats << "</tcmalloc-stats>" << endl;
|
|
transport->sendString(stats.str());
|
|
break;
|
|
}
|
|
if (cmd == "tcmalloc-set-tc") {
|
|
size_t tc_max;
|
|
|
|
MallocExtensionInstance()->
|
|
GetNumericProperty("tcmalloc.max_total_thread_cache_bytes", &tc_max);
|
|
|
|
size_t tcache = transport->getInt64Param("s");
|
|
bool retval = MallocExtensionInstance()->
|
|
SetNumericProperty("tcmalloc.max_total_thread_cache_bytes", tcache);
|
|
transport->sendString(retval == true ? "OK\n" : "FAILED\n");
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef USE_JEMALLOC
|
|
if (mallctl) {
|
|
if (cmd == "free-mem") {
|
|
// Purge all dirty unused pages.
|
|
int err = mallctl("arenas.purge", nullptr, nullptr, nullptr, 0);
|
|
if (err) {
|
|
std::ostringstream estr;
|
|
estr << "Error " << err << " in mallctl(\"arenas.purge\", ...)"
|
|
<< endl;
|
|
transport->sendString(estr.str());
|
|
} else {
|
|
transport->sendString("OK\n");
|
|
}
|
|
break;
|
|
}
|
|
if (cmd == "jemalloc-stats") {
|
|
// Force jemalloc to update stats cached for use by mallctl().
|
|
uint64_t epoch = 1;
|
|
mallctl("epoch", nullptr, nullptr, &epoch, sizeof(epoch));
|
|
|
|
size_t allocated = 0; // Initialize in case stats aren't enabled.
|
|
size_t sz = sizeof(size_t);
|
|
mallctl("stats.allocated", &allocated, &sz, nullptr, 0);
|
|
|
|
size_t active = 0;
|
|
mallctl("stats.active", &active, &sz, nullptr, 0);
|
|
|
|
size_t mapped = 0;
|
|
mallctl("stats.mapped", &mapped, &sz, nullptr, 0);
|
|
|
|
std::ostringstream stats;
|
|
stats << "<jemalloc-stats>" << endl;
|
|
stats << " <allocated>" << allocated << "</allocated>" << endl;
|
|
stats << " <active>" << active << "</active>" << endl;
|
|
stats << " <mapped>" << mapped << "</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);
|
|
malloc_write_fini(&mwo);
|
|
break;
|
|
}
|
|
if (cmd == "jemalloc-prof-activate") {
|
|
bool active = true;
|
|
int err = mallctl("prof.active", nullptr, nullptr, &active, sizeof(bool));
|
|
if (err) {
|
|
std::ostringstream estr;
|
|
estr << "Error " << err << " in mallctl(\"prof.active\", ...)"
|
|
<< endl;
|
|
transport->sendString(estr.str());
|
|
} else {
|
|
transport->sendString("OK\n");
|
|
}
|
|
break;
|
|
}
|
|
if (cmd == "jemalloc-prof-deactivate") {
|
|
bool active = false;
|
|
int err = mallctl("prof.active", nullptr, nullptr, &active, sizeof(bool));
|
|
if (err) {
|
|
std::ostringstream estr;
|
|
estr << "Error " << err << " in mallctl(\"prof.active\", ...)"
|
|
<< endl;
|
|
transport->sendString(estr.str());
|
|
} else {
|
|
transport->sendString("OK\n");
|
|
}
|
|
break;
|
|
}
|
|
if (cmd == "jemalloc-prof-dump") {
|
|
string f = transport->getParam("file");
|
|
if (f != "") {
|
|
const char *s = f.c_str();
|
|
int err = mallctl("prof.dump", nullptr, nullptr, (void *)&s,
|
|
sizeof(char *));
|
|
if (err) {
|
|
std::ostringstream estr;
|
|
estr << "Error " << err << " in mallctl(\"prof.dump\", ..., \"" << f
|
|
<< "\", ...)" << endl;
|
|
transport->sendString(estr.str());
|
|
break;
|
|
}
|
|
} else {
|
|
int err = mallctl("prof.dump", nullptr, nullptr, nullptr, 0);
|
|
if (err) {
|
|
std::ostringstream estr;
|
|
estr << "Error " << err << " in mallctl(\"prof.dump\", ...)"
|
|
<< endl;
|
|
transport->sendString(estr.str());
|
|
break;
|
|
}
|
|
}
|
|
transport->sendString("OK\n");
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
transport->sendString("Unknown command: " + cmd + "\n", 404);
|
|
} while (0);
|
|
GetAccessLog().log(transport, nullptr);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// stats commands
|
|
|
|
static bool toggle_switch(Transport *transport, bool &setting) {
|
|
setting = !setting;
|
|
transport->sendString(setting ? "On\n" : "Off\n");
|
|
return true;
|
|
}
|
|
|
|
static bool send_report(Transport *transport, ServerStats::Format format,
|
|
const char *mime) {
|
|
int64_t from = transport->getInt64Param ("from");
|
|
int64_t to = transport->getInt64Param ("to");
|
|
string agg = transport->getParam ("agg");
|
|
string keys = transport->getParam ("keys");
|
|
string url = transport->getParam ("url");
|
|
int code = transport->getIntParam ("code");
|
|
string prefix = transport->getParam ("prefix");
|
|
|
|
string out;
|
|
ServerStats::Report(out, format, from, to, agg, keys, url, code, prefix);
|
|
|
|
transport->addHeader("Content-Type", mime);
|
|
transport->sendString(out);
|
|
return true;
|
|
}
|
|
|
|
static bool send_status(Transport *transport, ServerStats::Format format,
|
|
const char *mime) {
|
|
string out;
|
|
ServerStats::ReportStatus(out, format);
|
|
|
|
transport->addHeader("Content-Type", mime);
|
|
transport->sendString(out);
|
|
return true;
|
|
}
|
|
|
|
namespace VM {
|
|
extern size_t hhbc_arena_capacity();
|
|
}
|
|
|
|
bool AdminRequestHandler::handleCheckRequest(const std::string &cmd,
|
|
Transport *transport) {
|
|
if (cmd == "check-load") {
|
|
int count = HttpServer::Server->getPageServer()->getActiveWorker();
|
|
transport->sendString(lexical_cast<string>(count));
|
|
return true;
|
|
}
|
|
if (cmd == "check-ev") {
|
|
int count =
|
|
HttpServer::Server->getPageServer()->getLibEventConnectionCount();
|
|
transport->sendString(lexical_cast<string>(count));
|
|
return true;
|
|
}
|
|
if (cmd == "check-queued") {
|
|
int count = HttpServer::Server->getPageServer()->getQueuedJobs();
|
|
transport->sendString(lexical_cast<string>(count));
|
|
return true;
|
|
}
|
|
if (cmd == "check-health") {
|
|
std::stringstream out;
|
|
bool first = true;
|
|
out << "{" << endl;
|
|
auto appendStat = [&](const char* name, int64_t value) {
|
|
out << (!first ? "," : "") << " \"" << name << "\":" << value << endl;
|
|
first = false;
|
|
};
|
|
ServerPtr server = HttpServer::Server->getPageServer();
|
|
appendStat("load", server->getActiveWorker());
|
|
appendStat("queued", server->getQueuedJobs());
|
|
VM::Transl::Translator* tx = VM::Transl::Translator::Get();
|
|
appendStat("hhbc-roarena-capac", VM::hhbc_arena_capacity());
|
|
appendStat("tc-size", tx->getCodeSize());
|
|
appendStat("tc-stubsize", tx->getStubSize());
|
|
appendStat("targetcache", tx->getTargetCacheSize());
|
|
appendStat("units", Eval::FileRepository::getLoadedFiles());
|
|
out << "}" << endl;
|
|
transport->sendString(out.str());
|
|
return true;
|
|
}
|
|
if (cmd == "check-pl-load") {
|
|
int count = PageletServer::GetActiveWorker();
|
|
transport->sendString(lexical_cast<string>(count));
|
|
return true;
|
|
}
|
|
if (cmd == "check-pl-queued") {
|
|
int count = PageletServer::GetQueuedJobs();
|
|
transport->sendString(lexical_cast<string>(count));
|
|
return true;
|
|
}
|
|
if (cmd == "check-mem") {
|
|
return toggle_switch(transport, RuntimeOption::CheckMemory);
|
|
}
|
|
if (cmd == "check-sql") {
|
|
string stats = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
|
|
stats += "<SQL>\n";
|
|
stats += MySqlStats::ReportStats();
|
|
stats += "</SQL>\n";
|
|
transport->sendString(stats);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool AdminRequestHandler::handleStatusRequest(const std::string &cmd,
|
|
Transport *transport) {
|
|
if (cmd == "status.xml") {
|
|
return send_status(transport, ServerStats::XML, "application/xml");
|
|
}
|
|
if (cmd == "status.json") {
|
|
return send_status(transport, ServerStats::JSON, "application/json");
|
|
}
|
|
if (cmd == "status.html" || cmd == "status.htm") {
|
|
return send_status(transport, ServerStats::HTML, "text/html");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool AdminRequestHandler::handleStatsRequest(const std::string &cmd,
|
|
Transport *transport) {
|
|
if (cmd == "stats-on") {
|
|
RuntimeOption::EnableStats = true;
|
|
transport->sendString("OK\n");
|
|
return true;
|
|
}
|
|
if (cmd == "stats-off") {
|
|
RuntimeOption::EnableStats = false;
|
|
transport->sendString("OK\n");
|
|
return true;
|
|
}
|
|
if (cmd == "stats-clear") {
|
|
ServerStats::Clear();
|
|
transport->sendString("OK\n");
|
|
return true;
|
|
}
|
|
|
|
if (cmd == "stats-web") {
|
|
return toggle_switch(transport, RuntimeOption::EnableWebStats);
|
|
}
|
|
if (cmd == "stats-mem") {
|
|
toggle_switch(transport, RuntimeOption::EnableMemoryStats);
|
|
return true;
|
|
}
|
|
if (cmd == "stats-malloc") {
|
|
toggle_switch(transport, RuntimeOption::EnableMallocStats);
|
|
LeakDetectable::EnableMallocStats(RuntimeOption::EnableMallocStats);
|
|
return true;
|
|
}
|
|
if (cmd == "stats-apc") {
|
|
return toggle_switch(transport, RuntimeOption::EnableAPCStats);
|
|
}
|
|
if (cmd == "stats-apc-key") {
|
|
return toggle_switch(transport, RuntimeOption::EnableAPCKeyStats);
|
|
}
|
|
if (cmd == "stats-mcc") {
|
|
return toggle_switch(transport, RuntimeOption::EnableMemcacheStats);
|
|
}
|
|
if (cmd == "stats-sql") {
|
|
return toggle_switch(transport, RuntimeOption::EnableSQLStats);
|
|
}
|
|
if (cmd == "stats-mutex") {
|
|
int sampling = transport->getIntParam("sampling");
|
|
if (sampling > 0) {
|
|
LockProfiler::s_profile_sampling = sampling;
|
|
}
|
|
return toggle_switch(transport, LockProfiler::s_profile);
|
|
}
|
|
|
|
if (cmd == "stats.keys") {
|
|
int64_t from = transport->getInt64Param("from");
|
|
int64_t to = transport->getInt64Param("to");
|
|
string out;
|
|
ServerStats::GetKeys(out, from, to);
|
|
transport->sendString(out);
|
|
return true;
|
|
}
|
|
if (cmd == "stats.xml") {
|
|
return send_report(transport, ServerStats::XML, "application/xml");
|
|
}
|
|
if (cmd == "stats.json") {
|
|
return send_report(transport, ServerStats::JSON, "application/json");
|
|
}
|
|
if (cmd == "stats.kvp") {
|
|
return send_report(transport, ServerStats::KVP, "text/plain");
|
|
}
|
|
if (cmd == "stats.html" || cmd == "stats.htm") {
|
|
return send_report(transport, ServerStats::HTML, "text/html");
|
|
}
|
|
|
|
if (cmd == "stats.xsl") {
|
|
string xsl;
|
|
if (!RuntimeOption::StatsXSLProxy.empty()) {
|
|
StringBuffer response;
|
|
if (HttpClient().get(RuntimeOption::StatsXSLProxy.c_str(), response) ==
|
|
200) {
|
|
xsl = response.data();
|
|
if (!xsl.empty()) {
|
|
transport->addHeader("Content-Type", "application/xml");
|
|
transport->sendString(xsl);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
transport->sendString("Not Found\n", 404);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// profile commands
|
|
|
|
bool AdminRequestHandler::handleProfileRequest(const std::string &cmd,
|
|
Transport *transport) {
|
|
if (cmd == "prof-exe") {
|
|
std::map<ThreadInfo::Executing, int> counts;
|
|
ThreadInfo::GetExecutionSamples(counts);
|
|
|
|
string res = "[ ";
|
|
for (std::map<ThreadInfo::Executing, int>::const_iterator iter =
|
|
counts.begin(); iter != counts.end(); ++iter) {
|
|
res += lexical_cast<string>(iter->first) + ", " +
|
|
lexical_cast<string>(iter->second) + ", ";
|
|
}
|
|
res += "-1 ]";
|
|
transport->sendString(res);
|
|
|
|
return true;
|
|
}
|
|
#ifdef GOOGLE_CPU_PROFILER
|
|
if (handleCPUProfilerRequest(cmd, transport)) {
|
|
return true;
|
|
}
|
|
#endif
|
|
#ifdef GOOGLE_HEAP_PROFILER
|
|
if (handleHeapProfilerRequest(cmd, transport)) {
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
#ifdef GOOGLE_CPU_PROFILER
|
|
bool AdminRequestHandler::handleCPUProfilerRequest(const std::string &cmd,
|
|
Transport *transport) {
|
|
string file = RuntimeOption::ProfilerOutputDir + "/" +
|
|
Process::HostName + "/hphp.prof";
|
|
|
|
if (cmd == "prof-cpu-on") {
|
|
if (Util::mkdir(file)) {
|
|
ProfilerStart(file.c_str());
|
|
transport->sendString("OK\n");
|
|
} else {
|
|
transport->sendString("Unable to mkdir for profile data.\n");
|
|
}
|
|
return true;
|
|
}
|
|
if (cmd == "prof-cpu-off") {
|
|
ProfilerStop();
|
|
ProfilerFlush();
|
|
transport->sendString("OK\n");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#ifdef GOOGLE_HEAP_PROFILER
|
|
bool AdminRequestHandler::handleHeapProfilerRequest(const std::string &cmd,
|
|
Transport *transport) {
|
|
string root = RuntimeOption::ProfilerOutputDir + "/" +
|
|
Process::HostName;
|
|
string file = root + "/hphp.prof";
|
|
|
|
if (cmd == "prof-heap-on") {
|
|
if (IsHeapProfilerRunning()) {
|
|
transport->sendString("HeapProfiler is already running.\n");
|
|
} else {
|
|
if (Util::mkdir(file)) {
|
|
// clean up leftovers
|
|
string cmd = "/bin/rm -f ";
|
|
cmd += file + ".*.heap";
|
|
Util::ssystem(cmd.c_str());
|
|
|
|
HeapProfilerStart(file.c_str());
|
|
HeapProfilerDump("start");
|
|
transport->sendString("OK\n");
|
|
} else {
|
|
transport->sendString("Unable to mkdir for profile data.\n");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
if (cmd == "prof-heap-dump") {
|
|
if (!IsHeapProfilerRunning()) {
|
|
transport->sendString("HeapProfiler is not running.\n");
|
|
} else {
|
|
HeapProfilerDump("end");
|
|
transport->sendString("OK\n");
|
|
}
|
|
return true;
|
|
}
|
|
if (cmd == "prof-heap-off") {
|
|
if (!IsHeapProfilerRunning()) {
|
|
transport->sendString("HeapProfiler is not running.\n");
|
|
} else {
|
|
HeapProfilerDump("end");
|
|
HeapProfilerStop();
|
|
transport->sendString("OK\n");
|
|
|
|
const char *argv[] = {"", root.c_str(), "-name", "*.heap", nullptr};
|
|
string files;
|
|
Process::Exec("find", argv, nullptr, files);
|
|
vector<string> out;
|
|
Util::split('\n', files.c_str(), out, true);
|
|
if (out.size() > 1) {
|
|
string base = "--base=";
|
|
base += out[0];
|
|
} else {
|
|
Logger::Error("Unable to find heap profiler output");
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// leak detection
|
|
|
|
bool AdminRequestHandler::handleLeakRequest(const std::string &cmd,
|
|
Transport *transport) {
|
|
if (cmd == "leak-on") {
|
|
LeakDetectable::BeginLeakChecking(); // class-based detection starts
|
|
LeakDetectable::MallocSampling = transport->getIntParam("sampling");
|
|
LeakDetectable::BeginMallocSampling(); // malloc-based detection starts
|
|
transport->sendString("OK\n");
|
|
return true;
|
|
}
|
|
if (cmd == "leak-off") {
|
|
std::string dumps;
|
|
|
|
// class-based detection ends
|
|
int count = LeakDetectable::EndLeakChecking(dumps, 1);
|
|
dumps += "\n";
|
|
dumps += boost::lexical_cast<string>(count) + " leaked LeakDetectables\n";
|
|
|
|
// malloc-based detection
|
|
int cutoff = transport->getIntParam("cutoff");
|
|
LeakDetectable::EndMallocSampling(dumps, cutoff ? cutoff : 20);
|
|
LeakDetectable::MallocSampling = 0;
|
|
|
|
transport->sendString(dumps);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// APC size profiling
|
|
|
|
bool AdminRequestHandler::handleAPCSizeRequest (const std::string &cmd,
|
|
Transport *transport) {
|
|
if (!RuntimeOption::EnableAPCSizeStats &&
|
|
(cmd == "apc-ss" || cmd == "apc-ss-keys" || cmd == "apc-ss-dump" ||
|
|
cmd == "apc-ss-flat")) {
|
|
transport->sendString("Not Enabled\n");
|
|
return true;
|
|
}
|
|
if (cmd == "apc-ss") {
|
|
std::string result = SharedStoreStats::report_basic();
|
|
transport->sendString(result);
|
|
return true;
|
|
}
|
|
if (cmd == "apc-ss-flat") {
|
|
std::string result = SharedStoreStats::report_basic_flat();
|
|
transport->sendString(result);
|
|
return true;
|
|
}
|
|
if (cmd == "apc-ss-keys") {
|
|
if (!RuntimeOption::EnableAPCSizeGroup) {
|
|
transport->sendString("Not Enabled\n");
|
|
return true;
|
|
}
|
|
std::string result = SharedStoreStats::report_keys();
|
|
transport->sendString(result);
|
|
return true;
|
|
}
|
|
if (cmd == "apc-ss-dump") {
|
|
if (!RuntimeOption::EnableAPCSizeDetail) {
|
|
transport->sendString("Not Enabled\n");
|
|
return true;
|
|
}
|
|
string key_sample = transport->getParam("keysample");
|
|
if (SharedStoreStats::snapshot("/tmp/APC_details", key_sample)) {
|
|
transport->sendString("Done\n");
|
|
} else {
|
|
transport->sendString("Failed\n");
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool AdminRequestHandler::handleConstSizeRequest (const std::string &cmd,
|
|
Transport *transport) {
|
|
if (!RuntimeOption::EnableConstLoad && cmd == "const-ss") {
|
|
transport->sendString("Not Enabled\n");
|
|
return true;
|
|
}
|
|
if (cmd == "const-ss") {
|
|
std::ostringstream result;
|
|
size_t size = get_const_map_size();
|
|
result << "{ \"hphp.const_map.size\":" << size << "}\n";
|
|
transport->sendString(result.str());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool AdminRequestHandler::handleStaticStringsRequest(const std::string& cmd,
|
|
Transport* transport) {
|
|
std::ostringstream result;
|
|
result << StringData::GetStaticStringCount();
|
|
transport->sendString(result.str());
|
|
return true;
|
|
}
|
|
|
|
namespace {
|
|
struct PCInfo {
|
|
PCInfo() : count(0), unique(0) {}
|
|
int count;
|
|
int unique;
|
|
};
|
|
typedef std::map<int, PCInfo> InfoMap;
|
|
}
|
|
|
|
bool AdminRequestHandler::handleVMRequest(const std::string &cmd,
|
|
Transport *transport) {
|
|
if (cmd == "vm-tcspace") {
|
|
transport->sendString(VM::Transl::Translator::Get()->getUsage());
|
|
return true;
|
|
}
|
|
if (cmd == "vm-preconsts") {
|
|
InfoMap counts;
|
|
using namespace HPHP::VM::Transl;
|
|
for (PreConstDepMap::iterator i = gPreConsts.begin(); i != gPreConsts.end();
|
|
++i) {
|
|
PreConstDep& dep = i->second;
|
|
PCInfo& info = counts[dep.preConsts.size()];
|
|
info.count++;
|
|
if (preConstVecHasUnique(dep.preConsts)) {
|
|
info.unique++;
|
|
}
|
|
}
|
|
|
|
using std::setw;
|
|
using std::right;
|
|
using std::setfill;
|
|
std::stringstream out;
|
|
const int width = 10;
|
|
out << setfill(' ') << right;
|
|
out << setw(width) << "size" << setw(width) << "count"
|
|
<< setw(width) << "unique" << endl;
|
|
for (InfoMap::iterator i = counts.begin(); i != counts.end(); ++i) {
|
|
out << setw(width) << i->first << setw(width) << i->second.count
|
|
<< setw(width) << i->second.unique << endl;
|
|
}
|
|
transport->sendString(out.str());
|
|
return true;
|
|
}
|
|
if (cmd == "vm-dump-tc") {
|
|
if (HPHP::VM::Transl::tc_dump()) {
|
|
transport->sendString("Done");
|
|
} else {
|
|
transport->sendString("Error dumping the translation cache");
|
|
}
|
|
return true;
|
|
}
|
|
if (cmd == "vm-tcreset") {
|
|
int64_t start = Timer::GetCurrentTimeMicros();
|
|
if (HPHP::VM::Transl::tx64->replace()) {
|
|
string msg;
|
|
Util::string_printf(msg, "Done %ld ms",
|
|
(Timer::GetCurrentTimeMicros() - start) / 1000);
|
|
transport->sendString(msg);
|
|
} else {
|
|
transport->sendString("Failed");
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Dump cache content
|
|
|
|
extern bool const_dump(const char *filename);
|
|
bool (*file_dump)(const char *filename) = nullptr;
|
|
|
|
bool AdminRequestHandler::handleDumpCacheRequest(const std::string &cmd,
|
|
Transport *transport) {
|
|
if (cmd == "dump-const") {
|
|
if (!RuntimeOption::EnableApc ||
|
|
!RuntimeOption::EnableConstLoad ||
|
|
RuntimeOption::ApcPrimeLibrary.empty()) {
|
|
transport->sendString("No Constant Cache\n");
|
|
return true;
|
|
}
|
|
const_dump("/tmp/const_map_dump");
|
|
transport->sendString("Done");
|
|
return true;
|
|
}
|
|
if (cmd == "dump-apc") {
|
|
if (!RuntimeOption::EnableApc) {
|
|
transport->sendString("No APC\n");
|
|
return true;
|
|
}
|
|
string keyOnlyParam = transport->getParam("keyonly");
|
|
bool keyOnly = false;
|
|
if (keyOnlyParam == "true" || keyOnlyParam == "1") {
|
|
keyOnly = true;
|
|
}
|
|
int waitSeconds = transport->getIntParam("waitseconds");
|
|
if (!waitSeconds) {
|
|
waitSeconds = RuntimeOption::RequestTimeoutSeconds > 0 ?
|
|
RuntimeOption::RequestTimeoutSeconds : 10;
|
|
}
|
|
apc_dump("/tmp/apc_dump", keyOnly, waitSeconds);
|
|
transport->sendString("Done");
|
|
return true;
|
|
}
|
|
if (cmd == "dump-file-repo") {
|
|
if (file_dump) {
|
|
(*file_dump)("/tmp/file_repo_dump");
|
|
}
|
|
transport->sendString("Done");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
}
|