/*********************************************************************** vmtouch - the Virtual Memory Toucher Portable file system cache diagnostics and control by Doug Hoyte (doug@hcsw.org) Compilation: gcc -Wall -O3 -o vmtouch vmtouch.c ************************************************************************ Copyright (c) 2009 Doug Hoyte. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ************************************************************************ CHANGES: 0.7.3 * Solaris support for page eviction 0.7.2 * Portability fixes 0.7.1 * First public release ************************************************************************ TODO: * -p "page mode" for touching, evicting, or locking just a range of pages in a file... same format as nmap -p. 4k-50k, 1.5G-2G, -5M, - * Continually call mincore() every time a residency chart is drawn * In daemon mode, make fatal errors go to syslog. ***********************************************************************/ #define VMTOUCH_VERSION "0.7.3" #define RESIDENCY_CHART_WIDTH 60 #define CHART_UPDATE_INTERVAL 0.1 #define MAX_CRAWL_DEPTH 1024 #ifdef __linux__ // Make sure off_t is 64 bits on linux #define _FILE_OFFSET_BITS 64 // Required for posix_fadvise() on some linux systems #define _XOPEN_SOURCE 600 // Required for mincore() on some linux systems #define _BSD_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include long pagesize; int64_t total_pages=0; int64_t total_pages_in_core=0; int64_t total_files=0; int64_t total_dirs=0; unsigned int junk_counter; // just to prevent any compiler optimizations int curr_crawl_depth=0; ino_t crawl_inodes[MAX_CRAWL_DEPTH]; int o_touch=0; int o_evict=0; int o_quiet=0; int o_verbose=0; int o_lock=0; int o_lockall=0; int o_daemon=0; int o_followsymlinks=0; size_t o_max_file_size=500*1024*1024; void usage() { printf("\n"); printf("vmtouch v%s - the Virtual Memory Toucher by Doug Hoyte\n", VMTOUCH_VERSION); printf("Portable file system cache diagnostics and control\n\n"); printf("Usage: vmtouch [OPTIONS] ... FILES OR DIRECTORIES ...\n\nOptions:\n"); printf(" -t touch pages into memory\n"); printf(" -e evict pages from memory\n"); printf(" -l lock pages in physical memory with mlock(2)\n"); printf(" -L lock pages in physical memory with mlockall(2)\n"); printf(" -d daemon mode\n"); printf(" -m max file size to touch\n"); printf(" -f follow symbolic links\n"); printf(" -v verbose\n"); printf(" -q quiet\n"); exit(1); } static void fatal(const char *fmt, ...) { va_list ap; char buf[4096]; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); fprintf(stderr, "vmtouch: FATAL: %s\n", buf); exit(1); } static void warning(const char *fmt, ...) { va_list ap; char buf[4096]; va_start(ap, fmt); vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if (!o_quiet) fprintf(stderr, "vmtouch: WARNING: %s\n", buf); } void go_daemon() { int rv; rv = fork(); if (rv == -1) fatal("fork: %s", strerror(errno)); if (rv) exit(0); if (setsid() == -1) fatal("setsid: %s", strerror(errno)); if (freopen("/dev/null", "r", stdin) == NULL || freopen("/dev/null", "w", stdout) == NULL || freopen("/dev/null", "w", stderr) == NULL) fatal("freopen: %s", strerror(errno)); } char *pretty_print_size(int64_t inp) { static char output[100]; if (inp<1024) { snprintf(output, sizeof(output), "%" PRId64, inp); return output; } inp /= 1024; if (inp<1024) { snprintf(output, sizeof(output), "%" PRId64 "K", inp); return output; } inp /= 1024; if (inp<1024) { snprintf(output, sizeof(output), "%" PRId64 "M", inp); return output; } inp /= 1024; snprintf(output, sizeof(output), "%" PRId64 "G", inp); return output; } int64_t parse_size(char *inp) { char *tp; int len=strlen(inp); char *errstr = "bad size. examples: 4096, 4k, 100M, 1.5G"; char mult_char; int mult=1; double val; if (len < 1) fatal(errstr); mult_char = tolower(inp[len-1]); if (isalpha(mult_char)) { switch(mult_char) { case 'k': mult = 1024; break; case 'm': mult = 1024*1024; break; case 'g': mult = 1024*1024*1024; break; default: fatal("unknown size multiplier: %c", mult_char); } inp[len-1] = '\0'; } val = strtod(inp, &tp); if (val <= 0 || tp == NULL) fatal(errstr); return (int64_t) (mult*val); } int64_t bytes2pages(int64_t bytes) { return (bytes+pagesize-1) / pagesize; } int aligned_p(void *p) { return 0 == ((long)p & (pagesize-1)); } int is_mincore_page_resident(char p) { return p & 0x1; } void increment_nofile_rlimit() { struct rlimit r; if (getrlimit(RLIMIT_NOFILE, &r)) fatal("increment_nofile_rlimit: getrlimit (%s)", strerror(errno)); r.rlim_cur = r.rlim_max + 1; r.rlim_max = r.rlim_max + 1; if (setrlimit(RLIMIT_NOFILE, &r)) { if (errno == EPERM) { if (getuid() == 0 || geteuid() == 0) fatal("system open file limit reached"); fatal("open file limit reached and unable to increase limit. retry as root"); } fatal("increment_nofile_rlimit: setrlimit (%s)", strerror(errno)); } } double gettimeofday_as_double() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec + (tv.tv_usec/1000000.0); } void print_page_residency_chart(FILE *out, char *mincore_array, int64_t pages_in_file) { int64_t pages_in_core=0; int64_t pages_per_char; int64_t i,j=0,curr=0; if (pages_in_file <= RESIDENCY_CHART_WIDTH) pages_per_char = 1; else pages_per_char = (pages_in_file / RESIDENCY_CHART_WIDTH) + 1; fprintf(out, "\r["); for (i=0; i o_max_file_size) { warning("file %s too large, skipping", path); return; } len_of_file = sb.st_size; retry_open: fd = open(path, O_RDONLY, 0); if (fd == -1) { if (errno == ENFILE || errno == EMFILE) { increment_nofile_rlimit(); goto retry_open; } warning("unable to open %s (%s), skipping", path, strerror(errno)); return; } mem = mmap(NULL, len_of_file, PROT_READ, MAP_SHARED, fd, 0); if (mem == MAP_FAILED) { warning("unable to mmap file %s (%s), skipping", path, strerror(errno)); close(fd); return; } if (!aligned_p(mem)) fatal("mmap(%s) wasn't page aligned", path); pages_in_file = bytes2pages(len_of_file); total_pages += pages_in_file; if (o_evict) { if (o_verbose) printf("Evicting %s\n", path); #if defined(__linux__) if (posix_fadvise(fd, 0, len_of_file, POSIX_FADV_DONTNEED)) warning("unable to posix_fadvise file %s (%s)", path, strerror(errno)); #elif defined(__FreeBSD__) || defined(__sun__) if (msync(mem, len_of_file, MS_INVALIDATE)) warning("unable to msync invalidate file %s (%s)", path, strerror(errno)); #else fatal("cache eviction not (yet?) supported on this platform"); #endif } else { char mincore_array[pages_in_file]; int64_t pages_in_core=0; double last_chart_print_time=0.0, temp_time; // 3rd arg to mincore is char* on BSD and unsigned char* on linux if (mincore(mem, len_of_file, (void*)mincore_array)) fatal("mincore %s (%s)", path, strerror(errno)); for (i=0; i (last_chart_print_time+CHART_UPDATE_INTERVAL)) { last_chart_print_time = temp_time; print_page_residency_chart(stdout, mincore_array, pages_in_file); } } } } if (o_verbose) { print_page_residency_chart(stdout, mincore_array, pages_in_file); printf("\n"); } } if (o_lock) { if (mlock(mem, len_of_file)) fatal("mlock: %s (%s)", path, strerror(errno)); } if (!o_lock && !o_lockall) { if (munmap(mem, len_of_file)) warning("unable to munmap file %s (%s)", path, strerror(errno)); close(fd); } } void vmtouch_crawl(char *path) { struct stat sb; DIR *dirp; struct dirent *de; char npath[PATH_MAX]; int res; int tp_path_len = strlen(path); int i; if (path[tp_path_len-1] == '/') path[tp_path_len-1] = '\0'; // prevent ugly double slashes when printing path names res = o_followsymlinks ? stat(path, &sb) : lstat(path, &sb); if (res) { warning("unable to stat %s (%s)", path, strerror(errno)); return; } else { if (S_ISLNK(sb.st_mode)) { warning("not following symbolic link %s", path); return; } if (S_ISDIR(sb.st_mode)) { for (i=0; id_name, ".") == 0 || strcmp(de->d_name, "..") == 0) continue; if (snprintf(npath, sizeof(npath), "%s/%s", path, de->d_name) >= sizeof(npath)) { warning("path too long %s", path); goto bail; } curr_crawl_depth++; vmtouch_crawl(npath); curr_crawl_depth--; } bail: if (closedir(dirp)) { warning("unable to closedir %s (%s)", path, strerror(errno)); return; } } else if (S_ISREG(sb.st_mode)) { total_files++; vmtouch_file(path); } else { warning("skipping non-regular file: %s", path); } } } int main(int argc, char **argv) { int ch, i; char *prog = argv[0]; struct timeval start_time; struct timeval end_time; pagesize = sysconf(_SC_PAGESIZE); while((ch = getopt(argc, argv,"tevqlLdfpb:m:")) != -1) { switch(ch) { case '?': usage(); break; case 't': o_touch = 1; break; case 'e': o_evict = 1; break; case 'q': o_quiet = 1; break; case 'v': o_verbose++; break; case 'l': o_lock = 1; o_touch = 1; break; case 'L': o_lockall = 1; o_touch = 1; break; case 'd': o_daemon = 1; o_quiet = 1; break; case 'f': o_followsymlinks = 1; break; case 'p': o_touch = 1; printf("%d %s %ld\n", sizeof(void*) == 4 ? 32 : 64, ((char*)&o_touch)[0] ? "little" : "big", pagesize); exit(0); case 'm': { int64_t val = parse_size(optarg); o_max_file_size = (size_t) val; if (val != (int64_t) o_max_file_size) fatal("value for -m too big to fit in a size_t"); break; } } } argc -= optind; argv += optind; if (o_touch) { if (o_evict) fatal("invalid option combination: -t and -e"); } if (o_evict) { if (o_lock) fatal("invalid option combination: -e and -l"); } if (o_lock && o_lockall) fatal("invalid option combination: -l and -L"); if (o_daemon) { if (!(o_lock || o_lockall)) fatal("daemon mode must be combined with -l or -L"); } if (o_quiet && o_verbose) fatal("invalid option combination: -q and -v"); if (!argc) { printf("%s: no files or directories specified\n", prog); usage(); } // Must be done now because mlock() not inherited across fork() if (o_daemon) go_daemon(); gettimeofday(&start_time, NULL); for (i=0; i