Chromium Code Reviews| Index: runtime/bin/vmstats_impl.cc |
| =================================================================== |
| --- runtime/bin/vmstats_impl.cc (revision 0) |
| +++ runtime/bin/vmstats_impl.cc (revision 0) |
| @@ -0,0 +1,293 @@ |
| +// 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 "bin/fdutils.h" |
| +#include "bin/file.h" |
| +#include "bin/platform.h" |
| +#include "bin/socket.h" |
| +#include "bin/thread.h" |
| +#include "include/dart_debugger_api.h" |
| +#include "platform/json.h" |
| + |
| +namespace dart { |
| + |
| +#define BUFSIZE 8192 |
| + |
| +// Global static pointer used to ensure a single instance of the class. |
| +VmStats* VmStats::instance_ = NULL; |
| +Mutex VmStats::instance_lock_; |
| + |
| +void VmStats::Start(int port, const char* root_dir) { |
| + if (instance_ != NULL) { |
|
siva
2013/02/15 05:59:10
Where is instance_ set? I don't see it being set t
Tom Ball
2013/02/16 00:57:19
It was the last statement in Start(), but I change
|
| + FATAL("VmStats already started."); |
| + } |
| + MutexLocker ml(&instance_lock_); |
| + VmStats* vmstats = new VmStats; |
|
siva
2013/02/15 05:59:10
probably you meant instance_ = new VmStats(); here
Tom Ball
2013/02/16 00:57:19
Done.
|
| + VmStatusService::InitOnce(); |
| + Socket::Initialize(); |
| + |
| + if (root_dir != NULL) { |
| + vmstats->root_directory_ = root_dir; |
| + } |
| + |
| + const intptr_t BACKLOG = 128; // Default value from HttpServer.dart |
| + intptr_t bind_address = |
| + ServerSocket::CreateBindListen("127.0.0.1", port, BACKLOG); |
| + if (bind_address < 0) { |
| + fprintf(stderr, "Failed binding VmStats socket: 127.0.0.1:%d\n", port); |
| + return; |
| + } |
|
siva
2013/02/15 05:59:10
If port is -1 we probably need code here to get th
Tom Ball
2013/02/16 00:57:19
Done.
|
| + int errno = Thread::Start(WebServer, bind_address); |
| + if (errno != 0) { |
| + fprintf(stderr, "Failed starting VmStats thread: %d\n", errno); |
| + return; |
| + } |
| + instance_ = vmstats; |
| +} |
| + |
| + |
| +void VmStats::Stop() { |
| + MutexLocker ml(&instance_lock_); |
| + if (instance_ != NULL) { |
| + delete instance_; |
|
siva
2013/02/15 05:59:10
The WebServer thread is still running and it acces
Tom Ball
2013/02/16 00:57:19
Reworked stop so it only sets a running_ instance
|
| + instance_ = NULL; |
| + } |
| +} |
| + |
| + |
| +void VmStats::AddIsolate(IsolateData* isolate_data, |
| + Dart_Isolate isolate) { |
| + MutexLocker ml(&instance_lock_); |
| + if (instance_ != NULL) { |
| + instance_->isolate_table_[isolate_data] = isolate; |
| + } |
| +} |
| + |
| + |
| +void VmStats::RemoveIsolate(IsolateData* isolate_data) { |
| + MutexLocker ml(&instance_lock_); |
| + if (instance_ != NULL) { |
| + instance_->isolate_table_.erase(isolate_data); |
| + } |
| +} |
| + |
| + |
| +static bool endsWith(const std::string s, const char* ending) { |
| + size_t ending_len = strlen(ending); |
| + if (s.length() > ending_len) { |
| + return s.compare(s.length() - ending_len, ending_len, ending) == 0; |
| + } else { |
| + return false; |
| + } |
| +} |
| + |
| + |
| +static const char* ContentType(const char* url) { |
| + // TODO(tball): replace with static map? |
| + std::string path(url); |
|
siva
2013/02/15 05:59:10
might be more efficient to just do a
const char* s
Tom Ball
2013/02/16 00:57:19
Done.
|
| + if (endsWith(path, ".html")) { |
| + return "text/html; charset=UTF-8"; |
| + } |
| + if (endsWith(path, ".dart")) { |
| + return "application/dart; charset=UTF-8"; |
| + } |
| + if (endsWith(path, ".js")) { |
| + return "application/javascript; charset=UTF-8"; |
| + } |
| + if (endsWith(path, ".css")) { |
| + return "text/css; charset=UTF-8"; |
| + } |
| + if (endsWith(path, ".gif")) { |
| + return "image/gif"; |
| + } |
| + if (endsWith(path, ".png")) { |
| + return "image/png"; |
| + } |
| + if (endsWith(path, ".jpg") || endsWith(path, ".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. |
| + sleep(1); |
| + continue; |
| + } |
| + if (socket < 0) { |
| + FATAL("Accepting new web server connection failed.\n"); |
| + } |
| + FDUtils::SetBlocking(socket); |
| + |
| + // TODO(tball): rewrite this to use STL, so as to eliminate the static |
| + // buffer and support resources that are bigger than BUFSIZE. |
| + |
| + // Read request. |
| + static char buffer[BUFSIZE + 1]; |
| + int len = read(socket, buffer, BUFSIZE); |
|
siva
2013/02/15 05:59:10
We may need to handle partial reads here but let u
Tom Ball
2013/02/16 00:57:19
This buffer works as long as the server only suppo
|
| + if (len <= 0) { |
| + // Invalid HTTP request, ignore. |
| + continue; |
| + } |
| + buffer[len] = '\0'; |
| + |
| + // Verify it's a GET request. |
| + if (strncmp("GET ", buffer, 4) != 0 && strncmp("get ", buffer, 4) != 0) { |
| + fprintf(stderr, "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)); |
|
siva
2013/02/15 05:59:10
Does Socket::Write assert that the entire buffer p
Tom Ball
2013/02/16 00:57:19
It does because I previously set the socket to blo
|
| + close(socket); |
|
siva
2013/02/15 05:59:10
Socket::Close(socket); ?
Tom Ball
2013/02/16 00:57:19
Done.
|
| + 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]; |
| + printf("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-1, |
| + "HTTP/1.1 200 OK\nContent-Type: application/json; charset=UTF-8\n" |
| + "Content-Length: %d\n\n", 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); |
| + 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)) { |
| + std::string content(text_buffer, len); |
| + const char* content_type = ContentType(url); |
| + len = snprintf(buffer, BUFSIZE-1, |
| + "HTTP/1.1 200 OK\nContent-Type: %s\n" |
| + "Content-Length: %d\n\n", |
| + content_type, static_cast<int>(content.length())); |
| + Socket::Write(socket, buffer, strlen(buffer)); |
| + Socket::Write(socket, content.c_str(), content.length()); |
| + 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); |
| + } |
| +} |
| + |
| + |
| +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), "/isolate/0x%llx", |
| + 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() { |
|
siva
2013/02/15 05:59:10
ASSERT(VmStatusService::Instance_ == NULL);
Tom Ball
2013/02/16 00:57:19
Done.
|
| + 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) { |
|
siva
2013/02/15 05:59:10
We should probably protect this call also under a
Tom Ball
2013/02/16 00:57:19
Done.
|
| + 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; |
| +} |
| + |
| +} // namespace dart |