OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 #include "bin/vmstats_impl.h" |
| 6 |
| 7 #include <sstream> |
| 8 |
| 9 #include "bin/fdutils.h" |
| 10 #include "bin/file.h" |
| 11 #include "bin/log.h" |
| 12 #include "bin/platform.h" |
| 13 #include "bin/socket.h" |
| 14 #include "bin/thread.h" |
| 15 #include "bin/utils.h" |
| 16 #include "include/dart_debugger_api.h" |
| 17 #include "platform/json.h" |
| 18 |
| 19 #define BUFSIZE 8192 |
| 20 #define RETRY_PAUSE 100 // milliseconds |
| 21 |
| 22 static const char* INDEX_HTML = "index.html"; |
| 23 static const char* VMSTATS_HTML = "vmstats.html"; |
| 24 static const char* DEFAULT_HOST = "localhost"; |
| 25 |
| 26 // Global static pointer used to ensure a single instance of the class. |
| 27 VmStats* VmStats::instance_ = NULL; |
| 28 dart::Monitor VmStats::instance_monitor_; |
| 29 dart::Mutex VmStatusService::mutex_; |
| 30 |
| 31 |
| 32 void VmStats::Start(int port, const char* root_dir) { |
| 33 if (instance_ != NULL) { |
| 34 FATAL("VmStats already started."); |
| 35 } |
| 36 MonitorLocker ml(&instance_monitor_); |
| 37 instance_ = new VmStats(); |
| 38 VmStatusService::InitOnce(); |
| 39 Socket::Initialize(); |
| 40 |
| 41 if (root_dir != NULL) { |
| 42 instance_->root_directory_ = root_dir; |
| 43 } |
| 44 |
| 45 // TODO(tball): allow host to be specified. |
| 46 char* host = const_cast<char*>(DEFAULT_HOST); |
| 47 OSError* os_error; |
| 48 const char* host_ip = Socket::LookupIPv4Address(host, &os_error); |
| 49 if (host_ip == NULL) { |
| 50 Log::PrintErr("Failed IP lookup of VmStats host %s: %s\n", |
| 51 host, os_error->message()); |
| 52 return; |
| 53 } |
| 54 |
| 55 const intptr_t BACKLOG = 128; // Default value from HttpServer.dart |
| 56 int64_t address = ServerSocket::CreateBindListen(host_ip, port, BACKLOG); |
| 57 if (address < 0) { |
| 58 Log::PrintErr("Failed binding VmStats socket: %s:%d\n", host, port); |
| 59 return; |
| 60 } |
| 61 instance_->bind_address_ = address; |
| 62 Log::Print( |
| 63 #if defined(TARGET_ARCH_X64) |
| 64 "VmStats URL: http://%s:%ld/\n", |
| 65 #else |
| 66 "VmStats URL: http://%s:%d/\n", |
| 67 #endif |
| 68 host, |
| 69 Socket::GetPort(address)); |
| 70 |
| 71 instance_->running_ = true; |
| 72 int errno = dart::Thread::Start(WebServer, address); |
| 73 if (errno != 0) { |
| 74 Log::PrintErr("Failed starting VmStats thread: %d\n", errno); |
| 75 Shutdown(); |
| 76 } |
| 77 } |
| 78 |
| 79 |
| 80 void VmStats::Stop() { |
| 81 MonitorLocker ml(&instance_monitor_); |
| 82 if (instance_ != NULL) { |
| 83 instance_->running_ = false; |
| 84 } |
| 85 } |
| 86 |
| 87 |
| 88 void VmStats::Shutdown() { |
| 89 MonitorLocker ml(&instance_monitor_); |
| 90 Socket::Close(instance_->bind_address_); |
| 91 delete instance_; |
| 92 instance_ = NULL; |
| 93 } |
| 94 |
| 95 |
| 96 void VmStats::AddIsolate(IsolateData* isolate_data, |
| 97 Dart_Isolate isolate) { |
| 98 MonitorLocker ml(&instance_monitor_); |
| 99 if (instance_ != NULL) { |
| 100 instance_->isolate_table_[isolate_data] = isolate; |
| 101 } |
| 102 } |
| 103 |
| 104 |
| 105 void VmStats::RemoveIsolate(IsolateData* isolate_data) { |
| 106 MonitorLocker ml(&instance_monitor_); |
| 107 if (instance_ != NULL) { |
| 108 instance_->isolate_table_.erase(isolate_data); |
| 109 } |
| 110 } |
| 111 |
| 112 |
| 113 static const char* ContentType(const char* url) { |
| 114 const char* suffix = strrchr(url, '.'); |
| 115 if (suffix != NULL) { |
| 116 if (!strcmp(suffix, ".html")) { |
| 117 return "text/html; charset=UTF-8"; |
| 118 } |
| 119 if (!strcmp(suffix, ".dart")) { |
| 120 return "application/dart; charset=UTF-8"; |
| 121 } |
| 122 if (!strcmp(suffix, ".js")) { |
| 123 return "application/javascript; charset=UTF-8"; |
| 124 } |
| 125 if (!strcmp(suffix, ".css")) { |
| 126 return "text/css; charset=UTF-8"; |
| 127 } |
| 128 if (!strcmp(suffix, ".gif")) { |
| 129 return "image/gif"; |
| 130 } |
| 131 if (!strcmp(suffix, ".png")) { |
| 132 return "image/png"; |
| 133 } |
| 134 if (!strcmp(suffix, ".jpg") || !strcmp(suffix, ".jpeg")) { |
| 135 return "image/jpeg"; |
| 136 } |
| 137 } |
| 138 return "text/plain"; |
| 139 } |
| 140 |
| 141 |
| 142 void VmStats::WebServer(uword bind_address) { |
| 143 while (true) { |
| 144 intptr_t socket = ServerSocket::Accept(bind_address); |
| 145 if (socket == ServerSocket::kTemporaryFailure) { |
| 146 // Not a real failure, woke up but no connection available. |
| 147 |
| 148 // Use MonitorLocker.Wait(), since it has finer granularity than sleep(). |
| 149 dart::Monitor m; |
| 150 MonitorLocker ml(&m); |
| 151 ml.Wait(RETRY_PAUSE); |
| 152 |
| 153 continue; |
| 154 } |
| 155 if (socket < 0) { |
| 156 // Stop() closed the socket. |
| 157 return; |
| 158 } |
| 159 FDUtils::SetBlocking(socket); |
| 160 |
| 161 // TODO(tball): rewrite this to use STL, so as to eliminate the static |
| 162 // buffer and support resource URLs that are longer than BUFSIZE. |
| 163 |
| 164 // Read request. |
| 165 // TODO(tball): support partial reads, possibly needed for POST uploads. |
| 166 char buffer[BUFSIZE + 1]; |
| 167 int len = read(socket, buffer, BUFSIZE); |
| 168 if (len <= 0) { |
| 169 // Invalid HTTP request, ignore. |
| 170 continue; |
| 171 } |
| 172 buffer[len] = '\0'; |
| 173 |
| 174 // Verify it's a GET request. |
| 175 // TODO(tball): support POST requests. |
| 176 if (strncmp("GET ", buffer, 4) != 0 && strncmp("get ", buffer, 4) != 0) { |
| 177 Log::PrintErr("Unsupported HTTP request type"); |
| 178 const char* response = "HTTP/1.1 403 Forbidden\n" |
| 179 "Content-Length: 120\n" |
| 180 "Connection: close\n" |
| 181 "Content-Type: text/html\n\n" |
| 182 "<html><head>\n<title>403 Forbidden</title>\n</head>" |
| 183 "<body>\n<h1>Forbidden</h1>\nUnsupported HTTP request type\n</body>" |
| 184 "</html>\n"; |
| 185 Socket::Write(socket, response, strlen(response)); |
| 186 Socket::Close(socket); |
| 187 continue; |
| 188 } |
| 189 |
| 190 // Extract GET URL, and null-terminate URL in case request line has |
| 191 // HTTP version. |
| 192 for (int i = 4; i < len; i++) { |
| 193 if (buffer[i] == ' ') { |
| 194 buffer[i] = '\0'; |
| 195 } |
| 196 } |
| 197 char* url = &buffer[4]; |
| 198 |
| 199 Log::Print("vmstats: %s requested\n", url); |
| 200 char* content = NULL; |
| 201 |
| 202 // Check for VmStats-specific URLs. |
| 203 if (strcmp(url, "/isolates") == 0) { |
| 204 content = instance_->IsolatesStatus(); |
| 205 } else { |
| 206 // Check plug-ins. |
| 207 content = VmStatusService::GetVmStatus(url); |
| 208 } |
| 209 |
| 210 if (content != NULL) { |
| 211 size_t content_len = strlen(content); |
| 212 len = snprintf(buffer, BUFSIZE, |
| 213 #if defined(TARGET_ARCH_X64) |
| 214 "HTTP/1.1 200 OK\nContent-Type: application/json; charset=UTF-8\n" |
| 215 "Content-Length: %lx\n\n", |
| 216 #else |
| 217 "HTTP/1.1 200 OK\nContent-Type: application/json; charset=UTF-8\n" |
| 218 "Content-Length: %d\n\n", |
| 219 #endif |
| 220 content_len); |
| 221 Socket::Write(socket, buffer, strlen(buffer)); |
| 222 Socket::Write(socket, content, content_len); |
| 223 Socket::Write(socket, "\n", 1); |
| 224 Socket::Write(socket, buffer, strlen(buffer)); |
| 225 free(content); |
| 226 } else { |
| 227 // No status content with this URL, return file or resource content. |
| 228 std::string path(instance_->root_directory_); |
| 229 path.append(url); |
| 230 |
| 231 // Expand directory URLs. |
| 232 if (strcmp(url, "/") == 0) { |
| 233 path.append(VMSTATS_HTML); |
| 234 } else if (url[strlen(url) - 1] == '/') { |
| 235 path.append(INDEX_HTML); |
| 236 } |
| 237 |
| 238 bool success = false; |
| 239 if (File::Exists(path.c_str())) { |
| 240 File* f = File::Open(path.c_str(), File::kRead); |
| 241 if (f != NULL) { |
| 242 intptr_t len = f->Length(); |
| 243 char* text_buffer = reinterpret_cast<char*>(malloc(len)); |
| 244 if (f->ReadFully(text_buffer, len)) { |
| 245 const char* content_type = ContentType(path.c_str()); |
| 246 snprintf(buffer, BUFSIZE, |
| 247 #if defined(TARGET_ARCH_X64) |
| 248 "HTTP/1.1 200 OK\nContent-Type: %s\n" |
| 249 "Content-Length: %ld\n\n", |
| 250 #else |
| 251 "HTTP/1.1 200 OK\nContent-Type: %s\n" |
| 252 "Content-Length: %d\n\n", |
| 253 #endif |
| 254 content_type, len); |
| 255 Socket::Write(socket, buffer, strlen(buffer)); |
| 256 Socket::Write(socket, text_buffer, len); |
| 257 Socket::Write(socket, "\n", 1); |
| 258 success = true; |
| 259 } |
| 260 free(text_buffer); |
| 261 delete f; |
| 262 } |
| 263 } else { |
| 264 // TODO(tball): look up linked in resource. |
| 265 } |
| 266 if (!success) { |
| 267 const char* response = "HTTP/1.1 404 Not Found\n\n"; |
| 268 Socket::Write(socket, response, strlen(response)); |
| 269 } |
| 270 } |
| 271 Socket::Close(socket); |
| 272 } |
| 273 |
| 274 Shutdown(); |
| 275 } |
| 276 |
| 277 |
| 278 char* VmStats::IsolatesStatus() { |
| 279 std::ostringstream stream; |
| 280 stream << '{' << std::endl; |
| 281 stream << "\"isolates\": [" << std::endl; |
| 282 IsolateTable::iterator itr; |
| 283 bool first = true; |
| 284 for (itr = isolate_table_.begin(); itr != isolate_table_.end(); ++itr) { |
| 285 Dart_Isolate isolate = itr->second; |
| 286 static char request[512]; |
| 287 snprintf(request, sizeof(request), |
| 288 #if defined(TARGET_ARCH_X64) |
| 289 "/isolate/0x%lx", |
| 290 #else |
| 291 "/isolate/0x%llx", |
| 292 #endif |
| 293 reinterpret_cast<int64_t>(isolate)); |
| 294 char* status = VmStatusService::GetVmStatus(request); |
| 295 if (status != NULL) { |
| 296 stream << status; |
| 297 if (!first) { |
| 298 stream << "," << std::endl; |
| 299 } |
| 300 first = false; |
| 301 } |
| 302 free(status); |
| 303 } |
| 304 stream << std::endl << "]"; |
| 305 stream << std::endl << '}' << std::endl; |
| 306 return strdup(stream.str().c_str()); |
| 307 } |
| 308 |
| 309 |
| 310 // Global static pointer used to ensure a single instance of the class. |
| 311 VmStatusService* VmStatusService::instance_ = NULL; |
| 312 |
| 313 |
| 314 void VmStatusService::InitOnce() { |
| 315 ASSERT(VmStatusService::instance_ == NULL); |
| 316 VmStatusService::instance_ = new VmStatusService(); |
| 317 |
| 318 // Register built-in status plug-ins. RegisterPlugin is not used because |
| 319 // this isn't called within an isolate, and because parameter checking |
| 320 // isn't necessary. |
| 321 instance_->RegisterPlugin(&Dart_GetVmStatus); |
| 322 |
| 323 // TODO(tball): dynamically load any additional plug-ins. |
| 324 } |
| 325 |
| 326 |
| 327 int VmStatusService::RegisterPlugin(Dart_VmStatusCallback callback) { |
| 328 MutexLocker ml(&mutex_); |
| 329 if (callback == NULL) { |
| 330 return -1; |
| 331 } |
| 332 VmStatusPlugin* plugin = new VmStatusPlugin(callback); |
| 333 VmStatusPlugin* list = instance_->registered_plugin_list_; |
| 334 if (list == NULL) { |
| 335 instance_->registered_plugin_list_ = plugin; |
| 336 } else { |
| 337 list->Append(plugin); |
| 338 } |
| 339 return 0; |
| 340 } |
| 341 |
| 342 |
| 343 char* VmStatusService::GetVmStatus(const char* request) { |
| 344 VmStatusPlugin* plugin = instance_->registered_plugin_list_; |
| 345 while (plugin != NULL) { |
| 346 char* result = (plugin->callback())(request); |
| 347 if (result != NULL) { |
| 348 return result; |
| 349 } |
| 350 plugin = plugin->next(); |
| 351 } |
| 352 return NULL; |
| 353 } |
OLD | NEW |