/* vmlog - high-speed logging for free VMs */ /* Copyright (C) 2006 Edwin Steiner */ /* This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "vmlog.h" #include #include #include #include #include #include #include #include #include #include #include /*** default macros **************************************************/ #ifndef VMLOG_LOCK #define VMLOG_LOCK(vml) #define VMLOG_UNLOCK(vml) #endif /* #define VMLOG_ENDIAN_CONVERT_WRITE */ /* #define VMLOG_HOST_LITTLE_ENDIAN */ #include "config.h" #if defined(WORDS_BIGENDIAN) # define VMLOG_ENDIAN_CONVERT_WRITE # undef VMLOG_HOST_LITTLE_ENDIAN #else # undef VMLOG_ENDIAN_CONVERT_WRITE # define VMLOG_HOST_LITTLE_ENDIAN #endif /*** constants *******************************************************/ /* currently vmlog does no rehashing, so these should be quite big */ #define VMLOG_INITIAL_STRING_HASH_SIZE 50000 /* XXX debug */ #define VMLOG_INITIAL_THREAD_HASH_SIZE 8 /* XXX debug */ /* initial size of the frame buffer - this is doubled each time */ /* the frame buffer has to grow */ #define VMLOG_INITIAL_FRAMES_CAPACITY 1 /* XXX debug */ /*** types ***********************************************************/ /* we declare this here because defining _LARGEFILE64_SOURCE works */ /* only if we are the first ones to include the headers, which may */ /* not be the case if vmlog.c is used as an include file. */ #ifndef _LARGEFILE64_SOURCE typedef long long off64_t; off64_t lseek64(int fd, off64_t offset, int whence); #endif /*** tag definitions *************************************************/ /* CAUTION: these must are indexed by the VMLOG_TAG_... constants! */ vmlog_tag_definition vmlog_tag_definitions[] = { { "enter", "enter", +1 }, { "leave", "leave", -1 }, { "throw", "throw", 0 }, { "catch", "catch", 0 }, { "unwnd", "unwnd", -1 }, { "signl", "signl", 0 }, { "unrol", "unrol", -1 }, { "rerol", "rerol", +1 }, { NULL , NULL , 0 } }; /*** global variables ************************************************/ static char *vmlog_progname = "vmlog"; /*** prototypes ******************************************************/ static void *vmlog_memdup(const void *m,int len); /*** error reporting *************************************************/ void vmlog_set_progname(const char *progname) { if (!progname) { progname = "vmlog (progname == NULL)"; } vmlog_progname = vmlog_memdup(progname,strlen(progname)+1); } void vmlog_die(const char *fmt,...) { va_list ap; fputs(vmlog_progname,stderr); fputs(": error: ",stderr); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fputc('\n',stderr); exit(1); } void vmlog_warn(const char *fmt,...) { va_list ap; fputs(vmlog_progname,stderr); fputs(": warning: ",stderr); va_start(ap,fmt); vfprintf(stderr,fmt,ap); va_end(ap); fputc('\n',stderr); } void vmlog_die_usage(const char *usage,int error) { assert(usage); fputs(usage,(error) ? stderr : stdout); exit((error) ? 1 : 0); } /*** utility functions ***********************************************/ static void *vmlog_memdup(const void *data,int len) { char *p; p = VMLOG_NEW_ARRAY(char,len); assert(p); memcpy(p,data,len); return p; } static void *vmlog_strdup(const void *data,int len) { char *p; p = VMLOG_NEW_ARRAY(char,len+1); assert(p); memcpy(p,data,len); p[len] = 0; return p; } char *vmlog_concat4len(const char *a,int alen,const char *b,int blen, const char *c,int clen,const char *d,int dlen, int *plen) { int len; char *p; char *pp; assert(a); assert(b); assert(c); assert(d); len = alen + blen + clen + dlen; if (plen) *plen = len; p = VMLOG_NEW_ARRAY(char,len+1); pp = p; memcpy(pp,a,alen); pp += alen; memcpy(pp,b,blen); pp += blen; memcpy(pp,c,clen); pp += clen; memcpy(pp,d,dlen); pp += dlen; *pp = 0; return p; } char *vmlog_concat3(const char *a,const char *b,const char *c,int *plen) { int len,lena,lenb,lenc; char *p; char *pp; assert(a); assert(b); assert(c); lena = strlen(a); lenb = strlen(b); lenc = strlen(c); len = lena + lenb + lenc; if (plen) *plen = len; p = VMLOG_NEW_ARRAY(char,len+1); pp = p; memcpy(pp,a,lena); pp += lena; memcpy(pp,b,lenb); pp += lenb; memcpy(pp,c,lenc); pp += lenc; *pp = 0; return p; } /*** file ops ********************************************************/ void vmlog_file_open(vmlog_file *file,const char *fname,vmlog_fmode fmode) { int r; struct stat st; int flags = 0; assert(file); switch (fmode) { case vmlogRead: flags = O_RDONLY; break; case vmlogAppend: flags = O_WRONLY | O_APPEND | O_CREAT; break; case vmlogTruncateAppend: flags = O_WRONLY | O_APPEND | O_CREAT | O_TRUNC; break; default: vmlog_die("unknown fmode for opening file: %s: %d", fname,fmode); } r = open(fname,flags,0644); if (r == -1) { vmlog_die("could not open file: %s: %s",fname,strerror(errno)); } file->fd = r; file->fnamelen = strlen(fname); file->fname = vmlog_memdup(fname,file->fnamelen+1); r = fstat(file->fd,&st); if (r == -1) { vmlog_die("could not stat file: %s: %s",fname,strerror(errno)); } file->ofs = (fmode == vmlogRead) ? 0 : st.st_size; } void vmlog_file_close(vmlog_file *file) { assert(file); if (file->fd == -1) return; close(file->fd); file->fd = -1; VMLOG_FREE_ARRAY(char,file->fnamelen+1,file->fname); file->fname = NULL; file->fnamelen = 0; } void vmlog_file_append(vmlog_file *file,const void *data,int len) { int r; assert(len >= 0); if (!len) return; assert(data); do { r = write(file->fd,data,len); } while (r == -1 && errno == EINTR); if (r == -1) { vmlog_die("could not write to file: %s: %s",file->fname,strerror(errno)); } if (r != len) { vmlog_die("could not write all data to file: %s",file->fname); } file->ofs += len; } void vmlog_file_stat(vmlog_file *file) { int r; struct stat st; r = fstat(file->fd,&st); if (r == -1) vmlog_die("could not stat file: %s: %s",file->fname,strerror(errno)); file->size = st.st_size; } void * vmlog_file_mmap(const char *fname,int *plen) { int fd; int r; struct stat st; void *m; fd = open(fname,O_RDONLY); if (fd == -1) vmlog_die("could not open file: %s: %s",fname,strerror(errno)); r = fstat(fd,&st); if (r == -1) vmlog_die("could not stat file: %s: %s",fname,strerror(errno)); if (plen) *plen = st.st_size; if (st.st_size) { m = mmap(NULL,st.st_size,PROT_READ,MAP_PRIVATE,fd,0); if (m == MAP_FAILED) { vmlog_die("could not mmap file: %s: %s",fname,strerror(errno)); } } else { /* fake a pointer */ m = VMLOG_NEW(char); } close(fd); return m; } void vmlog_file_munmap(void *m,int len) { int r; if (len) { r = munmap(m,len); if (r != 0) vmlog_warn("could not munmap file: %s",strerror(errno)); } else { VMLOG_FREE(char,m); } } void vmlog_file_seek(vmlog_file *file,vmlog_fofs_t ofs) { off64_t r; r = lseek64(file->fd,ofs,SEEK_SET); if (r == (off64_t)-1) vmlog_die("could not seek position in file: %s: %s", file->fname,strerror(errno)); file->ofs = ofs; } /*** string storage **************************************************/ static void vmlog_add_string(vmlog_log *vml,const char *data,int len) { vmlog_string_entry strent; #if defined(VMLOG_ENDIAN_CONVERT_WRITE) vmlog_fofs_t tmp; #endif assert(vml); if (vml->strfile.fd == -1) return; if (vml->idxfile.fd == -1) return; strent.ofs = vml->strfile.ofs; strent.len = len; #if defined(VMLOG_ENDIAN_CONVERT_WRITE) #if defined(VMLOG_HOST_LITTLE_ENDIAN) tmp = ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[0]) << 56) | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[1]) << 48) | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[2]) << 40) | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[3]) << 32) | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[4]) << 24) | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[5]) << 16) | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[6]) << 8) | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[7]) << 0); strent.ofs = tmp; tmp = ((vmlog_fofs_t)(((unsigned char*)&strent.len)[0]) << 24) | ((vmlog_fofs_t)(((unsigned char*)&strent.len)[1]) << 16) | ((vmlog_fofs_t)(((unsigned char*)&strent.len)[2]) << 8) | ((vmlog_fofs_t)(((unsigned char*)&strent.len)[3]) << 0); strent.len = tmp; #else tmp = ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[7]) << 56) | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[6]) << 48) | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[5]) << 40) | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[4]) << 32) | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[3]) << 24) | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[2]) << 16) | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[1]) << 8) | ((vmlog_fofs_t)(((unsigned char*)&strent.ofs)[0]) << 0); strent.ofs = tmp; tmp = ((vmlog_fofs_t)(((unsigned char*)&strent.len)[3]) << 24) | ((vmlog_fofs_t)(((unsigned char*)&strent.len)[2]) << 16) | ((vmlog_fofs_t)(((unsigned char*)&strent.len)[1]) << 8) | ((vmlog_fofs_t)(((unsigned char*)&strent.len)[0]) << 0); strent.len = tmp; #endif #endif /* defined(VMLOG_ENDIAN_CONVERT_WRITE) */ vmlog_file_append(&(vml->strfile),data,len); vmlog_file_append(&(vml->idxfile),&strent,sizeof(vmlog_string_entry)); } /*** index functions *************************************************/ static int vmlog_is_ignored(vmlog_log *vml,int index) { return (index < vml->ignorelistlen); } /*** thread log functions ********************************************/ static void vmlog_thread_log_alloc_logbuf(vmlog_thread_log *tlog,int cap) { assert(tlog); assert(cap >= 0); if (cap) { VMLOG_XZNEW_ARRAY(tlog->logbuf,vmlog_log_entry,cap); } else { tlog->logbuf = NULL; } tlog->logbufptr = tlog->logbuf; tlog->logbufend = tlog->logbuf + cap; tlog->logbufcap = cap; } static void vmlog_thread_log_flush(vmlog_thread_log *tlog) { assert(tlog); assert(tlog->logbuf); vmlog_file_append(&(tlog->logfile),tlog->logbuf, (tlog->logbufptr - tlog->logbuf) * sizeof(vmlog_log_entry)); tlog->logbufptr = tlog->logbuf; } static void vmlog_thread_log_realloc_frames(vmlog_thread_log *tlog,int cap) { vmlog_frame *oldframes; assert(tlog); assert(cap >= tlog->depth); oldframes = tlog->frames; if (cap) { VMLOG_XZNEW_ARRAY(tlog->frames,vmlog_frame,cap); } else { tlog->frames = NULL; } if (oldframes) { if (tlog->frames) { memcpy(tlog->frames,oldframes,sizeof(vmlog_frame) * tlog->depth); } VMLOG_FREE_ARRAY(vmlog_frame,tlog->framescap,oldframes); } tlog->framescap = cap; } void vmlog_thread_log_append(vmlog_thread_log *tlog,vmlog_log_entry *logent) { #if defined(VMLOG_ENDIAN_CONVERT_WRITE) unsigned int tmp; #if defined(VMLOG_HOST_LITTLE_ENDIAN) tmp = ((unsigned int)(((unsigned char*)logent)[3]) << 0) | ((unsigned int)(((unsigned char*)logent)[2]) << 8) | ((unsigned int)(((unsigned char*)logent)[1]) << 16); #else tmp = ((unsigned int)(((unsigned char*)logent)[1]) << 0) | ((unsigned int)(((unsigned char*)logent)[2]) << 8) | ((unsigned int)(((unsigned char*)logent)[3]) << 16); #endif logent->index = tmp; #endif /* defined(VMLOG_ENDIAN_CONVERT_WRITE) */ if (tlog->logbufptr) { if (tlog->logbufptr == tlog->logbufend) { vmlog_thread_log_flush(tlog); } *tlog->logbufptr++ = *logent; } else { vmlog_file_append(&(tlog->logfile),logent,sizeof(vmlog_log_entry)); } } #define VMLOG_INT2STR_BUFFER 20 vmlog_thread_log *vmlog_thread_log_new(vmlog_log *vml,void *threadid,int index) { vmlog_thread_log *tlog; char buf[VMLOG_INT2STR_BUFFER]; int r; char *name; int namelen; VMLOG_XZNEW(tlog,vmlog_thread_log); tlog->threadid = threadid; tlog->threadidx = index; tlog->logfile.fd = -1; vmlog_thread_log_realloc_frames(tlog,VMLOG_INITIAL_FRAMES_CAPACITY); if (vml && vml->prefix) { r = snprintf(buf,VMLOG_INT2STR_BUFFER,"%d",index); assert(r < VMLOG_INT2STR_BUFFER); buf[VMLOG_INT2STR_BUFFER-1] = 0; name = vmlog_concat4len(vml->prefix,vml->prefixlen, ".",1, buf,strlen(buf), ".log",4, &namelen); vmlog_file_open(&(tlog->logfile),name,vmlogTruncateAppend); VMLOG_FREE_ARRAY(char,namelen+1,name); } return tlog; } void vmlog_thread_log_free(vmlog_thread_log *tlog) { if (!tlog) return; if (tlog->logbuf) vmlog_thread_log_flush(tlog); vmlog_file_close(&(tlog->logfile)); if (tlog->frames) { VMLOG_FREE_ARRAY(vmlog_frame,tlog->framescap,tlog->frames); } VMLOG_FREE(vmlog_thread_log,tlog); } vmlog_frame * vmlog_thread_log_enter(vmlog_thread_log *tlog,int index,vmlog_seq_t seq) { vmlog_frame *frame; if (tlog->depth < 0) { vmlog_warn("negative call frame depth %d at seq " VMLOG_SEQ_FMT, tlog->depth,seq); return NULL; } if (tlog->depth >= tlog->framescap) vmlog_thread_log_realloc_frames(tlog,tlog->framescap * 2); frame = tlog->frames + tlog->depth; frame->index = index; frame->seq = seq; tlog->depth++; return frame; } vmlog_frame * vmlog_thread_log_leave(vmlog_thread_log *tlog,int index,vmlog_seq_t seq) { vmlog_frame *frame; if (--tlog->depth < 0) { vmlog_warn("negative call frame depth %d at seq " VMLOG_SEQ_FMT, tlog->depth,seq); return NULL; } frame = tlog->frames + tlog->depth; if (index != frame->index) vmlog_warn("mismatched leave at seq " VMLOG_SEQ_FMT ": entered index %d, left index %d", seq,frame->index,index); return frame; } /*** tag definitions *************************************************/ /* RETURNS */ /* the tag number, or -1 if the tag name is invalid */ int vmlog_tag_from_name(const char *name,int namelen) { vmlog_tag_definition *td; int i; if (!name || namelen < 1) return -1; td = vmlog_tag_definitions; i = 0; while (td->name) { if (namelen == strlen(td->name) && strncmp(td->name,name,namelen) == 0) { return i; } td++; i++; } return -1; } /*** hash functions **************************************************/ static unsigned int vmlog_thread_hash(void *threadid) { /* XXX use a better hash function? */ return (unsigned int)(ptrint)threadid; } static unsigned int vmlog_string_hash(const char *data,int len) { register const unsigned char *p = (const unsigned char *) data; register unsigned int hash; register int i; /* The algorithm is the "One-at-a-time" algorithm as published */ /* by Bob Jenkins on http://burtleburtle.net/bob/hash/doobs.html. */ hash = 0; for (i=len; i--;) { hash += *p++; hash += (hash << 10); hash ^= (hash >> 6); } hash += (hash << 3); hash ^= (hash >> 11); hash += (hash << 15); return hash; } /*** hash tables *****************************************************/ static vmlog_thread_log *vmlog_get_thread_log(vmlog_log *vml,void *threadid) { unsigned int h; vmlog_hash_entry *preventry = NULL; vmlog_hash_entry *entry; vmlog_thread_log *tlog; assert(vml); h = vmlog_thread_hash(threadid); entry = vml->threadhash.table + (h % vml->threadhash.size); do { tlog = (vmlog_thread_log *)entry->data; if (tlog && tlog->threadid == threadid) return tlog; preventry = entry; entry = entry->hashlink; } while (entry); /* this is a new threadid */ tlog = vmlog_thread_log_new(vml,threadid,vml->threadhash.nentries++); assert(preventry); if (preventry->data) { VMLOG_XZNEW(entry,vmlog_hash_entry); preventry->hashlink = entry; } else { entry = preventry; } entry->data = tlog; /* XXX maybe rehash */ return tlog; } int vmlog_get_string_index(vmlog_log *vml,const char *data,int len) { unsigned int hash; vmlog_hash_entry *entry; vmlog_hash_entry *preventry = NULL; assert(vml); assert(data); assert(len >= 0); hash = vmlog_string_hash(data,len); entry = vml->stringhash.table + (hash % vml->stringhash.size); do { if (entry->len == len && entry->data && memcmp(data,entry->data,len) == 0) return entry->index; preventry = entry; entry = entry->hashlink; } while (entry); /* this is a new string */ assert(preventry); if (preventry->data) { VMLOG_XZNEW(entry,vmlog_hash_entry); preventry->hashlink = entry; } else { entry = preventry; } entry->data = vmlog_memdup(data,len); entry->len = len; entry->index = vml->stringhash.nentries++; vmlog_add_string(vml,data,len); return entry->index; } static void vmlog_hashtable_init(vmlog_hash_table *ht,int size) { assert(ht); assert(size > 0); ht->size = size; VMLOG_XZNEW_ARRAY(ht->table,vmlog_hash_entry,size); ht->nentries = 0; } static void vmlog_hashtable_free(vmlog_hash_table *ht,vmlog_hash_entry_destructor destr) { int i; vmlog_hash_entry *entry,*next; assert(ht); for (i=0; isize; ++i) { entry = ht->table + i; if (destr) destr(entry); next = entry->hashlink; while (next) { entry = next; if (destr) destr(entry); next = entry->hashlink; VMLOG_FREE(vmlog_hash_entry,entry); } } VMLOG_FREE_ARRAY(vmlog_hash_entry,ht->size,ht->table); memset(ht,0,sizeof(vmlog_hash_table)); } static void vmlog_thread_log_destructor(vmlog_hash_entry *entry) { vmlog_thread_log *tlog; assert(entry); tlog = (vmlog_thread_log *)entry->data; vmlog_thread_log_free(tlog); } static void vmlog_string_destructor(vmlog_hash_entry *entry) { char *str; assert(entry); str = (char *)entry->data; if (str) { VMLOG_FREE_ARRAY(char,entry->len,str); } } static void vmlog_open_string_files(vmlog_log *vml,int truncate) { char *name; int namelen; int fmode; if (!vml->prefix) return; fmode = (truncate) ? vmlogTruncateAppend : vmlogAppend; name = vmlog_concat3(vml->prefix,"",".idx",&namelen); vmlog_file_open(&(vml->idxfile),name,fmode); VMLOG_FREE_ARRAY(char,namelen+1,name); name = vmlog_concat3(vml->prefix,"",".str",&namelen); vmlog_file_open(&(vml->strfile),name,fmode); VMLOG_FREE_ARRAY(char,namelen+1,name); } void vmlog_load_stringhash(vmlog_log *vml,const char *prefix) { int n; vmlog_string_entry *idxmap; vmlog_string_entry *strent; char *strmap; int idxlen; int strlen; char *idxfname; char *strfname; int idxnamelen; int strnamelen; assert(vml); assert(prefix); idxfname = vmlog_concat3(prefix,".","idx",&idxnamelen); strfname = vmlog_concat3(prefix,".","str",&strnamelen); vmlog_hashtable_free(&(vml->stringhash),vmlog_string_destructor); vmlog_hashtable_init(&(vml->stringhash),VMLOG_INITIAL_STRING_HASH_SIZE); vmlog_file_close(&(vml->idxfile)); vmlog_file_close(&(vml->strfile)); vmlog_open_string_files(vml,1); idxmap = vmlog_file_mmap(idxfname,&idxlen); strmap = vmlog_file_mmap(strfname,&strlen); n = idxlen / sizeof(vmlog_string_entry); strent = idxmap; while (n--) { vmlog_get_string_index(vml,strmap + strent->ofs,strent->len); strent++; } vmlog_file_munmap(idxmap,idxlen); vmlog_file_munmap(strmap,strlen); VMLOG_FREE_ARRAY(char,idxnamelen+1,idxfname); VMLOG_FREE_ARRAY(char,strnamelen+1,strfname); } /*** public functions ************************************************/ vmlog_log * vmlog_log_new(const char *prefix,int truncate) { vmlog_log *vml; VMLOG_XZNEW(vml,vmlog_log); vml->idxfile.fd = -1; vml->strfile.fd = -1; vmlog_hashtable_init(&(vml->stringhash),VMLOG_INITIAL_STRING_HASH_SIZE); vmlog_hashtable_init(&(vml->threadhash),VMLOG_INITIAL_THREAD_HASH_SIZE); if (prefix) { vml->prefixlen = strlen(prefix); vml->prefix = vmlog_memdup(prefix,vml->prefixlen+1); vmlog_open_string_files(vml,truncate); } return vml; } void vmlog_log_free(vmlog_log *vml) { if (!vml) return; VMLOG_FREE_ARRAY(char,vml->prefixlen+1,vml->prefix); vml->prefix = NULL; vml->prefixlen = 0; vmlog_hashtable_free(&(vml->threadhash),vmlog_thread_log_destructor); vmlog_hashtable_free(&(vml->stringhash),vmlog_string_destructor); vmlog_file_close(&(vml->idxfile)); vmlog_file_close(&(vml->strfile)); VMLOG_FREE(vmlog_log,vml); } static void vmlog_log_enter_tag(vmlog_log *vml,void *threadid,int tag,const char *name,int namelen) { vmlog_thread_log *tlog; int index; vmlog_log_entry logent; assert(vml); assert(name); assert(namelen >= 0); VMLOG_LOCK(); tlog = vmlog_get_thread_log(vml,threadid); index = vmlog_get_string_index(vml,name,namelen); VMLOG_UNLOCK(); if (tlog->ignoredepth) { tlog->ignoredepth++; return; } if (vmlog_is_ignored(vml,index)) { tlog->ignoredepth++; return; } logent.tag = tag; logent.index = index; vmlog_thread_log_append(tlog,&logent); tlog->seq++; } static void vmlog_log_leave_tag(vmlog_log *vml,void *threadid,int tag,const char *name,int namelen) { vmlog_thread_log *tlog; int index; vmlog_log_entry logent; assert(vml); assert(name); assert(namelen >= 0); VMLOG_LOCK(); tlog = vmlog_get_thread_log(vml,threadid); index = vmlog_get_string_index(vml,name,namelen); VMLOG_UNLOCK(); if (tlog->ignoredepth) { tlog->ignoredepth--; return; } logent.tag = tag; logent.index = index; vmlog_thread_log_append(tlog,&logent); tlog->seq++; } void vmlog_log_enter(vmlog_log *vml,void *threadid,const char *name,int namelen) { vmlog_log_enter_tag(vml,threadid,VMLOG_TAG_ENTER,name,namelen); } void vmlog_log_rerol(vmlog_log *vml,void *threadid,const char *name,int namelen) { vmlog_log_enter_tag(vml,threadid,VMLOG_TAG_REROL,name,namelen); } void vmlog_log_leave(vmlog_log *vml,void *threadid,const char *name,int namelen) { vmlog_log_leave_tag(vml,threadid,VMLOG_TAG_LEAVE,name,namelen); } void vmlog_log_unrol(vmlog_log *vml,void *threadid,const char *name,int namelen) { vmlog_log_leave_tag(vml,threadid,VMLOG_TAG_UNROL,name,namelen); } void vmlog_log_throw(vmlog_log *vml,void *threadid,const char *name,int namelen) { vmlog_thread_log *tlog; int index; vmlog_log_entry logent; assert(vml); assert(name); assert(namelen >= 0); VMLOG_LOCK(); tlog = vmlog_get_thread_log(vml,threadid); index = vmlog_get_string_index(vml,name,namelen); VMLOG_UNLOCK(); if (tlog->ignoredepth) return; logent.tag = VMLOG_TAG_THROW; logent.index = index; vmlog_thread_log_append(tlog,&logent); tlog->seq++; } void vmlog_log_catch(vmlog_log *vml,void *threadid,const char *name,int namelen) { vmlog_thread_log *tlog; int index; vmlog_log_entry logent; assert(vml); assert(name); assert(namelen >= 0); VMLOG_LOCK(); tlog = vmlog_get_thread_log(vml,threadid); index = vmlog_get_string_index(vml,name,namelen); VMLOG_UNLOCK(); if (tlog->ignoredepth) return; logent.tag = VMLOG_TAG_CATCH; logent.index = index; vmlog_thread_log_append(tlog,&logent); tlog->seq++; } void vmlog_log_unwnd(vmlog_log *vml,void *threadid,const char *name,int namelen) { vmlog_thread_log *tlog; int index; vmlog_log_entry logent; assert(vml); assert(name); assert(namelen >= 0); VMLOG_LOCK(); tlog = vmlog_get_thread_log(vml,threadid); index = vmlog_get_string_index(vml,name,namelen); VMLOG_UNLOCK(); if (tlog->ignoredepth) { tlog->ignoredepth--; return; } logent.tag = VMLOG_TAG_UNWND; logent.index = index; vmlog_thread_log_append(tlog,&logent); tlog->seq++; } void vmlog_log_signl(vmlog_log *vml,void *threadid,const char *name,int namelen) { vmlog_thread_log *tlog; int index; vmlog_log_entry logent; assert(vml); assert(name); assert(namelen >= 0); VMLOG_LOCK(); tlog = vmlog_get_thread_log(vml,threadid); index = vmlog_get_string_index(vml,name,namelen); VMLOG_UNLOCK(); logent.tag = VMLOG_TAG_SIGNL; logent.index = index; vmlog_thread_log_append(tlog,&logent); tlog->seq++; } void vmlog_log_load_ignorelist(vmlog_log *vml,const char *prefix) { assert(vml); assert(prefix); vmlog_load_stringhash(vml,prefix); vml->ignorelistlen = vml->stringhash.nentries; } /*** ring buffer functions *******************************************/ static void vmlog_ringbuf_visualize(vmlog_ringbuf *ring) { int i; fprintf(stdout,"vmlog_ringbuf %p: bufsize=%d availbefore=%d availafter=%d\n", (void*)ring,ring->bufsize, ring->debug_availbefore,ring->debug_availafter); for (i=0; i<=ring->bufsize; ++i) { if (i == ring->bufsize) { fprintf(stdout,"%3d: xxxxxxxxxxxxx",i); } else { fprintf(stdout,"%3d: %2d %10d",i,ring->buf[i].tag,ring->buf[i].index); } if (ring->start - ring->buf == i) fputs(" start",stdout); if (ring->cur - ring->buf == i) fputs(" cur",stdout); if (ring->end - ring->buf == i) fputs(" end",stdout); if (ring->cur - ring->buf == i) fprintf(stdout," (" VMLOG_SEQ_FMT ")",ring->seq); fputc('\n',stdout); } } static void vmlog_ringbuf_check_invariants(vmlog_ringbuf *ring) { /* vmlog_ringbuf_visualize(ring); */ assert(ring); assert(ring->bufsize > 0); assert(ring->bufend == ring->buf + ring->bufsize); assert(ring->start >= ring->buf && ring->start < ring->bufend); assert((ring->end > ring->buf && ring->end <= ring->bufend) || (ring->end == ring->start)); assert(ring->debug_availbefore >= 0); assert(ring->debug_availafter >= 0); assert(ring->debug_availbefore + ring->debug_availafter <= ring->bufsize); /* ring->cur can point to any present */ /* element (#) or be equal to ring->end*/ if (ring->end >= ring->start) { /* case A: ring->end >= ring->start */ /* */ /* -------#############----------- */ /* ^ ^ ^ ^ */ /* buf start end bufend */ assert(ring->cur >= ring->start && ring->cur <= ring->end); assert(ring->cur - ring->start == ring->debug_availbefore); assert(ring->end - ring->cur == ring->debug_availafter); } else { /* case B: ring->end < ring->start */ /* */ /* #######------------############ */ /* ^ ^ ^ ^ */ /* buf end start bufend */ assert((ring->cur >= ring->start && ring->cur < ring->bufend) || (ring->cur >= ring->buf && ring->cur <= ring->end)); if (ring->cur >= ring->start) { assert(ring->cur - ring->start == ring->debug_availbefore); assert((ring->bufend - ring->cur) + (ring->end - ring->buf) == ring->debug_availafter); } else { assert((ring->bufend - ring->start) + (ring->cur - ring->buf) == ring->debug_availbefore); assert(ring->end - ring->cur == ring->debug_availafter); } } } vmlog_ringbuf * vmlog_ringbuf_new(const char *fname,int bufsize) { vmlog_ringbuf *ring; assert(bufsize > 0); VMLOG_XZNEW(ring,vmlog_ringbuf); VMLOG_XZNEW_ARRAY(ring->buf,vmlog_log_entry,bufsize); ring->bufsize = bufsize; ring->bufend = ring->buf + bufsize; ring->start = ring->buf; ring->end = ring->buf; ring->cur = ring->buf; vmlog_file_open(&(ring->file),fname,vmlogRead); vmlog_file_stat(&(ring->file)); vmlog_ringbuf_check_invariants(ring); return ring; } void vmlog_ringbuf_free(vmlog_ringbuf *ring) { if (!ring) return; vmlog_ringbuf_check_invariants(ring); vmlog_file_close(&(ring->file)); VMLOG_FREE_ARRAY(vmlog_log_entry,ring->bufsize,ring->buf); VMLOG_FREE(vmlog_ringbuf,ring); } static int vmlog_ringbuf_read(vmlog_ringbuf *ring,vmlog_log_entry *buf, vmlog_seq_t seq,int n) { int r; vmlog_fofs_t ofs; ofs = seq * sizeof(vmlog_log_entry); if (ofs != ring->file.ofs) vmlog_file_seek(&(ring->file),ofs); do { /* fprintf(stdout,"vmlog_ringbuf_read(%p,%d,%d)\n", (void*)ring,buf-ring->buf,n); */ r = read(ring->file.fd,buf,n * sizeof(vmlog_log_entry)); } while (r == -1 && errno == EINTR); if (r == -1) vmlog_die("reading from file: %s: %s",ring->file.fname,strerror(errno)); ring->file.ofs += r; if (r % sizeof(vmlog_log_entry) != 0) { /* XXX */ vmlog_warn("partial log entry read from file: %s",ring->file.fname); } return r / sizeof(vmlog_log_entry); } static int vmlog_ringbuf_fill_forward(vmlog_ringbuf *ring,vmlog_log_entry *fillstart, vmlog_log_entry *fillend,vmlog_seq_t seq,int len) { int space; int n; int read; vmlog_log_entry *oldend; #if 0 fprintf(stdout,"vmlog_ringbuf_fill_forward(%p,%d,%d," VMLOG_SEQ_FMT ",%d)\n", (void*)ring,fillstart-ring->buf,fillend-ring->buf,seq,len); #endif vmlog_ringbuf_check_invariants(ring); space = fillend - fillstart; n = (len <= space) ? len : space; if (n <= 0) return 0; read = vmlog_ringbuf_read(ring,fillstart,seq,n); if (!read) return 0; oldend = ring->end; ring->end = fillstart + read; ring->debug_availafter += read; if (ring->cur == ring->bufend) ring->cur = ring->buf; if (ring->start >= fillstart && ring->start != oldend) { /* check if old entries have been overwritten */ if (ring->start <= ring->end) { ring->debug_availbefore -= ring->end - ring->start + 1; ring->start = ring->end + 1; if (ring->start >= ring->bufend) { ring->start = ring->buf; ring->debug_availbefore = ring->cur - ring->start; } } } vmlog_ringbuf_check_invariants(ring); return read; } static int vmlog_ringbuf_fill_backward(vmlog_ringbuf *ring,vmlog_log_entry *fillstart, vmlog_log_entry *fillend,vmlog_seq_t seq,int len) { int space; int n; int read; vmlog_log_entry *oldstart; vmlog_log_entry *oldend; #if 0 fprintf(stdout,"vmlog_ringbuf_fill_backward(%p,%d,%d," VMLOG_SEQ_FMT ",%d)\n", (void*)ring,fillstart-ring->buf,fillend-ring->buf,seq,len); #endif vmlog_ringbuf_check_invariants(ring); space = fillend - fillstart; n = (len <= space) ? len : space; if (n <= 0) return 0; seq += space - n; fillstart += space - n; read = vmlog_ringbuf_read(ring,fillstart,seq,n); if (read != n) vmlog_die("could not read backward in file: %s: %s", ring->file.fname,strerror(errno)); oldstart = ring->start; ring->start = fillstart; ring->debug_availbefore += read; oldend = ring->end; if (ring->end <= fillend && ring->end != oldstart) { /* check if old entries have been overwritten */ if (ring->start <= ring->end) { ring->debug_availafter -= ring->end - ring->start + 1; ring->end = ring->start - 1; if (ring->end <= ring->buf) { ring->end = ring->bufend; if (ring->cur == ring->buf && ring->end == ring->bufend) ring->cur = ring->bufend; ring->debug_availafter = ring->end - ring->cur; } } } if (ring->end == ring->buf) { assert(oldstart == oldend); ring->end = ring->bufend; } if (ring->cur == ring->buf && ring->end == ring->bufend) ring->cur = ring->bufend; vmlog_ringbuf_check_invariants(ring); return read; } int vmlog_ringbuf_fill(vmlog_ringbuf *ring,int len) { int count; int read; vmlog_log_entry *fillend; vmlog_seq_t seq; assert(ring); if (!len) return 0 /*XXX*/; count = 0; vmlog_ringbuf_check_invariants(ring); if (len > 0) { if (ring->end >= ring->cur) { /* case A'1: ring->end >= ring->start */ /* */ /* vvvvvvvvvvv */ /* ------OOOOO#########----------- */ /* ^ ^ ^ ^ ^ */ /* buf start cur end bufend */ /* case B'1: ring->end < ring->start */ /* */ /* vvvvvvvvvvv */ /* OOOOOOOOOOO#########----OOOOOOO */ /* ^ ^ ^ ^ ^ */ /* buf cur end st. bufend */ /* fill space at end of buf */ seq = ring->seq + (ring->end - ring->cur); read = vmlog_ringbuf_fill_forward(ring,ring->end,ring->bufend, seq,len); count += read; len -= read; if (ring->end != ring->bufend) goto no_more_entries; /* case A'1: ring->end >= ring->start */ /* */ /* vvvvvvvvvv */ /* ------OOOOO#################### */ /* ^ ^ ^ ^ */ /* buf start cur end==bufend */ /* case B'1: ring->end < ring->start */ /* */ /* vvvvvvvvvv */ /* OOOOOOOOOOO#################### */ /* ^ ^ ^ */ /* buf==start cur end==bufend */ /* fill space at beg of buf */ seq = ring->seq + (ring->end - ring->cur); fillend = (ring->cur == ring->bufend) ? ring->bufend : (ring->cur - 1); read = vmlog_ringbuf_fill_forward(ring,ring->buf,fillend, seq,len); count += read; len -= read; } else { /* ring->end < ring->cur */ /* no case A'2 */ assert(ring->end < ring->start); /* case B'2: ring->end < ring->start */ /* */ /* vvvvvvvvvvv */ /* #####------OOOOOO############## */ /* ^ ^ ^ ^ ^ */ /* buf end start cur bufend */ /* fill space in middle of buf */ seq = ring->seq + (ring->bufend - ring->cur) + (ring->end - ring->buf); read = vmlog_ringbuf_fill_forward(ring,ring->end,ring->cur - 1, seq,len); count += read; len -= read; } } else { len = -len; if (len > ring->seq) len = ring->seq; if (ring->start <= ring->cur) { /* case A'1: ring->end >= ring->start */ /* */ /* vvvvvv */ /* ------#####OOOOOOOOO----------- */ /* ^ ^ ^ ^ ^ */ /* buf start cur end bufend */ /* case B'2: ring->end < ring->start */ /* */ /* vvvvvvvvvvv */ /* OOOOO------######OOOOOOOOOOOOOO */ /* ^ ^ ^ ^ ^ */ /* buf end start cur bufend */ /* fill space at beg of buf */ seq = ring->seq - (ring->cur - ring->buf); read = vmlog_ringbuf_fill_backward(ring,ring->buf,ring->start, seq,len); count += read; len -= read; if (ring->start != ring->buf) goto no_more_entries; /* case A'1: ring->end >= ring->start */ /* */ /* vvvvvvvvvvvvvvvvvvv */ /* ###########OOOOOOOOO----------- */ /* ^ ^ ^ ^ */ /* buf=start cur end bufend */ /* case B'2: ring->end < ring->start */ /* */ /* vvvvvvvvvvvvv */ /* #################OOOOOOOOOOOOOO */ /* ^ ^ ^ */ /* buf=start cur end=bufend */ /* fill space at end of buf */ seq -= (ring->bufend - ring->cur - 1); read = vmlog_ringbuf_fill_backward(ring,ring->cur+1,ring->bufend, seq,len); count += read; len -= read; } else { /* ring->start > ring->cur */ /* case B'1: ring->end < ring->start */ /* */ /* vvvvvvvvvvvv */ /* ###########OOOOOOOOO----####### */ /* ^ ^ ^ ^ ^ */ /* buf cur end st. bufend */ /* no case A'2 */ assert(ring->end < ring->start); /* fill space in middle of buf */ seq = ring->seq - (ring->cur - ring->buf) - (ring->bufend - ring->cur - 1); read = vmlog_ringbuf_fill_backward(ring,ring->cur + 1,ring->start, seq,len); count += read; len -= read; } } no_more_entries: vmlog_ringbuf_check_invariants(ring); return count; } static void vmlog_ringbuf_reset(vmlog_ringbuf *ring) { ring->start = ring->buf; ring->cur = ring->buf; ring->end = ring->buf; ring->debug_availbefore = 0; ring->debug_availafter = 0; vmlog_ringbuf_check_invariants(ring); } static int vmlog_ringbuf_advance(vmlog_ringbuf *ring,int diff) { int space; if (diff > 0) { if (ring->end >= ring->start) { /* case A */ advance_cur_to_end: space = ring->end - ring->cur; if (space <= 0) return 0; if (space < diff) diff = space; simple_advance: ring->cur += diff; ring->seq += diff; ring->debug_availbefore += diff; ring->debug_availafter -= diff; return diff; } else { /* case B */ if (ring->end >= ring->cur) goto advance_cur_to_end; space = ring->bufend - ring->cur; if (space > diff) goto simple_advance; ring->cur = ring->buf - space; goto advance_cur_to_end; } } else if (diff < 0) { if (ring->end >= ring->start) { /* case A */ advance_cur_to_start: space = ring->cur - ring->start; if (space <= 0) return 0; if (-space > diff) diff = -space; goto simple_advance; } else { /* case B */ if (ring->cur >= ring->start) goto advance_cur_to_start; space = ring->cur - ring->buf; if (space >= -diff) goto simple_advance; ring->cur = ring->bufend + space; goto advance_cur_to_start; } } else { return 0; } } void vmlog_ringbuf_seek(vmlog_ringbuf *ring,vmlog_seq_t seq) { vmlog_seq_t diff; vmlog_ringbuf_check_invariants(ring); diff = seq - ring->seq; if (abs(diff) < ring->bufsize) diff -= vmlog_ringbuf_advance(ring,(int)diff); if (!diff) return; vmlog_ringbuf_reset(ring); vmlog_file_seek(&(ring->file),seq * sizeof(vmlog_log_entry)); ring->seq = seq; vmlog_ringbuf_check_invariants(ring); } vmlog_log_entry * vmlog_ringbuf_next(vmlog_ringbuf *ring,int prefetch) { vmlog_ringbuf_check_invariants(ring); while (1) { if (ring->end >= ring->start) { /* case A */ if (ring->cur < ring->end) { ring->debug_availafter--; ring->debug_availbefore++; ring->seq++; return ring->cur++; } } else { /* case B */ if (ring->end >= ring->cur) { if (ring->cur < ring->end) { ring->debug_availafter--; ring->debug_availbefore++; ring->seq++; return ring->cur++; } } else { if (ring->cur < ring->bufend) { vmlog_log_entry *r; r = ring->cur; ring->seq++; if (++ring->cur == ring->bufend) ring->cur = ring->buf; ring->debug_availafter--; ring->debug_availbefore++; return r; } } } if (!vmlog_ringbuf_fill(ring,prefetch)) return NULL; } assert(0); /* NOT REACHED */ return NULL; } vmlog_log_entry * vmlog_ringbuf_prev(vmlog_ringbuf *ring,int prefetch) { vmlog_ringbuf_check_invariants(ring); while (1) { if (ring->end >= ring->start) { /* case A */ if (ring->cur > ring->start) { ring->debug_availafter++; ring->debug_availbefore--; ring->seq--; return --ring->cur; } } else { /* case B */ if (ring->cur >= ring->start) { if (ring->cur > ring->start) { ring->debug_availafter++; ring->debug_availbefore--; ring->seq--; return --ring->cur; } } else { if (--ring->cur < ring->buf) ring->cur = ring->bufend - 1; ring->seq--; ring->debug_availafter++; ring->debug_availbefore--; return ring->cur; } } if (!vmlog_ringbuf_fill(ring,-prefetch)) return NULL; } assert(0); /* NOT REACHED */ return NULL; } /*** option parsing **************************************************/ vmlog_options *vmlog_opt_new(void) { vmlog_options *opts; VMLOG_XZNEW(opts,vmlog_options); return opts; } int vmlog_opt_parse_seq(const char *arg,int len,vmlog_seq_t *seq) { char *buf; char *endptr; int r; assert(arg); if (len < 1) return 0; buf = vmlog_strdup(arg,len); *seq = strtoll(buf,&endptr,10); r = (endptr[0] == 0); VMLOG_FREE_ARRAY(char,len+1,buf); return r; } int vmlog_opt_parse_range(const char *arg,vmlog_seq_t *start,vmlog_seq_t *end) { const char *sep; int len; sep = strchr(arg,':'); if (!sep) { len = strlen(arg); if (!vmlog_opt_parse_seq(arg,len,start)) return 0; *end = *start; return 1; } len = sep - arg; if (!len) { *start = 0; } else { if (!vmlog_opt_parse_seq(arg,len,start)) return 0; } len = strlen(arg) - len - 1; if (!len) { *end = VMLOG_SEQ_MAX; } else { if (!vmlog_opt_parse_seq(sep+1,len,end)) return 0; } return 1; } void vmlog_opt_set_prefix(vmlog_options *opts, const char *arg) { opts->prefix = vmlog_strdup(arg,strlen(arg)); } void vmlog_opt_set_stringprefix(vmlog_options *opts, const char *arg) { opts->stringprefix = vmlog_strdup(arg,strlen(arg)); } void vmlog_opt_set_ignoreprefix(vmlog_options *opts, const char *arg) { opts->ignoreprefix = vmlog_strdup(arg,strlen(arg)); } static int vmlog_opt_parse_one_option(vmlog_options *opts, const char *arg, const char *nextarg) { int eat; if (strncmp(arg,"-vmlog:",7) != 0) { return 0; } /* a vmlog option */ eat = 1; if (strcmp(arg,"-vmlog:prefix") == 0) { if (!nextarg) vmlog_die("expected a prefix after -vmlog:prefix"); vmlog_opt_set_prefix(opts,nextarg); eat++; } else if (strcmp(arg,"-vmlog:strings") == 0) { if (!nextarg) vmlog_die("expected a prefix after -vmlog:strings"); vmlog_opt_set_stringprefix(opts,nextarg); eat++; } else if (strcmp(arg,"-vmlog:ignore") == 0) { if (!nextarg) vmlog_die("expected a prefix after -vmlog:ignore"); vmlog_opt_set_ignoreprefix(opts,nextarg); eat++; } else { vmlog_die("unknown -vmlog:... option: %s",arg); } return eat; } vmlog_options *vmlog_opt_parse_cmd_line(int *pargc,char **argv) { int i; const char *arg; vmlog_options *opts; int eat; int left; assert(pargc); opts = vmlog_opt_new(); if (*pargc && argv[0]) opts->progname = vmlog_strdup(argv[0],strlen(argv[0])); i = 1; while (i < *pargc) { arg = argv[i]; left = *pargc - i - 1; eat = vmlog_opt_parse_one_option(opts,arg, (left) ? argv[i+1] : NULL); if (eat == 0) { i++; continue; } /* remove the option from the command line */ memmove(argv + i,argv + i + eat,sizeof(char*) * (*pargc - (i+eat))); *pargc -= eat; } return opts; } vmlog_options *vmlog_opt_parse_vmargs(JavaVMInitArgs *vmargs) { int i; const char *arg; vmlog_options *opts; int eat; assert(vmargs); opts = vmlog_opt_new(); i = 0; while (i < vmargs->nOptions) { arg = vmargs->options[i].optionString; eat = vmlog_opt_parse_one_option(opts,arg, (i+1 < vmargs->nOptions) ? vmargs->options[i+1].optionString : NULL); if (eat == 0) { i++; continue; } /* remove the option from the command line */ memmove(vmargs->options + i,vmargs->options + i + eat, sizeof(JavaVMOption) * (vmargs->nOptions - (i+eat))); vmargs->nOptions -= eat; } return opts; } void vmlog_opt_free(vmlog_options *opts) { if (!opts) return; if (opts->prefix) VMLOG_FREE_ARRAY(char,strlen(opts->prefix)+1,opts->prefix); if (opts->stringprefix) VMLOG_FREE_ARRAY(char,strlen(opts->stringprefix)+1,opts->stringprefix); if (opts->ignoreprefix) VMLOG_FREE_ARRAY(char,strlen(opts->ignoreprefix)+1,opts->ignoreprefix); VMLOG_FREE(vmlog_options,opts); } /* vim: noet ts=8 sw=8 */