Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Provide html status as well as /folve-status.html

First of all, this is neat, but second, this is needed for
stress-testing.
  • Loading branch information...
commit 30b49d68233b5106889259b357a03270fc2da1da 1 parent fea3d55
Henner Zeller authored

Showing 3 changed files with 144 additions and 64 deletions. Show diff stats Hide diff stats

  1. +29 5 folve-main.cc
  2. +101 55 status-server.cc
  3. +14 4 status-server.h
34 folve-main.cc
@@ -39,17 +39,20 @@
39 39 #include "status-server.h"
40 40 #include "util.h"
41 41
  42 +static const char kStatusFileName[] = "/folve-status.html";
  43 +
42 44 // Compilation unit variables to communicate with the fuse callbacks.
43 45 static struct FolveRuntime {
44 46 FolveRuntime() : fs(NULL), mount_point(NULL),
45 47 status_port(-1), refresh_time(10), parameter_error(false),
46   - readdir_dump_file(NULL) {}
  48 + readdir_dump_file(NULL), status_server(NULL) {}
47 49 FolveFilesystem *fs;
48 50 const char *mount_point;
49 51 int status_port;
50 52 int refresh_time;
51 53 bool parameter_error;
52 54 FILE *readdir_dump_file;
  55 + StatusServer *status_server;
53 56 } folve_rt;
54 57
55 58 // Logger that only prints to stderr; used for
@@ -104,6 +107,12 @@ static const char *assemble_orig_path(char *buf, const char *path) {
104 107 // Essentially lstat(). Just forward to the original filesystem (this
105 108 // will by lying: our convolved files are of different size...)
106 109 static int folve_getattr(const char *path, struct stat *stbuf) {
  110 + if (folve_rt.status_server && strcmp(path, kStatusFileName) == 0) {
  111 + FileHandler *status = folve_rt.status_server->CreateStatusFileHandler();
  112 + status->Stat(stbuf);
  113 + delete status;
  114 + return 0;
  115 + }
107 116 // If this is a currently open filename, we might be able to output a better
108 117 // estimate.
109 118 int result = folve_rt.fs->StatByFilename(path, stbuf);
@@ -127,6 +136,12 @@ static int folve_getattr(const char *path, struct stat *stbuf) {
127 136 // readdir(). Just forward to original filesystem.
128 137 static int folve_readdir(const char *path, void *buf, fuse_fill_dir_t filler,
129 138 off_t offset, struct fuse_file_info *fi) {
  139 + if (folve_rt.status_server && strcmp(path, "/") == 0) {
  140 + struct stat st;
  141 + memset(&st, 0, sizeof(st));
  142 + filler(buf, kStatusFileName + 1, &st, 0);
  143 + }
  144 +
130 145 DIR *dp;
131 146 struct dirent *de;
132 147 char path_buf[PATH_MAX];
@@ -172,6 +187,11 @@ static int folve_readlink(const char *path, char *buf, size_t size) {
172 187 }
173 188
174 189 static int folve_open(const char *path, struct fuse_file_info *fi) {
  190 + if (folve_rt.status_server && strcmp(path, kStatusFileName) == 0) {
  191 + fi->fh = (uint64_t) folve_rt.status_server->CreateStatusFileHandler();
  192 + return 0;
  193 + }
  194 +
175 195 // We want to be allowed to only return part of the requested data in read().
176 196 // That way, we can separate reading the ID3-tags from
177 197 // decoding of the music stream - that way indexing should be fast.
@@ -194,7 +214,11 @@ static int folve_read(const char *path, char *buf, size_t size, off_t offset,
194 214 }
195 215
196 216 static int folve_release(const char *path, struct fuse_file_info *fi) {
197   - folve_rt.fs->Close(path, reinterpret_cast<FileHandler *>(fi->fh));
  217 + if (folve_rt.status_server && strcmp(path, kStatusFileName) == 0) {
  218 + delete reinterpret_cast<FileHandler *>(fi->fh);
  219 + } else {
  220 + folve_rt.fs->Close(path, reinterpret_cast<FileHandler *>(fi->fh));
  221 + }
198 222 return 0;
199 223 }
200 224
@@ -219,11 +243,11 @@ static void *folve_init(struct fuse_conn_info *conn) {
219 243
220 244 if (folve_rt.status_port > 0) {
221 245 // Need to start status server after we're daemonized.
222   - StatusServer *status_server = new StatusServer(folve_rt.fs);
223   - if (status_server->Start(folve_rt.status_port)) {
  246 + folve_rt.status_server = new StatusServer(folve_rt.fs);
  247 + if (folve_rt.status_server->Start(folve_rt.status_port)) {
224 248 syslog(LOG_INFO, "HTTP status server on port %d; refresh=%d",
225 249 folve_rt.status_port, folve_rt.refresh_time);
226   - status_server->set_meta_refresh(folve_rt.refresh_time);
  250 + folve_rt.status_server->set_meta_refresh(folve_rt.refresh_time);
227 251 } else {
228 252 syslog(LOG_ERR, "Couldn't start HTTP server on port %d\n",
229 253 folve_rt.status_port);
156 status-server.cc
@@ -59,7 +59,8 @@ static const char kStartHtmlHeader[] = "<html><head>"
59 59 "href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2"
60 60 "AAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9wJDwUlEA/UBrsA"
61 61 "AABSSURBVCjPrZIxDgAgDAKh8f9froOTirU1ssKFYqS7Q4mktAxFRQDJcsPORMDYsDCXhn331"
62   - "9GPwHJVuaFl3l4D1+h0UjIdbTh9SpP2KQ2AgSfVAdEQGx23tOopAAAAAElFTkSuQmCC'/>\n";
  62 + "9GPwHJVuaFl3l4D1+h0UjIdbTh9SpP2KQ2AgSfVAdEQGx23tOopAAAAAElFTkSuQmCC'/>\n"
  63 + "<meta http-equiv='Content-Type' content='text/html'; charset='utf-8'>\n";
63 64
64 65 // TODO: someone with a bit more stylesheet-fu can attempt to make this
65 66 // more pretty and the HTML more compact.
@@ -100,6 +101,38 @@ static const char kCSS[] =
100 101 " -moz-border-radius: 3px; }\n" // format box
101 102 "</style>";
102 103
  104 +class StatusServer::HtmlFileHandler : public FileHandler {
  105 +public:
  106 + HtmlFileHandler(StatusServer *server) : FileHandler("") {
  107 + server->CreatePage(false, &content_);
  108 + memset(&stat_, 0, sizeof(stat_));
  109 + stat_.st_size = content_.length();
  110 + stat_.st_mtime = time(NULL);
  111 + stat_.st_nlink = 1;
  112 + stat_.st_mode = 0100444; // regular file. readonly.
  113 + }
  114 +
  115 + virtual int Read(char *buf, size_t size, off_t offset) {
  116 + int end = size + offset;
  117 + if (end > (int) content_.length()) end = content_.length();
  118 + if (offset >= end) return 0;
  119 + memcpy(buf, content_.data() + offset, end - offset);
  120 + return end - offset;
  121 + }
  122 +
  123 + virtual int Stat(struct stat *st) {
  124 + *st = stat_;
  125 + return 0;
  126 + }
  127 +
  128 + // Get handler status.
  129 + virtual void GetHandlerStatus(HandlerStats *s) {}
  130 +
  131 +private:
  132 + std::string content_;
  133 + struct stat stat_;
  134 +};
  135 +
103 136 // Callback function called by micro http daemon. Gets the StatusServer pointer
104 137 // in the user_argument.
105 138 int StatusServer::HandleHttp(void* user_argument,
@@ -120,7 +153,7 @@ int StatusServer::HandleHttp(void* user_argument,
120 153 MHD_add_response_header(response, "Location", "/");
121 154 ret = MHD_queue_response(connection, 302, response);
122 155 } else {
123   - const std::string &page = server->CreatePage();
  156 + const std::string &page = server->CreateHttpPage();
124 157 response = MHD_create_response_from_data(page.length(), (void*) page.data(),
125 158 MHD_NO, MHD_NO);
126 159 MHD_add_response_header(response, "Content-Type",
@@ -141,6 +174,10 @@ StatusServer::StatusServer(FolveFilesystem *fs)
141 174 fs->handler_cache()->SetObserver(this);
142 175 }
143 176
  177 +FileHandler *StatusServer::CreateStatusFileHandler() {
  178 + return new HtmlFileHandler(this);
  179 +}
  180 +
144 181 void StatusServer::SetFilter(const char *filter) {
145 182 if (filter == NULL) return;
146 183 filter_switched_ = filesystem_->SwitchCurrentConfigDir(filter);
@@ -219,8 +256,9 @@ static void AppendSanitizedHTML(const std::string &in, std::string *out) {
219 256
220 257 void StatusServer::AppendFileInfo(const char *progress_access_color,
221 258 const char *progress_buffer_color,
222   - const HandlerStats &stats) {
223   - content_.append("<tr>");
  259 + const HandlerStats &stats,
  260 + std::string *out) {
  261 + out->append("<tr>");
224 262 const char *status = "";
225 263 switch (stats.status) {
226 264 case HandlerStats::OPEN: status = "open"; break;
@@ -230,16 +268,16 @@ void StatusServer::AppendFileInfo(const char *progress_access_color,
230 268 }
231 269
232 270 if (!stats.message.empty()) {
233   - Appendf(&content_, sMessageRowHtml, status, stats.message.c_str());
  271 + Appendf(out, sMessageRowHtml, status, stats.message.c_str());
234 272 } else if (stats.access_progress == 0 && stats.buffer_progress <= 0) {
235 273 // TODO(hzeller): we really need a way to display message and progress
236 274 // bar in parallel.
237   - Appendf(&content_, sMessageRowHtml, status, "Only header accessed");
  275 + Appendf(out, sMessageRowHtml, status, "Only header accessed");
238 276 } else {
239 277 float accessed = stats.access_progress;
240 278 float buffered
241 279 = stats.buffer_progress > accessed ? stats.buffer_progress - accessed : 0;
242   - Appendf(&content_, sProgressRowHtml, status,
  280 + Appendf(out, sProgressRowHtml, status,
243 281 stats.in_gapless ? "&rarr;" : "",
244 282 (int) (kProgressWidth * accessed), progress_access_color,
245 283 (int) (kProgressWidth * buffered), progress_buffer_color,
@@ -248,31 +286,32 @@ void StatusServer::AppendFileInfo(const char *progress_access_color,
248 286 const int secs = stats.duration_seconds;
249 287 const int fract_sec = stats.access_progress * secs;
250 288 if (secs >= 0 && fract_sec >= 0) {
251   - Appendf(&content_, sTimeColumns,
  289 + Appendf(out, sTimeColumns,
252 290 fract_sec / 60, fract_sec % 60,
253 291 secs / 60, secs % 60);
254 292 } else {
255   - content_.append("<td colspan='3'>-</td>");
  293 + out->append("<td colspan='3'>-</td>");
256 294 }
257 295
258 296 if (stats.max_output_value > 1e-6) {
259   - Appendf(&content_, sDecibelColumn,
  297 + Appendf(out, sDecibelColumn,
260 298 stats.max_output_value > 1.0 ? " style='background:#FF8080;'" : "",
261 299 20 * log10f(stats.max_output_value));
262 300 } else {
263   - content_.append("<td>-</td>");
  301 + out->append("<td>-</td>");
264 302 }
265 303
266 304 const char *filter_dir = stats.filter_dir.empty()
267 305 ? "Pass Through" : stats.filter_dir.c_str();
268   - Appendf(&content_, "<td class='fb'>&nbsp;%s (", stats.format.c_str());
269   - AppendSanitizedHTML(filter_dir, &content_);
270   - content_.append(")&nbsp;</td><td class='fn'>");
271   - AppendSanitizedHTML(stats.filename, &content_);
272   - content_.append("</td></tr>\n");
  306 + Appendf(out, "<td class='fb'>&nbsp;%s (", stats.format.c_str());
  307 + AppendSanitizedHTML(filter_dir, out);
  308 + out->append(")&nbsp;</td><td class='fn'>");
  309 + AppendSanitizedHTML(stats.filename, out);
  310 + out->append("</td></tr>\n");
273 311 }
274 312
275   -static void CreateSelection(const std::set<std::string> &options,
  313 +static void CreateSelection(bool for_http,
  314 + const std::set<std::string> &options,
276 315 const std::string &selected,
277 316 std::string *result) {
278 317 if (options.size() == 1) {
@@ -287,7 +326,7 @@ static void CreateSelection(const std::set<std::string> &options,
287 326 result->append("<span class='filter_sel active'>");
288 327 AppendSanitizedHTML(title, result);
289 328 result->append("</span>");
290   - } else {
  329 + } else if (for_http) { // only provide links if we have this in http.
291 330 Appendf(result, "<a class='filter_sel inactive' href='%s?f=",
292 331 kSettingsUrl);
293 332 AppendSanitizedUrlParam(*it, result);
@@ -298,21 +337,20 @@ static void CreateSelection(const std::set<std::string> &options,
298 337 }
299 338 }
300 339
301   -void StatusServer::AppendSettingsForm() {
302   - content_.append("<p><span class='filter_sel'>Active filter:</span>");
  340 +void StatusServer::AppendSettingsForm(bool for_http, std::string *out) {
  341 + out->append("<p><span class='filter_sel'>Active filter:</span>");
303 342 std::set<std::string> available_dirs = filesystem_->GetAvailableConfigDirs();
304   - CreateSelection(available_dirs,
305   - filesystem_->current_config_subdir(),
306   - &content_);
  343 + CreateSelection(for_http,
  344 + available_dirs, filesystem_->current_config_subdir(), out);
307 345 if (available_dirs.empty() == 1) {
308   - content_.append(" (This is a boring configuration, add filter directories)");
  346 + out->append(" (This is a boring configuration, add filter directories)");
309 347 } else if (filter_switched_) {
310   - content_.append("<span class='rounded_box' "
311   - "style='font-size:small;background:#FFFFa0;'>"
312   - "Affects re- or newly opened files.</span>");
  348 + out->append("<span class='rounded_box' "
  349 + "style='font-size:small;background:#FFFFa0;'>"
  350 + "Affects re- or newly opened files.</span>");
313 351 filter_switched_ = false; // only show once.
314 352 }
315   - content_.append("</p><hr style='clear:both;'/>");
  353 + out->append("</p><hr style='clear:both;'/>");
316 354 }
317 355
318 356 struct CompareStats {
@@ -323,57 +361,64 @@ struct CompareStats {
323 361 }
324 362 };
325 363
326   -const std::string &StatusServer::CreatePage() {
  364 +
  365 +const std::string &StatusServer::CreateHttpPage() {
  366 + CreatePage(true, &http_content_);
  367 + return http_content_;
  368 +}
  369 +
  370 +void StatusServer::CreatePage(bool for_http, std::string *content) {
327 371 const double start = folve::CurrentTime();
328 372 // We re-use a string to avoid re-allocing memory every time we generate
329 373 // a page. Since we run with MHD_USE_SELECT_INTERNALLY, this is only accessed
330 374 // by one thread.
331   - content_.clear();
332   - content_.append(kStartHtmlHeader);
333   - if (meta_refresh_time_ > 0) {
334   - Appendf(&content_, "<meta http-equiv='refresh' content='%d'>\n",
  375 + content->clear();
  376 + content->append(kStartHtmlHeader);
  377 + if (for_http && meta_refresh_time_ > 0) {
  378 + Appendf(content, "<meta http-equiv='refresh' content='%d'>\n",
335 379 meta_refresh_time_);
336 380 }
337   - content_.append(kCSS);
338   - content_.append("</head>\n");
  381 + content->append(kCSS);
  382 + content->append("</head>\n");
339 383
340   - content_.append("<body>\n");
341   - Appendf(&content_, "<center style='background-color:#A0FFA0;'>"
  384 + content->append("<body>\n");
  385 + Appendf(content, "<center style='background-color:#A0FFA0;'>"
342 386 "Welcome to "
343 387 "<a href='https://github.com/hzeller/folve#readme'>Folve</a> "
344 388 FOLVE_VERSION "</center>\n");
345 389 if (show_details()) {
346   - Appendf(&content_, "Convolving audio files from <code>%s</code>; "
  390 + Appendf(content, "Convolving audio files from <code>%s</code>; "
347 391 "Filter base directory <code>%s</code>\n",
348 392 filesystem_->underlying_dir().c_str(),
349 393 filesystem_->base_config_dir().c_str());
350 394 }
351   - AppendSettingsForm();
  395 +
  396 + AppendSettingsForm(for_http, content);
352 397
353 398 std::vector<HandlerStats> stat_list;
354 399 filesystem_->handler_cache()->GetStats(&stat_list);
355 400
356 401 if (show_details()) {
357   - Appendf(&content_, "Total opening files <b>%d</b> "
  402 + Appendf(content, "Total opening files <b>%d</b> "
358 403 ".. and re-opened from recency cache <b>%d</b><br/>",
359 404 filesystem_->total_file_openings(),
360 405 filesystem_->total_file_reopen());
361 406 }
362 407
363   - Appendf(&content_, "<h3>Accessed Recently</h3>\n%zd in recency cache.\n",
  408 + Appendf(content, "<h3>Accessed Recently</h3>\n%zd in recency cache.\n",
364 409 stat_list.size());
365 410
366 411 if (filesystem_->gapless_processing()) {
367   - content_.append("Gapless transfers indicated with '&rarr;'.\n");
  412 + content->append("Gapless transfers indicated with '&rarr;'.\n");
368 413 }
369 414 if (filesystem_->pre_buffer_size() > 0) {
370   - Appendf(&content_,
  415 + Appendf(content,
371 416 "Accessed: <span class='lbox' style='background:%s;'>&nbsp;</span> "
372 417 "Buffered: <span class='lbox' style='background:%s;'>&nbsp;</span>",
373 418 kActiveAccessProgress, kActiveBufferProgress);
374 419 }
375   - content_.append("<table>\n");
376   - Appendf(&content_, "<tr><th>Stat</th><td><!--gapless in--></td>"
  420 + content->append("<table>\n");
  421 + Appendf(content, "<tr><th>Stat</th><td><!--gapless in--></td>"
377 422 "<th width='%dpx'>Progress</th>" // progress bar.
378 423 "<td><!-- gapless out --></td>"
379 424 "<th>Pos</th><td></td><th>Len</th><th>Max&nbsp;out</th>"
@@ -388,27 +433,29 @@ const std::string &StatusServer::CreatePage() {
388 433 CompareStats comparator;
389 434 std::sort(stat_ptrs.begin(), stat_ptrs.end(), comparator);
390 435 for (size_t i = 0; i < stat_ptrs.size(); ++i) {
391   - AppendFileInfo(kActiveAccessProgress, kActiveBufferProgress, *stat_ptrs[i]);
  436 + AppendFileInfo(kActiveAccessProgress, kActiveBufferProgress, *stat_ptrs[i],
  437 + content);
392 438 }
393   - content_.append("</table><hr/>\n");
  439 + content->append("</table><hr/>\n");
394 440
395 441 if (retired_.size() > 0) {
396   - content_.append("<h3>Retired</h3>\n");
397   - content_.append("<table>\n");
  442 + content->append("<h3>Retired</h3>\n");
  443 + content->append("<table>\n");
398 444 folve::MutexLock l(&retired_mutex_);
399 445 for (RetiredList::const_iterator it = retired_.begin();
400 446 it != retired_.end(); ++it) {
401   - AppendFileInfo(kRetiredAccessProgress, kRetiredBufferProgress, *it);
  447 + AppendFileInfo(kRetiredAccessProgress, kRetiredBufferProgress, *it,
  448 + content);
402 449 }
403   - content_.append("</table>\n");
  450 + content->append("</table>\n");
404 451 if (expunged_retired_ > 0) {
405   - Appendf(&content_, "... (%d more)<p></p>", expunged_retired_);
  452 + Appendf(content, "... (%d more)<p></p>", expunged_retired_);
406 453 }
407   - content_.append("<hr/>");
  454 + content->append("<hr/>");
408 455 }
409 456
410 457 const double duration = folve::CurrentTime() - start;
411   - Appendf(&content_,
  458 + Appendf(content,
412 459 "<span style='float:left;font-size:x-small;'>%.2fms</span>"
413 460 "<span style='float:right;font-size:x-small;'>"
414 461 "&copy; 2012 Henner Zeller"
@@ -416,5 +463,4 @@ const std::string &StatusServer::CreatePage() {
416 463 " | Conveyed under the terms of the "
417 464 "<a href='http://www.gnu.org/licenses/gpl.html'>GPLv3</a>.</span>"
418 465 "</body></html>\n", duration * 1000.0);
419   - return content_;
420 466 }
18 status-server.h
@@ -39,23 +39,33 @@ class StatusServer : protected FileHandlerCache::Observer {
39 39 // Start server, listing on given port.
40 40 bool Start(int port);
41 41
  42 + // A file handler, that provides the current status as HTML-file. This
  43 + // allows to acces the current status even if there is no status port.
  44 + FileHandler *CreateStatusFileHandler();
  45 +
42 46 // Set browser meta-refresh time. < 0 to disable.
43 47 void set_meta_refresh(int seconds) { meta_refresh_time_ = seconds; }
44 48
45 49 private:
  50 + class HtmlFileHandler;
  51 + friend class HtmlFileHandler;
  52 +
46 53 // micro-httpd callback
47 54 static int HandleHttp(void* user_argument,
48 55 struct MHD_Connection *,
49 56 const char *, const char *, const char *,
50 57 const char *, size_t *, void **);
51 58
52   - const std::string &CreatePage();
  59 + void CreatePage(bool for_http, std::string *content);
  60 +
  61 + const std::string &CreateHttpPage();
53 62
54 63 // Some helper functions to create the page:
55   - void AppendSettingsForm();
  64 + void AppendSettingsForm(bool for_http, std::string *out);
56 65 void AppendFileInfo(const char *progress_access_color,
57 66 const char *progress_buffer_color,
58   - const HandlerStats &stats);
  67 + const HandlerStats &stats,
  68 + std::string *out);
59 69
60 70 // Set filter from http-request. Gracefully handles garbage.
61 71 void SetFilter(const char *value);
@@ -77,7 +87,7 @@ class StatusServer : protected FileHandlerCache::Observer {
77 87 int meta_refresh_time_;
78 88 FolveFilesystem *filesystem_;
79 89 struct MHD_Daemon *daemon_;
80   - std::string content_;
  90 + std::string http_content_;
81 91 bool filter_switched_;
82 92 };
83 93

0 comments on commit 30b49d6

Please sign in to comment.
Something went wrong with that request. Please try again.