1/** 2 * \file 3 * \brief NFS-populated file cache for HTTP server 4 * 5 * This very stupid "cache" assumes that: 6 * All regular files in a hardcoded NFS mount point are cached at startup 7 * The contents of the cache never change nor expire 8 */ 9 10/* 11 * Copyright (c) 2008, ETH Zurich. 12 * All rights reserved. 13 * 14 * This file is distributed under the terms in the attached LICENSE file. 15 * If you do not find this file, copies can be found by writing to: 16 * ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group. 17 */ 18 19#include <stdio.h> 20#include <barrelfish/barrelfish.h> 21#include <nfs/nfs.h> 22#include <net_sockets/net_sockets.h> 23#include <trace/trace.h> 24#include <trace_definitions/trace_defs.h> 25#include <timer/timer.h> 26#include <netbench/netbench.h> 27#include "webserver_network.h" 28#include "webserver_debug.h" 29#include "webserver_session.h" 30#include "http_cache.h" 31 32/* NOTE: enable following #def if you want cache to be preloaded */ 33#define PRELOAD_WEB_CACHE 1 34 35/* Enable tracing only when it is globally enabled */ 36#if CONFIG_TRACE && NETWORK_STACK_TRACE 37//#define ENABLE_WEB_TRACING 1 38#endif // CONFIG_TRACE && NETWORK_STACK_TRACE 39 40//#define MAX_NFS_READ 14000 41#define MAX_NFS_READ 1330 /* 14000 */ /* to avoid packet reassembly inside driver */ 42 43/* Maximum staleness allowed */ 44#define MAX_STALENESS ((cycles_t)9000000) 45 46static void (*init_callback)(void); 47 48 49struct http_cache_entry { 50 int valid; /* flag for validity of the data */ 51 char *name; /* name of the cached file */ 52 size_t copied; /* how much data is copied? */ 53 int loading; /* flag indicating if data is loading */ 54 struct buff_holder *hbuff; /* holder for buffer */ 55 struct nfs_fh3 file_handle; /* for NFS purpose */ 56 struct http_conn *conn; /* list of connections waiting for data */ 57 struct http_conn *last; /* for quick insertions at end */ 58 struct http_cache_entry *next; /* for linked list */ 59}; 60 61/* global states */ 62static struct nfs_fh3 nfs_root_fh; /* reference to the root dir of NFS */ 63static struct nfs_client *my_nfs_client; /* The NFS client */ 64 65static struct http_cache_entry *cache_table = NULL; /*root cached entry array */ 66static struct http_cache_entry *error_cache = NULL; /* cache entry for error */ 67 68 69#ifdef PRELOAD_WEB_CACHE 70/* Initial cache loading state variables */ 71static bool cache_loading_phase = false; 72static bool readdir_complete = false; 73static int cache_lookups_started = 0; 74static int cache_loaded_counter = 0; 75static int cache_loading_probs = 0; 76static void handle_cache_load_done(void); 77#endif // PRELOAD_WEB_CACHE 78 79// Variables for time measurement for performance 80static uint64_t last_ts = 0; 81 82/* allocate the buffer and initialize it. */ 83static struct buff_holder *allocate_buff_holder (size_t len) 84{ 85 struct buff_holder *result = NULL; 86 result = (struct buff_holder *) malloc (sizeof (struct buff_holder)); 87 assert (result != NULL ); 88 memset (result, 0, sizeof(struct buff_holder)); 89 if ( len > 0) { 90 result->data = malloc (len); 91 assert (result->data != NULL); 92 } 93 /* NOTE: 0 is valid length and used by error_cache */ 94 result->len = len; 95 result->r_counter = 1; /* initiating ref_counter to 1, and using it as ref 96 for free */ 97 return result; 98} /* end function: allocate_buff_holder */ 99 100/* increments and returns the incremented value of the ref_counter for bh */ 101static long increment_buff_holder_ref (struct buff_holder *bh) 102{ 103 ++bh->r_counter; 104 return (bh->r_counter); 105} /* end Function: increment_buff_holder_ref */ 106 107/* Decrements value of the ref_counter for bh 108 if r_counter reaches zero then free all the memory */ 109long decrement_buff_holder_ref (struct buff_holder *bh) 110{ 111 if (bh == NULL) { 112 return 0; 113 } 114 115 --bh->r_counter; 116 if (bh->r_counter > 0) { 117 return (bh->r_counter); 118 } 119 120 if (bh->len > 0 && bh->data != NULL) { 121 free (bh->data); 122 } 123 free (bh); 124 return 0; 125} /* end Function: increment_buff_holder_ref */ 126 127 128/* allocates the memory for the cacheline */ 129static struct http_cache_entry * cache_entry_allocate (void) 130{ 131 struct http_cache_entry *e; 132 e = (struct http_cache_entry *) 133 malloc (sizeof(struct http_cache_entry)); 134 assert (e != NULL ); 135 memset (e, 0, sizeof(struct http_cache_entry)); 136 return e; 137} 138 139/* Function create_404_page_cache creates error_cache entry, 140 this entry will be used to reply when file is not found */ 141static void create_404_page_cache (void) 142{ 143 error_cache = cache_entry_allocate(); 144 error_cache->hbuff = allocate_buff_holder (0); 145 increment_buff_holder_ref (error_cache->hbuff); 146 error_cache->valid = 1; 147} /* end function: create_404_page_cache */ 148 149/* adds the http connection cs at the end of the list of connections waiting 150 for the arrival of the data on this cacheline e. */ 151static void add_connection(struct http_cache_entry *e, struct http_conn *cs) 152{ 153 assert (e != NULL); 154 assert (cs != NULL); 155 DEBUGPRINT ("%d: adding conn to waiting list of cacheline [%s]\n", 156 cs->request_no, e->name); 157 increment_http_conn_reference (cs); 158 /* maintaining linked list with O(c) time and space complexity */ 159 if (e->conn == NULL) { /* list is empty, this is first element */ 160 assert(e->last == NULL); /* NOTE: just for safety, not actually needed*/ 161 e->conn = cs; 162 e->last = cs; 163 return; 164 } 165 /* adding element at end of the list */ 166 e->last->next = cs; 167 e->last = cs; 168} /* end function: add_connection */ 169 170/* Finds the cacheline associated with given name 171 if no cacheline exists, it will create one, 172 copy name as the key for cacheline */ 173static struct http_cache_entry *find_cacheline (const char *name) 174{ 175 struct http_cache_entry *e; 176 int l; 177 // FIXME: simple linear search 178 for (e = cache_table; e != NULL; e = e->next) { 179 if (strcmp(name, e->name) == 0) { 180 DEBUGPRINT ("cache-hit for [%s] == [%s]\n", name, e->name); 181 return e; 182 } 183 } /* end for : for each cacheline */ 184 /* create new cacheline */ 185 e = cache_entry_allocate(); 186 /* copying the filename */ 187 l = strlen (name); 188 e->name = (char *)malloc(sizeof(char)*(l+1)); 189 assert(e->name != NULL); 190 strcpy(e->name, name); 191 DEBUGPRINT ("cache-miss for [%s] so, created [%s]\n", name, e->name); 192 e->next = cache_table; 193 cache_table = e; 194 return e; 195} /* end function: find_cacheline */ 196 197static void delete_cacheline_from_cachelist (struct http_cache_entry *target) 198{ 199 struct http_cache_entry *prev; 200 struct http_cache_entry *e; 201 202 if (cache_table == target){ 203 cache_table = target->next; 204 return; 205 } 206 207 // FIXME: simple linear search 208 prev = NULL; 209 for (e = cache_table; e != NULL; e = e->next) { 210 if (e == target) { 211 prev->next = e->next; 212 return; 213 } 214 prev = e; 215 } /* end for : for each cacheline */ 216} /* end function: delete_cacheline_from_cachelist */ 217 218 219static void trigger_callback (struct http_conn *cs, struct http_cache_entry *e) 220{ 221 222 if ( cs->mark_invalid ){ 223 /* This http_conn is not good anymore (most probably, it received RST)*/ 224 DEBUGPRINT ("%d: ERROR: callback triggered on invalid conn\n", 225 cs->request_no ); 226 return; 227 } 228 229 /* making the callback */ 230 cs->hbuff = e->hbuff; 231 /* increasing the reference */ 232 increment_buff_holder_ref (cs->hbuff); 233 cs->callback(cs); 234} /* end function: trigger_callback */ 235 236 237/* send callback for each pending http_conn */ 238static void handle_pending_list(struct http_cache_entry *e) 239{ 240 struct http_conn *cs = NULL; 241 struct http_conn *next_cs = NULL; 242 if(e == NULL) { 243 printf("ERROR: handle_pending_list: null passed for cache entry\n"); 244 return; 245 } 246 if(e->conn == NULL){ 247// printf("ERROR: handle_pending_list: no one waiting\n"); 248 return; 249 } 250 cs = e->conn; 251 while (cs != NULL) { 252 e->conn = cs->next; 253 trigger_callback(cs, e); 254 next_cs = cs->next; 255 /* Copying the next of cs as following function can release the cs. */ 256 decrement_http_conn_reference(cs); 257 cs = next_cs; 258 } 259 260 /* clear the list attached to the cache */ 261 e->conn = NULL; 262 e->last = NULL; 263} /* end function : handle_pending_list */ 264 265 266static void read_callback (void *arg, struct nfs_client *client, 267 READ3res *result) 268{ 269 struct http_cache_entry *e = arg; 270 assert( e != NULL); 271 272 assert (result != NULL); 273 assert (result->status == NFS3_OK); 274 READ3resok *res = &result->READ3res_u.resok; 275 assert(res->count == res->data.data_len); 276 277 assert (e->hbuff != NULL); 278 assert (e->hbuff->data != NULL ); 279 280 assert (e->hbuff->len >= e->copied + res->data.data_len); 281 memcpy (e->hbuff->data + e->copied, res->data.data_val, res->data.data_len); 282 e->copied += res->data.data_len; 283 284 DEBUGPRINT ("got response of len %d, filesize %lu for file %s\n", 285 res->data.data_len, e->copied, e->name); 286 287 // free arguments 288 xdr_READ3res(&xdr_free, result); 289 290 if (!res->eof) { 291 // more data to come, read the next chunk 292 errval_t err = nfs_read(client, e->file_handle, e->copied, MAX_NFS_READ, 293 read_callback, e); 294 assert(err == SYS_ERR_OK); 295 return; 296 } 297 298 /* This is the end-of-file, so deal with it. */ 299 e->valid = 1; 300 e->loading = 0; 301 decrement_buff_holder_ref (e->hbuff); 302 303#ifdef PRELOAD_WEB_CACHE 304 if (!cache_loading_phase) { 305 handle_pending_list (e); /* done! */ 306 return; 307 } 308 309 /* This is cache loading going on... */ 310 printf("Copied %zu bytes for file [%s] of length: %zu\n", 311 e->copied, e->name, e->hbuff->len); 312 ++cache_loaded_counter; 313 handle_cache_load_done(); 314 315#else // PRELOAD_WEB_CACHE 316 handle_pending_list(e); /* done! */ 317#endif // PRELOAD_WEB_CACHE 318} 319 320 321static void lookup_callback (void *arg, struct nfs_client *client, 322 LOOKUP3res *result) 323{ 324 LOOKUP3resok *resok = &result->LOOKUP3res_u.resok; 325 errval_t r; 326 struct http_cache_entry *e = arg; 327 328 DEBUGPRINT ("inside lookup_callback_file for file %s\n", e->name); 329 assert(result != NULL ); 330 331 /* initiate a read for every regular file */ 332 if ( result->status == NFS3_OK && 333 resok->obj_attributes.attributes_follow && 334 resok->obj_attributes.post_op_attr_u.attributes.type == NF3REG) { 335 336 /* FIXME: check if the file is recently modified or not. */ 337 // resok->obj_attributes.post_op_attr_u.attributes.ctime; 338 339 340 DEBUGPRINT("Copying %s of size %lu\n", e->name, 341 resok->obj_attributes.post_op_attr_u.attributes.size ); 342 343 /* Store the nfs directory handle in current_session (cs) */ 344 nfs_copyfh (&e->file_handle, resok->object); 345 /* GLOBAL: Storing the global reference for cache entry */ 346 /* NOTE: this memory is freed in reset_cache_entry() */ 347 348 /* Allocate memory for holding the file-content */ 349 /* Allocate the buff_holder */ 350 e->hbuff = allocate_buff_holder( 351 resok->obj_attributes.post_op_attr_u.attributes.size ); 352 /* NOTE: this memory will be freed by decrement_buff_holder_ref */ 353 354 /* increment buff_holder reference */ 355 increment_buff_holder_ref (e->hbuff); 356 357 e->hbuff->len = resok->obj_attributes.post_op_attr_u.attributes.size; 358 359 /* Set the size of the how much data is read till now. */ 360 e->copied = 0; 361 362 r = nfs_read(client, e->file_handle, 0, MAX_NFS_READ, 363 read_callback, e); 364 assert (r == SYS_ERR_OK); 365 366 // free arguments 367 xdr_LOOKUP3res(&xdr_free, result); 368 return; 369 } 370 371 /* Most probably the file does not exist */ 372 DEBUGPRINT ("Error: file [%s] does not exist, or wrong type\n", e->name); 373 374#ifdef PRELOAD_WEB_CACHE 375 if (cache_loading_phase){ 376 ++cache_loading_probs; 377 handle_cache_load_done(); 378 return; 379 } 380#endif // PRELOAD_WEB_CACHE 381 382 if (e->conn == NULL) { 383 /* No connection is waiting for this loading */ 384 return; 385 } 386 387 /* as file does not exist, send all the http_conns to error page. */ 388 error_cache->conn = e->conn; 389 error_cache->last = e->last; 390 handle_pending_list (error_cache); /* done! */ 391 392 /* free this cache entry as it is pointing to invalid page */ 393 e->conn = NULL; 394 e->last = NULL; 395 delete_cacheline_from_cachelist (e); 396 if (e->name != NULL ) free(e->name); 397 free(e); 398 399 // free arguments 400 xdr_LOOKUP3res(&xdr_free, result); 401 return; 402} /* end function: lookup_callback_file */ 403 404static errval_t async_load_cache_entry(struct http_cache_entry *e) 405{ 406 errval_t r; 407 assert(e != NULL); 408 409 // FIXME: currently only works for files in root directory. 410 // Do lookup for given filename in root dir 411 DEBUGPRINT ("pageloading starting with nfs_lookup\n"); 412 r = nfs_lookup(my_nfs_client, nfs_root_fh, e->name, 413 lookup_callback, e); 414 assert(r == SYS_ERR_OK); 415 return SYS_ERR_OK; 416} /* end function : async_load_cache_entry */ 417 418 419errval_t http_cache_lookup (const char *name, struct http_conn *cs) 420{ 421 struct http_cache_entry *e; 422 assert(cs != NULL); 423 424 e = find_cacheline(name); 425 if (e->valid == 1) { 426 /* fresh matching cache-entry found */ 427 DEBUGPRINT ("%d: Fresh cache-entry, returning page [%s]\n", 428 cs->request_no, name); 429 trigger_callback (cs, e); 430 return SYS_ERR_OK; 431 } /* end if: valid cacheline */ 432 433 /* data not in cache */ 434 /* add this connection to the list of waiting on cacheline */ 435 add_connection (e, cs); 436 437 /* Check if no-one is loading the data */ 438 if (e->loading == 0 ) { 439 e->loading = 1; 440 DEBUGPRINT ("%d: starting the page loading\n", cs->request_no); 441 async_load_cache_entry(e); 442 } /* end if: no-one else is loading the data */ 443 else { 444 DEBUGPRINT ("%d: someone else is loading the page, so just waiting\n", 445 cs->request_no); 446 } 447 448 return SYS_ERR_OK; 449} /* end function: http_cache_lookup */ 450 451 452 453 454static void cache_timeout_event (struct timer *timer, void *arg) 455{ 456 struct http_cache_entry *cache = arg; 457 struct http_cache_entry *e; 458 459 DEBUGPRINT ("CACHE_TIMEOUT: marking all cache entries for reload\n"); 460 for (e = cache; e != NULL; e = e->next) { 461 if (e->valid == 1) { 462 /* this if is to avoid any complications that might come because 463 of getting cache_timeout when cacheline was in loading */ 464 e->valid = 0; 465 e->loading = 0; 466 467 decrement_buff_holder_ref (e->hbuff); /* Decrement buffer ref. */ 468 e->hbuff = NULL; /* loosing the pointer to buff */ 469 } /* end if: if cacheline is valid */ 470 } /* end for : for each cacheline */ 471 DEBUGPRINT ("CACHE_TIMEOUT: Done with invalidating the cache entries\n"); 472} /* end function : cache_timeout_event */ 473 474 475#ifdef PRELOAD_WEB_CACHE 476 477static void readdir_callback(void *arg, struct nfs_client *client, 478 READDIR3res *result) 479{ 480 READDIR3resok *resok = &result->READDIR3res_u.resok; 481 struct http_cache_entry *ce; 482 entry3 *last = NULL; 483 errval_t r; 484 485 DEBUGPRINT ("readdir_callback came in\n"); 486 assert(result != NULL && result->status == NFS3_OK); 487 488 // FIXME: start here the measurement of file loading time 489 490 // FIXME: Start the trace 491#if ENABLE_WEB_TRACING 492 printf("Starting tracing\n"); 493 494 errval_t err = trace_control(TRACE_EVENT(TRACE_SUBSYS_NNET, 495 TRACE_EVENT_NNET_START, 0), 496 TRACE_EVENT(TRACE_SUBSYS_NNET, 497 TRACE_EVENT_NNET_STOP, 0), 0); 498 if(err_is_fail(err)) { 499 USER_PANIC_ERR(err, "trace_control failed"); 500 } 501 trace_event(TRACE_SUBSYS_NNET, TRACE_EVENT_NNET_START, 0); 502#else // ENABLE_WEB_TRACING 503 printf("Tracing not enabled\n"); 504#endif // ENABLE_WEB_TRACING 505 506 507 last_ts = rdtsc(); 508// lwip_benchmark_control(1, BMS_START_REQUEST, 0, 0); 509 // initiate a lookup for every entry 510 for (entry3 *e = resok->reply.entries; e != NULL; e = e->nextentry) { 511 if (strlen(e->name) < 3 ) { 512 printf("ignoring the file %s\n", e->name); 513 continue; 514 } 515 ++cache_lookups_started; 516 printf("Loading the file %s\n", e->name); 517 ce = find_cacheline(e->name); 518 ce->loading = 1; 519 async_load_cache_entry(ce); 520 e->name = NULL; // prevent freeing by XDR 521 last = e; 522 523 // continue handling events till this page is not completly loaded. 524 525/* 526 struct waitset *ws = get_default_waitset(); 527 errval_t err; 528 529 DEBUGPRINT ("looping for events inside loop of readdir_callback\n"); 530 while (ce->valid != 1) { 531 err = event_dispatch(ws); 532 if (err_is_fail(err)) { 533 DEBUG_ERR(err, "in event_dispatch"); 534 break; 535 } 536 } 537 DEBUGPRINT ("readdir_callback: %s file loaded\n", e->name); 538*/ 539 540 } 541 542 /* more in the directory: repeat call */ 543 if (!resok->reply.eof) { 544 assert(last != NULL); 545 r = nfs_readdir(client, nfs_root_fh, last->cookie, 546 resok->cookieverf, readdir_callback, NULL); 547 assert(r == SYS_ERR_OK); 548 } else { 549 readdir_complete = true; 550 handle_cache_load_done(); 551 } 552 553 // free arguments 554 xdr_READDIR3res(&xdr_free, result); 555} 556 557 558 559static bool are_all_pages_loaded(void) 560{ 561 if (readdir_complete == false ) { 562 return false; 563 } 564 return (cache_lookups_started == (cache_loaded_counter + cache_loading_probs)); 565} 566 567static void handle_cache_load_done(void) 568{ 569 if (!are_all_pages_loaded()) { 570 return; 571 } 572 DEBUGPRINT("initial_cache_load: entire cache loaded done\n"); 573 cache_loading_phase = false; 574 575 // lwip_benchmark_control(1, BMS_STOP_REQUEST, 0, 0); 576 // 577 // Report the cache loading time 578 579 uint64_t total_loading_time = rdtsc() - last_ts; 580 printf("Cache loading time %"PU", %"PRIu64"\n", 581 in_seconds(total_loading_time), total_loading_time); 582 583// lwip_print_interesting_stats(); 584 585 /* stop the trace. */ 586#if ENABLE_WEB_TRACING 587 trace_event(TRACE_SUBSYS_NNET, TRACE_EVENT_NNET_STOP, 0); 588 589 char *trace_buf_area = malloc(CONSOLE_DUMP_BUFLEN); 590 assert(trace_buf_area); 591 size_t used_bytes = 0; 592 trace_dump_core(trace_buf_area, CONSOLE_DUMP_BUFLEN, &used_bytes, NULL, 593 disp_get_core_id(), true, true); 594 595 printf("\n%s\n", "dump trac buffers: Start"); 596 printf("\n%s\n", trace_buf_area); 597 printf("\n%s\n", "dump trac buffers: Stop"); 598 trace_reset_all(); 599// abort(); 600 601 printf("Cache loading time %"PU", %"PRIu64"\n", 602 in_seconds(total_loading_time), total_loading_time); 603 604#endif // ENABLE_WEB_TRACING 605 606 607 /* continue with the web-server initialization. */ 608 init_callback(); /* do remaining initialization! */ 609} 610 611static void initial_cache_load(struct nfs_client *client) 612{ 613 errval_t r; 614 cache_loading_phase = true; 615 cache_lookups_started = 0; 616 cache_loaded_counter = 0; 617 cache_loading_probs = 0; 618 619 //my_nfs_client 620 r = nfs_readdir(client, nfs_root_fh, NFS_READDIR_COOKIE, 621 NFS_READDIR_COOKIEVERF, readdir_callback, NULL); 622 assert(r == SYS_ERR_OK); 623} 624 625#endif // PRELOAD_WEB_CACHE 626 627 628static void mount_callback(void *arg, struct nfs_client *client, 629 enum mountstat3 mountstat, struct nfs_fh3 fhandle) 630{ 631 assert(mountstat == MNT3_OK); 632 nfs_root_fh = fhandle; /* GLOBAL: assigning root of filesystem handle */ 633#ifdef PRELOAD_WEB_CACHE 634 DEBUGPRINT ("nfs_mount successful, loading initial cache.\n"); 635 initial_cache_load(client); /* Initial load of files for cache. */ 636#else // PRELOAD_WEB_CACHE 637 DEBUGPRINT ("nfs_mount successful\n"); 638 init_callback(); /* done! */ 639#endif // PRELOAD_WEB_CACHE 640} /* end function: mount_callback */ 641 642errval_t http_cache_init(struct in_addr server, const char *path, 643 void (*callback)(void)) 644{ 645 struct timer *cache_timer; /* timer for triggering cache timeouts */ 646 init_callback = callback; 647 648 DEBUGPRINT ("nfs_mount calling.\n"); 649 my_nfs_client = nfs_mount(server, path, mount_callback, NULL); 650 DEBUGPRINT ("nfs_mount calling done.\n"); 651 652 assert(my_nfs_client != NULL); 653 /* creating the empty cache */ 654 cache_table = NULL; 655 create_404_page_cache(); 656 657 658 cache_timer = timer_create(MAX_STALENESS, true, cache_timeout_event, 659 cache_table); 660 assert (cache_timer != NULL); 661 if (cache_timer == NULL) { 662 printf ("http_cache_init failed in timer_create\n"); 663 return LWIP_ERR_MEM; 664 } 665 timer_start(cache_timer); 666 DEBUGPRINT ("http_cache_init done\n"); 667 return SYS_ERR_OK; 668} /* end function: http_cache_init */ 669