Compare commits

...
Author SHA1 Message Date
DJ Delorie 02f4ecb623 Updates to trace2wl
* command line option -p to show progress
* command line option -f to use file-based buffers
* reduced memory footprint
* more 32/64-bit fixes
2016-11-10 16:08:28 -05:00
DJ Delorie 8926b32dba More merge-related tweaks
* add --enable-experimental-malloc/--disable-experimental-malloc
  (default: enabled)

* syntax errors related to new lock macros

* add some missing #if USE_TCACHE pairs

* Undo test tweak to environment variable scanner
2016-11-08 23:47:08 -05:00
DJ Delorie 4f460c8944 Merge branch 'master' into dj/malloc 2016-11-08 18:44:02 -05:00
DJ Delorie 275d577540 Merge branch 'master' into dj/malloc 2016-08-15 23:47:06 -04:00
DJ Delorie 528795b019 Merge branch 'master' into dj/malloc 2016-08-11 18:42:42 -04:00
DJ Delorie 4a6779937b Various namespace issues 2016-08-10 22:52:24 -04:00
DJ Delorie ae99936eea Remove debugging; fix trace error handling
Comment out _m_printf until it's needed again.

Properly unlock the trace mutex when we error out because
of file errors; also disable tracing when that happens.
2016-08-10 13:02:27 -04:00
DJ Delorie 56414a409a Various minor fixes
Replace "int" with "size_t" as appropriate.
Appease gcc's array-bounds warning
Process tcache after hooks to support MALLOC_CHECK_
2016-08-09 19:42:47 -04:00
DJ Delorie 599a9da8e9 Migrate trace2wl from C++ to C
Also add posix_memalign support
2016-08-08 15:07:02 -04:00
DJ Delorie 6f47f3e632 Yet more 32-bit fixes.
Make sure trace_dump doesn't overflow
2016-07-22 18:19:52 -04:00
Carlos O'Donell c841d237c1 Add quick_run compilation mode.
- Add quick_run compilation mode.
- Remove disabling of fast bins.
2016-07-22 12:46:51 -04:00
DJ Delorie 7a738e7f52 More 32-bit fixes.
Various fixes to handle traces and workloads bigger than 2 Gb.
2016-07-22 01:23:10 -04:00
DJ Delorie b096a21663 Add various bin-related trace path flags 2016-07-21 15:33:27 -04:00
DJ Delorie 9c879c0b31 Add note about the timing of recording an mremap event. 2016-07-20 21:18:10 -04:00
DJ Delorie f214e72974 Reschedule trace record commits to avoid inversion.
This change decouples "collecting trace data" from "allocating
a trace record" so that the record can be inserted into the
trace buffer in the correct sequence wrt when it "owns" the pointers
being recorded (i.e. malloc should record its event after it does
its allocation, but free should record its event before it returns
the memory to the arena).  It splits starting a trace record
(function entry) with committing to the buffer (trace recording)
so that path data can be accumulated easily.

Trace inversion happens when one thread records a malloc, but
before it can actually do the allocation, the kernel schedules
a thread that free's a block, which the malloc later returns.
The events are free->malloc, but the trace records are malloc->free.
2016-07-20 17:16:50 -04:00
DJ Delorie f57aaed3b2 Minor tweaks to trace_run and trace2wl
trace_run - fix realloc returning NULL behavior

trace2wl - hard stop on multi-level inversion, print number
of fixed inversions.
2016-07-19 22:22:46 -04:00
DJ Delorie 2c5e3bf401 Fix trace window unmapping bug
We were recording window number, not trace count, resulting in
windows not getting unmapped.
2016-07-19 16:53:49 -04:00
DJ Delorie b28259c68e Detect single trace inversions and correct them.
Trace inversion happens when:

* thread A calls malloc, starts a trace record, and then is
  suspended by the kernel.

* thread B calls free, writes a trace record, and frees address X.

* thread A is scheduled, and returns address X.

The trace would show thread A's malloc returning pointer X before
thread B free's it, which is "trace inversion".

This patch detects a single inversion (multiple inversions can happen,
although rare) and reschedules the malloc to happen right after the
free.
2016-07-19 15:40:11 -04:00
DJ Delorie 25cfec5974 Change trace_run from mmap to read
To avoid huge memory requirements for huge workloads, and unreliable
RSS size due to unmlock'able maps, switch trace_run to a read-as-you-go
design.  Data is read per-thread in 4k or 64k chunks (based on workload
size) into a fixed buffer.
2016-07-18 22:26:54 -04:00
Carlos O'Donell f9a7d78b73 Enhance the tracer with new data and fixes.
* Increase trace entry to 64-bytes.

The following patch increases the trace entry to 64-bytes, still a
proper multiple of the shared memory window size. While we have doubled
the entry size the on-disk format is still smaller than the ASCII
version. In the future we may wish to add variable sized records, but
for now the simplicity of this method works well.

With the extra bytes we are going to:
- Record internal size information for incoming (free) and outgoing
  chunks (malloc, calloc, realloc, etc).
  - Simplifies accounting of RSS usage and provides an extra cross check
    between malloc<->free based on internal chunk sizes.
- Record alignment information for memalign, and posix_memalign.
  - Continues to extend the tracer to the full API.
- Leave 128-bits of padding for future path uses.
  - Useful for more path information.

Additionally __MTB_TYPE_POSIX_MEMALIGN is added for the sole purpose of
recording the trace only so that we can hard-fail in the workload
converter when we see such an entry.

Lastly C_MEMALIGN, C_VALLOC, C_PVALLOC, and C_POSIX_MEMALIGN are added
for workload entries for the sake of completeness.

Builds on x86_64, capture looks good and it works.

* Teach trace_dump about the new entries.

The following patch teaches trace_dump about the new posix_memalign
entry. It also teaches trace_dump about the new size2 and size3 fields.
Tested by tracing a program that uses malloc, free, and memalign and
verifying that the extra fields show the expected chunk sizes, and
alignments dumped with trace_dump.

Tested on x86_64 with no apparently problems.

* Teach trace2wl and trace_run about new entries

(a) trace2wl changes:

The following patch teaches trace2wl how to output entries for valloc
and pvalloc, it does so exactly the same way it does for malloc, since
from the perspective of the API they are identical.

Additionally trace2wl is taught how to output an event for memalign,
storing alignment and size in the event record.

Lastly posix_memalign is detected and the converter aborted if it's
seen.  It is my opinion that we should not ignore this data during
conversion.  If we see a need for it we should implement it later.

(b) trace_run changes:

Some cosmetic cleanup in printing 'pthread_t' which is always an address
of the struct pthread structure in memory, so to make debugging easier
we should print the value as a hex pointer.

Teach the simulator how to run memalign. With the newly recorded
alignment information we double check that the resulting memory is
correctly aligned.

We do not implement valloc and pvalloc, they will abort the simulator.
This is incremental progress.

Tested on x86_64 by converting and running a multithreaded test
application that calls calloc, malloc, free, and memalign.

* Disable recursive traces and save new data.

(a) Adds support for disabling recurisvely recorded traces e.g. realloc
calling malloc no longer produces a realloc and malloc trace event. We
solve this by using a per-thread variable to disable new trace creation,
but allow path bits to be set.  This lets us record the code paths
taken, but only record one public API event.

(b) Save internal chunk size information into trace events for all APIs.
The most important is free where we record the free size, this allows
easier tooling to compute running idea RSS values.

Tested on x86_64 with some small applications and test programs.
2016-07-16 22:19:03 -04:00
DJ Delorie e4650ee4a8 Add tunables for tcache count and max size 2016-07-15 21:39:46 -04:00
DJ Delorie fcf17bd0d9 Fix NULL return value handling
Decided that a call that returns NULL should be encoded in the
workload but that the simulator should just skip those calls,
rather than skip them in the converter.
2016-07-15 18:26:14 -04:00
DJ Delorie 0eacff38a3 Fix mmap/munmap trace bits 2016-07-15 17:21:07 -04:00
DJ Delorie cfc89672ed Add trace_dump tool
trace_dump <binary-trace-or-workload>
autodetects trace file vs workload, outputs the contents thereof
2016-07-13 22:50:28 -04:00
Anton Blanchard 497bcfe9ac Fix a 32-bit sign-extension bug. 2016-07-13 21:16:03 -04:00
DJ Delorie 6ce11061fb Fix double-padding bug
The tcache was calling request2size which resulted in double padding.
Store tcache's copy in a separate variable to avoid this.
2016-07-13 11:02:39 -04:00
DJ Delorie b856f645a2 Update to new binary file-based trace file.
In order to not lose records, or need to guess ahead of time how
many records you need, this switches to a mmap'd file for the trace
buffer, and grows it as needed.

The trace2dat perl script is replaced with a trace2wl C++ program
that runs a lot faster and can handle the binary format.
2016-07-12 22:00:44 -04:00
DJ Delorie e4cff5fd83 Add README for testing copr repo of dj/malloc branch
Includes some details on tracing and simulating too.
2016-07-06 17:42:08 -04:00
Carlos O'Donell 77e65f7bfe Use __gettid() function for tracing.
Integrate with thread 'tid' cache and use the cached value if
present, otherwise update the cache. This should be much faster
than a syscall per trace event.
2016-07-06 15:30:36 -04:00
DJ Delorie 1329743b58 32-bit fixes, RSS tracking, Free wiping.
More 32-bit vs 64-bit fixes.

We now track "ideal RSS" and report its maximum vs what the kernel
thinks our max RSS is.

Memory is filled with a constant when free'd.
2016-07-05 17:26:59 -04:00
DJ Delorie 2d7af358a3 Merge branch 'master' into dj/malloc 2016-06-30 16:19:04 -04:00
DJ Delorie 7328eadb40 Build fixes for in-tree and 32/64-bit
Expand the comments in mtrace-ctl.c to better explain how to use
this tracing controller.  The new docs assume the SO is built
and installed.

Build fixed for trace_run.c

Additional build pedantry to let trace_run.c be built with more
warnings/errors turned on.

Build/install trace_run and trace2dat

trace2dat takes dump files from mtrace-ctl.so and turns them into
mmap'able data files for trace_run, which "plays back" the logged
calls.

32-bit compatibility

Redesign tcache macros to account for differences between 64 and
32 bit systems.
2016-06-30 16:13:32 -04:00
DJ Delorie 0742c96f78 Merge branch 'master' into dj/malloc 2016-05-12 19:55:18 -04:00
DJ Delorie 8aecd66e28 Add libmtracectl.so
Update Makefiles to build libmtracectl.so

Tweak mtrace-ctl.c to avoid compiler warnings.
2016-05-12 19:49:36 -04:00
DJ Delorie 06f4408f64 Merge branch 'master' into dj/malloc
Periodic sync
2016-04-29 15:07:11 -04:00
DJ Delorie 46b44370f4 changes to per-thread cache algorithms
Core algorithm changes:

* Per-thread cache is refilled from existing fastbins and smallbins
  instead of always needing a bigger chunk.

* Caches are linked, and cache is cleaned up when the thread exits
  (incomplete for now, needed framework for chunk scanner).

* Fixes to mutex placement - needed to sync chunk headers across
  threads.

Enabling the per-thread cache (tcache) gives about a 20-30% speedup at
a 20-30% memory cost (due to fragmentation).  Still working on that :-)

Debugging helpers (temporary):

* __malloc_scan_chunks() calls back to the app for each chunk in each
  heap.

* _m_printf() helper for "safe" printing within malloc

* Lots of calls to the above, commented out, in case you need them.

* trace_run scans leftover chunks too.
2016-04-29 15:01:16 -04:00
DJ Delorie 5bd0885c9d Merge branch 'master' into dj/malloc 2016-03-18 22:19:43 -04:00
DJ Delorie 9ca7776445 Switch to datafile-based simulation
Compiling a 78,000,000 entry trace proved to be... difficult.
No, impossible.  Now the trace is distilled into a pseudo-code
data file that can be mmap'd into trace_run's address space
and interpreted.
2016-03-18 21:40:14 -04:00
DJ Delorie d6045255ae Replace int with size_t as appropriate 2016-03-17 13:45:05 -04:00
DJ Delorie d35e84028c Add trace2c script
Usage: trace2c /tmp/mtrace.out > sample.c

Converts a trace file to a compilable program that "somewhat"
reflects the same malloc workload as the program which was
traced.
2016-02-19 17:12:57 -05:00
DJ Delorie 9c79af3a2c More trace hooks
Add hooks to pvalloc and calloc

Add path flag for when a call is handled via a hook function
2016-02-19 17:09:02 -05:00
DJ Delorie 1322011d96 Update malloc tracing utility.
Change head pointer to be total calls; adjust users to modulo after
incrementing.

Use mmap() instead of sbrk().

Split environment variables so count and file can be specified.

Export trace hooks so mtrace-ctl can be built against libc.so.

Allow NULL to be passed to __mtrace_get_trace_buffer.

Add some error handling to mtrace-ctl.
2016-02-11 01:49:48 -05:00
DJ Delorie 649255b5d3 Initial tracing functionality
First attempt at a low-overhead tracing feature.  To enable, you
build mtrace-ctl.c into a .so and LD_PRELOAD it.  That uses a
private API to set up a trace buffer, and calls to malloc et all
fill in records in the trace buffer.  At program exit, mtrace-ctl
reads the buffer and stores the data on disk.

Internally, the only contention point is the atomic update of the
buffer head pointer.  Once aquired, each thread fills in its
record without needing locks.
2016-02-09 17:32:14 -05:00
DJ Delorie 850602a447 Initial attempt at a per-thread cache
If a malloc of size MAX_TCACHE_SIZE or smaller is asked for, a
thread-local cache is used.  An entry in the cache is returned if
available, else a chunk of size N*8 is requested from the arena,
and broken into 8 (TCACHE_FILL_COUNT+1) N-sized chunks.  One chunk
is returned and the rest are stored in the cache.

free() can also fill the cache, as long as there are fewer than
7 items in the cache, else the chunk is free'd as usual.

The cache is per-size so no searching is required.
2016-02-09 17:18:28 -05:00
14 changed files with 3442 additions and 28 deletions
+1 -1
View File
@@ -848,7 +848,7 @@ endif # $(+cflags) == ""
libio-include = -I$(..)libio
# List of non-library modules that we build.
built-modules = iconvprogs iconvdata ldconfig lddlibc4 libmemusage \
built-modules = iconvprogs iconvdata ldconfig lddlibc4 libmemusage libmtracectl \
libSegFault libpcprofile librpcsvc locale-programs \
memusagestat nonlib nscd extramodules libnldbl
+2
View File
@@ -74,6 +74,8 @@ multi-arch = @multi_arch@
mach-interface-list = @mach_interface_list@
experimental-malloc = @experimental_malloc@
nss-crypt = @libc_cv_nss_crypt@
# Configuration options.
Vendored
+13
View File
@@ -666,6 +666,7 @@ build_pt_chown
build_nscd
link_obsolete_rpc
libc_cv_nss_crypt
experimental_malloc
enable_werror
all_warnings
force_install
@@ -770,6 +771,7 @@ enable_kernel
enable_all_warnings
enable_werror
enable_multi_arch
enable_experimental_malloc
enable_nss_crypt
enable_obsolete_rpc
enable_systemtap
@@ -1436,6 +1438,8 @@ Optional Features:
--disable-werror do not build with -Werror
--enable-multi-arch enable single DSO with optimizations for multiple
architectures
--disable-experimental-malloc
disable experimental malloc features
--enable-nss-crypt enable libcrypt to use nss
--enable-obsolete-rpc build and install the obsolete RPC code for
link-time usage
@@ -3492,6 +3496,15 @@ else
fi
# Check whether --enable-experimental-malloc was given.
if test "${enable_experimental_malloc+set}" = set; then :
enableval=$enable_experimental_malloc; experimental_malloc=$enableval
else
experimental_malloc=yes
fi
# Check whether --enable-nss-crypt was given.
if test "${enable_nss_crypt+set}" = set; then :
enableval=$enable_nss_crypt; nss_crypt=$enableval
+7
View File
@@ -301,6 +301,13 @@ AC_ARG_ENABLE([multi-arch],
[multi_arch=$enableval],
[multi_arch=default])
AC_ARG_ENABLE([experimental-malloc],
AC_HELP_STRING([--disable-experimental-malloc],
[disable experimental malloc features]),
[experimental_malloc=$enableval],
[experimental_malloc=yes])
AC_SUBST(experimental_malloc)
AC_ARG_ENABLE([nss-crypt],
AC_HELP_STRING([--enable-nss-crypt],
[enable libcrypt to use nss]),
+22 -3
View File
@@ -49,7 +49,7 @@ install-lib := libmcheck.a
non-lib.a := libmcheck.a
# Additional library.
extra-libs = libmemusage
extra-libs = libmemusage libmtracectl
extra-libs-others = $(extra-libs)
# Helper objects for some tests.
@@ -64,11 +64,30 @@ test-extras = \
libmemusage-routines = memusage
libmemusage-inhibit-o = $(filter-out .os,$(object-suffixes))
libmtracectl-routines = mtrace-ctl
libmtracectl-inhibit-o = $(filter-out .os,$(object-suffixes))
others: $(objpfx)trace_run $(objpfx)trace2wl $(objpfx)trace_dump
install-bin = trace_run trace2wl trace_dump
$(objpfx)trace_run: $(objpfx)trace_run.o
$(LINK.o) -o $@ $(objpfx)trace_run.o -lpthread
$(objpfx)trace_dump: $(objpfx)trace_dump.o
$(LINK.o) -o $@ $(objpfx)trace_dump.o
$(objpfx)trace2wl: $(objpfx)trace2wl.o
$(LINK.o) -o $@ $(objpfx)trace2wl.o
$(objpfx)tst-malloc-backtrace: $(shared-thread-library)
$(objpfx)tst-malloc-thread-exit: $(shared-thread-library)
$(objpfx)tst-malloc-thread-fail: $(shared-thread-library)
$(objpfx)tst-malloc-fork-deadlock: $(shared-thread-library)
ifeq ($(experimental-malloc),yes)
CPPFLAGS-malloc.c += -DUSE_TCACHE
endif
# Export the __malloc_initialize_hook variable to libc.so.
LDFLAGS-tst-mallocstate = -rdynamic
@@ -80,7 +99,7 @@ aux := set-freeres thread-freeres
# The Perl script to analyze the output of the mtrace functions.
ifneq ($(PERL),no)
install-bin-script = mtrace
install-bin-script += mtrace
generated += mtrace
# The Perl script will print addresses and to do this nicely we must know
@@ -99,7 +118,7 @@ ifneq ($(cross-compiling),yes)
# If the gd library is available we build the `memusagestat' program.
ifneq ($(LIBGD),no)
others: $(objpfx)memusage
install-bin = memusagestat
install-bin += memusagestat
install-bin-script += memusage
generated += memusagestat memusage
extra-objs += memusagestat.o
+198
View File
@@ -0,0 +1,198 @@
<title>Using the dj/malloc GLIBC COPR repo</title>
<H1 align=center>Using the dj/malloc GLIBC COPR repo</H1>
<p>The purpose of this document is to assist folks in testing out my
custom dj/malloc branch of the upstream GLIBC git repo. This COPR
repo has pre-built RPMs for easy installation in a test
environment.</p>
See <a href="https://copr.fedorainfracloud.org/coprs/djdelorie/glibc_dj_malloc/">https://copr.fedorainfracloud.org/coprs/djdelorie/glibc_dj_malloc/</a>
for links and other information.
<h2>Installing the COPR Repo</h2>
<pre>
$ <b>cd /etc/yum.repos.d/</b>
</pre>
<h3>RHEL7</h3>
<pre>
$ <b>wget https://copr.fedorainfracloud.org/coprs/djdelorie/glibc_dj_malloc/repo/epel-7/djdelorie-glibc_dj_malloc-epel-7.repo</b>
$ <b>yum update</b>
$ <b>init 6</b>
</pre>
<h3>Fedora</h3>
<pre>
$ <b>dnf copr enable djdelorie/glibc_dj_malloc</b>
$ <b>dnf clean all</b> (optional)
$ <b>dnf update</b>
$ <b>init 6</b>
</pre>
<h3>Missing Dependencies</h2>
If dnf complains about missing dependencies, see if you have
non-x86_64 variants of glibc installed, and remove them:
<pre>
$ <b>rpm -qa | grep ^glibc | grep -v x86_64</b>
</pre>
<h3>Confirming Installation</h2>
<pre>
$ <b>rpm -qa | grep glibc</b>
glibc-all-langpacks-2.23.90-alphadj9.fc23.x86_64
glibc-2.23.90-alphadj9.fc23.x86_64
glibc-common-2.23.90-alphadj9.fc23.x86_64
</pre>
<h2>Capturing to the Trace Buffer</h2>
<p>One key new feature in this malloc is a high-speed trace buffer
that records every malloc, free, etc call with a minimum of added
latency. This is an improvement over the existing trace feature for
applications that are performance-critical. There is a private
(i.e. glibc-internal) API for activating this buffer, which is
enabled via a provided DSO:</p>
<pre>
$ <b>LD_PRELOAD=/lib64/libmtracectl.so ls</b>
</pre>
<p>Replace lib64 with lib, or whatever suitable path you've installed
into, for 32-bit machines or machines with non-standard layouts, which
I don't support, but you never know...</p>
<pre>
$ <b>ls -l /tmp/mtrace-*</b>
-rw-r--r--. 1 root root 12422 Jun 2 20:53 mtrace.out.1188
</pre>
<p>Each generated file is a binary file, specific to the architecture,
with one record per trace record entry. Some programs are included
in the COPR repo to process the generated files. Please make sure
you process these files on the same architecture as they were
generated on.</p>
<h2>Sending Us Trace Files</h2>
<p>If we ask you to send us a trace file, please rename and compress
it to make the file easier to transfer and keep track of.</p>
<pre>
$ <b>cd /tmp</b>
$ <b>gzip -9 mtrace.out.1188</b>
$ <b>mv mtrace.out.1188.gz f24-ls-fred.mtrace.gz</b> (or whatever name fits :)
</pre>
<p>Then mail <tt>f24-ls-fred.mtrace.gz</tt> to dj@redhat.com (or
whoever is asking for it, of course)</p>
<h2>Workload Simulator</h2>
<p>This build also includes a set of tools to "play back" a recorded
trace, which can be helpful in diagnosing memory-related performance
issues. Such workloads might be locally generated as part of a
benchmark suite, for example.</p>
<pre>
trace2wl <em>outfile</em> [<em>infile ...</em>]
</pre>
If an infile is not provided, input is read from stdin.
<pre>
$ trace2wl /tmp/ls.wl /tmp/mtrace-22172.out
</pre>
The resulting file is a "workload" - a data file that tells the
simulator how to play back all the malloc/free/etc calls. This file
is not human-readable, but a compact binary datafile intended to be
used only by the simulator.
<pre>
trace_run <em>workload.wl</em>
</pre>
<p>Note: trace_run only works on intel processors with the RDTSCP
opcode, which is only available on reasonably modern processors. To
see if your processor supports this opcode, look for
the <b>rdtscp</b> cpu flag:
<pre>
$ <b>grep rdtscp /proc/cpuinfo</b>
</pre>
If you get lines like "flags : <lots of flags>" then you have support
and trace_run will work. If the grep returns nothing, you don't.
<pre>
$ <b>trace_run /tmp/ls.wl</b>
488,004 cycles
106 usec wall time
0 usec across 1 thread
0 Kb Max RSS (1,228 -> 1,228)
Avg malloc time: 385 in 154 calls
Avg calloc time: 0 in 1 calls
Avg realloc time: 0 in 1 calls
Avg free time: 194 in 14 calls
Total call time: 62,033 cycles
</pre>
Note:
see <a href="http://developers.redhat.com/blog/2016/03/11/practical-micro-benchmarking-with-ltrace-and-sched/">Practical
Micro-Benchmarking with ltrace and sched</a> to get more stable
numbers.
<h2>Tunables</h2>
<pre>
MALLOC_TCACHE_COUNT=<i>count</i>
MALLOC_TCACHE_MAX=<i>bytes</i>
</pre>
<p><tt>count</tt> can be anything from 0 to whatever.</p>
<p><tt>bytes</tt> can be anything from 0 to 63*2*sizeof(void *)-1 (503
for 32-bit, 1007 for 64-bit).</p>
<p><tt>mallopt</tt> parameters are (private):</p>
<pre>
#define M_TCACHE_COUNT -9
#define M_TCACHE_MAX -10
</pre>
<h2>Uninstalling</h2>
To uninstall the custom build and revert to an official release, you
"simly" disable the COPR repo and downgrade to the latest "released" version:
<pre>
$ <b>vi /etc/yum.repos.d/_copr_djdelorie-glibc_dj_malloc.repo</b>
</pre>
change this line from 1 to 0:
<pre>
enabled=0
</pre>
Then:
<pre>
$ <b>dnf --allowerasing downgrade glibc</b>
</pre>
(replace "dnf" with "yum" for RHEL 7)
+9
View File
@@ -72,5 +72,14 @@ libc {
__libc_scratch_buffer_grow;
__libc_scratch_buffer_grow_preserve;
__libc_scratch_buffer_set_array_size;
# malloc trace hooks for mtrace-ctl
__malloc_trace_init;
__malloc_trace_pause;
__malloc_trace_unpause;
__malloc_trace_stop;
__malloc_trace_sync;
__malloc_scan_chunks;
}
}
+16
View File
@@ -319,7 +319,23 @@ ptmalloc_init (void)
if (memcmp (envline, "ARENA_TEST", 10) == 0)
__libc_mallopt (M_ARENA_TEST, atoi (&envline[11]));
}
#if USE_TCACHE
if (!__builtin_expect (__libc_enable_secure, 0))
{
if (memcmp (envline, "TCACHE_MAX", 10) == 0)
__libc_mallopt (M_TCACHE_MAX, atoi (&envline[11]));
}
#endif
break;
#if USE_TCACHE
case 12:
if (!__builtin_expect (__libc_enable_secure, 0))
{
if (memcmp (envline, "TCACHE_COUNT", 12) == 0)
__libc_mallopt (M_TCACHE_COUNT, atoi (&envline[13]));
}
break;
#endif
case 15:
if (!__builtin_expect (__libc_enable_secure, 0))
{
+1079 -24
View File
File diff suppressed because it is too large Load Diff
+195
View File
@@ -0,0 +1,195 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
/* This module is a stand-alone control program for malloc's internal
trace buffer. It is intended to be preloaded like this:
LD_PRELOAD=/usr/lib/libmtracectl.so ./myprog
This module uses the following environment variables:
MTRACE_CTL_COUNT - how many records to store (default: 1000). Each
record is 32 bytes, and the entire buffer is mmap'd at once. If
the buffer isn't big enough, it will overwrite early records with
newer ones. The total number of trace records is reported in the
output file so that a larger buffer may be allocated on future runs.
MTRACE_CTL_FILE - the output file name (default:
/tmp/mtrace-$$.out). Note that the default is per-pid but there is
no way to specify a per-pid pattern via this environment variable.
The output file will contain a header that says how many trace
records were seen (which is usually more or less than the trace
buffer size). The trace buffer is then dumped one entry per line.
*/
#include "mtrace.h"
#define estr(str) write (2, str, strlen (str))
#if 0
static void
err(const char *str)
{
estr (str);
estr ("\n");
}
#endif
/*
* mtrace_start - checks for buffer, allocates one if needed, starts trace.
* mtrace_stop - stops tracing
* mtrace_sync - syncs the buffer
* mtrace_reset - resets buffer state to intial state
*/
struct _malloc_trace_buffer_s *mtrace_buffer = NULL;
size_t mtrace_buffer_bytesize = 0;
int
mtrace_start (void)
{
const char *e;
char *fname;
int sequence = 0;
e = getenv("MTRACE_CTL_COUNT");
if (!e)
e = "1000";
e = getenv("MTRACE_CTL_FILE");
if (!e)
e = "/tmp/mtrace.out";
fname = alloca (strlen(e) + 30);
sprintf(fname, "%s.%d", e, getpid());
while (access (fname, F_OK) == 0)
{
sequence ++;
sprintf(fname, "%s.%d.%d", e, getpid(), sequence);
}
estr ("mtrace-ctl: writing to ");
estr (fname);
estr ("\n");
__malloc_trace_init (fname);
return 0;
}
void
mtrace_stop (void)
{
size_t count;
char line[100];
count = __malloc_trace_stop ();
sprintf (line, "mtrace-ctl: %lld entries recorded\n", (long long)count);
estr (line);
}
void
mtrace_sync (void)
{
__malloc_trace_sync ();
// __malloc_trace_buffer_ptr buf = __malloc_get_trace_buffer (&size, &head);
// msync (buf, size * sizeof(struct __malloc_trace_buffer_s), MS_SYNC | MS_INVALIDATE);
}
void
mtrace_reset (void)
{
__malloc_trace_stop ();
mtrace_start ();
}
void __attribute__((constructor))
mtrace_ctor(void)
{
if (mtrace_start ())
exit (1);
}
void __attribute__((destructor))
mtrace_dtor(void)
{
mtrace_stop ();
mtrace_sync ();
}
#if 0
const char * const typenames[] = {
"unused ",
"malloc ",
"calloc ",
"free ",
"realloc ",
"memalign",
"valloc ",
"pvalloc ",
};
void __attribute__((destructor))
djend(void)
{
char *e;
FILE *outf;
size_t head, size, i;
e = getenv("MTRACE_CTL_FILE");
if (!e)
{
static char fname[100];
sprintf(fname, "/tmp/mtrace-%d.out", getpid());
e = fname;
}
outf = fopen(e, "w");
if (!outf)
err("cannot open output file");
setbuf (outf, NULL);
fprintf (outf, "%ld out of %ld events captured\n", (long)head, (long)size);
fprintf (outf, "threadid type path ptr1 size ptr2\n");
for (i=0; i<size; i++)
{
__malloc_trace_buffer_ptr t = buf + (i+head) % size;
switch (t->type)
{
case __MTB_TYPE_UNUSED:
break;
default:
fprintf (outf, "%08x %s %c%c%c%c%c%c%c%c %016llx %016llx %016llx\n",
t->thread,
typenames[t->type],
t->path_thread_cache ? 'T' : '-',
t->path_cpu_cache ? 'c' : '-',
t->path_cpu_cache2 ? 'C' : '-',
t->path_sbrk ? 's' : '-',
t->path_mmap ? 'M' : '-',
t->path_munmap ? 'U' : '-',
t->path_m_f_realloc ? 'R' : '-',
t->path_hook ? 'H' : '-',
(long long unsigned int) (size_t) t->ptr1,
(long long unsigned int) t->size,
(long long unsigned int) (size_t) t->ptr2);
break;
}
}
fclose (outf);
munmap (buf, size * sizeof(struct __malloc_trace_buffer_s));
return;
}
#endif
+145
View File
@@ -0,0 +1,145 @@
/* Malloc trace buffer API and definitions.
Copyright (C) 2016 Free Software Foundation, Inc.
This file is part of the GNU C Library.
Contributed by DJ Delorie <dj@redhat.com>.
The GNU C 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.
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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; see the file COPYING.LIB. If
not, see <http://www.gnu.org/licenses/>. */
/* The trace buffer contains a minimal amount of information which can
be used to help reconstruct the memory profile of the calling
application. */
struct __malloc_trace_buffer_s {
int32_t thread; /* if thread_t > 32 bits, this is the LSBs */
uint32_t type:8; /* call type - malloc, free, etc */
uint32_t path_thread_cache:1; /* malloc/free interacted with the thread cache */
uint32_t path_cpu_cache:1; /* malloc/free interacted with the per-cpu cache */
uint32_t path_cpu_cache2:1; /* per-cpu cache contention required realloc */
uint32_t path_sbrk:1; /* sbrk was called */
uint32_t path_mmap:1; /* mmap was called */
uint32_t path_munmap:1; /* munmap was called */
uint32_t path_m_f_realloc:1; /* realloc became malloc/free (i.e. next few records) */
uint32_t path_hook:1; /* A hook was used to complete the request */
uint32_t path_unsorted_add:1; /* something was added to the unsorted bin */
uint32_t path_unsorted_remove:1; /* something was removed from the unsorted bin */
uint32_t path_unsorted_empty:1; /* the unsorted bin was emptied */
uint32_t path_fastbin_add:1; /* something was added to a fastbin */
uint32_t path_fastbin_remove:1; /* something was removed from a fastbin */
uint32_t path_malloc_consolidate:1; /* something was removed from a fastbin */
uint32_t path:10; /* remaining bits */
/* FREE - pointer to allocation to free.
REALLOC - pointer to original allocation.
POSIX_MEMALIGN - error code */
void *ptr1;
/* pointer to new allocation. */
void *ptr2;
/* requested size. */
size_t size;
/* FREE - internal size of deallocation.
REALLOC - internal size of original allocation.
MEMALIGN - alignment.
POSIX_MEMALIGN - alignment. */
size_t size2;
/* internal size of new allocation. */
size_t size3;
/* Pad out to 64-bytes for future uses and mmap'd window alignment. */
size_t pad[2];
};
typedef struct __malloc_trace_buffer_s *__malloc_trace_buffer_ptr;
/* Initialize the trace buffer and backing file. The file is
overwritten if it already exists. */
void __malloc_trace_init (char *filename);
/* All remaining functions return current count of trace records. */
/* Pause - but don't stop - tracing. */
size_t __malloc_trace_pause (void);
/* Resume tracing where it left off when paused. */
size_t __malloc_trace_unpause (void);
/* Stop tracing and clean up all the trace buffer mappings. */
size_t __malloc_trace_stop (void);
/* Sync all buffer data to file (typically a no-op on Linux). */
size_t __malloc_trace_sync (void);
#define __MTB_TYPE_UNUSED 0
/* ptr1 is 0x1234, size is sizeof(void *) - there is one of these at
the beginning of the trace. */
#define __MTB_TYPE_MAGIC 255
/* ptr2 = malloc (size) */
#define __MTB_TYPE_MALLOC 1
/* ptr2 = calloc (size) */
#define __MTB_TYPE_CALLOC 2
/* free (ptr1) */
#define __MTB_TYPE_FREE 3
/* ptr2 = realloc (ptr1, size) */
#define __MTB_TYPE_REALLOC 4
/* ptr2 = memalign (size2, size) */
#define __MTB_TYPE_MEMALIGN 5
/* ptr2 = valloc (size) */
#define __MTB_TYPE_VALLOC 6
/* ptr2 = pvalloc (size) */
#define __MTB_TYPE_PVALLOC 7
/* ptr2 = posix_memalign (ptr1, size2, size) */
#define __MTB_TYPE_POSIX_MEMALIGN 8
typedef enum {
MSCAN_UNUSED,
MSCAN_ARENA,
MSCAN_HEAP,
MSCAN_CHUNK_USED,
MSCAN_CHUNK_FREE,
MSCAN_FASTBIN_FREE,
MSCAN_UNSORTED,
MSCAN_TOP,
MSCAN_TCACHE
} MSCAN_Types;
void __malloc_scan_chunks (void (*callback)(void * /*ptr*/, size_t /*length*/, int /*type*/));
/* Codes for the simulator/workload programs. */
#define C_NOP 0
#define C_DONE 1
#define C_MALLOC 2
#define C_CALLOC 3
#define C_REALLOC 4
#define C_FREE 5
#define C_SYNC_W 6
#define C_SYNC_R 7
#define C_ALLOC_PTRS 8
#define C_ALLOC_SYNCS 9
#define C_NTHREADS 10
#define C_START_THREAD 11
#define C_MEMALIGN 12
#define C_VALLOC 13
#define C_PVALLOC 14
#define C_POSIX_MEMALIGN 15
+812
View File
@@ -0,0 +1,812 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <getopt.h>
#include <time.h>
/* The trace file looks like an array of struct __malloc_trace_buffer_s */
#include "mtrace.h"
/* This application is "run once and exit" so there's no cleanup code. */
typedef unsigned char byte;
struct __malloc_trace_buffer_s *trace_records;
size_t num_trace_records;
int verbose = 0;
int use_file_buffers = 0;
//------------------------------------------------------------
// File data buffers
static int tmpfile_idx = 0;
static char *tmpdir;
static int tmpdir_len;
#define BUFFER_SIZE 4096
/* If we're using memory buffers, we chain from first_buffer to
last_buffer as a linked list. If we're using disk buffers, we only
use last_buffer, which points to a fixed buffer. */
typedef struct BufferBlock {
struct BufferBlock *next;
byte buf[BUFFER_SIZE];
} BufferBlock;
typedef struct Buffer {
char *filename;
int fd;
BufferBlock *first_buffer;
BufferBlock *last_buffer;
size_t count_total;
size_t count_last;
} Buffer;
void
Buffer__ctor(Buffer *this)
{
if (use_file_buffers)
{
this->filename = (char *) malloc (tmpdir_len + 7);
sprintf (this->filename, "%s%06d", tmpdir, tmpfile_idx);
tmpfile_idx ++;
this->fd = -1;
}
this->first_buffer = this->last_buffer = (BufferBlock *) malloc (sizeof(BufferBlock));
this->first_buffer->next = NULL;
this->count_total = this->count_last = 0;
}
void
Buffer__add (Buffer *this, char x)
{
if (this->count_last == BUFFER_SIZE)
{
if (use_file_buffers)
{
if (this->fd == -1)
{
this->fd = open(this->filename, O_WRONLY|O_CREAT|O_TRUNC, 0666);
if (this->fd < 0)
{
fprintf(stderr, "cannot create temporary file %s for writing\n", this->filename);
perror("The error was");
exit(1);
}
}
write (this->fd, this->last_buffer->buf, BUFFER_SIZE);
}
else
{
BufferBlock *b = (BufferBlock *) malloc (sizeof(BufferBlock));
b->next = NULL;
this->last_buffer->next = b;
this->last_buffer = b;
}
this->count_last = 0;
}
this->last_buffer->buf[this->count_last] = x;
this->count_last ++;
this->count_total ++;
}
void
Buffer__add_int (Buffer *this, size_t val)
{
byte buf[sizeof(size_t)*2];
int i = 0;
buf[i++] = val & 127;
while (val > 127)
{
val >>= 7;
buf[i++] = (val & 127) | 128;
}
while (i > 0)
Buffer__add (this, buf[--i]);
}
void
Buffer__write (Buffer *this, int fd)
{
if (use_file_buffers)
{
byte buf[BUFFER_SIZE];
int count;
struct stat s;
if (this->fd != -1)
close (this->fd);
if (this->count_total != this->count_last)
{
if (stat(this->filename, &s) < 0)
{
fprintf(stderr, "Cannot stat %s\n", this->filename);
perror("The error was");
exit(1);
}
if (s.st_size != this->count_total - this->count_last)
{
fprintf(stderr, "File %s is %ld, not %ld-%ld !\n", this->filename, s.st_size, this->count_total, this->count_last);
exit(1);
}
this->fd = open (this->filename, O_RDONLY);
while ((count = read (this->fd, buf, BUFFER_SIZE)) > 0)
write (fd, buf, count);
unlink (this->filename);
}
this->fd = -1;
}
else
{
BufferBlock *b;
for (b = this->first_buffer; b != this->last_buffer; b = b->next)
write (fd, b->buf, BUFFER_SIZE);
}
if (this->count_last)
write (fd, this->last_buffer->buf, this->count_last);
this->count_last = 0;
}
void
Buffer__clear (Buffer *this)
{
if (use_file_buffers)
{
if (this->fd != -1)
close (this->fd);
unlink (this->filename);
this->fd = -1;
}
else
while (this->first_buffer != this->last_buffer)
{
BufferBlock *b = this->first_buffer->next;
free (this->first_buffer);
this->first_buffer = b;
}
this->count_total = this->count_last = 0;
}
//------------------------------------------------------------
typedef struct Hash {
/* Each page table is an array of pointers to page tables... */
void *pt[256];
int count;
} Hash;
void **
Hash__hash (Hash *this, size_t key)
{
int i, pi;
void **pt;
if (sizeof (size_t) >= 8)
key >>= 3;
else
key >>= 2;
pt = this->pt;
for (i=sizeof(size_t)-1; i>0; i--)
{
pi = (key >> (i*8)) & 0xff;
if (pt[pi] == NULL)
pt[pi] = (void **) calloc (256, sizeof(void *));
pt = pt[pi];
}
pi = key & 0xff;
return &(pt[pi]);
}
void
Hash__ctor (Hash *this)
{
memset (&this->pt, 0, sizeof(this->pt));
this->count = 0;
}
void *
Hash__lookup (Hash *this, size_t key)
{
void **pp = Hash__hash (this, key);
return *pp;
}
void
Hash__add (Hash *this, size_t key, void *data)
{
void **pp = Hash__hash (this, key);
*pp = data;
this->count ++;
}
void
Hash__remove (Hash *this, size_t key)
{
void **pp = Hash__hash (this, key);
*pp = NULL;
this->count --;
}
void
Hash__getvals_1 (void **pt, int *count, int level)
{
int i;
for (i=0; i<256; i++)
if (pt[i])
{
if (level == sizeof(size_t)-1)
(*count) ++;
else
Hash__getvals_1 ((void **) pt[i], count, level+1);
}
}
void
Hash__getvals_2 (void **pt, void ***table, int level)
{
int i;
for (i=0; i<256; i++)
if (pt[i])
{
if (level == sizeof(size_t)-1)
*(*table)++ = pt[i];
else
Hash__getvals_2 ((void **) pt[i], table, level+1);
}
}
int
Hash__getvals (Hash *this, void ***vals)
{
int count = 0;
void **valtmp;
Hash__getvals_1 (this->pt, &count, 0);
printf("count is %d vs %d\n", count, this->count);
*vals = (void **) malloc (count * sizeof (void *));
valtmp = (*vals);
Hash__getvals_2 (this->pt, &valtmp, 0);
printf("vals is %ld\n", valtmp - *vals);
return count;
}
//------------------------------------------------------------
typedef struct PerThread {
int started;
Buffer workload;
} PerThread;
void
PerThread__ctor (PerThread *this)
{
this->started = 0;
Buffer__ctor (&(this->workload));
}
void
PerThread__add (PerThread *this, byte x)
{
Buffer__add (&(this->workload), x);
}
void
PerThread__add_int (PerThread *this, size_t x)
{
Buffer__add_int (&(this->workload), x);
}
Hash *per_thread;
typedef enum {
R_no_reason,
R_not_seen,
R_alloc,
R_previously_freed,
R_previously_realloced,
R_realloc,
R_memalign,
R_posix_memalign
} Reasons;
const char *reasons_str[] = {
"",
"not seen",
"alloc",
"previously free'd",
"previously realloc'd",
"realloc",
"memalign",
"posix memalign"
};
typedef struct PerAddr {
PerThread *owner;
void *ptr;
size_t idx;
size_t reason_idx;
struct __malloc_trace_buffer_s *inverted;
unsigned char valid;
unsigned char reason;
} PerAddr;
void
PerAddr__ctor (PerAddr *this, void *_ptr)
{
this->owner = NULL;
this->ptr = _ptr;
this->valid = 0;
this->reason = R_not_seen;
this->inverted = NULL;
}
// Don't start at zero, zero is special.
size_t addr_count = 1;
Hash *per_addr;
PerAddr *
get_addr (void *ptr)
{
PerAddr *p;
if (ptr == NULL)
return NULL;
p = Hash__lookup (per_addr, (size_t)ptr);
if (!p)
{
p = (PerAddr *) malloc (sizeof (PerAddr));
PerAddr__ctor (p, ptr);
Hash__add (per_addr, (size_t)ptr, p);
p->idx = addr_count ++;
}
return p;
}
size_t sync_counter = 0;
// Insert a release/acquire pair to transfer ownership of data
// from thread TREL to thread TACK
void
sync_threads (PerThread *trel, PerThread *tacq)
{
if (trel == tacq)
return;
sync_counter ++;
PerThread__add (trel, C_SYNC_W);
PerThread__add_int (trel, sync_counter);
PerThread__add (tacq, C_SYNC_R);
PerThread__add_int (tacq, sync_counter);
}
void
acq_ptr (PerThread *thread, PerAddr *addr)
{
if (addr == NULL)
return;
if (addr->owner != NULL && addr->owner != thread)
sync_threads (addr->owner, thread);
addr->owner = thread;
}
//------------------------------------------------------------
/* These are the state variables for the whole trace. */
PerThread *master_thread = NULL;
int last_tid = -1;
PerThread *thread = NULL;
int pending_inversions = 0;
int fixed_inversions = 0;
static void
process_one_trace_record (struct __malloc_trace_buffer_s *r)
{
size_t i = r - trace_records;
// Quick-skip for NULs at EOF
if (r->type == __MTB_TYPE_UNUSED)
return;
if (verbose > 1)
printf("\033[32m%8x %2x (0x%p, 0x%x) = 0x%p\033[0m\n",
r->thread, r->type, r->ptr1, (int)r->size, r->ptr2);
if (r->thread != last_tid)
{
thread = Hash__lookup (per_thread, r->thread);
if (thread == NULL)
{
thread = (PerThread *) malloc (sizeof (PerThread));
PerThread__ctor (thread);
Hash__add (per_thread, r->thread, thread);
}
last_tid = r->thread;
}
if (!master_thread)
{
master_thread = thread;
thread->started = 1;
}
else if (!thread->started)
{
sync_threads (master_thread, thread);
thread->started = 1;
}
PerAddr *pa1 = get_addr(r->ptr1);
PerAddr *pa2 = get_addr(r->ptr2);
switch (r->type)
{
case __MTB_TYPE_UNUSED:
case __MTB_TYPE_MAGIC:
break;
case __MTB_TYPE_MALLOC:
case __MTB_TYPE_CALLOC:
case __MTB_TYPE_VALLOC:
case __MTB_TYPE_PVALLOC:
if (pa2 && pa2->valid)
{
if (pa2->inverted)
{
printf ("%ld: pointer %p alloc'd again? (possible multi-level inversion) size %ld %ld:%s\n",
i, pa2->ptr, (long int)r->size, pa2->reason_idx, reasons_str[pa2->reason]);
// exit (1);
}
pa2->inverted = r;
fixed_inversions ++;
pending_inversions ++;
return;
}
acq_ptr (thread, pa2);
if (r->type == __MTB_TYPE_MALLOC)
PerThread__add (thread, C_MALLOC);
if (r->type == __MTB_TYPE_CALLOC)
PerThread__add (thread, C_CALLOC);
if (r->type == __MTB_TYPE_VALLOC)
PerThread__add (thread, C_VALLOC);
if (r->type == __MTB_TYPE_PVALLOC)
PerThread__add (thread, C_PVALLOC);
PerThread__add_int (thread, pa2 ? pa2->idx : 0);
PerThread__add_int (thread, r->size);
if (pa2)
{
pa2->valid = 1;
pa2->reason = R_alloc;
pa2->reason_idx = i;
}
break;
case __MTB_TYPE_FREE:
acq_ptr (thread, pa1);
if (pa1 == NULL)
{
PerThread__add (thread, C_FREE);
PerThread__add_int (thread, 0);
}
else if (pa1->valid)
{
PerThread__add (thread, C_FREE);
PerThread__add_int (thread, pa1->idx);
pa1->valid = 0;
pa1->reason = R_previously_freed;
pa1->reason_idx = i;
if (pa1->inverted)
{
process_one_trace_record (pa1->inverted);
pa1->inverted = NULL;
pending_inversions --;
}
}
else
{
printf("%ld: invalid pointer %p passed to free: %ld:%s\n",
i, pa1->ptr, pa1->reason_idx, reasons_str[pa1->reason]);
}
break;
case __MTB_TYPE_REALLOC:
if (pa1 && pa1->owner)
acq_ptr (thread, pa1);
if (pa2 && pa2->owner)
acq_ptr (thread, pa2);
PerThread__add (thread, C_REALLOC);
PerThread__add_int (thread, pa2 ? pa2->idx : 0);
PerThread__add_int (thread, pa1 ? pa1->idx : 0);
PerThread__add_int (thread, r->size);
// handle inversion here too, eventually - both the alloc and free sides.
if (pa1)
{
pa1->valid = 0;
pa1->reason = R_previously_realloced;
pa1->reason_idx = i;
}
if (pa2)
{
pa2->valid = 1;
pa2->reason = R_realloc;
pa2->reason_idx = i;
}
break;
case __MTB_TYPE_MEMALIGN:
acq_ptr (thread, pa2);
if (pa2 && pa2->valid)
printf ("%ld: pointer %p memalign'd again? %ld:%s\n",
i, pa2->ptr, pa2->reason_idx, reasons_str[pa2->reason]);
PerThread__add (thread, C_MEMALIGN);
PerThread__add_int (thread, pa2 ? pa2->idx : 0);
PerThread__add_int (thread, r->size2);
PerThread__add_int (thread, r->size);
if (pa2)
{
pa2->valid = 1;
pa2->reason = R_memalign;
pa2->reason_idx = i;
}
break;
case __MTB_TYPE_POSIX_MEMALIGN:
/* ptr1 is return value (0 or EINVAL etc)
ptr2 is address of allocated memory
size is the allocation size
size2 is the alignment */
PerThread__add (thread, C_MEMALIGN);
PerThread__add_int (thread, (r->ptr1 == 0) ? pa2->idx : 0);
PerThread__add_int (thread, r->size2);
PerThread__add_int (thread, r->size);
if (r->ptr1 == 0)
{
pa2->valid = 1;
pa2->reason = R_posix_memalign;
pa2->reason_idx = i;
}
break;
}
}
//------------------------------------------------------------
static const char * const month_abbrevs[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static struct option longopts[] = {
{ "verbose", 0, NULL, 'v' },
{ "progress", 0, NULL, 'p' },
{ "file-buffers", 0, NULL, 'f' },
{ "tmpdir", 1, NULL, 't' },
{ NULL, 0, NULL, 0 }
};
static void
print_help (void)
{
fprintf (stderr, "Usage: trace2wl [options] <outputfile.wl> <inputfile.mtrace>\n");
fprintf (stderr, " -v --verbose print stats about workload\n");
fprintf (stderr, " -p --progress show progress info\n");
fprintf (stderr, " -f --file-buffers use temporary files to store intermediate buffers\n");
fprintf (stderr, " -t <file> --tmpdir=<file> overrides $TMPDIR or /tmp\n");
exit(1);
}
int
main(int argc, char **argv)
{
int trace_fd, wl_fd;
struct stat stbuf;
unsigned long i;
int opt;
const char *tmp = NULL;
size_t timer_divisor, old_percent_done=0;
time_t start_time, cur_time;
int show_progress = 0;
while ((opt = getopt_long (argc, argv, "vhpft:", longopts, NULL)) != -1)
{
switch (opt) {
case 'v':
verbose ++;
break;
case 'p':
show_progress ++;
break;
case 'f':
use_file_buffers ++;
break;
case 't':
tmp = optarg;
printf("tmpdir: %s\n", tmp);
break;
case 'h':
default:
print_help();
}
}
if (use_file_buffers)
{
if (tmp == NULL)
{
tmp = getenv("TMPDIR");
printf("$TMPDIR: %s", tmp);
if (tmp == NULL)
{
tmp = "/tmp";
printf(" (using %s)", tmp);
}
printf("\n");
}
tmpdir = (char *) malloc (strlen(tmp) + strlen("/wl__") + 50);
sprintf(tmpdir, "%s/wl_%d_", tmp, getpid());
tmpdir_len = strlen(tmpdir);
}
per_addr = (Hash *) malloc (sizeof (Hash));
Hash__ctor (per_addr);
per_thread = (Hash *) malloc (sizeof (Hash));
Hash__ctor (per_thread);
if (argc-optind != 2)
print_help();
if (access (argv[optind], F_OK) == 0)
{
fprintf (stderr, "Error: output file %s already exists, will not overwrite\n", argv[optind]);
exit(1);
}
trace_fd = open (argv[optind+1], O_RDONLY, 0666);
if (trace_fd < 0)
{
fprintf (stderr, "Can't open %s for reading\n", argv[optind+1]);
perror("The error was");
exit(1);
}
if (stat (argv[optind+1], &stbuf) < 0)
{
fprintf (stderr, "Can't stat %s for reading\n", argv[optind+1]);
perror("The error was");
exit(1);
}
trace_records =
(struct __malloc_trace_buffer_s *)
mmap (NULL, stbuf.st_size, PROT_READ, MAP_SHARED, trace_fd, 0);
if (trace_records == (void *)(-1))
{
fprintf (stderr, "Can't map %s for reading\n", argv[optind+1]);
perror("The error was");
exit(1);
}
num_trace_records = stbuf.st_size / sizeof(*trace_records);
timer_divisor = num_trace_records / 100;
time(&start_time);
for (i = 0; i < num_trace_records; i++)
{
if (show_progress)
{
int percent_done = i / timer_divisor;
if (percent_done != old_percent_done)
{
struct tm *tm;
old_percent_done = percent_done;
time(&cur_time);
cur_time = cur_time + (cur_time - start_time) * 100 / percent_done;
tm = localtime(&cur_time);
printf(" %3d%% done, ETA %3s %2d %2d:%02d \r", percent_done,
month_abbrevs[tm->tm_mon], tm->tm_mday,
tm->tm_hour, tm->tm_min);
fflush (stdout);
}
}
process_one_trace_record (trace_records + i);
}
if (show_progress)
printf(" 100%% done, writing out buffers...\033[K\n");
PerThread **threads;
int n_threads;
n_threads = Hash__getvals (per_thread, (void ***)&threads);
size_t *thread_off = (size_t *) malloc (n_threads * sizeof (size_t));
if(verbose)
printf("%d threads\n", n_threads);
for (i = 0; i < n_threads; i ++)
{
PerThread__add(threads[i], C_DONE);
if(verbose)
printf("thread: %ld bytes\n", (long)threads[i]->workload.count_total);
}
/* The location of each thread's workload depends on the size of the
startup block, but the size of the startup block depends on the
size of the thread's location encoding. So, we loop until it
stabilizes. */
size_t old_len = 1;
size_t new_len = 2;
Buffer main_loop;
Buffer__ctor (&main_loop);
while (old_len != new_len)
{
size_t off = new_len;
int i;
old_len = new_len;
Buffer__clear (&main_loop);
Buffer__add (&main_loop, C_ALLOC_PTRS);
Buffer__add_int (&main_loop, addr_count);
Buffer__add (&main_loop, C_ALLOC_SYNCS);
Buffer__add_int (&main_loop, sync_counter);
Buffer__add (&main_loop, C_NTHREADS);
Buffer__add_int (&main_loop, n_threads);
for (i=0; i<n_threads; i++)
{
thread_off[i] = off;
Buffer__add (&main_loop, C_START_THREAD);
Buffer__add_int (&main_loop, off);
off += threads[i]->workload.count_total;
}
Buffer__add (&main_loop, C_DONE);
new_len = main_loop.count_total;
}
wl_fd = open (argv[optind], O_CREAT|O_EXCL|O_RDWR, 0666);
if (wl_fd < 0)
{
fprintf (stderr, "Can't open %s for writing\n", argv[optind]);
perror("The error was");
exit(1);
}
Buffer__write (&main_loop, wl_fd);
for (i=0; i<n_threads; i++)
{
if (verbose || show_progress)
printf("Start thread[%ld] offset 0x%lx\n", i, (long)thread_off[i]);
Buffer__write (&(threads[i]->workload), wl_fd);
}
close (wl_fd);
if (pending_inversions)
printf("%d pending inversions remain\n", pending_inversions);
if (fixed_inversions)
printf("%d inversions fixed\n", fixed_inversions);
return 0;
}
+212
View File
@@ -0,0 +1,212 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
// The trace file looks like an array of struct __malloc_trace_buffer_s
#include "mtrace.h"
typedef long long BIG;
static BIG
get_int (unsigned char **ptr)
{
BIG rv = 0;
while (1)
{
unsigned char c = *(*ptr)++;
rv |= (c & 0x7f);
if (c & 0x80)
rv <<= 7;
else
return rv;
}
}
int
data_looks_like_raw_trace (unsigned char *data, long n_data)
{
long lim = n_data > 1024 ? 1020 : (n_data - 4);
long i;
// free and malloc calls will have a NULL we can look for
for (i=0; i<lim; i++)
if (memcmp (data+i, "\0\0\0\0", 4) == 0)
return 1;
return 0;
}
const char * const typenames[] = {
"unused",
"malloc",
"calloc",
"free",
"realloc",
"memalign",
"valloc",
"pvalloc",
"posix_memalign",
};
void
dump_raw_trace (unsigned char *data, long n_data)
{
unsigned char *edata = data + n_data;
long head;
head = n_data / sizeof (struct __malloc_trace_buffer_s);
printf ("%ld out of %ld events captured (I think)\n", head, head);
printf ("%8s %8s %8s %16s %16s %16s %16s %16s\n",
"threadid", "type", "path", "ptr1", "size", "ptr2", "size2", "size3");
while (data <= edata - sizeof (struct __malloc_trace_buffer_s))
{
struct __malloc_trace_buffer_s *t = (struct __malloc_trace_buffer_s *)data;
switch (t->type)
{
case __MTB_TYPE_UNUSED:
break;
default:
/* Consider 'memalign' to be the largest API word we want to align
on so make the name 8 chars wide at a minimum. */
printf ("%08x %8s %c%c%c%c%c%c%c%c%c%c%c%c%c%c %016llx %016llx %016llx %016llx %016llx\n",
t->thread,
t->type == __MTB_TYPE_MAGIC ? "magic" : typenames[t->type],
t->path_thread_cache ? 'T' : '-',
t->path_cpu_cache ? 'c' : '-',
t->path_cpu_cache2 ? 'C' : '-',
t->path_sbrk ? 's' : '-',
t->path_mmap ? 'M' : '-',
t->path_munmap ? 'U' : '-',
t->path_m_f_realloc ? 'R' : '-',
t->path_hook ? 'H' : '-',
t->path_unsorted_add ? 'U' : '-',
t->path_unsorted_remove ? 'u' : '-',
t->path_unsorted_empty ? 'E' : '-',
t->path_fastbin_add ? 'F' : '-',
t->path_fastbin_remove ? 'f' : '-',
t->path_malloc_consolidate ? 'C' : '-',
(long long unsigned int) (size_t) t->ptr1,
(long long unsigned int) t->size,
(long long unsigned int) (size_t) t->ptr2,
(long long unsigned int) t->size2,
(long long unsigned int) t->size3);
break;
}
data += sizeof (struct __malloc_trace_buffer_s);
}
}
void
dump_workload (unsigned char *data, long n_data)
{
unsigned char *orig_data = data;
unsigned char *edata = data + n_data;
BIG thread_idx = 0;
BIG n_ptrs, n_syncs, n_threads, idx, p1, p2, sz;
while (data < edata)
{
printf("%016lx: %4lld: ", data - orig_data, thread_idx);
switch (*data++)
{
case C_NOP:
break;
case C_ALLOC_PTRS:
n_ptrs = get_int(&data);
printf("AllocPtrs: %lld\n", n_ptrs);
break;
case C_ALLOC_SYNCS:
n_syncs = get_int(&data);
printf("AllocSyncs: %lld\n", n_syncs);
break;
case C_NTHREADS:
n_threads = get_int (&data);
printf("NThreads: %lld\n", n_threads);
break;
case C_START_THREAD:
idx = get_int (&data);
printf("StartThread: 0x%llx\n", idx);
break;
case C_DONE:
printf("Done\n");
thread_idx ++;
break;
case C_MALLOC:
p2 = get_int (&data);
sz = get_int (&data);
printf("Malloc (%lld) -> %lld\n", sz, p2);
break;
case C_CALLOC:
p2 = get_int (&data);
sz = get_int (&data);
printf("Calloc (%lld) -> %lld\n", sz, p2);
break;
case C_REALLOC:
p2 = get_int (&data);
p1 = get_int (&data);
sz = get_int (&data);
printf("Realloc (%lld, %lld) -> %lld\n", p1, sz, p2);
break;
case C_FREE:
p1 = get_int (&data);
printf("Free (%lld)\n", p1);
break;
case C_SYNC_W:
p1 = get_int(&data);
printf("SyncW (%lld)\n", p1);
break;
case C_SYNC_R:
p1 = get_int(&data);
printf("SyncR (%lld)\n", p1);
break;
default:
printf("(unknown:%d)\n", *--data);
exit(1);
}
}
}
int
main (int argc, char **argv)
{
int fd;
struct stat statb;
unsigned char *data;
fd = open(argv[1], O_RDONLY);
if (fd < 0)
{
fprintf(stderr, "Unable to open %s for reading\n", argv[1]);
perror("The error was");
exit(1);
}
fstat (fd, &statb);
data = (unsigned char *) mmap (NULL, statb.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (data_looks_like_raw_trace (data, statb.st_size))
dump_raw_trace (data, statb.st_size);
else
dump_workload (data, statb.st_size);
return 0;
}
+731
View File
@@ -0,0 +1,731 @@
#define _LARGEFILE64_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <pthread.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <fcntl.h>
#include <unistd.h>
#include "malloc.h"
#include "mtrace.h"
#if UINTPTR_MAX == 0xffffffffffffffff
#define ticks_t int64_t
/* Setting quick_run to 1 allows the simulator to model
only the allocation and deallocation accounting via
atomic_rss. The actual allocations are skipped. This
mode is useful to verify the workload file. */
#define quick_run 0
static __inline__ ticks_t rdtsc_s(void)
{
unsigned a, d;
asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx");
asm volatile("rdtscp" : "=a" (a), "=d" (d));
return ((unsigned long long)a) | (((unsigned long long)d) << 32);
}
static __inline__ ticks_t rdtsc_e(void)
{
unsigned a, d;
asm volatile("rdtscp" : "=a" (a), "=d" (d));
asm volatile("cpuid" ::: "%rax", "%rbx", "%rcx", "%rdx");
return ((unsigned long long)a) | (((unsigned long long)d) << 32);
}
#else
#define ticks_t int32_t
static __inline__ ticks_t rdtsc_s(void)
{
unsigned a, d;
asm volatile("cpuid" ::: "%ax", "%bx", "%cx", "%dx");
asm volatile("rdtsc" : "=a" (a), "=d" (d));
return ((unsigned long)a) | (((unsigned long)d) << 16);
}
static __inline__ ticks_t rdtsc_e(void)
{
unsigned a, d;
asm volatile("rdtscp" : "=a" (a), "=d" (d));
asm volatile("cpuid" ::: "%ax", "%bx", "%cx", "%dx");
return ((unsigned long)a) | (((unsigned long)d) << 16);
}
#endif
static ticks_t diff_timeval (struct timeval e, struct timeval s)
{
ticks_t usec;
if (e.tv_usec < s.tv_usec)
usec = (e.tv_usec + 1000000 - s.tv_usec) + (e.tv_sec-1 - s.tv_sec)*1000000;
else
usec = (e.tv_usec - s.tv_usec) + (e.tv_sec - s.tv_sec)*1000000;
return usec;
}
#if 1
#define Q1
#define Q2
#else
pthread_mutex_t genmutex = PTHREAD_MUTEX_INITIALIZER;
#define Q1 pthread_mutex_lock(&genmutex)
#define Q2 pthread_mutex_unlock(&genmutex)
#endif
pthread_mutex_t cmutex = PTHREAD_MUTEX_INITIALIZER;
#define NCBUF 10
static char cbuf[NCBUF][30];
static int ci = 0;
char *comma(ticks_t x)
{
char buf[30], *bs, *bd;
int l, i, idx;
pthread_mutex_lock(&cmutex);
ci = (ci + 1) % NCBUF;
idx = ci;
pthread_mutex_unlock(&cmutex);
bs = buf;
bd = cbuf[idx];
sprintf(buf, "%lld", (long long int)x);
l = strlen(buf);
i = l;
while (*bs)
{
*bd++ = *bs++;
i--;
if (i % 3 == 0 && *bs)
*bd++ = ',';
}
*bd = 0;
return cbuf[idx];
}
static volatile void **ptrs;
static volatile size_t *sizes;
static size_t n_ptrs;
static volatile char *syncs;
static pthread_mutex_t *mutexes;
static pthread_cond_t *conds;
static size_t n_syncs;
static pthread_mutex_t stat_mutex = PTHREAD_MUTEX_INITIALIZER;
ticks_t malloc_time = 0, malloc_count = 0;
ticks_t calloc_time = 0, calloc_count = 0;
ticks_t realloc_time = 0, realloc_count = 0;
ticks_t free_time = 0, free_count = 0;
size_t ideal_rss = 0;
size_t max_ideal_rss = 0;
static pthread_mutex_t rss_mutex = PTHREAD_MUTEX_INITIALIZER;
void atomic_rss (ssize_t delta)
{
pthread_mutex_lock (&rss_mutex);
ideal_rss += delta;
if (max_ideal_rss < ideal_rss)
max_ideal_rss = ideal_rss;
pthread_mutex_unlock (&rss_mutex);
}
pthread_mutex_t stop_mutex = PTHREAD_MUTEX_INITIALIZER;
int threads_done = 0;
//#define dprintf printf
#define dprintf(...) (void)1
//#define mprintf printf
//#define MDEBUG 1
#define mprintf(...) (void)1
#define myabort() my_abort_2(thrc, __LINE__)
void
my_abort_2 (pthread_t thrc, int line)
{
fprintf(stderr, "Abort thread %p at line %d\n", (void *)thrc, line);
abort();
}
/*------------------------------------------------------------*/
/* Wrapper around I/O routines */
int io_fd;
#define IOSIZE 65536
#define IOMIN 4096
static pthread_mutex_t io_mutex = PTHREAD_MUTEX_INITIALIZER;
typedef struct {
unsigned char buf[IOSIZE];
size_t incr;
size_t max_incr;
size_t buf_base;
size_t buf_idx;
int saw_eof;
} IOPerThreadType;
IOPerThreadType main_io;
IOPerThreadType *thread_io;
void
io_init (IOPerThreadType *io, size_t file_offset, int incr)
{
if (incr > IOSIZE)
incr = IOSIZE;
if (incr < IOMIN)
incr = IOMIN;
io->buf_base = file_offset;
io->buf_idx = 0;
io->incr = incr;
pthread_mutex_lock (&io_mutex);
lseek64 (io_fd, io->buf_base, SEEK_SET);
// short read OK, the eof is just to prevent runaways from bad data.
if (read (io_fd, io->buf, incr) < 0)
io->saw_eof = 1;
else
io->saw_eof = 0;
pthread_mutex_unlock (&io_mutex);
}
unsigned char
io_read (IOPerThreadType *io)
{
if (io->buf_idx >= io->incr)
io_init (io, io->buf_base + io->buf_idx, io->incr);
if (io->saw_eof)
return 0xff;
return io->buf [io->buf_idx++];
}
unsigned char
io_peek (IOPerThreadType *io)
{
if (io->buf_idx >= io->incr)
io_init (io, io->buf_base + io->buf_idx, io->incr);
if (io->saw_eof)
return 0xff;
return io->buf [io->buf_idx];
}
size_t
io_pos (IOPerThreadType *io)
{
return io->buf_base + io->buf_idx;
}
/*------------------------------------------------------------*/
static void
wmem (volatile void *ptr, int count)
{
char *p = (char *)ptr;
int i;
if (!p)
return;
for (i=0; i<count; i++)
p[i] = 0x11;
}
#define xwmem(a,b)
static size_t get_int (IOPerThreadType *io)
{
size_t rv = 0;
while (1)
{
unsigned char c = io_read (io);
rv |= (c & 0x7f);
if (c & 0x80)
rv <<= 7;
else
return rv;
}
}
static void free_wipe (size_t idx)
{
char *cp = (char *)ptrs[idx];
if (cp == NULL)
return;
size_t sz = sizes[idx];
size_t i;
for (i=0; i<sz; i++)
{
if (i % 8 == 1)
cp[i] = i / 8;
else
cp[i] = 0x22;
}
}
static void *
thread_common (void *my_data_v)
{
pthread_t thrc = pthread_self ();
size_t p1, p2, sz, sz2;
IOPerThreadType *io = (IOPerThreadType *)my_data_v;
ticks_t my_malloc_time = 0, my_malloc_count = 0;
ticks_t my_calloc_time = 0, my_calloc_count = 0;
ticks_t my_realloc_time = 0, my_realloc_count = 0;
ticks_t my_free_time = 0, my_free_count = 0;
ticks_t stime, etime;
int thread_idx = io - thread_io;
#ifdef MDEBUG
volatile void *tmp;
#endif
while (1)
{
unsigned char this_op = io_peek (io);
if (io->saw_eof)
myabort();
dprintf("op %p:%ld is %d\n", (void *)thrc, io_pos (io), io_peek (io));
switch (io_read (io))
{
case C_NOP:
break;
case C_DONE:
dprintf("op %p:%ld DONE\n", (void *)thrc, io_pos (io));
pthread_mutex_lock (&stat_mutex);
malloc_time += my_malloc_time;
calloc_time += my_calloc_time;
realloc_time += my_realloc_time;
free_time += my_free_time;
malloc_count += my_malloc_count;
calloc_count += my_calloc_count;
realloc_count += my_realloc_count;
free_count += my_free_count;
threads_done ++;
pthread_mutex_unlock (&stat_mutex);
pthread_mutex_lock(&stop_mutex);
pthread_mutex_unlock(&stop_mutex);
return NULL;
case C_MEMALIGN:
p2 = get_int (io);
sz2 = get_int (io);
sz = get_int (io);
dprintf("op %p:%ld %ld = MEMALIGN %ld %ld\n", (void *)thrc, io_pos (io), p2, sz2, sz);
/* we can't force memalign to return NULL (fail), so just skip it. */
if (p2 == 0)
break;
if (p2 > n_ptrs)
myabort();
stime = rdtsc_s();
Q1;
if (ptrs[p2])
{
if (!quick_run)
free ((void *)ptrs[p2]);
atomic_rss (-sizes[p2]);
}
if (!quick_run)
ptrs[p2] = memalign (sz2, sz);
else
ptrs[p2] = (void *)p2;
/* Verify the alignment matches what is expected. */
if (((size_t)ptrs[p2] & (sz2 - 1)) != 0)
myabort ();
sizes[p2] = sz;
mprintf("%p = memalign(%lx, %lx)\n", ptrs[p2], sz2, sz);
Q2;
etime = rdtsc_e();
if (ptrs[p2] != NULL)
atomic_rss (sz);
if (etime < stime)
{
printf("s: %llx e:%llx d:%llx\n", (long long)stime, (long long)etime, (long long)(etime-stime));
}
my_malloc_time += etime - stime;
my_malloc_count ++;
if (!quick_run)
wmem(ptrs[p2], sz);
break;
case C_MALLOC:
p2 = get_int (io);
sz = get_int (io);
dprintf("op %p:%ld %ld = MALLOC %ld\n", (void *)thrc, io_pos (io), p2, sz);
/* we can't force malloc to return NULL (fail), so just skip it. */
if (p2 == 0)
break;
if (p2 > n_ptrs)
myabort();
stime = rdtsc_s();
Q1;
if (ptrs[p2])
{
if (!quick_run)
free ((void *)ptrs[p2]);
atomic_rss (-sizes[p2]);
}
if (!quick_run)
ptrs[p2] = malloc (sz);
else
ptrs[p2] = (void *)p2;
sizes[p2] = sz;
mprintf("%p = malloc(%lx)\n", ptrs[p2], sz);
Q2;
etime = rdtsc_e();
if (ptrs[p2] != NULL)
atomic_rss (sz);
if (etime < stime)
{
printf("s: %llx e:%llx d:%llx\n", (long long)stime, (long long)etime, (long long)(etime-stime));
}
my_malloc_time += etime - stime;
my_malloc_count ++;
if (!quick_run)
wmem(ptrs[p2], sz);
break;
case C_CALLOC:
p2 = get_int (io);
sz = get_int (io);
dprintf("op %p:%ld %ld = CALLOC %ld\n", (void *)thrc, io_pos (io), p2, sz);
/* we can't force calloc to return NULL (fail), so just skip it. */
if (p2 == 0)
break;
if (p2 > n_ptrs)
myabort();
if (ptrs[p2])
{
if (!quick_run)
free ((void *)ptrs[p2]);
atomic_rss (-sizes[p2]);
}
stime = rdtsc_s();
Q1;
if (!quick_run)
ptrs[p2] = calloc (sz, 1);
else
ptrs[p2] = (void *)p2;
sizes[p2] = sz;
mprintf("%p = calloc(%lx)\n", ptrs[p2], sz);
Q2;
if (ptrs[p2])
atomic_rss (sz);
my_calloc_time += rdtsc_e() - stime;
my_calloc_count ++;
if (!quick_run)
wmem(ptrs[p2], sz);
break;
case C_REALLOC:
p2 = get_int (io);
p1 = get_int (io);
sz = get_int (io);
dprintf("op %p:%ld %ld = REALLOC %ld %ld\n", (void *)thrc, io_pos (io), p2, p1, sz);
if (p1 > n_ptrs)
myabort();
if (p2 > n_ptrs)
myabort();
/* we can't force realloc to return NULL (fail), so just skip it. */
if (p2 == 0)
break;
if (ptrs[p1])
atomic_rss (-sizes[p1]);
if (!quick_run)
free_wipe(p1);
stime = rdtsc_s();
Q1;
#ifdef MDEBUG
tmp = ptrs[p1];
#endif
if (!quick_run)
ptrs[p2] = realloc ((void *)ptrs[p1], sz);
else
ptrs[p2] = (void *)p2;
sizes[p2] = sz;
mprintf("%p = relloc(%p,%lx)\n", ptrs[p2], tmp,sz);
Q2;
my_realloc_time += rdtsc_e() - stime;
my_realloc_count ++;
if (!quick_run)
wmem(ptrs[p2], sz);
if (p1 != p2)
ptrs[p1] = 0;
if (ptrs[p2])
atomic_rss (sizes[p2]);
break;
case C_FREE:
p1 = get_int (io);
if (p1 > n_ptrs)
myabort();
dprintf("op %p:%ld FREE %ld\n", (void *)thrc, io_pos (io), p1);
if (!quick_run)
free_wipe (p1);
if (ptrs[p1])
atomic_rss (-sizes[p1]);
stime = rdtsc_s();
Q1;
mprintf("free(%p)\n", ptrs[p1]);
if (!quick_run)
free ((void *)ptrs[p1]);
Q2;
my_free_time += rdtsc_e() - stime;
my_free_count ++;
ptrs[p1] = 0;
break;
case C_SYNC_W:
p1 = get_int(io);
dprintf("op %p:%ld SYNC_W %ld\n", (void *)thrc, io_pos (io), p1);
if (p1 > n_syncs)
myabort();
pthread_mutex_lock (&mutexes[p1]);
syncs[p1] = 1;
pthread_cond_signal (&conds[p1]);
__sync_synchronize ();
pthread_mutex_unlock (&mutexes[p1]);
break;
case C_SYNC_R:
p1 = get_int(io);
dprintf("op %p:%ld SYNC_R %ld\n", (void *)thrc, io_pos (io), p1);
if (p1 > n_syncs)
myabort();
pthread_mutex_lock (&mutexes[p1]);
while (syncs[p1] != 1)
{
pthread_cond_wait (&conds[p1], &mutexes[p1]);
__sync_synchronize ();
}
pthread_mutex_unlock (&mutexes[p1]);
break;
default:
printf("op %d - unsupported, thread %d addr %lu\n",
this_op, thread_idx, (long unsigned int)io_pos (io));
myabort();
}
}
}
static void *alloc_mem (size_t amt)
{
void *rv = mmap (NULL, amt, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
mlock (rv, amt);
memset (rv, 0, amt);
return rv;
}
static pthread_t *thread_ids;
void *
my_malloc (const char *msg, int size, IOPerThreadType *io, size_t *psz, size_t count)
{
void *rv;
if (psz)
count = *psz = get_int (io);
dprintf ("my_malloc for %s size %d * %ld\n", msg, size, count);
rv = alloc_mem(size * count);
if (!rv)
{
fprintf(stderr, "calloc(%lu,%lu) failed\n", (long unsigned)size, (long unsigned)*psz);
exit(1);
}
mlock (rv, size * count);
return rv;
}
static const char * const scan_names[] = {
"UNUSED",
"ARENA",
"HEAP",
"CHUNK_USED",
"CHUNK_FREE",
"FASTBIN_FREE",
"UNSORTED",
"TOP",
"TCACHE",
"USED"
};
void
malloc_scan_callback (void *ptr, size_t length, int type)
{
printf("%s: ptr %p length %llx\n", scan_names[type], ptr, (long long)length);
}
#define MY_ALLOC(T, psz) \
(typeof (T)) my_malloc (#T, sizeof(*T), &main_io, psz, 0)
#define MY_ALLOCN(T, count) \
(typeof (T)) my_malloc (#T, sizeof(*T), &main_io, NULL, count)
int
main(int argc, char **argv)
{
ticks_t start=0;
ticks_t end;
ticks_t usec;
struct timeval tv_s, tv_e;
int thread_idx = 0;
int i;
size_t n_threads = 0;
size_t idx;
struct rusage res_start, res_end;
int done;
size_t guessed_io_size = 4096;
struct stat statb;
if (argc < 2)
{
fprintf(stderr, "Usage: %s <trace2dat.outfile>\n", argv[0]);
exit(1);
}
io_fd = open(argv[1], O_RDONLY);
if (io_fd < 0)
{
fprintf(stderr, "Unable to open %s for reading\n", argv[1]);
perror("The error was");
exit(1);
}
fstat (io_fd, &statb);
io_init (&main_io, 0, IOMIN);
pthread_mutex_lock(&stop_mutex);
done = 0;
while (!done)
{
switch (io_read (&main_io))
{
case C_NOP:
break;
case C_ALLOC_PTRS:
ptrs = MY_ALLOC (ptrs, &n_ptrs);
sizes = alloc_mem(sizeof(sizes[0]) * n_ptrs);
ptrs[0] = 0;
break;
case C_ALLOC_SYNCS:
n_syncs = get_int(&main_io);
syncs = MY_ALLOCN (syncs, n_syncs);
conds = MY_ALLOCN (conds, n_syncs);
mutexes = MY_ALLOCN (mutexes, n_syncs);
for (idx=0; idx<n_syncs; idx++)
{
pthread_mutex_init (&mutexes[idx], NULL);
pthread_cond_init (&conds[idx], NULL);
}
break;
case C_NTHREADS:
thread_ids = MY_ALLOC (thread_ids, &n_threads);
thread_io = MY_ALLOCN (thread_io, n_threads);
guessed_io_size = ((statb.st_size / n_threads) < (1024*1024)) ? 65536 : 4096;
/* The next thing in the workscript is thread creation */
getrusage (RUSAGE_SELF, &res_start);
gettimeofday (&tv_s, NULL);
start = rdtsc_s();
break;
case C_START_THREAD:
idx = get_int (&main_io);
io_init (& thread_io[thread_idx], idx, guessed_io_size);
pthread_create (&thread_ids[thread_idx], NULL, thread_common, thread_io + thread_idx);
dprintf("Starting thread %lld at offset %lu %lx\n", (long long)thread_ids[thread_idx], (unsigned long)idx, (unsigned long)idx);
thread_idx ++;
break;
case C_DONE:
do
{
pthread_mutex_lock (&stat_mutex);
i = threads_done;
pthread_mutex_unlock (&stat_mutex);
} while (i < thread_idx);
done = 1;
break;
}
}
if (!quick_run)
{
end = rdtsc_e();
gettimeofday (&tv_e, NULL);
getrusage (RUSAGE_SELF, &res_end);
printf("%s cycles\n", comma(end - start));
usec = diff_timeval (tv_e, tv_s);
printf("%s usec wall time\n", comma(usec));
usec = diff_timeval (res_end.ru_utime, res_start.ru_utime);
printf("%s usec across %d thread%s\n",
comma(usec), (int)n_threads, n_threads == 1 ? "" : "s");
printf("%s Kb Max RSS (%s -> %s)\n",
comma(res_end.ru_maxrss - res_start.ru_maxrss),
comma(res_start.ru_maxrss), comma(res_end.ru_maxrss));
}
printf("%s Kb Max Ideal RSS\n", comma (max_ideal_rss / 1024));
if (malloc_count == 0) malloc_count ++;
if (calloc_count == 0) calloc_count ++;
if (realloc_count == 0) realloc_count ++;
if (free_count == 0) free_count ++;
if (!quick_run)
{
printf("\n");
printf("sizeof ticks_t is %lu\n", sizeof(ticks_t));
printf("Avg malloc time: %6s in %10s calls\n", comma(malloc_time/malloc_count), comma(malloc_count));
printf("Avg calloc time: %6s in %10s calls\n", comma(calloc_time/calloc_count), comma(calloc_count));
printf("Avg realloc time: %5s in %10s calls\n", comma(realloc_time/realloc_count), comma(realloc_count));
printf("Avg free time: %8s in %10s calls\n", comma(free_time/free_count), comma(free_count));
printf("Total call time: %s cycles\n", comma(malloc_time+calloc_time+realloc_time+free_time));
printf("\n");
}
#if 0
/* Free any still-held chunks of memory. */
for (idx=0; idx<n_ptrs; idx++)
if (ptrs[idx])
{
free((void *)ptrs[idx]);
ptrs[idx] = 0;
}
#endif
#if 0
/* This will fail (crash) for system glibc but that's OK. */
__malloc_scan_chunks(malloc_scan_callback);
malloc_info (0, stdout);
#endif
#if 0
/* ...or report them as used. */
for (idx=0; idx<n_ptrs; idx++)
if (ptrs[idx])
{
char *p = (char *)ptrs[idx] - 2*sizeof(size_t);
size_t *sp = (size_t *)p;
size_t size = sp[1] & ~7;
malloc_scan_callback (sp, size, 9);
}
#endif
/* Now that we've scanned all the per-thread caches, it's safe to
let them exit and clean up. */
pthread_mutex_unlock(&stop_mutex);
for (i=0; i<thread_idx; i++)
pthread_join (thread_ids[i], NULL);
return 0;
}