Profiler (take 4)

Mike McCormack mike at codeweavers.com
Fri Aug 15 00:37:03 CDT 2003


Hi,

This patch adds a simple profiler to Wine.  Charles has added some fixes 
since I last posted this, so it should work properly now.  I have just 
updated for Alexandre's latest commits, and changed the way we protect 
pthread functions from being profiled (now uses a Makefile hack instead 
of a function attribute).

Profiles are generated in /tmp/wmon.out.<pid>, and can be examined with 
the wprof tool from wine/libs/wprof/wprof.

I haven't tested this, but it is working in the Crossover tree, where I 
merged it from. Let me know of any success or failures.

Mike


ChangeLog:
Charles Loep <charles at codeweavers.com>
Mike McCormack <mike at codeweavers.com>
* add a profiler
-------------- next part --------------
Index: configure.ac
===================================================================
RCS file: /home/wine/wine/configure.ac,v
retrieving revision 1.171
diff -u -r1.171 configure.ac
--- configure.ac	13 Aug 2003 01:27:48 -0000	1.171
+++ configure.ac	15 Aug 2003 05:15:46 -0000
@@ -20,6 +20,25 @@
 AC_ARG_WITH(curses,    AC_HELP_STRING([--without-curses],[do not use curses]))
 AC_ARG_WITH(nptl,      AC_HELP_STRING([--with-nptl],[use glibc NPTL threading support]))
 AC_ARG_WITH(wine-tools,AC_HELP_STRING([--with-wine-tools=<dir>],[use Wine tools from directory <dir>]))
+AC_ARG_WITH(profiler,AC_HELP_STRING([--with-profiler],[build the profiler]))
+
+if test "x$with_profiler" = "xyes"
+then
+  GLIBC_THREAD_CFLAGS=`glib-config --cflags gthread`
+  GLIBC_THREAD_LIBS=`glib-config --libs gthread`
+  if test "x$GLIBC_THREAD_CFLAGS" != x
+  then
+    PROFILE_CFLAGS="-finstrument-functions  -D__NO_STRING_INLINES -DWINE_NO_PROFILE=\"__attribute__((no_instrument_function))\""
+    PROFILE_LIBS="-L\$(TOPOBJDIR)/libs/wprof -lwineprof -lm"
+    PROFILE_LIBNAME="libwineprof"
+    AC_DEFINE(HAVE_PROFILER,1,[Define to use glibc NPTL threading support.])
+  fi
+fi
+AC_SUBST(PROFILE_CFLAGS)
+AC_SUBST(PROFILE_LIBS)
+AC_SUBST(PROFILE_LIBNAME)
+AC_SUBST(GLIBC_THREAD_CFLAGS)
+AC_SUBST(GLIBC_THREAD_LIBS)
 
 AC_SUBST(WIN16_FILES,"\$(WIN16_FILES)")
 AC_SUBST(WIN16_INSTALL,"\$(WIN16_INSTALL)")
@@ -1543,6 +1564,7 @@
 libs/uuid/Makefile
 libs/wine/Makefile
 libs/wpp/Makefile
+libs/wprof/Makefile
 miscemu/Makefile
 programs/Makefile
 programs/avitools/Makefile
Index: Make.rules.in
===================================================================
RCS file: /home/wine/wine/Make.rules.in,v
retrieving revision 1.159
diff -u -r1.159 Make.rules.in
--- Make.rules.in	21 Jul 2003 22:01:07 -0000	1.159
+++ Make.rules.in	15 Aug 2003 05:15:46 -0000
@@ -54,7 +54,7 @@
 LINTFLAGS = @LINTFLAGS@
 INCLUDES     = -I$(SRCDIR) -I. -I$(TOPSRCDIR)/include -I$(TOPOBJDIR)/include $(EXTRAINCL)
 EXTRACFLAGS  = @EXTRACFLAGS@
-ALLCFLAGS    = $(INCLUDES) $(DEFS) $(EXTRACFLAGS) $(CPPFLAGS) $(CFLAGS)
+ALLCFLAGS    = $(INCLUDES) $(DEFS) $(EXTRACFLAGS) $(CPPFLAGS) $(CFLAGS) $(PROFILE_CFLAGS)
 ALLLINTFLAGS = $(INCLUDES) $(DEFS) $(LINTFLAGS)
 MKINSTALLDIRS= $(TOPSRCDIR)/tools/mkinstalldirs -m 755
 WINAPI_CHECK = $(TOPSRCDIR)/tools/winapi_check/winapi_check
Index: dlls/Makedll.rules.in
===================================================================
RCS file: /home/wine/wine/dlls/Makedll.rules.in,v
retrieving revision 1.56
diff -u -r1.56 Makedll.rules.in
--- dlls/Makedll.rules.in	13 Jun 2003 23:26:02 -0000	1.56
+++ dlls/Makedll.rules.in	15 Aug 2003 05:15:46 -0000
@@ -9,13 +9,16 @@
 # plus all variables required by the global Make.rules.in
 #
 
+PROFILE_LIBS    = @PROFILE_LIBS@
+PROFILE_CFLAGS  = @PROFILE_CFLAGS@
+
 DEFS        = @DLLFLAGS@ -D__WINESRC__ $(EXTRADEFS)
 DLLEXT      = @DLLEXT@
 MAINSPEC    = $(MODULE:%.dll=%).spec
 SPEC_DEF    = $(MAINSPEC).def
 WIN16_FILES = $(SPEC_SRCS16:.spec=.spec.o) $(C_SRCS16:.c=.o) $(EXTRA_OBJS16)
 ALL_OBJS    = @WIN16_FILES@ $(OBJS) $(MODULE).dbg.o
-ALL_LIBS    = $(LIBWINE) $(EXTRALIBS) $(LIBPORT) $(LDFLAGS) $(LIBS)
+ALL_LIBS    = $(LIBWINE) $(EXTRALIBS) $(LIBPORT) $(LDFLAGS) $(LIBS) $(PROFILE_LIBS)
 IMPORTLIBS  = $(DELAYIMPORTS:%=$(DLLDIR)/lib%.$(IMPLIBEXT)) $(IMPORTS:%=$(DLLDIR)/lib%.$(IMPLIBEXT))
 
 all: $(MODULE)$(DLLEXT) $(SUBDIRS)
Index: libs/Makefile.in
===================================================================
RCS file: /home/wine/wine/libs/Makefile.in,v
retrieving revision 1.5
diff -u -r1.5 Makefile.in
--- libs/Makefile.in	1 May 2003 03:16:21 -0000	1.5
+++ libs/Makefile.in	15 Aug 2003 05:15:47 -0000
@@ -4,12 +4,15 @@
 VPATH     = @srcdir@
 MODULE    = none
 
+PROFILE_LIBNAME = @PROFILE_LIBNAME@
+
 SUBDIRS = \
 	port \
 	unicode \
 	uuid \
 	wine \
-	wpp
+	wpp \
+	wprof
 
 INSTALLSUBDIRS = \
 	unicode \
@@ -21,7 +24,8 @@
 	libwine_port.a \
 	libwine_unicode.$(LIBEXT) \
 	libwine_uuid.a \
-	libwpp.a
+	libwpp.a \
+	$(PROFILE_LIBNAME:%=%.$(LIBEXT))
 
 @MAKE_RULES@
 
@@ -51,6 +55,9 @@
 
 libwpp.a: wpp/libwpp.a
 	$(RM) $@ && $(LN_S) wpp/$@ $@
+
+libwineprof.so libwineprof.so.1 libwineprof.a: wprof/libwineprof.$(LIBEXT)
+	$(RM) $@ && $(LN_S) wprof/$@ $@
 
 # Directory dependencies
 
Index: miscemu/Makefile.in
===================================================================
RCS file: /home/wine/wine/miscemu/Makefile.in,v
retrieving revision 1.21
diff -u -r1.21 Makefile.in
--- miscemu/Makefile.in	22 May 2003 03:40:41 -0000	1.21
+++ miscemu/Makefile.in	15 Aug 2003 05:15:47 -0000
@@ -11,10 +11,13 @@
 
 @MAKE_RULES@
 
+PROFILE_CFLAGS = @PROFILE_CFLAGS@
+PROFILE_LIBS = @PROFILE_LIBS@
+
 LDEXECFLAGS = @LDEXECFLAGS@
 
 $(MODULE): $(OBJS) Makefile.in $(DLLDIR)/libntdll.dll.$(LIBEXT)
-	$(CC) -o $@ $(LDEXECFLAGS) $(OBJS) -L$(DLLDIR) -lntdll.dll $(LIBWINE) $(LIBUNICODE) $(LIBPORT) $(LDFLAGS)
+	$(CC) -o $@ $(LDEXECFLAGS) $(OBJS) -L$(DLLDIR) -lntdll.dll $(LIBWINE) $(LIBUNICODE) $(LIBPORT) $(LDFLAGS) $(PROFILE_LIBS)
 
 install:: $(MODULE)
 	$(MKINSTALLDIRS) $(bindir)
Index: programs/Makeprog.rules.in
===================================================================
RCS file: /home/wine/wine/programs/Makeprog.rules.in,v
retrieving revision 1.29
diff -u -r1.29 Makeprog.rules.in
--- programs/Makeprog.rules.in	13 Jun 2003 23:26:01 -0000	1.29
+++ programs/Makeprog.rules.in	15 Aug 2003 05:15:47 -0000
@@ -9,10 +9,13 @@
 # plus all variables required by the global Make.rules.in
 #
 
+PROFILE_LIBS   = @PROFILE_LIBS@
+PROFILE_CFLAGS = @PROFILE_CFLAGS@
+
 DEFS        = @DLLFLAGS@ $(EXTRADEFS)
 LDDLLFLAGS  = @LDDLLFLAGS@
 ALL_OBJS    = $(OBJS) $(MODULE).dbg.o
-ALL_LIBS    = $(LIBWINE) $(EXTRALIBS) $(LIBPORT) $(LDFLAGS) $(LIBS)
+ALL_LIBS    = $(LIBWINE) $(EXTRALIBS) $(LIBPORT) $(LDFLAGS) $(LIBS) $(PROFILE_LIBS)
 BASEMODULE  = $(MODULE:.exe=)
 TESTIMPORTS = $(DELAYIMPORTS) $(IMPORTS)
 RUNTESTFLAGS= -q -P wine -T $(TOPOBJDIR) $(PLTESTPROGRAM:%=-p %)
Index: dlls/ntdll/Makefile.in
===================================================================
RCS file: /home/wine/wine/dlls/ntdll/Makefile.in,v
retrieving revision 1.70
diff -u -r1.70 Makefile.in
--- dlls/ntdll/Makefile.in	27 Jun 2003 04:08:05 -0000	1.70
+++ dlls/ntdll/Makefile.in	15 Aug 2003 05:15:47 -0000
@@ -107,6 +107,12 @@
 
 @MAKE_DLL_RULES@
 
+#explicit rule for building pthread.c without profiling flags
+ALLCFLAGSNOPROFILE = $(INCLUDES) $(DEFS) $(EXTRACFLAGS) $(CPPFLAGS) $(CFLAGS)
+
+$(TOPOBJDIR)/scheduler/pthread.o: $(TOPOBJDIR)/scheduler/pthread.c
+	$(CC) -c $(ALLCFLAGSNOPROFILE) -o $@ $<
+
 relay16.s: $(WINEBUILD)
 	$(WINEBUILD) $(DEFS) -o $@ --relay16
 
--- /dev/null	1994-07-18 08:46:18.000000000 +0900
+++ libs/wprof/Makefile.in	2003-05-30 18:04:36.000000000 +0900
@@ -0,0 +1,39 @@
+# Makefile for wineprof
+
+TOPSRCDIR = @top_srcdir@
+TOPOBJDIR = ../..
+SRCDIR    = @srcdir@
+VPATH     = @srcdir@
+MODULE    = none
+
+PROFILE_LIBNAME = @PROFILE_LIBNAME@
+
+ at MAKE_RULES@
+
+TARGETS=$(PROFILE_LIBNAME:%=%.$(LIBEXT))
+
+CFLAGS += -ggdb @GLIBC_THREAD_CFLAGS@ -fPIC #-DDEBUG
+LIBS += @GLIBC_THREAD_LIBS@
+
+all: $(TARGETS)
+
+$(TARGETS): wprof
+
+proftest: proftest.c
+	$(CC) -O2 -ggdb proftest.c -o proftest -finstrument-functions $(LIBS) \
+	-lwineprof -lpthread
+
+proftest_pg: proftest.c
+	$(CC) -O2 -ggdb -pg proftest.c -o proftest_pg $(LIBS)
+
+wprof: wineprof.o
+	$(CC) wineprof.o -o wprof $(LIBS)
+
+LIBWPROF_OBJ = profiler.o wrappers.o string.o
+
+$(PROFILE_LIBNAME).$(LIBEXT): $(LIBWPROF_OBJ)
+	$(CC) -static --shared $< -o $@ -Wl,-soname,libwineprof.so
+
+clean::
+	rm -f wprof proftest proftest_pg
+
--- /dev/null	1994-07-18 08:46:18.000000000 +0900
+++ libs/wprof/atomicity.h	2003-05-30 17:19:45.000000000 +0900
@@ -0,0 +1,57 @@
+/* Low-level functions for atomic operations.  ix86 version, x >= 4.
+   Copyright (C) 1997, 2000 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#ifndef _ATOMICITY_H
+#define _ATOMICITY_H	1
+
+#include <inttypes.h>
+
+
+static inline uint32_t
+__attribute__ ((unused))
+exchange_and_add (volatile uint32_t *mem, uint32_t val)
+{
+  register uint32_t result;
+  __asm__ __volatile__ ("lock; xaddl %0,%2"
+			: "=r" (result) : "0" (val), "m" (*mem) : "memory");
+  return result;
+}
+
+static inline void
+__attribute__ ((unused))
+atomic_add (volatile uint32_t *mem, int val)
+{
+  __asm__ __volatile__ ("lock; addl %0,%1"
+			: : "ir" (val), "m" (*mem) : "memory");
+}
+
+static inline char
+__attribute__ ((unused))
+compare_and_swap (volatile long int *p, long int oldval, long int newval)
+{
+  char ret;
+  long int readval;
+
+  __asm__ __volatile__ ("lock; cmpxchgl %3, %1; sete %0"
+                        : "=q" (ret), "=m" (*p), "=a" (readval)
+                        : "r" (newval), "m" (*p), "a" (oldval));
+  return ret;
+}
+
+#endif /* atomicity.h */
--- /dev/null	1994-07-18 08:46:18.000000000 +0900
+++ libs/wprof/profiler.c	2003-08-15 11:27:31.000000000 +0900
@@ -0,0 +1,555 @@
+/* Wine Profiler
+ *
+ * Copyright 2000-2003 Codeweavers, Charles Loep
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/*
+ * It's unlikely that this works on anything other than linux/i386
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "atomicity.h"
+#include "profiler.h"
+
+
+#define WPROF_MAX_CALL_DEPTH 1024
+#define WPROF_MAX_ARC        1000000
+
+#define WPROF_CURRENT        getpid()
+#define WPROF_WMON_PREFIX    "/tmp/wmon.out."
+
+
+#define DEBUG
+
+#ifdef DEBUG
+#define db_printf(args...) printf(args)
+#else
+#define db_printf(args...)
+#endif
+
+
+typedef struct cstack_s {
+    arc_t      *current;
+    counter64_t entry_time;
+} cstack_t;
+
+
+typedef struct ptinfo_s {
+    pid_t pid;
+    arc_t *root;
+    int map_fd;
+    int map_size;
+    wprof_header_t *header;
+    cstack_t cstack[WPROF_MAX_CALL_DEPTH];
+    int cstackp;
+    long state;
+    struct ptinfo_s *next;
+} ptinfo_t;
+
+
+#define PTINFO_FINALIZED ((void *)-1)
+#define PTINFO_TOTAL 32768
+static ptinfo_t *ptinfo_map[PTINFO_TOTAL];
+
+
+typedef enum {
+    WPROF_STATE_OFF = 0,
+    WPROF_STATE_ON,
+    WPROF_STATE_BUSY
+} WPROF_STATE;
+
+
+/* global data */
+static pid_t wprof_master = 0;
+static counter32_t wprof_cpu_hz = 0;
+static long wprof_global_state;
+static int wprof_thread_count = 0;
+
+void __attribute__ ((constructor)) wprof_init();
+void __attribute__ ((destructor)) wprof_exit();
+
+
+static void wprof_finalize_thread(ptinfo_t *pt);
+static arc_t *wprof_alloc_arc(ptinfo_t *pt, address32_t addr);
+
+
+/*
+ * determine the number of ticks per second
+ */
+static unsigned long wprof_get_hz()
+{
+    FILE *fp;
+    double ticks = 0.0;
+
+    fp = fopen("/proc/cpuinfo", "r");
+    while(!feof(fp))
+        {
+
+            char line[1024];
+
+            if(!fgets(line, 1024, fp))
+                break;
+            if(sscanf(line, "cpu MHz         : %lf", &ticks) != 1)
+                continue;
+            db_printf("WPROF: ticks per second: %.0lf\n", (ticks * 1000000.0));
+            break;
+        }
+    fclose(fp);
+
+    if(ticks == 0.0)
+	{
+	    printf("WPROF: unable to determine HZ (%lf)\n", ticks);
+	    _exit(1);
+	}
+
+    return (unsigned long)(ticks * 1000000.0);
+}
+
+static counter64_t get_tsc() 
+{
+        counter64_t value;
+        __asm__ __volatile__("rdtsc" : "=A" (value));
+	return value;
+}
+
+
+
+/*
+ * constructor
+ */
+void wprof_init()
+{
+    wprof_master = WPROF_CURRENT;
+    wprof_cpu_hz = wprof_get_hz();
+ 
+    db_printf("WPROF: initialized!\n");
+
+    wprof_global_state = WPROF_STATE_ON;
+}
+
+/*
+ * destructor
+ */
+void wprof_exit()
+{
+    int i;
+
+    wprof_global_state = WPROF_STATE_OFF;
+    db_printf("WPROF: unloading %d (%d threads)\n", WPROF_CURRENT, wprof_thread_count);
+
+#if 1
+    // finalize any threads that didnt we didn't catch before
+    for(i = 0; i < PTINFO_TOTAL; i++) {
+	if(ptinfo_map[i]) {
+	    ptinfo_t *pt = ptinfo_map[i];
+
+	    if(pt == PTINFO_FINALIZED) {
+		    db_printf("WPROF: finalizing %d (skipped)\n", i);
+		    continue;
+	    }
+
+	    db_printf("WPROF: finalizing %d\n", pt->pid);
+	    wprof_finalize_thread(pt);
+	    ptinfo_map[i] = 0;
+	}
+    }
+#endif
+
+}
+
+
+static char digits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
+static void strintcat(char *str, int i)
+{
+    int pos = 11;
+    char buf[12];
+        
+    buf[11] = 0;
+    do {
+	buf[--pos] = digits[i % 10];
+	i = i / 10;
+    } while(i != 0);
+    
+    strcat(str, &buf[pos]);
+}
+
+
+/*
+ * map the profile output file for the current thread
+ */
+static ptinfo_t *wprof_map_output()
+{
+    ptinfo_t *pt = NULL;
+    char filename[256] = WPROF_WMON_PREFIX;
+    void *addr;
+    int map_size, map_fd;
+
+    map_size = sizeof(wprof_header_t) + (WPROF_MAX_ARC * sizeof(arc_t)) + sizeof(ptinfo_t);
+
+    strintcat(filename, WPROF_CURRENT);
+    db_printf("WPROF: creating %s\n", filename);
+
+    map_fd = open(filename, O_RDWR|O_CREAT|O_TRUNC, 0664);
+    ftruncate(map_fd, map_size);
+    addr = mmap(NULL, map_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0);
+
+    pt = (ptinfo_t *)(addr + map_size - sizeof(ptinfo_t));
+    pt->map_size = map_size;
+    pt->map_fd = map_fd;
+    pt->header = (wprof_header_t *)addr;
+    pt->header->vaddr = (unsigned int)addr;
+
+    return pt;
+}
+
+/* 
+ * allocate a thread info struct
+ */
+static ptinfo_t *wprof_alloc_ptinfo()
+{
+    ptinfo_t *pt;
+    arc_t *arc;
+    pid_t current = WPROF_CURRENT;
+
+    db_printf("WPROF: new thread; pid %d\n", current);
+
+    pt = wprof_map_output();
+    pt->pid  = current;
+    pt->root = NULL;
+    pt->next = NULL;
+
+    pt->header->magic   = WPROF_MAGIC;
+    pt->header->version = WPROF_VERSION;
+    pt->header->pid     = pt->pid;
+    pt->header->cpu_hz  = wprof_cpu_hz;
+    gettimeofday(&pt->header->time_start, NULL);
+    gettimeofday(&pt->header->time_stop, NULL);
+
+    arc = wprof_alloc_arc(pt, 0);
+    pt->root = arc;
+    pt->cstackp = 0;
+    pt->cstack[pt->cstackp].current = pt->root;
+    pt->cstack[pt->cstackp].entry_time = get_tsc();
+
+    ptinfo_map[current] = pt;
+    pt->state = WPROF_STATE_ON;
+
+    wprof_thread_count++;
+
+    return pt;
+}
+
+
+/*
+ * retrieve or allocate a thread info struct
+ */
+static ptinfo_t *wprof_get_ptinfo()
+{
+    ptinfo_t *pt;
+    pid_t current = WPROF_CURRENT;
+
+    pt = ptinfo_map[current];
+    
+    if(pt == PTINFO_FINALIZED)
+	    return NULL;
+
+    if(!pt)
+	    pt = wprof_alloc_ptinfo();
+    return pt;
+}
+
+
+static void wprof_unlink_ptinfo(ptinfo_t *ptrem)
+{
+	ptinfo_map[ptrem->pid] = PTINFO_FINALIZED;
+}
+
+static int wprof_has_ptinfo()
+{
+    pid_t current = WPROF_CURRENT;
+
+    if(ptinfo_map[current])
+	return 1;
+    return 0;
+}
+
+/*
+ * allocate new arc
+ * FIXME: make sure there's enough free space left for the allocation
+ */
+static arc_t *wprof_alloc_arc(ptinfo_t *pt, address32_t addr)
+{
+    arc_t *arc;
+
+    arc = &pt->header->root[pt->header->arc_count++];
+    arc->addr = addr;
+
+    //db_printf("WPROF: allocating arc for %x\n", addr);
+    return arc;
+}
+
+
+/*
+ * append arc to the list
+ */
+static arc_t *wprof_arc_list_append(arc_t *root, arc_t *arc)
+{
+    arc_t *tmp;
+
+    if(!root)
+	return arc;
+
+    tmp = root;
+    while(tmp->next)
+	tmp = tmp->next;
+    tmp->next = arc;
+
+    return root;
+}
+
+
+/*
+ * find arc with address addr or allocate a new one
+ */
+static arc_t *wprof_find_arc(ptinfo_t *pt, arc_t *current, address32_t addr)
+{
+    arc_t *arc;
+
+    for(arc = current->children; arc; arc = arc->next)
+	{
+	    if(arc->addr == addr)
+		return arc;
+	}
+
+    arc = wprof_alloc_arc(pt, addr);
+    current->children = wprof_arc_list_append(current->children, arc);
+
+    return arc;
+}
+
+#define CHECKPT(pt)      do { if(!pt) return; if(pt == PTINFO_FINALIZED) return; } while(0)
+
+
+/*
+ * profile entry point
+ * this is called first thing in a profiled function
+ */
+void __cyg_profile_func_enter(address32_t this_fn, address32_t call_site)
+{
+    ptinfo_t *pt;
+    cstack_t *top;
+    arc_t *arc;
+
+    pt = wprof_get_ptinfo();
+    CHECKPT(pt);
+
+    if(pt->state == WPROF_STATE_OFF)
+	    return;
+
+    /* FIXME
+     * what to do when a signal arrives in the middle of this?
+     * for now we'll just ignore it and return
+     */
+    if(!compare_and_swap(&pt->state, WPROF_STATE_ON, WPROF_STATE_BUSY))
+	{
+	    db_printf("WPROF: warning; re-entry in __menter (state=%ld)\n", pt->state);
+	    return;
+	}
+
+    top = &pt->cstack[pt->cstackp++];
+    arc = wprof_find_arc(pt, top->current, this_fn);
+    arc->count++;
+
+    top++;
+    top->current = arc;
+    top->entry_time = get_tsc();
+    
+    compare_and_swap(&pt->state, WPROF_STATE_BUSY, WPROF_STATE_ON);
+}
+
+
+/*
+ * called at exit of a profiled function
+ */
+void __cyg_profile_func_exit(address32_t this_fn, address32_t call_site)
+{
+    counter64_t after;
+    ptinfo_t *pt;
+    cstack_t *top;
+    arc_t *arc;
+
+    after = get_tsc();
+    pt = wprof_get_ptinfo();
+    CHECKPT(pt);
+
+    if(pt->state == WPROF_STATE_OFF)
+	    return;
+
+    /*
+     * this happens when __menter was re-entered (by a signal)
+     * ignore the signal handler exit
+     */
+    if(pt->state == WPROF_STATE_BUSY)
+	{
+            db_printf("WPROF: warning; BUSY in __mexit\n");
+	    return;
+	}
+    top = &pt->cstack[pt->cstackp];
+    arc = top->current;
+    arc->ticks += (after - top->entry_time);
+
+    pt->cstackp--;
+}
+
+
+static void sim_func_exit(ptinfo_t *pt)
+{
+	counter64_t after;
+	cstack_t *top;
+	arc_t *arc;
+
+	after = get_tsc();
+
+	top = &pt->cstack[pt->cstackp];
+	arc = top->current;
+	arc->ticks += (after - top->entry_time);
+
+	pt->cstackp--;
+}
+
+
+
+/*
+ * grab a copy of /proc/self/maps and save the relevant entries to 
+ * the profile output file.
+ * FIXME: cant use /proc/self/.. since this may be called from a different process
+ */
+static void wprof_write_maps(ptinfo_t *pt, char *addr)
+{
+    FILE *fp;
+
+    fp = fopen("/proc/self/maps", "r");
+    if(!fp)
+	return;
+    
+    while(!feof(fp))
+	{
+	    wprof_map_t map;
+	    char tmp[1024], soname[256], perm[16];
+	    int start, end, offset, res;
+
+	    if(!fgets(tmp, 1024, fp))
+		break;
+
+	    res = sscanf(tmp, "%x-%x %s %x %*s %*d %[^\n]\n", 
+			 &start, &end, perm, &offset, soname);
+	    if(res != 5)
+		continue;
+	
+	    /* only save entries mapped as executable */
+	    if(!strstr(perm, "x"))
+		continue;
+	
+	    memset(&map, 0, sizeof(wprof_map_t));
+	    map.addr_from = start;
+	    map.addr_to   = end;
+	    map.offset    = offset;
+	    strncpy(map.filename, soname, sizeof(map.filename));
+
+	    memcpy(addr, &map, sizeof(wprof_map_t));
+	    addr += sizeof(wprof_map_t);
+	    pt->header->map_count++;
+	}
+
+    fclose(fp);
+}
+
+
+/*
+ * finalize a threads output file
+ */
+static void wprof_finalize_thread(ptinfo_t *pt)
+{
+    size_t end;
+
+    CHECKPT(pt);
+
+    while(pt->cstackp > 0)
+	{
+		//db_printf("WPROF: unwind cstack %d\n", pt->cstackp);
+	    sim_func_exit(pt);
+	}
+
+    pt->state = WPROF_STATE_OFF;
+    
+    db_printf("[%d] profile_exit\n", pt->pid);
+    db_printf("[%d] arc_count=%d (mem=%d)\n",
+	   pt->pid, pt->header->arc_count, 
+	   pt->header->arc_count * sizeof(arc_t));
+    
+    end = sizeof(wprof_header_t) + (pt->header->arc_count * sizeof(arc_t));
+    
+    if(lseek(pt->map_fd, end, SEEK_SET) != -1) {
+	    void *addr = (void *)pt->header;
+	    int map_fd = pt->map_fd;
+	    int map_size = pt->map_size;
+	    
+	    wprof_write_maps(pt, (char *)(pt->header->vaddr + end));
+	    pt->header->flags |= WFLAG_CLOSED;
+            wprof_unlink_ptinfo(pt);
+	    
+	    ftruncate(map_fd, end + (pt->header->map_count * sizeof(wprof_map_t)));
+	    
+	    munmap(addr, map_size);
+	    close(map_fd);
+    } else {
+	    printf("WPROF: lseek failed\n");
+    }
+}
+
+
+/*
+ * exit the CURRENT thread
+ */
+void wprof_exit_thread(int status)
+{
+	ptinfo_t *pt;
+
+	db_printf("WPROF: wprof_exit_thread(%d)\n", status);
+
+	pt = wprof_get_ptinfo();
+	CHECKPT(pt);
+
+	/* record exit code */
+	pt->header->exitcode = status;
+
+	/* store some useful info */
+	pt->header->flags |= WFLAG_HAS_RUSAGE;
+	getrusage(RUSAGE_SELF, &pt->header->rusage);
+	gettimeofday(&pt->header->time_stop, NULL);
+
+	wprof_finalize_thread(pt);
+}
--- /dev/null	1994-07-18 08:46:18.000000000 +0900
+++ libs/wprof/profiler.h	2003-05-30 17:19:45.000000000 +0900
@@ -0,0 +1,87 @@
+/* Wine Profiler
+ *
+ * Copyright 2000-2003 Codeweavers, Charles Loep
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __WPROF_H__
+#define __WPROF_H__
+
+#include <unistd.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+
+#define XSTDCALL __attribute((__stdcall__))
+#define XNOPROF  __attribute((no_instrument_function))
+
+#define WPROF_MAGIC   0x4E4F4D57
+#define WPROF_VERSION 0x02
+
+
+typedef unsigned long long counter64_t;
+typedef unsigned long      counter32_t;
+
+typedef void * address32_t;
+
+
+typedef struct arc_s {
+    address32_t   addr;
+    counter32_t   count;
+    counter64_t   ticks;
+    struct arc_s *children;
+    struct arc_s *next;
+} arc_t;
+
+
+enum {
+	WFLAG_NONE       = 0,
+	WFLAG_CLOSED     = 1,
+	WFLAG_HAS_RUSAGE = 2,
+};
+
+typedef struct wprof_header_s {
+    unsigned int   magic;
+    unsigned char  version;
+    unsigned char  flags;
+    unsigned char  resv[2];
+    pid_t          pid;
+    struct timeval time_start;
+    struct timeval time_stop;
+    struct rusage  rusage;
+    int            exitcode;
+    counter32_t    cpu_hz;
+    unsigned int   vaddr;
+    size_t         map_count;
+    unsigned int   map_start;
+    size_t         arc_count;
+    arc_t          root[0];
+} wprof_header_t;
+
+
+
+typedef struct wprof_map_s {
+    unsigned int addr_from;
+    unsigned int addr_to;
+    unsigned int offset;
+    char         filename[256];
+} wprof_map_t;
+
+
+
+void wprof_exit_thread(int status);
+
+
+#endif /* __WPROF_H__ */
--- /dev/null	1994-07-18 08:46:18.000000000 +0900
+++ libs/wprof/proftest.c	2003-05-30 17:19:45.000000000 +0900
@@ -0,0 +1,95 @@
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <fcntl.h>
+
+#include <pthread.h>
+
+void handler()
+{
+    printf("ping %d\n", getpid());
+}
+
+int bar(int a)
+{
+     return foo(!a);
+}
+
+int foo(int a)
+{
+    switch(a)
+	{
+	case 0:
+	    return 12345;
+	case 1:
+	    return bar(a);
+	default:
+	    return 0;
+	}
+}
+
+void blah()
+{
+    foo(2);
+}
+
+void set_timer()
+{
+    struct itimerval it;
+    struct sigaction sa;
+
+    memset(&sa, 0, sizeof(sa));
+    sa.sa_handler = handler;
+    sa.sa_flags   = SA_RESTART;
+    sigaction(SIGALRM, &sa, NULL);
+
+    memset(&it, 0, sizeof(it));
+    it.it_value.tv_usec    = 100000;
+    it.it_interval.tv_usec = 100000;
+    setitimer(ITIMER_REAL, &it, NULL);
+
+
+}
+
+int get_random(int q)
+{
+    return random() % q;
+}
+
+
+void *runner(void *unused)
+{
+    int i;
+
+    set_timer();
+
+    for(i = 0; i < 1000000; i++)
+        foo(get_random(2));
+}
+
+
+void test1()
+{
+  int fd = open("/dev/null", O_RDONLY);
+  close(fd);
+}
+
+int main()
+{
+#if 1
+    pthread_t t1, t2;
+
+    pthread_create(&t1, NULL, runner, NULL);
+    pthread_create(&t2, NULL, runner, NULL);
+
+    pthread_join(t1, 0);
+    pthread_join(t2, 0);
+#else
+    //runner(0);
+    test1();
+#endif
+	printf("proftest %d exiting\n", getpid());
+    exit(101);
+}
--- /dev/null	1994-07-18 08:46:18.000000000 +0900
+++ libs/wprof/string.c	2003-05-30 17:19:45.000000000 +0900
@@ -0,0 +1,10 @@
+
+#define __STRING_INLINE
+#include <string.h>
+
+long double __sqrtl(long double __x)
+{
+    register long double __result;
+    __asm __volatile__("fsqrt" : "=t" (__result) : "0" (__x));
+    return __result;
+}
--- /dev/null	1994-07-18 08:46:18.000000000 +0900
+++ libs/wprof/wineprof.c	2003-05-30 17:19:45.000000000 +0900
@@ -0,0 +1,871 @@
+/* Wine Profiler
+ *
+ * Copyright 2000-2003 Codeweavers, Charles Loep
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <time.h>
+
+#include <getopt.h>
+
+#include <glib.h>
+
+#include "profiler.h"
+#include "wineprof.h"
+
+
+/*--------------------------------------------------------*/
+// options
+/*--------------------------------------------------------*/
+ 
+#define OPT_ON   1
+#define OPT_OFF  0
+
+struct options {
+    int debug;
+    int flat;
+    int callers;
+    int calltree;
+    int soprefix;
+    int merge;
+    int listso;
+    int source;
+    char *sort;
+    int precision;
+    GList *ignore;
+    int compress;
+};
+
+struct options opt = {
+    debug     : OPT_OFF,
+    flat      : OPT_ON,
+    callers   : OPT_OFF,
+    calltree  : OPT_OFF,
+    soprefix  : OPT_OFF,
+    merge     : OPT_OFF,
+    listso    : OPT_OFF,
+    source    : OPT_OFF,
+    sort      : "self",
+    precision : 3,
+    ignore    : NULL,
+    compress  : OPT_ON,
+};
+
+
+static const struct option long_options[] =
+{
+    { "flat",           no_argument,       &opt.flat,     OPT_ON  },
+    { "no-flat",        no_argument,       &opt.flat,     OPT_OFF },
+    { "callers",        no_argument,       &opt.callers,  OPT_ON },
+    { "no-callers",     no_argument,       &opt.callers,  OPT_OFF },
+    { "calltree",       no_argument,       &opt.calltree, OPT_ON  },
+    { "no-calltree",    no_argument,       &opt.calltree, OPT_OFF },
+
+    { "dso-prefix",     no_argument,       &opt.soprefix, OPT_ON  },
+    { "no-dso-prefix",  no_argument,       &opt.soprefix, OPT_OFF },
+
+    { "merge",          no_argument,       &opt.merge,    OPT_ON  },
+    { "no-merge",       no_argument,       &opt.merge,    OPT_OFF },
+    { "list-dso",       no_argument,       &opt.listso,   OPT_ON  },
+    { "no-list-dso",    no_argument,       &opt.listso,   OPT_OFF },
+    { "source",         no_argument,       &opt.source,   OPT_ON  },
+    { "no-source",      no_argument,       &opt.source,   OPT_OFF },
+
+    { "compress",       no_argument,       &opt.compress, OPT_ON  },
+    { "no-compress",    no_argument,       &opt.compress, OPT_OFF },
+
+    { "ignore",         required_argument, NULL,          'i' },
+    { "sort",           required_argument, NULL,          's' },
+    { "precision",      required_argument, NULL,          'p' },
+
+    { "debug",          no_argument,       &opt.debug,    OPT_ON  },
+    { "help",           no_argument,       NULL,          'h' },
+};
+
+
+/*--------------------------------------------------------*/
+// prototypes
+/*--------------------------------------------------------*/
+ 
+void usage();
+void view_profile_flat(profile_t *prof);
+void view_profile_calltree(profile_t *prof);
+
+
+
+/*--------------------------------------------------------*/
+// the code!
+/*--------------------------------------------------------*/
+
+
+static unsigned long wprof_get_hz()
+{
+    FILE *fp;
+    double ticks = 0.0;
+
+    fp = fopen("/proc/cpuinfo", "r");
+    while(!feof(fp))
+        {
+
+            char line[1024];
+
+            if(!fgets(line, 1024, fp))
+                break;
+            if(sscanf(line, "cpu MHz         : %lf", &ticks) != 1)
+                continue;
+            printf("WPROF: ticks per second: %.0lf\n", (ticks * 1000000.0));
+            break;
+        }
+    fclose(fp);
+
+    if(ticks == 0.0)
+        {
+            printf("WPROF: unable to determine CPU HZ\n");
+	    exit(1);
+        }
+
+    return (long)(ticks * 1000000.0);
+}
+
+
+static size_t file_size_for_fd(int fd)
+{
+    struct stat st;
+    fstat(fd, &st);
+    return st.st_size;
+}
+
+static char *basename(char *file)
+{
+    char *p = strrchr(file, '/');
+    return p ? ++p : file;
+}
+
+static char *fullname(symbol_t *sym)
+{
+    static char tmp[1024];
+
+    if(opt.soprefix)
+	sprintf(tmp, "%s:%s", basename(sym->soname), sym->name);
+    else
+	return sym->name;
+
+    return tmp;
+}
+
+static counter64_t msec(counter64_t hz, counter64_t count)
+{
+    double v;
+
+    v = (double)count / ((double)hz / 1000.0);
+    return (counter64_t)v;
+}
+
+static double dsec(counter64_t hz, counter64_t count)
+{
+    double v;
+
+    v = (double)count / (double)hz;
+    return v;
+}
+
+static double tv_to_double(struct timeval *tv)
+{
+	return (double)tv->tv_sec + ((double)tv->tv_usec / 1000000.0);
+}
+
+
+static char *repeat_char(char ch, int count)
+{
+	static char tmp[1024];
+	memset(tmp, ch, count);
+	tmp[count] = 0;
+	return tmp;
+}
+
+
+
+/*
+ * allocate a symbol
+ */
+symbol_t *symbol_new(char *name, address32_t addr)
+{
+    symbol_t *sym = g_new0(symbol_t, 1);
+    sym->name = g_strdup(name);
+    sym->addr = (unsigned int)addr;
+
+    return sym;
+}
+
+
+/*
+ * fetch all symbols from soname
+ * <lazy> we use nm for now </lazy>
+ */
+static int read_symtab(GHashTable *hash, unsigned int base, char *soname)
+{
+    FILE *fp;
+    char cmd[512], line[256];
+
+    if(opt.source)
+	sprintf(cmd, "nm --defined-only --line-numbers '%s' 2> /dev/null", soname);
+    else
+	sprintf(cmd, "nm --defined-only '%s' 2> /dev/null", soname);
+
+    fp = popen(cmd, "r");
+    if(!fp)
+	return 0;
+
+    soname = g_strdup(soname);
+
+    while(!feof(fp))
+	{
+	    unsigned int addr = 0;
+	    char type = 0;
+	    char symbol[256], source[512] = { 0 };
+	    symbol_t *sym;
+	    int res = 0;
+
+	    if(!fgets(line, 256, fp))
+		break;
+
+	    if(opt.source)
+		res = sscanf(line, "%x %c %s %s\n", &addr, &type, symbol, source);
+	
+	    else if(!opt.source || res != 4)
+		{
+		    if(sscanf(line, "%x %c %s\n", &addr, &type, symbol) != 3)
+			continue;
+		}
+	    
+	    if(addr < base)
+		addr += base;
+
+	    //printf("%c %x %s\n", type, addr, symbol);
+
+	    if(!g_hash_table_lookup(hash, (gpointer)addr) || type == 'T')
+		{
+		    sym = symbol_new(symbol, (address32_t)addr);
+		    sym->soname = soname;
+		    if(strlen(source))
+			sym->source = g_strdup(source);
+		    g_hash_table_insert(hash, (gpointer)addr, sym);
+		}
+	}
+    pclose(fp);
+
+    return 1;
+}
+
+/*
+ * returns true if sym's name is in the ingore list
+ */
+static int symbol_is_ignored(symbol_t *sym)
+{
+    GList *e;
+
+    if(!sym)
+	return 0;
+    //FIXME this should be a hashtable
+    for(e = opt.ignore; e; e = e->next)
+	{
+	    char *str = e->data;
+	    if(!strcmp(str, sym->name))
+		return 1;
+	}
+    return 0;
+}
+
+
+/*
+ * helper for symbol_add_caller
+ */
+static int cmp_callinfo_sym(gconstpointer a, gconstpointer b)
+{
+    callinfo_t *ci = (callinfo_t *)a;
+    symbol_t *sym = (symbol_t *)b;
+
+    return !(ci->caller == sym);
+}
+
+/*
+ * add a caller to sym
+ */
+static void symbol_add_caller(symbol_t *sym, symbol_t *caller, arc_t *arc)
+{
+    callinfo_t *ci = 0;
+
+    if(opt.compress)
+	{
+	    GList *e = g_list_find_custom(sym->callers, caller, cmp_callinfo_sym);
+	    if(e)
+		ci = e->data;
+	}
+
+    if(!ci)
+	{
+	    ci = g_new0(callinfo_t, 1);
+	    ci->caller = caller;
+	    sym->callers = g_list_append(sym->callers, ci);
+	}
+
+    ci->calls += arc->count;
+    ci->ticks += arc->ticks;
+}
+
+counter64_t calc_all_ticks(arc_t *root, GHashTable *symtab, symbol_t *caller)
+{
+    counter64_t total_time = 0;
+    counter64_t children_time = 0;
+    arc_t *arc;
+
+    if(!root)
+        return 0;
+
+    for(arc = root->children; arc; arc = arc->next)
+        {
+            symbol_t *sym;
+
+            sym = g_hash_table_lookup(symtab, (gpointer)arc->addr);
+	    if(!sym) { 
+		    char name[32];
+		    sprintf(name, "(0x%X)", (unsigned int)arc->addr);
+		    sym = symbol_new(strdup(name), arc->addr);
+		    //printf("SYMBOL NOT FOUND %x\n", arc->addr); 
+                    g_hash_table_insert(symtab, (gpointer)arc->addr, sym);
+	    }
+
+            children_time = calc_all_ticks(arc, symtab, sym);
+	    //printf("children_time = %llu\n", children_time);
+            total_time += children_time;
+	    if(!symbol_is_ignored(sym)) {
+		    total_time += (arc->ticks - children_time);
+		    
+		    sym->count += arc->count;
+                    sym->ticks += arc->ticks;
+                    sym->ticks_kids += children_time;
+                    sym->ticks_self += arc->ticks - children_time;
+
+		    if(caller)
+			{
+			    symbol_add_caller(sym, caller, arc);
+			    //printf("SYM %-20s  CALLER %s\n", sym->name, caller->name);
+			}
+		}
+        }
+
+    return total_time;
+}
+
+
+static GHashTable *process_maps(wprof_map_t *mp, int num_maps)
+{
+    GHashTable *hash;
+    int i;
+
+    hash = g_hash_table_new(g_direct_hash, g_direct_equal);
+
+    for(i = 0; i < num_maps; i++)
+        {
+	    if(opt.listso)
+		printf("%08x-%08x %08x %s  \n", mp->addr_from, mp->addr_to, mp->offset, mp->filename);
+	    read_symtab(hash, mp->addr_from, mp->filename);
+            mp++;
+        }
+    return hash;
+}
+
+
+profile_t *profile_open(char *filename)
+{
+    profile_t *prof;
+    wprof_header_t header, *hp;
+    double idle, systime, usertime;
+    void *addr;
+    int fd;
+
+    fd = open(filename, O_RDONLY);
+    read(fd, &header, sizeof(header));
+    if(header.magic != WPROF_MAGIC)
+        {
+            printf("WPROF: invalid magic number in %s\n", filename);
+            close(fd);
+            return 0;
+        }
+    if(header.version != WPROF_VERSION)
+	{
+	    printf("WPROF: version mismatch in %s\n", filename);
+	    close(fd);
+	    return 0;
+	}
+
+    if(!(header.flags & WFLAG_CLOSED))
+    {
+	printf("WPROF: profile not properly closed.\n");
+	//close(fd);
+	//return 0;
+      }
+
+    prof = g_new0(profile_t, 1);
+
+    usertime = tv_to_double(&header.rusage.ru_utime);
+    systime  = tv_to_double(&header.rusage.ru_stime);
+
+    idle = tv_to_double(&header.time_stop) - tv_to_double(&header.time_start);
+    idle -= tv_to_double(&header.rusage.ru_utime) + tv_to_double(&header.rusage.ru_stime);
+    
+    if(opt.debug)
+	{
+	    printf("WPROF pid:   %d\n", header.pid);
+	    printf("WPROF start: %s", ctime(&header.time_start.tv_sec));
+	    printf("WPROF stop:  %s", ctime(&header.time_stop.tv_sec));
+	    
+	    printf("WPROF utime: %.3lf sec\n", usertime);
+	    printf("WPROF stime: %.3lf sec\n", systime);
+	    printf("WPROF idle:  %.3lf sec\n", idle);
+	    
+	    printf("WPROF exit:  %d\n", header.exitcode);
+	    printf("WPROF clock: %lu\n", header.cpu_hz);
+	    printf("WPROF addr:  %x\n", header.vaddr);
+	    printf("WPROF #arc:  %d\n", header.arc_count);
+	    printf("WPROF #map:  %d\n", header.map_count);
+	}
+
+    if(header.flags & WFLAG_HAS_RUSAGE) {
+	    printf("pid   utime    stime    idle     total\n");
+	    printf("----- -------- -------- -------- --------\n");
+	    printf("%5d %8.3lf %8.3lf %8.3lf %8.3lf\n\n",
+		   header.pid,
+		   usertime,
+		   systime,
+		   idle,
+		   usertime + systime + idle
+		    );
+    } else {
+	    printf("WPROF: no rusage available.\n");
+    }
+
+    addr = mmap((void *)header.vaddr, file_size_for_fd(fd), PROT_READ,
+                MAP_SHARED, fd, 0);
+    if(addr == NULL || addr != (void *)header.vaddr)
+	{
+	    /* FIXME
+	     * <lazy> we should relocate the file if it can't be mapped at the
+	     * address specified in the header </lazy>
+	     */
+	    perror("mmap");
+	    printf("Oops! failed to map profile at %x - bug Charles to fix it if this ever happens.\n", header.vaddr);
+	    exit(1);
+	}
+    if(opt.debug)
+	printf("WPROF mapped at: %p\n", addr);
+
+    hp = (wprof_header_t *)addr;
+
+    prof->fd     = fd;
+    prof->header = hp;
+    prof->symtab = process_maps((wprof_map_t *)(addr + sizeof(wprof_header_t) + (hp->arc_count * sizeof(arc_t))), hp->map_count);
+    prof->total_ticks = calc_all_ticks(&hp->root[0], prof->symtab, 0);
+
+    return prof;
+}
+
+
+void profile_close(profile_t *prof)
+{
+    //FIXME: free profile
+}
+
+
+
+static void do_make_list(gpointer key, gpointer value, gpointer user_data)
+{
+    GList *list = *(GList **)user_data;
+    *(GList **)user_data = g_list_prepend(list, value);
+}
+
+GList *symtab_to_symlist(GHashTable *symtab)
+{
+    GList *list = 0;
+    g_hash_table_foreach(symtab, do_make_list, &list);
+
+    return list;
+}
+
+
+
+/*--------------------------------------------------------*/
+// sorting functions
+/*--------------------------------------------------------*/
+
+
+#define COMPARE(a, b) ((a) == (b) ? 0 : (a) < (b) ? -1 : 1)  
+
+#define DEFINE_SORT(TYPE, FIELD, NAME) \
+gint NAME (gconstpointer a, gconstpointer b) \
+{ \
+  TYPE *val_a = (TYPE *)a; \
+  TYPE *val_b = (TYPE *)b; \
+  return COMPARE(val_b->FIELD, val_a->FIELD); \
+}
+
+DEFINE_SORT(symbol_t, count, do_sort_by_calls);
+DEFINE_SORT(symbol_t, ticks, do_sort_by_ticks);
+DEFINE_SORT(symbol_t, ticks_self, do_sort_by_ticks_self);
+DEFINE_SORT(symbol_t, ticks_kids, do_sort_by_ticks_kids);
+DEFINE_SORT(symbol_t, name, do_sort_by_name);
+
+
+static GList *sort_symlist(GList *symlist)
+{
+    if(!strcmp(opt.sort, "name"))
+	return g_list_sort(symlist, do_sort_by_name);
+
+    if(!strcmp(opt.sort, "calls"))
+        return g_list_sort(symlist, do_sort_by_calls);
+
+    if(!strcmp(opt.sort, "ticks"))
+        return g_list_sort(symlist, do_sort_by_ticks);
+
+    if(!strcmp(opt.sort, "self"))
+        return g_list_sort(symlist, do_sort_by_ticks_self);
+
+    if(!strcmp(opt.sort, "child"))
+        return g_list_sort(symlist, do_sort_by_ticks_kids);
+
+    return symlist;
+}
+
+DEFINE_SORT(callinfo_t, caller->name, do_sort_ci_by_name);
+DEFINE_SORT(callinfo_t, calls, do_sort_ci_by_calls);
+DEFINE_SORT(callinfo_t, ticks, do_sort_ci_by_ticks);
+
+
+static GList *sort_callinfo(GList *callers)
+{
+    if(!strcmp(opt.sort, "name"))
+	return g_list_sort(callers, do_sort_ci_by_name);
+
+    if(!strcmp(opt.sort, "calls"))
+	return g_list_sort(callers, do_sort_ci_by_calls);
+    
+    return g_list_sort(callers, do_sort_ci_by_ticks);
+}
+
+
+/*
+ * Display the flat profile
+ */
+void view_profile_flat(profile_t *prof)
+{
+    GList *list, *e;
+    counter64_t total;
+    counter64_t current = 0;
+    counter64_t hz = prof->header->cpu_hz;
+
+    list = symtab_to_symlist(prof->symtab);
+    list = sort_symlist(list);
+
+    total = prof->total_ticks;
+
+    printf("\n\nFlat Profile:\n\n");
+
+    if(opt.debug)
+	{
+	    printf("CPU HZ: %llu\n", hz);
+	    printf("Total ticks: %llu  (%lf sec)\n", total, dsec(hz, total));
+	}
+
+    /* FIXME: add support for variable precision to other fields too */
+
+    printf("\n");
+    printf("%-*.*s cmul %% calls    self sec all sec  avg self avg all  function\n",
+	   opt.precision + 4, opt.precision + 4, "% time");
+    printf("%s ------ -------- -------- -------- -------- -------- --------\n", 
+	   repeat_char('-', opt.precision + 4));
+
+    for(e = list; e; e = e->next)
+	{
+	    symbol_t *sym = e->data;
+	    double percent, cmul;
+	    double sec_self, sec_all, sec_self_avg, sec_all_avg;
+
+	    if(sym->count == 0)
+		continue;
+	    if(symbol_is_ignored(sym))
+		continue;
+	    //printf("sym->ticks_self = %lf  total = %lf\n", (double)sym->ticks_self, (double)total);
+	    percent = (double)sym->ticks_self / (double)total * 100.0;
+
+	    current += sym->ticks_self;
+	    cmul = (double)current / (double)total * 100.0;
+
+	    sec_self = dsec(hz, sym->ticks_self);
+	    sec_all  = dsec(hz, sym->ticks_self + sym->ticks_kids);
+	    
+	    sec_self_avg = dsec(hz, sym->ticks_self / sym->count);
+	    sec_all_avg  = dsec(hz, (sym->ticks_self + sym->ticks_kids) / sym->count);
+
+
+	    printf("%*.*lf %6.2lf %8lu %8.3lf %8.3lf %8.3lf %8.3lf %s\n",
+		   opt.precision + 4, opt.precision, percent,
+		   cmul,
+		   sym->count,
+		   sec_self,
+		   sec_all,
+		   sec_self_avg,
+		   sec_all_avg,
+		   fullname(sym));
+
+	    if(opt.source && sym->source)
+		printf("- %s\n", sym->source);
+
+#if 0
+	    if(sym->callers)
+		{
+		    GList *ce;
+		    int line = 0;
+		    for(ce = sym->callers; ce; ce = ce->next)
+			{
+			    callinfo_t *ci = ce->data;
+			    printf("%*s %8lu %s\n",
+				   opt.precision+4+6+1, "",
+				   ci->calls, 
+				   ci->caller->name);
+			    line++;
+			}
+		}
+#endif
+
+	}
+}
+
+/*
+ * Display the callers profile
+ */
+void view_profile_callers(profile_t *prof)
+{
+    GList *list, *e;
+    counter64_t total;
+    counter64_t hz = wprof_get_hz(); //prof->header->cpu_hz;
+
+    list = symtab_to_symlist(prof->symtab);
+    list = sort_symlist(list);
+
+    total = prof->total_ticks;
+
+    printf("\n\nCallers Profile:\n\n");
+
+    for(e = list; e; e = e->next)
+	{
+            symbol_t *sym = e->data;
+
+	    if(sym->count == 0)
+		continue;
+	    
+	    printf("%s  (%lu call%s, %.3lf sec) called by:\n", 
+		   fullname(sym),
+		   sym->count,
+		   sym->count == 1 ? "" : "s",
+		   dsec(hz, sym->ticks_kids + sym->ticks_self)
+		   );
+	    
+	                                                          
+	    printf("-calls----------- -time-------- -caller-------------\n");
+	    if(sym->callers)
+		{
+		    GList *ce;
+		    for(ce = sort_callinfo(sym->callers); ce; ce = ce->next)
+			{
+			    callinfo_t *ci = ce->data;
+			    symbol_t *caller = ci->caller;
+
+#if 1
+			    printf("    %8lu %3.0f%% %8.3lf %3.0f%% %s\n",
+				   ci->calls,
+				   100.0 * ci->calls / sym->count,
+				   dsec(hz, ci->ticks),
+				   100.0 * ci->ticks / (sym->ticks_kids + sym->ticks_self),
+				   caller->name);
+#else
+			    printf("    %s for %lu calls, %.3lf sec\n",
+				   fullname(caller),
+				   ci->calls,
+				   dsec(hz, ci->ticks)
+				   );
+#endif
+			}
+		}
+	    else
+		printf("                                <unknown>\n");
+
+	    printf("\n");
+
+	}
+}
+
+
+static void do_view_profile_calltree(arc_t *root, GHashTable *symtab, int idx)
+{
+	arc_t *arc;
+
+	if(!root)
+		return;
+
+	for(arc = root->children; arc; arc = arc->next)
+        {
+		symbol_t *sym;
+		int i;
+
+		sym = g_hash_table_lookup(symtab, (gpointer)arc->addr);
+
+		for(i = 0; i < idx; i++)
+			printf("  ");
+
+		if(sym)
+			printf("%s  (%lu calls, %llu ticks)\n", fullname(sym), arc->count, arc->ticks);
+		else
+			printf("%x  (%lu calls, %llu ticks)\n", (unsigned int)arc->addr, arc->count, arc->ticks);
+
+		do_view_profile_calltree(arc, symtab, idx + 1);
+        }
+}
+
+/*
+ * Display the calltree
+ */
+void view_profile_calltree(profile_t *prof)
+{
+        printf("\n\nCalltree:\n\n");
+        do_view_profile_calltree(&prof->header->root[0], prof->symtab, 0);
+}
+
+
+
+
+/*--------------------------------------------------------*/
+// MAIN
+/*--------------------------------------------------------*/
+
+
+int main(int argc, char **argv)
+{
+    int option_index = 0;
+
+    while(1)
+	{
+	    int c = getopt_long (argc, argv, "h?i:s:",
+				 long_options, &option_index);
+	    if(c == -1)
+		break;
+
+	    switch(c)
+		{
+		case 0:
+		    break;
+
+		    /*FIXME not sure if this option is useful/works correctly */
+		case 'i':
+		    {
+			gchar **strv = g_strsplit(optarg, ",", 0);
+			int i = 0;
+			while(strv[i])
+			    {
+				opt.ignore = g_list_append(opt.ignore, g_strdup(strv[i]));
+				i++;
+			    }
+			g_strfreev(strv);
+		    }
+		    break;
+
+		case 's':
+		    opt.sort = strdup(optarg);
+		    break;
+		    
+		case 'p':
+		    opt.precision = atoi(optarg);
+		    break;
+
+		case '?':
+		case 'h':
+		    usage();
+		    exit(0);
+		    break;
+		default:
+		    printf("Something is messed up ...\n");
+		    break;
+		}
+	}
+
+
+    if(optind >= argc)
+	{
+	    usage();
+	    printf("\nError: no files specified.\n");
+	    exit(1);
+	}
+
+    while(optind < argc)
+	{
+	    profile_t *prof;
+	    char *filename = argv[optind++];
+
+	    prof = profile_open(filename);
+	    if(!prof)
+		continue;
+
+	    if(opt.flat)
+		view_profile_flat(prof);
+
+	    if(opt.callers)
+		view_profile_callers(prof);
+
+	    if(opt.calltree)
+		view_profile_calltree(prof);
+
+	    profile_close(prof);
+	}
+
+    exit(0);
+}
+
+
+void usage()
+{
+	static const char usage_msg[] = "Usage: wprof [OPTION]... [FILE]...\n\n"
+		"  --flat            display flat profile\n"
+		"  --callers         display callers profile\n"
+		"  --calltree        display entire calltree (can be huge!)\n"
+		"! --merge           merge specified profile files\n"
+		"  --list-dso        list shared objects used by the program\n"
+		"  --dso-prefix      prefix symbols with the name of the object they reside in\n"
+		"  --ignore SYM      ignore symbol SYM (comma separated list)\n"
+		"  --source          display source files and line numbers\n"
+		"  --compress        compress callers with the same name into a single entry\n"
+		"  --sort KEY        sort output by KEY (name, calls, ticks, self, child)\n"
+		"  --help            display this help text\n\n"
+		"Most options can be prefixed with 'no' to disable it, e.g. --no-flat\n";
+	
+	printf("%s", usage_msg);
+}
--- /dev/null	1994-07-18 08:46:18.000000000 +0900
+++ libs/wprof/wineprof.h	2003-05-30 17:19:45.000000000 +0900
@@ -0,0 +1,56 @@
+/* Wine Profiler
+ *
+ * Copyright 2000-2003 Codeweavers, Charles Loep
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef __WINEPROF_H__
+#define __WINEPROF_H__
+
+struct callinfo_s;
+struct symbol_s;
+struct profile_s;
+
+
+typedef struct callinfo_s {
+	counter32_t      calls;
+	counter64_t      ticks;
+	struct symbol_s *caller;
+} callinfo_t;
+
+
+typedef struct symbol_s {
+	char         *name;
+	char         *soname;
+	char         *source;
+	unsigned int  addr;
+	counter32_t   count;
+	counter64_t   ticks;
+	counter64_t   ticks_self;
+	counter64_t   ticks_kids;
+	GList        *callers;
+} symbol_t;
+
+typedef struct profile_s {
+	int             fd;
+	wprof_header_t *header;
+	GHashTable     *symtab;
+	GList          *symlist;
+	counter64_t     total_ticks;
+} profile_t;
+
+
+#endif /*__WINEPROF_H__*/
--- /dev/null	1994-07-18 08:46:18.000000000 +0900
+++ libs/wprof/wrappers.c	2003-08-15 11:27:31.000000000 +0900
@@ -0,0 +1,104 @@
+/* Wine Profiler
+ *
+ * Copyright 2000-2003 Codeweavers, Charles Loep
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define __USE_GNU
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <dlfcn.h>
+
+#include "profiler.h"
+
+
+#ifndef RTLD_NEXT
+#define RTLD_NEXT ((void *)-1L)
+#endif
+
+
+#ifdef DEBUG
+#define db_printf(args...) printf(args)
+#else
+#define db_printf(args...)
+#endif
+
+
+#define WPROF_DETECT_EXIT
+
+
+#ifdef WPROF_DETECT_EXIT
+
+static int wrappers_initialized = 0;
+static void *(*real_dlopen)(const char *filename, int mode) = 0;
+static int (*real_dlclose)(void *handle) = 0;
+static void (*real_exit)(int status) __attribute__((__noreturn__)) = 0;
+static void (*real__exit)(int status) = 0;
+
+
+
+static void init_wrappers()
+{
+	real_dlopen = dlsym(RTLD_NEXT, "dlopen");
+	real_dlclose = dlsym(RTLD_NEXT, "dlclose");
+	real_exit = dlsym(RTLD_NEXT, "exit");
+	real__exit = dlsym(RTLD_NEXT, "_exit");
+}
+
+
+void *dlopen(const char *filename, int flag)
+{
+	if(!wrappers_initialized)
+		init_wrappers();
+
+        db_printf("[%d] dlopen(%s, %d)\n", getpid(), filename, flag);
+        return real_dlopen(filename, flag);
+}
+
+int dlclose(void *handle)
+{
+	if(!wrappers_initialized)
+		init_wrappers();
+	
+	db_printf("[%d] dlclose(%p)\n", getpid(), handle);
+	return real_dlclose(handle);
+}
+
+void exit(int status)
+{
+	if(!wrappers_initialized)
+		init_wrappers();
+
+	db_printf("[%d] exit(%d)\n", getpid(), status);
+	wprof_exit_thread(status);
+
+	real_exit(status);
+}
+
+void _exit(int status)
+{
+	if(!wrappers_initialized)
+		init_wrappers();
+
+	db_printf("[%d] _exit(%d)\n", getpid(), status);
+	wprof_exit_thread(status);
+
+	real__exit(status);
+}
+
+#endif /*WPROF_DETECT_EXIT*/


More information about the wine-patches mailing list