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