OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
siva
2013/02/09 01:00:57
2013
Tom Ball
2013/02/14 23:45:16
Done.
| |
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.h" | |
6 | |
7 #include "bin/fdutils.h" | |
8 #include "bin/file.h" | |
9 #include "bin/platform.h" | |
10 #include "bin/socket.h" | |
11 #include "platform/json.h" | |
12 #include "platform/thread.h" | |
13 | |
14 namespace dart { | |
15 | |
16 #define BUFSIZE 8192 | |
17 | |
18 // Global static pointer used to ensure a single instance of the class. | |
19 VmStats* VmStats::instance_ = NULL; | |
20 | |
21 | |
22 void VmStats::Start(int port) { | |
23 if (instance_ != NULL) { | |
24 FATAL("VmStats already started."); | |
25 } | |
26 VmStats* vmstats = new VmStats; | |
27 Socket::Initialize(); | |
28 | |
29 // TODO(tball): replace $HOME with SDK-specific path to web server's | |
30 // root directory. | |
31 intptr_t num_vars; | |
32 char** env = Platform::Environment(&num_vars); | |
siva
2013/02/09 01:00:57
Are you going to switch this to be a command line
Tom Ball
2013/02/14 23:45:16
Done. I added a flag that optionally sets root dir
| |
33 for (int i = 0; i < num_vars; i++) { | |
34 char* var = env[i]; | |
35 if (strncmp(var, "HOME=", 5) == 0) { | |
36 char* home = var + 5; | |
37 ASSERT(strlen(home) > 0); | |
38 vmstats->root_directory_ = home; | |
39 vmstats->root_directory_.append("/vmstats"); | |
40 break; | |
41 } | |
42 } | |
43 Platform::FreeEnvironment(env, num_vars); | |
44 ASSERT(vmstats->root_directory_.length() > 0); | |
45 | |
46 const intptr_t BACKLOG = 128; // Default value from HttpServer.dart | |
47 intptr_t bind_address = | |
48 ServerSocket::CreateBindListen("127.0.0.1", port, BACKLOG); | |
siva
2013/02/09 01:00:57
fine for now but we may want to make 127.0.0.1 a p
Tom Ball
2013/02/14 23:45:16
Sounds great -- any security issues with making th
| |
49 if (bind_address < 0) { | |
50 fprintf(stderr, "Failed binding VmStats socket: 127.0.0.1:%d\n", port); | |
51 return; | |
52 } | |
53 int errno = Thread::Start(WebServer, bind_address); | |
54 if (errno != 0) { | |
55 fprintf(stderr, "Failed starting VmStats thread: %d\n", errno); | |
56 return; | |
57 } | |
58 instance_ = vmstats; | |
59 } | |
60 | |
61 | |
62 void VmStats::Stop() { | |
63 if (instance_ != NULL) { | |
64 delete instance_; | |
65 instance_ = NULL; | |
66 } | |
67 } | |
68 | |
69 | |
70 | |
71 void VmStats::AddIsolate(IsolateData* isolate_data, | |
72 Dart_Isolate isolate) { | |
siva
2013/02/09 01:00:57
.... indentation.
Tom Ball
2013/02/14 23:45:16
Done.
| |
73 if (instance_ != NULL) { | |
74 instance_->isolate_table_[isolate_data] = isolate; | |
siva
2013/02/11 19:14:55
This method needs to be thread safe right as we ca
Tom Ball
2013/02/14 23:45:16
Done.
| |
75 } | |
76 } | |
77 | |
78 | |
79 void VmStats::RemoveIsolate(IsolateData* isolate_data) { | |
siva
2013/02/11 19:14:55
Ditto comment about this method being thread safe.
Tom Ball
2013/02/14 23:45:16
Done.
| |
80 if (instance_ != NULL) { | |
81 instance_->isolate_table_.erase(isolate_data); | |
82 } | |
83 } | |
84 | |
85 | |
86 static bool endsWith(const std::string s, const char* ending) { | |
87 size_t ending_len = strlen(ending); | |
88 if (s.length() > ending_len) { | |
89 return s.compare(s.length() - ending_len, ending_len, ending) == 0; | |
90 } else { | |
91 return false; | |
92 } | |
93 } | |
94 | |
95 | |
96 static const char* ContentType(const char* url) { | |
97 // TODO(tball): replace with static map? | |
98 std::string path(url); | |
99 if (endsWith(path, ".html")) { | |
100 return "text/html; charset=UTF-8"; | |
101 } | |
102 if (endsWith(path, ".dart")) { | |
103 return "application/dart; charset=UTF-8"; | |
104 } | |
105 if (endsWith(path, ".js")) { | |
106 return "application/javascript; charset=UTF-8"; | |
107 } | |
108 if (endsWith(path, ".css")) { | |
109 return "text/css; charset=UTF-8"; | |
110 } | |
111 if (endsWith(path, ".gif")) { | |
112 return "image/gif"; | |
113 } | |
114 if (endsWith(path, ".png")) { | |
115 return "image/png"; | |
116 } | |
117 if (endsWith(path, ".jpg") || endsWith(path, ".jpeg")) { | |
118 return "image/jpeg"; | |
119 } | |
120 return "text/plain"; | |
121 } | |
122 | |
123 | |
124 void VmStats::WebServer(uword bind_address) { | |
125 while (true) { | |
126 intptr_t socket = ServerSocket::Accept(bind_address); | |
127 if (socket == ServerSocket::kTemporaryFailure) { | |
128 // Not a real failure, woke up but no connection available. | |
129 sleep(1); | |
130 continue; | |
131 } | |
132 if (socket < 0) { | |
133 FATAL("Accepting new web server connection failed.\n"); | |
134 } | |
135 FDUtils::SetBlocking(socket); | |
136 | |
137 // Read request. | |
138 static char buffer[BUFSIZE + 1]; | |
139 int len = read(socket, buffer, BUFSIZE); | |
siva
2013/02/09 01:00:57
How do you deal with partial reads here
(e.g: the
| |
140 if (len <= 0) { | |
141 // Invalid HTTP request, ignore. | |
142 continue; | |
143 } | |
144 buffer[len] = '\0'; | |
145 | |
146 // Verify it's a GET request. | |
147 if (strncmp("GET ", buffer, 4) != 0 && strncmp("get ", buffer, 4) != 0) { | |
148 fprintf(stderr, "Unsupported HTTP request type"); | |
149 const char* response = "HTTP/1.1 403 Forbidden\n" | |
150 "Content-Length: 120\n" | |
151 "Connection: close\n" | |
152 "Content-Type: text/html\n\n" | |
153 "<html><head>\n<title>403 Forbidden</title>\n</head>" | |
154 "<body>\n<h1>Forbidden</h1>\nUnsupported HTTP request type\n</body>" | |
155 "</html>\n"; | |
156 Socket::Write(socket, response, strlen(response)); | |
siva
2013/02/09 01:00:57
In all these writes I would assert that the return
| |
157 close(socket); | |
158 continue; | |
159 } | |
160 | |
161 // Extract GET URL. | |
162 for (int i = 4; i < len; i++) { | |
163 if (buffer[i] == ' ') { | |
164 buffer[i] = '\0'; | |
165 break; | |
166 } | |
167 } | |
168 char* url = &buffer[4]; | |
169 printf("vmstats: %s requested\n", url); | |
170 // TODO(tball): parse URL to determine data requested. | |
171 if (strcmp(url, "/isolates") == 0) { | |
172 std::ostringstream stream; | |
173 stream << '{' << std::endl; | |
174 instance_->MemoryUsed(&stream); | |
siva
2013/02/09 01:00:57
Instead of MemoryUsed you could probably call this
Tom Ball
2013/02/14 23:45:16
Done.
| |
175 // TODO(tball): add other metrics, separated by commas. | |
176 stream << std::endl << '}' << std::endl; | |
177 std::string content = stream.str(); | |
178 | |
179 len = snprintf(buffer, BUFSIZE-1, | |
180 "HTTP/1.1 200 OK\n:Content-Type: application/json; charset=UTF-8\n" | |
181 "Content-Length: %d\n\n", static_cast<int>(content.length())); | |
182 Socket::Write(socket, buffer, strlen(buffer)); | |
183 Socket::Write(socket, content.c_str(), content.length()); | |
184 Socket::Write(socket, "\n", 1); | |
185 Socket::Write(socket, buffer, strlen(buffer)); | |
186 } else { | |
siva
2013/02/09 01:00:57
I assume this else section is present for serving
Tom Ball
2013/02/14 23:45:16
Done.
| |
187 std::string path(instance_->root_directory_); | |
188 path.append(url); | |
189 bool success = false; | |
190 if (File::Exists(path.c_str())) { | |
191 File* f = File::Open(path.c_str(), File::kRead); | |
192 if (f != NULL) { | |
193 intptr_t len = f->Length(); | |
194 char* text_buffer = reinterpret_cast<char*>(malloc(len)); | |
195 if (f->ReadFully(text_buffer, len)) { | |
196 std::string content(text_buffer, len); | |
197 const char* content_type = ContentType(url); | |
198 len = snprintf(buffer, BUFSIZE-1, | |
199 "HTTP/1.1 200 OK\n:Content-Type: %s\n" | |
200 "Content-Length: %d\n\n", | |
201 content_type, static_cast<int>(content.length())); | |
202 Socket::Write(socket, buffer, strlen(buffer)); | |
203 Socket::Write(socket, content.c_str(), content.length()); | |
204 Socket::Write(socket, "\n", 1); | |
205 success = true; | |
206 } | |
207 free(text_buffer); | |
208 delete f; | |
209 } | |
210 } | |
211 if (!success) { | |
212 const char* response = "HTTP/1.1 404 Not Found\n\n"; | |
213 Socket::Write(socket, response, strlen(response)); | |
214 } | |
215 } | |
216 close(socket); | |
217 } | |
218 } | |
219 | |
220 | |
221 void VmStats::MemoryUsed(std::ostringstream* stream) { | |
222 *stream << "\"isolates\": [" << std::endl; | |
223 IsolateTable::iterator itr; | |
224 bool first = true; | |
225 for (itr = isolate_table_.begin(); itr != isolate_table_.end(); ++itr) { | |
226 if (!first) { | |
227 *stream << "," << std::endl; | |
228 } | |
229 first = false; | |
230 | |
231 Dart_Isolate isolate = itr->second; | |
232 static char request[512]; | |
233 snprintf(request, sizeof(request), "/isolate/0x%llx", | |
234 reinterpret_cast<int64_t>(isolate)); | |
235 char* status = Dart_GetVmStatus(request); | |
236 *stream << status; | |
237 free(status); | |
238 } | |
239 *stream << std::endl << "]"; | |
240 } | |
241 | |
242 | |
243 } // namespace dart | |
OLD | NEW |