| 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 "bin/file.h" | |
| 8 #include "bin/log.h" | |
| 9 #include "bin/platform.h" | |
| 10 #include "bin/resources.h" | |
| 11 #include "bin/socket.h" | |
| 12 #include "bin/thread.h" | |
| 13 #include "bin/utils.h" | |
| 14 #include "include/dart_debugger_api.h" | |
| 15 #include "platform/json.h" | |
| 16 | |
| 17 | |
| 18 namespace dart { | |
| 19 namespace bin { | |
| 20 | |
| 21 #define BUFSIZE 8192 | |
| 22 #define RETRY_PAUSE 100 // milliseconds | |
| 23 | |
| 24 static const char* INDEX_HTML = "index.html"; | |
| 25 static const char* VMSTATS_HTML = "vmstats.html"; | |
| 26 static const char* DEFAULT_HOST = "localhost"; | |
| 27 | |
| 28 // Global static pointer used to ensure a single instance of the class. | |
| 29 VmStats* VmStats::instance_ = NULL; | |
| 30 dart::Monitor* VmStats::instance_monitor_; | |
| 31 dart::Mutex* VmStatusService::mutex_; | |
| 32 | |
| 33 | |
| 34 void VmStats::Start(int port, const char* root_dir, bool verbose) { | |
| 35 if (instance_ != NULL) { | |
| 36 FATAL("VmStats already started."); | |
| 37 } | |
| 38 instance_ = new VmStats(verbose); | |
| 39 instance_monitor_ = new dart::Monitor(); | |
| 40 Initialize(); | |
| 41 VmStatusService::InitOnce(); | |
| 42 | |
| 43 if (port >= 0) { | |
| 44 StartServer(port, root_dir); | |
| 45 } | |
| 46 } | |
| 47 | |
| 48 | |
| 49 void VmStats::StartServer(int port, const char* root_dir) { | |
| 50 ASSERT(port >= 0); | |
| 51 ASSERT(instance_ != NULL); | |
| 52 ASSERT(instance_monitor_ != NULL); | |
| 53 Socket::Initialize(); | |
| 54 | |
| 55 if (root_dir != NULL) { | |
| 56 instance_->root_directory_ = root_dir; | |
| 57 } | |
| 58 | |
| 59 // TODO(tball): allow host to be specified. | |
| 60 char* host = const_cast<char*>(DEFAULT_HOST); | |
| 61 OSError* os_error; | |
| 62 AddressList<SocketAddress>* addresses = | |
| 63 Socket::LookupAddress(host, -1, &os_error); | |
| 64 if (addresses == NULL) { | |
| 65 Log::PrintErr("Failed IP lookup of VmStats host %s: %s\n", | |
| 66 host, os_error->message()); | |
| 67 return; | |
| 68 } | |
| 69 const intptr_t BACKLOG = 128; // Default value from HttpServer.dart | |
| 70 int64_t address = ServerSocket::CreateBindListen( | |
| 71 addresses->GetAt(0)->addr(), port, BACKLOG); | |
| 72 delete addresses; | |
| 73 if (address < 0) { | |
| 74 Log::PrintErr("Failed binding VmStats socket: %s:%d\n", host, port); | |
| 75 return; | |
| 76 } | |
| 77 instance_->bind_address_ = address; | |
| 78 Log::Print("VmStats URL: http://%s:%"Pd"/\n", host, Socket::GetPort(address)); | |
| 79 | |
| 80 MonitorLocker ml(instance_monitor_); | |
| 81 instance_->running_ = true; | |
| 82 int err = dart::Thread::Start(WebServer, address); | |
| 83 if (err != 0) { | |
| 84 Log::PrintErr("Failed starting VmStats thread: %d\n", err); | |
| 85 Shutdown(); | |
| 86 } | |
| 87 } | |
| 88 | |
| 89 void VmStats::Stop() { | |
| 90 ASSERT(instance_ != NULL); | |
| 91 MonitorLocker ml(instance_monitor_); | |
| 92 instance_->running_ = false; | |
| 93 } | |
| 94 | |
| 95 | |
| 96 void VmStats::Shutdown() { | |
| 97 ASSERT(instance_ != NULL); | |
| 98 MonitorLocker ml(instance_monitor_); | |
| 99 Socket::Close(instance_->bind_address_); | |
| 100 delete instance_; | |
| 101 instance_ = NULL; | |
| 102 } | |
| 103 | |
| 104 | |
| 105 void VmStats::AddIsolate(IsolateData* isolate_data, | |
| 106 Dart_Isolate isolate) { | |
| 107 MonitorLocker ml(instance_monitor_); | |
| 108 instance_->isolate_table_[isolate_data] = isolate; | |
| 109 } | |
| 110 | |
| 111 | |
| 112 void VmStats::RemoveIsolate(IsolateData* isolate_data) { | |
| 113 MonitorLocker ml(instance_monitor_); | |
| 114 instance_->isolate_table_.erase(isolate_data); | |
| 115 } | |
| 116 | |
| 117 | |
| 118 static const char* ContentType(const char* url) { | |
| 119 const char* suffix = strrchr(url, '.'); | |
| 120 if (suffix != NULL) { | |
| 121 if (!strcmp(suffix, ".html")) { | |
| 122 return "text/html; charset=UTF-8"; | |
| 123 } | |
| 124 if (!strcmp(suffix, ".dart")) { | |
| 125 return "application/dart; charset=UTF-8"; | |
| 126 } | |
| 127 if (!strcmp(suffix, ".js")) { | |
| 128 return "application/javascript; charset=UTF-8"; | |
| 129 } | |
| 130 if (!strcmp(suffix, ".css")) { | |
| 131 return "text/css; charset=UTF-8"; | |
| 132 } | |
| 133 if (!strcmp(suffix, ".gif")) { | |
| 134 return "image/gif"; | |
| 135 } | |
| 136 if (!strcmp(suffix, ".png")) { | |
| 137 return "image/png"; | |
| 138 } | |
| 139 if (!strcmp(suffix, ".jpg") || !strcmp(suffix, ".jpeg")) { | |
| 140 return "image/jpeg"; | |
| 141 } | |
| 142 } | |
| 143 return "text/plain"; | |
| 144 } | |
| 145 | |
| 146 | |
| 147 // Return a malloc'd string from a format string and arguments. | |
| 148 intptr_t alloc_printf(char** result, const char* format, ...) { | |
| 149 va_list args; | |
| 150 va_start(args, format); | |
| 151 intptr_t len = vsnprintf(NULL, 0, format, args) + 1; | |
| 152 *result = reinterpret_cast<char*>(malloc(len)); | |
| 153 return vsnprintf(*result, len, format, args); | |
| 154 } | |
| 155 | |
| 156 | |
| 157 void writeResponse(intptr_t socket, const char* content_type, | |
| 158 const char* data, size_t length) { | |
| 159 char* header; | |
| 160 intptr_t len = alloc_printf(&header, | |
| 161 "HTTP/1.1 200 OK\nContent-Type: %s\nContent-Length: %"Pu"\n\n", | |
| 162 content_type, length); | |
| 163 Socket::Write(socket, header, len); | |
| 164 Socket::Write(socket, data, length); | |
| 165 Socket::Write(socket, "\n", 1); | |
| 166 free(header); | |
| 167 } | |
| 168 | |
| 169 | |
| 170 void writeErrorResponse(intptr_t socket, intptr_t error_num, const char* error, | |
| 171 const char* description) { | |
| 172 if (description != NULL) { | |
| 173 // Create body first, so its length is known when creating the header. | |
| 174 char* body; | |
| 175 intptr_t body_len = alloc_printf(&body, | |
| 176 "<html><head><title>%d %s</title></head>\n" | |
| 177 "<body>\n<h1>%s</h1>\n%s\n</body></html>\n", | |
| 178 error_num, error, error, description); | |
| 179 char* header; | |
| 180 intptr_t header_len = alloc_printf(&header, | |
| 181 "HTTP/1.1 %d %s\n" | |
| 182 "Content-Length: %d\n" | |
| 183 "Connection: close\n" | |
| 184 "Content-Type: text/html\n\n", | |
| 185 error_num, error, body_len); | |
| 186 Socket::Write(socket, header, header_len); | |
| 187 Socket::Write(socket, body, body_len); | |
| 188 free(header); | |
| 189 free(body); | |
| 190 } else { | |
| 191 char* response; | |
| 192 intptr_t len = | |
| 193 alloc_printf(&response, "HTTP/1.1 %d %s\n\n", error_num, error); | |
| 194 Socket::Write(socket, response, len); | |
| 195 free(response); | |
| 196 } | |
| 197 } | |
| 198 | |
| 199 | |
| 200 void VmStats::WebServer(uword bind_address) { | |
| 201 while (true) { | |
| 202 intptr_t socket = ServerSocket::Accept(bind_address); | |
| 203 if (socket == ServerSocket::kTemporaryFailure) { | |
| 204 // Not a real failure, woke up but no connection available. | |
| 205 | |
| 206 // Use MonitorLocker.Wait(), since it has finer granularity than sleep(). | |
| 207 dart::Monitor m; | |
| 208 MonitorLocker ml(&m); | |
| 209 ml.Wait(RETRY_PAUSE); | |
| 210 | |
| 211 continue; | |
| 212 } | |
| 213 if (socket < 0) { | |
| 214 // Stop() closed the socket. | |
| 215 return; | |
| 216 } | |
| 217 Socket::SetBlocking(socket); | |
| 218 | |
| 219 // TODO(tball): rewrite this to use STL, so as to eliminate the static | |
| 220 // buffer and support resource URLs that are longer than BUFSIZE. | |
| 221 | |
| 222 // Read request. | |
| 223 char buffer[BUFSIZE + 1]; | |
| 224 intptr_t len = Socket::Read(socket, buffer, BUFSIZE); | |
| 225 if (len <= 0) { | |
| 226 // Invalid HTTP request, ignore. | |
| 227 continue; | |
| 228 } | |
| 229 buffer[len] = '\0'; | |
| 230 | |
| 231 // Verify it's a GET request. | |
| 232 // TODO(tball): support POST requests. | |
| 233 if (strncmp("GET ", buffer, 4) != 0 && strncmp("get ", buffer, 4) != 0) { | |
| 234 Log::PrintErr("Unsupported HTTP request type"); | |
| 235 writeErrorResponse(socket, 403, "Forbidden", | |
| 236 "Unsupported HTTP request type"); | |
| 237 Socket::Close(socket); | |
| 238 continue; | |
| 239 } | |
| 240 | |
| 241 // Extract GET URL, and null-terminate URL in case request line has | |
| 242 // HTTP version. | |
| 243 for (int i = 4; i < len; i++) { | |
| 244 if (buffer[i] == ' ') { | |
| 245 buffer[i] = '\0'; | |
| 246 } | |
| 247 } | |
| 248 char* url = strdup(&buffer[4]); | |
| 249 | |
| 250 if (instance_->verbose_) { | |
| 251 Log::Print("vmstats: %s requested\n", url); | |
| 252 } | |
| 253 char* content = NULL; | |
| 254 | |
| 255 // Check for VmStats-specific URLs. | |
| 256 if (strcmp(url, "/isolates") == 0) { | |
| 257 content = instance_->IsolatesStatus(); | |
| 258 } else { | |
| 259 // Check plug-ins. | |
| 260 content = VmStatusService::GetVmStatus(url); | |
| 261 } | |
| 262 | |
| 263 if (content != NULL) { | |
| 264 writeResponse(socket, "application/json", content, strlen(content)); | |
| 265 free(content); | |
| 266 } else { | |
| 267 // No status content with this URL, return file or resource content. | |
| 268 dart::TextBuffer path(strlen(instance_->root_directory_) + strlen(url)); | |
| 269 path.AddString(instance_->root_directory_); | |
| 270 path.AddString(url); | |
| 271 | |
| 272 // Expand directory URLs. | |
| 273 if (strcmp(url, "/") == 0) { | |
| 274 path.AddString(VMSTATS_HTML); | |
| 275 } else if (url[strlen(url) - 1] == '/') { | |
| 276 path.AddString(INDEX_HTML); | |
| 277 } | |
| 278 | |
| 279 bool success = false; | |
| 280 char* text_buffer = NULL; | |
| 281 const char* content_type = ContentType(path.buf()); | |
| 282 if (File::Exists(path.buf())) { | |
| 283 File* f = File::Open(path.buf(), File::kRead); | |
| 284 if (f != NULL) { | |
| 285 intptr_t len = f->Length(); | |
| 286 text_buffer = reinterpret_cast<char*>(malloc(len)); | |
| 287 if (f->ReadFully(text_buffer, len)) { | |
| 288 writeResponse(socket, content_type, text_buffer, len); | |
| 289 success = true; | |
| 290 } | |
| 291 free(text_buffer); | |
| 292 delete f; | |
| 293 } | |
| 294 } else { | |
| 295 const char* resource; | |
| 296 intptr_t len = Resources::ResourceLookup(path.buf(), &resource); | |
| 297 if (len != Resources::kNoSuchInstance) { | |
| 298 ASSERT(len >= 0); | |
| 299 writeResponse(socket, content_type, resource, len); | |
| 300 success = true; | |
| 301 } | |
| 302 } | |
| 303 if (!success) { | |
| 304 char* description; | |
| 305 alloc_printf( | |
| 306 &description, "URL <a href=\"%s\">%s</a> not found.", url, url); | |
| 307 writeErrorResponse(socket, 404, "Not Found", description); | |
| 308 free(description); | |
| 309 } | |
| 310 } | |
| 311 Socket::Close(socket); | |
| 312 free(url); | |
| 313 } | |
| 314 | |
| 315 Shutdown(); | |
| 316 } | |
| 317 | |
| 318 | |
| 319 char* VmStats::IsolatesStatus() { | |
| 320 dart::TextBuffer text(64); | |
| 321 text.Printf("{\n\"isolates\": [\n"); | |
| 322 IsolateTable::iterator itr; | |
| 323 bool first = true; | |
| 324 for (itr = isolate_table_.begin(); itr != isolate_table_.end(); ++itr) { | |
| 325 Dart_Isolate isolate = itr->second; | |
| 326 static char request[512]; | |
| 327 snprintf(request, sizeof(request), | |
| 328 "/isolate/0x%"Px, | |
| 329 reinterpret_cast<intptr_t>(isolate)); | |
| 330 char* status = VmStatusService::GetVmStatus(request); | |
| 331 if (status != NULL) { | |
| 332 if (!first) { | |
| 333 text.AddString(",\n"); | |
| 334 } | |
| 335 text.AddString(status); | |
| 336 first = false; | |
| 337 free(status); | |
| 338 } | |
| 339 } | |
| 340 text.AddString("\n]\n}\n"); | |
| 341 return strdup(text.buf()); | |
| 342 } | |
| 343 | |
| 344 | |
| 345 // Advance the scanner to the value token of a specified name-value pair. | |
| 346 void SeekNamedValue(const char* name, dart::JSONScanner* scanner) { | |
| 347 while (!scanner->EOM()) { | |
| 348 scanner->Scan(); | |
| 349 if (scanner->IsStringLiteral(name)) { | |
| 350 scanner->Scan(); | |
| 351 ASSERT(scanner->CurrentToken() == dart::JSONScanner::TokenColon); | |
| 352 scanner->Scan(); | |
| 353 return; | |
| 354 } | |
| 355 } | |
| 356 } | |
| 357 | |
| 358 | |
| 359 // Windows doesn't have strndup(), so this is a simple, private version. | |
| 360 static char* StrNDup(const char* s, uword len) { | |
| 361 if (strlen(s) < len) { | |
| 362 len = strlen(s); | |
| 363 } | |
| 364 char* result = reinterpret_cast<char*>(malloc(len + 1)); | |
| 365 memmove(result, s, len); | |
| 366 result[len + 1] = '\0'; | |
| 367 return result; | |
| 368 } | |
| 369 | |
| 370 | |
| 371 void VmStats::DumpStackThread(uword unused) { | |
| 372 Log::Print("Isolate dump:\n"); | |
| 373 IsolateTable::iterator itr; | |
| 374 MonitorLocker ml(instance_monitor_); | |
| 375 for (itr = instance_->isolate_table_.begin(); | |
| 376 itr != instance_->isolate_table_.end(); ++itr) { | |
| 377 Dart_Isolate isolate = itr->second; | |
| 378 | |
| 379 // Print isolate name and details. | |
| 380 static char buffer[512]; | |
| 381 snprintf(buffer, sizeof(buffer), | |
| 382 "/isolate/0x%"Px, reinterpret_cast<intptr_t>(isolate)); | |
| 383 char* isolate_details = VmStatusService::GetVmStatus(buffer); | |
| 384 if (isolate_details != NULL) { | |
| 385 dart::JSONScanner scanner(isolate_details); | |
| 386 SeekNamedValue("name", &scanner); | |
| 387 char* name = StrNDup(scanner.TokenChars(), scanner.TokenLen()); | |
| 388 SeekNamedValue("port", &scanner); | |
| 389 char* port = StrNDup(scanner.TokenChars(), scanner.TokenLen()); | |
| 390 Log::Print("\"%s\" port=%s\n", name, port); | |
| 391 free(isolate_details); | |
| 392 free(port); | |
| 393 free(name); | |
| 394 } | |
| 395 | |
| 396 // Print stack trace. | |
| 397 snprintf(buffer, sizeof(buffer), | |
| 398 "/isolate/0x%"Px"/stacktrace", | |
| 399 reinterpret_cast<intptr_t>(isolate)); | |
| 400 char* trace = VmStatusService::GetVmStatus(buffer); | |
| 401 if (trace != NULL) { | |
| 402 dart::JSONScanner scanner(trace); | |
| 403 while (true) { | |
| 404 SeekNamedValue("url", &scanner); | |
| 405 if (scanner.CurrentToken() == dart::JSONScanner::TokenEOM) { | |
| 406 break; | |
| 407 } | |
| 408 char* url = StrNDup(scanner.TokenChars(), scanner.TokenLen()); | |
| 409 SeekNamedValue("line", &scanner); | |
| 410 char* line = StrNDup(scanner.TokenChars(), scanner.TokenLen()); | |
| 411 SeekNamedValue("function", &scanner); | |
| 412 char* function = StrNDup(scanner.TokenChars(), scanner.TokenLen()); | |
| 413 Log::Print(" at %s(%s:%s)\n", function, url, line); | |
| 414 free(url); | |
| 415 free(line); | |
| 416 free(function); | |
| 417 } | |
| 418 free(trace); | |
| 419 } | |
| 420 } | |
| 421 } | |
| 422 | |
| 423 | |
| 424 void VmStats::DumpStack() { | |
| 425 int err = dart::Thread::Start(DumpStackThread, 0); | |
| 426 if (err != 0) { | |
| 427 Log::PrintErr("Failed starting VmStats stackdump thread: %d\n", err); | |
| 428 Shutdown(); | |
| 429 } | |
| 430 } | |
| 431 | |
| 432 | |
| 433 // Global static pointer used to ensure a single instance of the class. | |
| 434 VmStatusService* VmStatusService::instance_ = NULL; | |
| 435 | |
| 436 | |
| 437 void VmStatusService::InitOnce() { | |
| 438 ASSERT(VmStatusService::instance_ == NULL); | |
| 439 VmStatusService::instance_ = new VmStatusService(); | |
| 440 VmStatusService::mutex_ = new dart::Mutex(); | |
| 441 | |
| 442 // Register built-in status plug-ins. RegisterPlugin is not used because | |
| 443 // this isn't called within an isolate, and because parameter checking | |
| 444 // isn't necessary. | |
| 445 instance_->RegisterPlugin(&Dart_GetVmStatus); | |
| 446 | |
| 447 // TODO(tball): dynamically load any additional plug-ins. | |
| 448 } | |
| 449 | |
| 450 | |
| 451 int VmStatusService::RegisterPlugin(Dart_VmStatusCallback callback) { | |
| 452 ASSERT(VmStatusService::instance_ != NULL); | |
| 453 ASSERT(VmStatusService::mutex_ != NULL); | |
| 454 MutexLocker ml(mutex_); | |
| 455 if (callback == NULL) { | |
| 456 return -1; | |
| 457 } | |
| 458 VmStatusPlugin* plugin = new VmStatusPlugin(callback); | |
| 459 VmStatusPlugin* list = instance_->registered_plugin_list_; | |
| 460 if (list == NULL) { | |
| 461 instance_->registered_plugin_list_ = plugin; | |
| 462 } else { | |
| 463 list->Append(plugin); | |
| 464 } | |
| 465 return 0; | |
| 466 } | |
| 467 | |
| 468 | |
| 469 char* VmStatusService::GetVmStatus(const char* request) { | |
| 470 ASSERT(VmStatusService::instance_ != NULL); | |
| 471 VmStatusPlugin* plugin = instance_->registered_plugin_list_; | |
| 472 while (plugin != NULL) { | |
| 473 char* result = (plugin->callback())(request); | |
| 474 if (result != NULL) { | |
| 475 return result; | |
| 476 } | |
| 477 plugin = plugin->next(); | |
| 478 } | |
| 479 return NULL; | |
| 480 } | |
| 481 | |
| 482 } // namespace bin | |
| 483 } // namespace dart | |
| OLD | NEW |