| Index: runtime/bin/vmstats_impl.cc
|
| ===================================================================
|
| --- runtime/bin/vmstats_impl.cc (revision 0)
|
| +++ runtime/bin/vmstats_impl.cc (revision 0)
|
| @@ -0,0 +1,353 @@
|
| +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +#include "bin/vmstats_impl.h"
|
| +
|
| +#include <sstream>
|
| +
|
| +#include "bin/fdutils.h"
|
| +#include "bin/file.h"
|
| +#include "bin/log.h"
|
| +#include "bin/platform.h"
|
| +#include "bin/socket.h"
|
| +#include "bin/thread.h"
|
| +#include "bin/utils.h"
|
| +#include "include/dart_debugger_api.h"
|
| +#include "platform/json.h"
|
| +
|
| +#define BUFSIZE 8192
|
| +#define RETRY_PAUSE 100 // milliseconds
|
| +
|
| +static const char* INDEX_HTML = "index.html";
|
| +static const char* VMSTATS_HTML = "vmstats.html";
|
| +static const char* DEFAULT_HOST = "localhost";
|
| +
|
| +// Global static pointer used to ensure a single instance of the class.
|
| +VmStats* VmStats::instance_ = NULL;
|
| +dart::Monitor VmStats::instance_monitor_;
|
| +dart::Mutex VmStatusService::mutex_;
|
| +
|
| +
|
| +void VmStats::Start(int port, const char* root_dir) {
|
| + if (instance_ != NULL) {
|
| + FATAL("VmStats already started.");
|
| + }
|
| + MonitorLocker ml(&instance_monitor_);
|
| + instance_ = new VmStats();
|
| + VmStatusService::InitOnce();
|
| + Socket::Initialize();
|
| +
|
| + if (root_dir != NULL) {
|
| + instance_->root_directory_ = root_dir;
|
| + }
|
| +
|
| + // TODO(tball): allow host to be specified.
|
| + char* host = const_cast<char*>(DEFAULT_HOST);
|
| + OSError* os_error;
|
| + const char* host_ip = Socket::LookupIPv4Address(host, &os_error);
|
| + if (host_ip == NULL) {
|
| + Log::PrintErr("Failed IP lookup of VmStats host %s: %s\n",
|
| + host, os_error->message());
|
| + return;
|
| + }
|
| +
|
| + const intptr_t BACKLOG = 128; // Default value from HttpServer.dart
|
| + int64_t address = ServerSocket::CreateBindListen(host_ip, port, BACKLOG);
|
| + if (address < 0) {
|
| + Log::PrintErr("Failed binding VmStats socket: %s:%d\n", host, port);
|
| + return;
|
| + }
|
| + instance_->bind_address_ = address;
|
| + Log::Print(
|
| +#if defined(TARGET_ARCH_X64)
|
| + "VmStats URL: http://%s:%ld/\n",
|
| +#else
|
| + "VmStats URL: http://%s:%d/\n",
|
| +#endif
|
| + host,
|
| + Socket::GetPort(address));
|
| +
|
| + instance_->running_ = true;
|
| + int errno = dart::Thread::Start(WebServer, address);
|
| + if (errno != 0) {
|
| + Log::PrintErr("Failed starting VmStats thread: %d\n", errno);
|
| + Shutdown();
|
| + }
|
| +}
|
| +
|
| +
|
| +void VmStats::Stop() {
|
| + MonitorLocker ml(&instance_monitor_);
|
| + if (instance_ != NULL) {
|
| + instance_->running_ = false;
|
| + }
|
| +}
|
| +
|
| +
|
| +void VmStats::Shutdown() {
|
| + MonitorLocker ml(&instance_monitor_);
|
| + Socket::Close(instance_->bind_address_);
|
| + delete instance_;
|
| + instance_ = NULL;
|
| +}
|
| +
|
| +
|
| +void VmStats::AddIsolate(IsolateData* isolate_data,
|
| + Dart_Isolate isolate) {
|
| + MonitorLocker ml(&instance_monitor_);
|
| + if (instance_ != NULL) {
|
| + instance_->isolate_table_[isolate_data] = isolate;
|
| + }
|
| +}
|
| +
|
| +
|
| +void VmStats::RemoveIsolate(IsolateData* isolate_data) {
|
| + MonitorLocker ml(&instance_monitor_);
|
| + if (instance_ != NULL) {
|
| + instance_->isolate_table_.erase(isolate_data);
|
| + }
|
| +}
|
| +
|
| +
|
| +static const char* ContentType(const char* url) {
|
| + const char* suffix = strrchr(url, '.');
|
| + if (suffix != NULL) {
|
| + if (!strcmp(suffix, ".html")) {
|
| + return "text/html; charset=UTF-8";
|
| + }
|
| + if (!strcmp(suffix, ".dart")) {
|
| + return "application/dart; charset=UTF-8";
|
| + }
|
| + if (!strcmp(suffix, ".js")) {
|
| + return "application/javascript; charset=UTF-8";
|
| + }
|
| + if (!strcmp(suffix, ".css")) {
|
| + return "text/css; charset=UTF-8";
|
| + }
|
| + if (!strcmp(suffix, ".gif")) {
|
| + return "image/gif";
|
| + }
|
| + if (!strcmp(suffix, ".png")) {
|
| + return "image/png";
|
| + }
|
| + if (!strcmp(suffix, ".jpg") || !strcmp(suffix, ".jpeg")) {
|
| + return "image/jpeg";
|
| + }
|
| + }
|
| + return "text/plain";
|
| +}
|
| +
|
| +
|
| +void VmStats::WebServer(uword bind_address) {
|
| + while (true) {
|
| + intptr_t socket = ServerSocket::Accept(bind_address);
|
| + if (socket == ServerSocket::kTemporaryFailure) {
|
| + // Not a real failure, woke up but no connection available.
|
| +
|
| + // Use MonitorLocker.Wait(), since it has finer granularity than sleep().
|
| + dart::Monitor m;
|
| + MonitorLocker ml(&m);
|
| + ml.Wait(RETRY_PAUSE);
|
| +
|
| + continue;
|
| + }
|
| + if (socket < 0) {
|
| + // Stop() closed the socket.
|
| + return;
|
| + }
|
| + FDUtils::SetBlocking(socket);
|
| +
|
| + // TODO(tball): rewrite this to use STL, so as to eliminate the static
|
| + // buffer and support resource URLs that are longer than BUFSIZE.
|
| +
|
| + // Read request.
|
| + // TODO(tball): support partial reads, possibly needed for POST uploads.
|
| + char buffer[BUFSIZE + 1];
|
| + int len = read(socket, buffer, BUFSIZE);
|
| + if (len <= 0) {
|
| + // Invalid HTTP request, ignore.
|
| + continue;
|
| + }
|
| + buffer[len] = '\0';
|
| +
|
| + // Verify it's a GET request.
|
| + // TODO(tball): support POST requests.
|
| + if (strncmp("GET ", buffer, 4) != 0 && strncmp("get ", buffer, 4) != 0) {
|
| + Log::PrintErr("Unsupported HTTP request type");
|
| + const char* response = "HTTP/1.1 403 Forbidden\n"
|
| + "Content-Length: 120\n"
|
| + "Connection: close\n"
|
| + "Content-Type: text/html\n\n"
|
| + "<html><head>\n<title>403 Forbidden</title>\n</head>"
|
| + "<body>\n<h1>Forbidden</h1>\nUnsupported HTTP request type\n</body>"
|
| + "</html>\n";
|
| + Socket::Write(socket, response, strlen(response));
|
| + Socket::Close(socket);
|
| + continue;
|
| + }
|
| +
|
| + // Extract GET URL, and null-terminate URL in case request line has
|
| + // HTTP version.
|
| + for (int i = 4; i < len; i++) {
|
| + if (buffer[i] == ' ') {
|
| + buffer[i] = '\0';
|
| + }
|
| + }
|
| + char* url = &buffer[4];
|
| +
|
| + Log::Print("vmstats: %s requested\n", url);
|
| + char* content = NULL;
|
| +
|
| + // Check for VmStats-specific URLs.
|
| + if (strcmp(url, "/isolates") == 0) {
|
| + content = instance_->IsolatesStatus();
|
| + } else {
|
| + // Check plug-ins.
|
| + content = VmStatusService::GetVmStatus(url);
|
| + }
|
| +
|
| + if (content != NULL) {
|
| + size_t content_len = strlen(content);
|
| + len = snprintf(buffer, BUFSIZE,
|
| +#if defined(TARGET_ARCH_X64)
|
| + "HTTP/1.1 200 OK\nContent-Type: application/json; charset=UTF-8\n"
|
| + "Content-Length: %lx\n\n",
|
| +#else
|
| + "HTTP/1.1 200 OK\nContent-Type: application/json; charset=UTF-8\n"
|
| + "Content-Length: %d\n\n",
|
| +#endif
|
| + content_len);
|
| + Socket::Write(socket, buffer, strlen(buffer));
|
| + Socket::Write(socket, content, content_len);
|
| + Socket::Write(socket, "\n", 1);
|
| + Socket::Write(socket, buffer, strlen(buffer));
|
| + free(content);
|
| + } else {
|
| + // No status content with this URL, return file or resource content.
|
| + std::string path(instance_->root_directory_);
|
| + path.append(url);
|
| +
|
| + // Expand directory URLs.
|
| + if (strcmp(url, "/") == 0) {
|
| + path.append(VMSTATS_HTML);
|
| + } else if (url[strlen(url) - 1] == '/') {
|
| + path.append(INDEX_HTML);
|
| + }
|
| +
|
| + bool success = false;
|
| + if (File::Exists(path.c_str())) {
|
| + File* f = File::Open(path.c_str(), File::kRead);
|
| + if (f != NULL) {
|
| + intptr_t len = f->Length();
|
| + char* text_buffer = reinterpret_cast<char*>(malloc(len));
|
| + if (f->ReadFully(text_buffer, len)) {
|
| + const char* content_type = ContentType(path.c_str());
|
| + snprintf(buffer, BUFSIZE,
|
| +#if defined(TARGET_ARCH_X64)
|
| + "HTTP/1.1 200 OK\nContent-Type: %s\n"
|
| + "Content-Length: %ld\n\n",
|
| +#else
|
| + "HTTP/1.1 200 OK\nContent-Type: %s\n"
|
| + "Content-Length: %d\n\n",
|
| +#endif
|
| + content_type, len);
|
| + Socket::Write(socket, buffer, strlen(buffer));
|
| + Socket::Write(socket, text_buffer, len);
|
| + Socket::Write(socket, "\n", 1);
|
| + success = true;
|
| + }
|
| + free(text_buffer);
|
| + delete f;
|
| + }
|
| + } else {
|
| + // TODO(tball): look up linked in resource.
|
| + }
|
| + if (!success) {
|
| + const char* response = "HTTP/1.1 404 Not Found\n\n";
|
| + Socket::Write(socket, response, strlen(response));
|
| + }
|
| + }
|
| + Socket::Close(socket);
|
| + }
|
| +
|
| + Shutdown();
|
| +}
|
| +
|
| +
|
| +char* VmStats::IsolatesStatus() {
|
| + std::ostringstream stream;
|
| + stream << '{' << std::endl;
|
| + stream << "\"isolates\": [" << std::endl;
|
| + IsolateTable::iterator itr;
|
| + bool first = true;
|
| + for (itr = isolate_table_.begin(); itr != isolate_table_.end(); ++itr) {
|
| + Dart_Isolate isolate = itr->second;
|
| + static char request[512];
|
| + snprintf(request, sizeof(request),
|
| +#if defined(TARGET_ARCH_X64)
|
| + "/isolate/0x%lx",
|
| +#else
|
| + "/isolate/0x%llx",
|
| +#endif
|
| + reinterpret_cast<int64_t>(isolate));
|
| + char* status = VmStatusService::GetVmStatus(request);
|
| + if (status != NULL) {
|
| + stream << status;
|
| + if (!first) {
|
| + stream << "," << std::endl;
|
| + }
|
| + first = false;
|
| + }
|
| + free(status);
|
| + }
|
| + stream << std::endl << "]";
|
| + stream << std::endl << '}' << std::endl;
|
| + return strdup(stream.str().c_str());
|
| +}
|
| +
|
| +
|
| +// Global static pointer used to ensure a single instance of the class.
|
| +VmStatusService* VmStatusService::instance_ = NULL;
|
| +
|
| +
|
| +void VmStatusService::InitOnce() {
|
| + ASSERT(VmStatusService::instance_ == NULL);
|
| + VmStatusService::instance_ = new VmStatusService();
|
| +
|
| + // Register built-in status plug-ins. RegisterPlugin is not used because
|
| + // this isn't called within an isolate, and because parameter checking
|
| + // isn't necessary.
|
| + instance_->RegisterPlugin(&Dart_GetVmStatus);
|
| +
|
| + // TODO(tball): dynamically load any additional plug-ins.
|
| +}
|
| +
|
| +
|
| +int VmStatusService::RegisterPlugin(Dart_VmStatusCallback callback) {
|
| + MutexLocker ml(&mutex_);
|
| + if (callback == NULL) {
|
| + return -1;
|
| + }
|
| + VmStatusPlugin* plugin = new VmStatusPlugin(callback);
|
| + VmStatusPlugin* list = instance_->registered_plugin_list_;
|
| + if (list == NULL) {
|
| + instance_->registered_plugin_list_ = plugin;
|
| + } else {
|
| + list->Append(plugin);
|
| + }
|
| + return 0;
|
| +}
|
| +
|
| +
|
| +char* VmStatusService::GetVmStatus(const char* request) {
|
| + VmStatusPlugin* plugin = instance_->registered_plugin_list_;
|
| + while (plugin != NULL) {
|
| + char* result = (plugin->callback())(request);
|
| + if (result != NULL) {
|
| + return result;
|
| + }
|
| + plugin = plugin->next();
|
| + }
|
| + return NULL;
|
| +}
|
|
|