diff options
author | csilvers <csilvers@6b5cf1ce-ec42-a296-1ba9-69fdba395a50> | 2009-12-02 18:15:13 +0000 |
---|---|---|
committer | csilvers <csilvers@6b5cf1ce-ec42-a296-1ba9-69fdba395a50> | 2009-12-02 18:15:13 +0000 |
commit | a94d5f797412bac7b811bf8d69d9298fec54f5cc (patch) | |
tree | 450a1654e76492ca99c444a083b19f8fe9146dff | |
parent | 5b80f01df1137337131b4c50ce97faaff9973e90 (diff) | |
download | gperftools-a94d5f797412bac7b811bf8d69d9298fec54f5cc.tar.gz |
* Prefer __environ to /proc/self/environ (csilvers)
* Add HEAP_CHECK_MAX_LEAKS envvar (glider)
* BUGFIX: debugallocation now calls cpp_alloc for new (willchan)
* BUGFIX: tc_set_new_mode() respected for realloc and calloc (willchan)
* BUGFIX: fix opt-mode maybe-crash on debugallocation_test (csilvers)
* Print alloc size when mmap fails (hakon)
* Add ITIMER_REAL support (csilvers, nabeelmian)
* BUGFIX: correctly report double-frees (csilvers)
* Export tc_set_new_mode() from the .h file (willchan)
* Restructure Symbolize to make it more efficient (glider)
* PORTING: Augment sysinfo to work on 64-bit OS X (csilvers)
* Add two numeric pageheap properties to MallocExtension (fikes)
* PORTING: Use libunwind for i386 when using --omitfp (ppluzhnikov)
* Add ReleaseToSystem(num_bytes) (kash)
* Provide correct library filenames under solaris (jeffrey)
* BUGFIX: simple fix in pprof --raw mode (mrabkin)
* PORTING: Prefer sys/ucontext.h to fix OS 10.6 builds (csilvers)
* Improve support for inlined functions in pprof (sanjay)
* Update wget code to not use keepalive (mrabkin, csilvers)
* PORTING: correctly handle x86_64 machines that use fp's (csilvers)
git-svn-id: http://gperftools.googlecode.com/svn/trunk@79 6b5cf1ce-ec42-a296-1ba9-69fdba395a50
-rwxr-xr-x | configure | 29 | ||||
-rw-r--r-- | configure.ac | 9 | ||||
-rw-r--r-- | doc/cpuprofile.html | 12 | ||||
-rw-r--r-- | doc/heap_checker.html | 10 | ||||
-rw-r--r-- | m4/compiler_characteristics.m4 | 2 | ||||
-rw-r--r-- | src/base/logging.h | 11 | ||||
-rw-r--r-- | src/base/sysinfo.cc | 30 | ||||
-rw-r--r-- | src/config.h.in | 3 | ||||
-rw-r--r-- | src/debugallocation.cc | 53 | ||||
-rw-r--r-- | src/google/malloc_extension.h | 3 | ||||
-rw-r--r-- | src/google/malloc_extension_c.h | 2 | ||||
-rw-r--r-- | src/google/tcmalloc.h.in | 1 | ||||
-rw-r--r-- | src/heap-profile-table.cc | 24 | ||||
-rw-r--r-- | src/malloc_extension.cc | 6 | ||||
-rw-r--r-- | src/memfs_malloc.cc | 4 | ||||
-rwxr-xr-x | src/pprof | 1 | ||||
-rw-r--r-- | src/profile-handler.cc | 30 | ||||
-rw-r--r-- | src/symbolize.cc | 78 | ||||
-rw-r--r-- | src/symbolize.h | 44 | ||||
-rw-r--r-- | src/tcmalloc.cc | 29 | ||||
-rw-r--r-- | src/tests/atomicops_unittest.cc | 142 | ||||
-rw-r--r-- | src/tests/debugallocation_test.cc | 47 | ||||
-rw-r--r-- | src/tests/malloc_extension_test.cc | 30 | ||||
-rw-r--r-- | src/tests/profile-handler_unittest.cc | 121 | ||||
-rwxr-xr-x | src/tests/profiler_unittest.sh | 10 | ||||
-rw-r--r-- | src/tests/tcmalloc_unittest.cc | 44 | ||||
-rw-r--r-- | src/windows/config.h | 3 | ||||
-rw-r--r-- | src/windows/google/tcmalloc.h | 1 |
28 files changed, 533 insertions, 246 deletions
@@ -20592,7 +20592,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext fi { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_formatting_prius_prefix" >&5 $as_echo "$ac_cv_formatting_prius_prefix" >&6; } - if test -z "$ac_cv_formatting_prius_defined"; then + if test -z "$ac_cv_prius_defined"; then ac_cv_formatting_prius_prefix=z; fi @@ -20643,6 +20643,33 @@ fi rm -f core conftest.err conftest.$ac_objext \ conftest$ac_exeext conftest.$ac_ext +# Check if __environ is available (for GetenvBeforeMain) +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __environ" >&5 +$as_echo_n "checking for __environ... " >&6; } +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <unistd.h> +int +main () +{ +char **env = __environ + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + +$as_echo "#define HAVE___ENVIRON 1" >>confdefs.h + + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + # If we support __thread, that can speed up tcmalloc a bit. # Note, however, that our code tickles a bug in gcc < 4.1.2 # involving TLS and -fPIC (which our libraries will use) on x86: diff --git a/configure.ac b/configure.ac index 4d2a271..0c8a232 100644 --- a/configure.ac +++ b/configure.ac @@ -236,6 +236,15 @@ AC_LINK_IFELSE([AC_LANG_PROGRAM(, [void *sp = __builtin_stack_pointer()])], AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no])]) +# Check if __environ is available (for GetenvBeforeMain) +AC_MSG_CHECKING([for __environ]) +AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <unistd.h>], + [char **env = __environ])], + [AC_DEFINE(HAVE___ENVIRON, 1, + [Define to 1 if compiler supports __environ]) + AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no])]) + # If we support __thread, that can speed up tcmalloc a bit. # Note, however, that our code tickles a bug in gcc < 4.1.2 # involving TLS and -fPIC (which our libraries will use) on x86: diff --git a/doc/cpuprofile.html b/doc/cpuprofile.html index 4f71c02..3d2b4cc 100644 --- a/doc/cpuprofile.html +++ b/doc/cpuprofile.html @@ -86,6 +86,18 @@ environment variables.</p> </td> </tr> +<tr valign=top> + <td><code>CPUPROFILE_REALTIME=1</code></td> + <td>default: [not set]</td> + <td> + If set to any value (including 0 or the empty string), use + ITIMER_REAL instead of ITIMER_PROF to gather profiles. In + general, ITIMER_REAL is not as accurate as ITIMER_PROF, and also + interacts badly with use of alarm(), so prefer ITIMER_PROF unless + you have a reason prefer ITIMER_REAL. + </td> +</tr> + </table> diff --git a/doc/heap_checker.html b/doc/heap_checker.html index 491ec3d..87e497a 100644 --- a/doc/heap_checker.html +++ b/doc/heap_checker.html @@ -316,6 +316,16 @@ checking.</p> </td> </tr> +<tr valign=top> + <td><code>HEAP_CHECK_MAX_LEAKS</code></td> + <td>Default: 20</td> + <td> + The maximum number of leaks to be reported. If negative or zero, print all + the leaks found. + </td> +</tr> + + </table> <p>These options apply to all types of leak checking.</p> diff --git a/m4/compiler_characteristics.m4 b/m4/compiler_characteristics.m4 index f4d29c9..2b62893 100644 --- a/m4/compiler_characteristics.m4 +++ b/m4/compiler_characteristics.m4 @@ -15,7 +15,7 @@ AC_DEFUN([AC_COMPILER_CHARACTERISTICS], AC_TYPES_COMPATIBLE(unsigned long long, size_t, ac_cv_formatting_prius_prefix=ll; ac_cv_prius_defined=1 )]) - if test -z "$ac_cv_formatting_prius_defined"; then + if test -z "$ac_cv_prius_defined"; then ac_cv_formatting_prius_prefix=z; fi AC_DEFINE_UNQUOTED(PRIuS, "${ac_cv_formatting_prius_prefix}u", AC_PRIUS_COMMENT) diff --git a/src/base/logging.h b/src/base/logging.h index 3b1dacf..6aa5c3f 100644 --- a/src/base/logging.h +++ b/src/base/logging.h @@ -136,17 +136,26 @@ enum { DEBUG_MODE = 1 }; #define CHECK_GE(val1, val2) CHECK_OP(>=, val1, val2) #define CHECK_GT(val1, val2) CHECK_OP(> , val1, val2) -// A synonym for CHECK_* that is used in some unittests. +// Synonyms for CHECK_* that are used in some unittests. #define EXPECT_EQ(val1, val2) CHECK_EQ(val1, val2) #define EXPECT_NE(val1, val2) CHECK_NE(val1, val2) #define EXPECT_LE(val1, val2) CHECK_LE(val1, val2) #define EXPECT_LT(val1, val2) CHECK_LT(val1, val2) #define EXPECT_GE(val1, val2) CHECK_GE(val1, val2) #define EXPECT_GT(val1, val2) CHECK_GT(val1, val2) +#define ASSERT_EQ(val1, val2) EXPECT_EQ(val1, val2) +#define ASSERT_NE(val1, val2) EXPECT_NE(val1, val2) +#define ASSERT_LE(val1, val2) EXPECT_LE(val1, val2) +#define ASSERT_LT(val1, val2) EXPECT_LT(val1, val2) +#define ASSERT_GE(val1, val2) EXPECT_GE(val1, val2) +#define ASSERT_GT(val1, val2) EXPECT_GT(val1, val2) // As are these variants. #define EXPECT_TRUE(cond) CHECK(cond) #define EXPECT_FALSE(cond) CHECK(!(cond)) #define EXPECT_STREQ(a, b) CHECK(strcmp(a, b) == 0) +#define ASSERT_TRUE(cond) EXPECT_TRUE(cond) +#define ASSERT_FALSE(cond) EXPECT_FALSE(cond) +#define ASSERT_STREQ(a, b) EXPECT_STREQ(a, b) // Used for (libc) functions that return -1 and set errno #define CHECK_ERR(invocation) PCHECK((invocation) != -1) diff --git a/src/base/sysinfo.cc b/src/base/sysinfo.cc index 3919ba4..7af0495 100644 --- a/src/base/sysinfo.cc +++ b/src/base/sysinfo.cc @@ -100,11 +100,33 @@ // Some non-trivial getenv-related functions. // ---------------------------------------------------------------------- +// It's not safe to call getenv() in the malloc hooks, because they +// might be called extremely early, before libc is done setting up +// correctly. In particular, the thread library may not be done +// setting up errno. So instead, we use the built-in __environ array +// if it exists, and otherwise read /proc/self/environ directly, using +// system calls to read the file, and thus avoid setting errno. +// /proc/self/environ has a limit of how much data it exports (around +// 8K), so it's not an ideal solution. const char* GetenvBeforeMain(const char* name) { +#if defined(HAVE___ENVIRON) // if we have it, it's declared in unistd.h + const int namelen = strlen(name); + for (char** p = __environ; *p; p++) { + if (!memcmp(*p, name, namelen) && (*p)[namelen] == '=') // it's a match + return *p + namelen+1; // point after = + } + return NULL; +#elif defined(PLATFORM_WINDOWS) + // TODO(mbelshe) - repeated calls to this function will overwrite the + // contents of the static buffer. + static char envbuf[1024]; // enough to hold any envvar we care about + if (!GetEnvironmentVariableA(name, envbuf, sizeof(envbuf)-1)) + return NULL; + return envbuf; +#else // static is ok because this function should only be called before // main(), when we're single-threaded. static char envbuf[16<<10]; -#ifndef PLATFORM_WINDOWS if (*envbuf == '\0') { // haven't read the environ yet int fd = safeopen("/proc/self/environ", O_RDONLY); // The -2 below guarantees the last two bytes of the buffer will be \0\0 @@ -129,12 +151,6 @@ const char* GetenvBeforeMain(const char* name) { p = endp + 1; } return NULL; // env var never found -#else - // TODO(mbelshe) - repeated calls to this function will overwrite the - // contents of the static buffer. - if (!GetEnvironmentVariableA(name, envbuf, sizeof(envbuf)-1)) - return NULL; - return envbuf; #endif } diff --git a/src/config.h.in b/src/config.h.in index 1b1a3d9..1ad2642 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -153,6 +153,9 @@ /* define if your compiler has __attribute__ */ #undef HAVE___ATTRIBUTE__ +/* Define to 1 if compiler supports __environ */ +#undef HAVE___ENVIRON + /* Define to 1 if the system has the type `__int64'. */ #undef HAVE___INT64 diff --git a/src/debugallocation.cc b/src/debugallocation.cc index 47fef16..1a9ddcb 100644 --- a/src/debugallocation.cc +++ b/src/debugallocation.cc @@ -152,18 +152,20 @@ extern "C" { // The do_* functions are defined in tcmalloc.cc, // which is included before this file // when TCMALLOC_FOR_DEBUGALLOCATION is defined. -#define BASE_MALLOC do_malloc -#define BASE_FREE do_free -#define BASE_MALLOPT do_mallopt -#define BASE_MALLINFO do_mallinfo +#define BASE_MALLOC_NEW(size) cpp_alloc(size, false) +#define BASE_MALLOC do_malloc_or_cpp_alloc +#define BASE_FREE do_free +#define BASE_MALLOPT do_mallopt +#define BASE_MALLINFO do_mallinfo #else // We are working on top of standard libc's malloc library -#define BASE_MALLOC __libc_malloc -#define BASE_FREE __libc_free -#define BASE_MALLOPT __libc_mallopt -#define BASE_MALLINFO __libc_mallinfo +#define BASE_MALLOC_NEW __libc_malloc +#define BASE_MALLOC __libc_malloc +#define BASE_FREE __libc_free +#define BASE_MALLOPT __libc_mallopt +#define BASE_MALLINFO __libc_mallinfo #endif @@ -524,10 +526,14 @@ class MallocBlock { } b = (MallocBlock*) (p + (num_pages - 1) * pagesize - sz); } else { - b = (MallocBlock*) BASE_MALLOC(real_malloced_size(size)); + b = (MallocBlock*) (type == kMallocType ? + BASE_MALLOC(real_malloced_size(size)) : + BASE_MALLOC_NEW(real_malloced_size(size))); } #else - b = (MallocBlock*) BASE_MALLOC(real_malloced_size(size)); + b = (MallocBlock*) (type == kMallocType ? + BASE_MALLOC(real_malloced_size(size)) : + BASE_MALLOC_NEW(real_malloced_size(size))); #endif // It would be nice to output a diagnostic on allocation failure @@ -656,25 +662,24 @@ class MallocBlock { reinterpret_cast<void*>( PRINTABLE_PTHREAD(queue_entry.deleter_threadid))); - SymbolMap symbolization_table; + SymbolTable symbolization_table; const int num_symbols = queue_entry.num_deleter_pcs; // short alias name for (int i = 0; i < num_symbols; i++) { // Symbolizes the previous address of pc because pc may be in the // next function. This may happen when the function ends with // a call to a function annotated noreturn (e.g. CHECK). - uintptr_t pc = - reinterpret_cast<uintptr_t>(queue_entry.deleter_pcs[i]) - 1; - symbolization_table[pc] = ""; + char* pc = + reinterpret_cast<char*>(queue_entry.deleter_pcs[i]) - 1; + symbolization_table.Add(pc); } - int sym_buffer_len = kSymbolSize * num_symbols; - char *sym_buffer = new char[sym_buffer_len]; if (FLAGS_symbolize_stacktrace) - Symbolize(sym_buffer, sym_buffer_len, &symbolization_table); + symbolization_table.Symbolize(); for (int i = 0; i < num_symbols; i++) { - uintptr_t pc = - reinterpret_cast<uintptr_t>(queue_entry.deleter_pcs[i]) - 1; - TracePrintf(STDERR_FILENO, " @ %p %s\n", - reinterpret_cast<void*>(pc), symbolization_table[pc]); + char *pc = + reinterpret_cast<char*>(queue_entry.deleter_pcs[i]) - 1; + TracePrintf(STDERR_FILENO, " @ %"PRIxPTR" %s\n", + reinterpret_cast<uintptr_t>(pc), + symbolization_table.GetSymbol(pc)); } } else { RAW_LOG(ERROR, @@ -696,6 +701,12 @@ class MallocBlock { // Find the header just before client's memory. MallocBlock *mb = reinterpret_cast<MallocBlock *>( reinterpret_cast<char *>(p) - data_offset); + // If mb->alloc_type_ is kMagicDeletedInt, we're not an ok pointer. + if (mb->alloc_type_ == kMagicDeletedInt) { + RAW_LOG(FATAL, "memory allocation bug: object at %p has been already" + " deallocated; or else a word before the object has been" + " corrupted (memory stomping bug)", p); + } // If mb->offset_ is zero (common case), mb is the real header. If // mb->offset_ is non-zero, this block was allocated by memalign, and // mb->offset_ is the distance backwards to the real header from mb, diff --git a/src/google/malloc_extension.h b/src/google/malloc_extension.h index 0342843..fc272c9 100644 --- a/src/google/malloc_extension.h +++ b/src/google/malloc_extension.h @@ -198,8 +198,7 @@ class PERFTOOLS_DLL_DECL MallocExtension { // system for reuse. Use this extension with caution -- to get this // memory back may require faulting pages back in by the OS, and // that may be slow. (Currently only implemented in tcmalloc.) - // A negative values for num_bytes results in a noop. - virtual void ReleaseToSystem(ssize_t num_bytes); + virtual void ReleaseToSystem(size_t num_bytes); // Same as ReleaseToSystem() but release as much memory as possible. virtual void ReleaseFreeMemory(); diff --git a/src/google/malloc_extension_c.h b/src/google/malloc_extension_c.h index 95f7f4c..fcaa8cd 100644 --- a/src/google/malloc_extension_c.h +++ b/src/google/malloc_extension_c.h @@ -75,7 +75,7 @@ PERFTOOLS_DLL_DECL int MallocExtension_GetNumericProperty(const char* property, PERFTOOLS_DLL_DECL int MallocExtension_SetNumericProperty(const char* property, size_t value); PERFTOOLS_DLL_DECL void MallocExtension_MarkThreadIdle(void); PERFTOOLS_DLL_DECL void MallocExtension_MarkThreadBusy(void); -PERFTOOLS_DLL_DECL void MallocExtension_ReleaseToSystem(ssize_t num_bytes); +PERFTOOLS_DLL_DECL void MallocExtension_ReleaseToSystem(size_t num_bytes); PERFTOOLS_DLL_DECL void MallocExtension_ReleaseFreeMemory(void); PERFTOOLS_DLL_DECL size_t MallocExtension_GetEstimatedAllocatedSize(size_t size); PERFTOOLS_DLL_DECL size_t MallocExtension_GetAllocatedSize(void* p); diff --git a/src/google/tcmalloc.h.in b/src/google/tcmalloc.h.in index cf62c70..e5c873d 100644 --- a/src/google/tcmalloc.h.in +++ b/src/google/tcmalloc.h.in @@ -89,6 +89,7 @@ extern "C" { #endif #ifdef __cplusplus + PERFTOOLS_DLL_DECL int tc_set_new_mode(int flag) __THROW; PERFTOOLS_DLL_DECL void* tc_new(size_t size); PERFTOOLS_DLL_DECL void tc_delete(void* p) __THROW; PERFTOOLS_DLL_DECL void* tc_newarray(size_t size); diff --git a/src/heap-profile-table.cc b/src/heap-profile-table.cc index ce65c47..66e4f20 100644 --- a/src/heap-profile-table.cc +++ b/src/heap-profile-table.cc @@ -82,6 +82,10 @@ DEFINE_bool(cleanup_old_heap_profiles, EnvToBool("HEAP_PROFILE_CLEANUP", true), "At initialization time, delete old heap profiles."); +DEFINE_int32(heap_check_max_leaks, + EnvToInt("HEAP_CHECK_MAX_LEAKS", 20), + "The maximum number of leak reports to print."); + //---------------------------------------------------------------------- // header of the dumped heap profile @@ -539,26 +543,23 @@ void HeapProfileTable::Snapshot::ReportLeaks(const char* checker_name, // Report a bounded number of leaks to keep the leak report from // growing too long. - const int to_report = (n > 20) ? 20 : n; + const int to_report = + (FLAGS_heap_check_max_leaks > 0 && + n > FLAGS_heap_check_max_leaks) ? FLAGS_heap_check_max_leaks : n; RAW_LOG(ERROR, "The %d largest leaks:", to_report); // Print - SymbolMap symbolization_table; - int num_symbols = 0; + SymbolTable symbolization_table; for (int i = 0; i < to_report; i++) { const Entry& e = entries[i]; for (int j = 0; j < e.bucket->depth; j++) { - const void* pc = e.bucket->stack[j]; - symbolization_table[reinterpret_cast<uintptr_t>(pc)] = ""; - num_symbols++; + symbolization_table.Add(e.bucket->stack[j]); } } static const int kBufSize = 2<<10; char buffer[kBufSize]; - int sym_buffer_len = kSymbolSize * num_symbols; - char *sym_buffer = new char[sym_buffer_len]; if (should_symbolize) - Symbolize(sym_buffer, sym_buffer_len, &symbolization_table); + symbolization_table.Symbolize(); for (int i = 0; i < to_report; i++) { const Entry& e = entries[i]; base::RawPrinter printer(buffer, kBufSize); @@ -566,12 +567,11 @@ void HeapProfileTable::Snapshot::ReportLeaks(const char* checker_name, e.bytes, e.count); for (int j = 0; j < e.bucket->depth; j++) { const void* pc = e.bucket->stack[j]; - printer.Printf("\t@ %p %s\n", - pc, symbolization_table[reinterpret_cast<uintptr_t>(pc)]); + printer.Printf("\t@ %"PRIxPTR" %s\n", + reinterpret_cast<uintptr_t>(pc), symbolization_table.GetSymbol(pc)); } RAW_LOG(ERROR, "%s", buffer); } - delete[] sym_buffer; if (to_report < n) { RAW_LOG(ERROR, "Skipping leaks numbered %d..%d", diff --git a/src/malloc_extension.cc b/src/malloc_extension.cc index 95fd1c1..4ce262f 100644 --- a/src/malloc_extension.cc +++ b/src/malloc_extension.cc @@ -143,12 +143,12 @@ void MallocExtension::MarkThreadBusy() { // Default implementation does nothing } -void MallocExtension::ReleaseToSystem(ssize_t num_bytes) { +void MallocExtension::ReleaseToSystem(size_t num_bytes) { // Default implementation does nothing } void MallocExtension::ReleaseFreeMemory() { - ReleaseToSystem(LONG_MAX); + ReleaseToSystem(static_cast<size_t>(-1)); // SIZE_T_MAX } void MallocExtension::SetMemoryReleaseRate(double rate) { @@ -333,6 +333,6 @@ C_SHIM(SetNumericProperty, int, C_SHIM(MarkThreadIdle, void, (void), ()); C_SHIM(MarkThreadBusy, void, (void), ()); C_SHIM(ReleaseFreeMemory, void, (void), ()); -C_SHIM(ReleaseToSystem, void, (ssize_t num_bytes), (num_bytes)); +C_SHIM(ReleaseToSystem, void, (size_t num_bytes), (num_bytes)); C_SHIM(GetEstimatedAllocatedSize, size_t, (size_t size), (size)); C_SHIM(GetAllocatedSize, size_t, (void* p), (p)); diff --git a/src/memfs_malloc.cc b/src/memfs_malloc.cc index bef2d3c..fbef9c8 100644 --- a/src/memfs_malloc.cc +++ b/src/memfs_malloc.cc @@ -161,8 +161,8 @@ void* HugetlbSysAllocator::Alloc(size_t size, size_t *actual_size, MAP_SHARED, hugetlb_fd_, hugetlb_base_); if (result == reinterpret_cast<void*>(MAP_FAILED)) { if (!FLAGS_memfs_malloc_ignore_mmap_fail) { - TCMalloc_MESSAGE(__FILE__, __LINE__, "mmap failed: %s\n", - strerror(errno)); + TCMalloc_MESSAGE(__FILE__, __LINE__, "mmap of size %"PRIuS" failed: %s\n", + size + extra, strerror(errno)); failed_ = true; if (FLAGS_memfs_malloc_abort_on_fail) { CRASH("memfs_malloc_abort_on_fail is set\n"); @@ -1968,6 +1968,7 @@ sub RemoveUninterestingFrames { 'tc_newarray_nothrow', 'do_malloc', '::do_malloc', # new name -- got moved to an unnamed ns + '::do_malloc_or_cpp_alloc', 'DoSampledAllocation', 'simple_alloc::allocate', '__malloc_alloc_template::allocate', diff --git a/src/profile-handler.cc b/src/profile-handler.cc index e658d30..b68dbc2 100644 --- a/src/profile-handler.cc +++ b/src/profile-handler.cc @@ -89,18 +89,18 @@ class ProfileHandler { // Registers a callback routine to receive profile timer ticks. The returned // token is to be used when unregistering this callback and must not be // deleted by the caller. Registration of the first callback enables the - // SIGPROF handler. + // SIGPROF handler (or SIGALRM if using ITIMER_REAL). ProfileHandlerToken* RegisterCallback(ProfileHandlerCallback callback, void* callback_arg); // Unregisters a previously registered callback. Expects the token returned // by the corresponding RegisterCallback routine. Unregistering the last - // callback disables the SIGPROF handler. + // callback disables the SIGPROF handler (or SIGALRM if using ITIMER_REAL). void UnregisterCallback(ProfileHandlerToken* token) NO_THREAD_SAFETY_ANALYSIS; // Unregisters all the callbacks, stops the timer if shared, disables the - // SIGPROF handler and clears the timer_sharing_ state. + // SIGPROF (or SIGALRM) handler and clears the timer_sharing_ state. void Reset(); // Gets the current state of profile handler. @@ -127,12 +127,15 @@ class ProfileHandler { // Initializes the ProfileHandler singleton via GoogleOnceInit. static void Init(); - // Counts the number of SIGPROF interrupts received. + // The number of SIGPROF (or SIGALRM for ITIMER_REAL) interrupts received. int64 interrupts_ GUARDED_BY(signal_lock_); - // SIGPROF interrupt frequency, read-only after construction. + // SIGPROF/SIGALRM interrupt frequency, read-only after construction. int32 frequency_; + // ITIMER_PROF (which uses SIGPROF), or ITIMER_REAL (which uses SIGALRM) + int timer_type_; + // Counts the number of callbacks registered. int32 callback_count_ GUARDED_BY(control_lock_); @@ -196,7 +199,7 @@ class ProfileHandler { // Disables (ignores) the timer interrupt signal. void DisableHandler() EXCLUSIVE_LOCKS_REQUIRED(control_lock_); - // SIGPROF handler. Iterate over and call all the registered callbacks. + // SIGPROF/SIGALRM handler. Iterate over and call all the registered callbacks. static void SignalHandler(int sig, siginfo_t* sinfo, void* ucontext); DISALLOW_EVIL_CONSTRUCTORS(ProfileHandler); @@ -241,6 +244,9 @@ ProfileHandler::ProfileHandler() callback_count_(0), timer_sharing_(TIMERS_UNTOUCHED) { SpinLockHolder cl(&control_lock_); + + timer_type_ = (getenv("CPUPROFILE_REALTIME") ? ITIMER_REAL : ITIMER_PROF); + // Get frequency of interrupts (if specified) char junk; const char* fr = getenv("CPUPROFILE_FREQUENCY"); @@ -390,18 +396,18 @@ void ProfileHandler::StartTimer() { timer.it_interval.tv_sec = 0; timer.it_interval.tv_usec = 1000000 / frequency_; timer.it_value = timer.it_interval; - setitimer(ITIMER_PROF, &timer, 0); + setitimer(timer_type_, &timer, 0); } void ProfileHandler::StopTimer() { struct itimerval timer; memset(&timer, 0, sizeof timer); - setitimer(ITIMER_PROF, &timer, 0); + setitimer(timer_type_, &timer, 0); } bool ProfileHandler::IsTimerRunning() { struct itimerval current_timer; - RAW_CHECK(0 == getitimer(ITIMER_PROF, ¤t_timer), "getitimer"); + RAW_CHECK(0 == getitimer(timer_type_, ¤t_timer), "getitimer"); return (current_timer.it_value.tv_sec != 0 || current_timer.it_value.tv_usec != 0); } @@ -411,7 +417,8 @@ void ProfileHandler::EnableHandler() { sa.sa_sigaction = SignalHandler; sa.sa_flags = SA_RESTART | SA_SIGINFO; sigemptyset(&sa.sa_mask); - RAW_CHECK(sigaction(SIGPROF, &sa, NULL) == 0, "sigprof (enable)"); + const int signal_number = (timer_type_ == ITIMER_PROF ? SIGPROF : SIGALRM); + RAW_CHECK(sigaction(signal_number, &sa, NULL) == 0, "sigprof (enable)"); } void ProfileHandler::DisableHandler() { @@ -419,7 +426,8 @@ void ProfileHandler::DisableHandler() { sa.sa_handler = SIG_IGN; sa.sa_flags = SA_RESTART; sigemptyset(&sa.sa_mask); - RAW_CHECK(sigaction(SIGPROF, &sa, NULL) == 0, "sigprof (disable)"); + const int signal_number = (timer_type_ == ITIMER_PROF ? SIGPROF : SIGALRM); + RAW_CHECK(sigaction(signal_number, &sa, NULL) == 0, "sigprof (disable)"); } void ProfileHandler::SignalHandler(int sig, siginfo_t* sinfo, void* ucontext) { diff --git a/src/symbolize.cc b/src/symbolize.cc index 6fe44b9..9dd890e 100644 --- a/src/symbolize.cc +++ b/src/symbolize.cc @@ -65,30 +65,37 @@ DEFINE_string(symbolize_pprof, // a more-permanent copy that won't ever get destroyed. static string* g_pprof_path = new string(FLAGS_symbolize_pprof); +void SymbolTable::Add(const void* addr) { + symbolization_table_[addr] = ""; +} + +const char* SymbolTable::GetSymbol(const void* addr) { + return symbolization_table_[addr]; +} + // Updates symbolization_table with the pointers to symbol names corresponding // to its keys. The symbol names are stored in out, which is allocated and // freed by the caller of this routine. // Note that the forking/etc is not thread-safe or re-entrant. That's // ok for the purpose we need -- reporting leaks detected by heap-checker // -- but be careful if you decide to use this routine for other purposes. -extern bool Symbolize(char *out, int out_size, - SymbolMap *symbolization_table) { +int SymbolTable::Symbolize() { #if !defined(HAVE_UNISTD_H) || !defined(HAVE_SYS_SOCKET_H) || !defined(HAVE_SYS_WAIT_H) - return false; + return 0; #elif !defined(HAVE_PROGRAM_INVOCATION_NAME) - return false; // TODO(csilvers): get argv[0] somehow + return 0; // TODO(csilvers): get argv[0] somehow #else // All this work is to do two-way communication. ugh. extern char* program_invocation_name; // gcc provides this int child_in[2]; // file descriptors int child_out[2]; // for now, we don't worry about child_err if (socketpair(AF_UNIX, SOCK_STREAM, 0, child_in) == -1) { - return false; + return 0; } if (socketpair(AF_UNIX, SOCK_STREAM, 0, child_out) == -1) { close(child_in[0]); close(child_in[1]); - return false; + return 0; } switch (fork()) { case -1: { // error @@ -96,7 +103,7 @@ extern bool Symbolize(char *out, int out_size, close(child_in[1]); close(child_out[0]); close(child_out[1]); - return false; + return 0; } case 0: { // child close(child_in[1]); // child uses the 0's, parent uses the 1's @@ -125,30 +132,36 @@ extern bool Symbolize(char *out, int out_size, struct pollfd pfd = { child_in[1], POLLOUT, 0 }; if (!poll(&pfd, 1, 0) || !(pfd.revents & POLLOUT) || (pfd.revents & (POLLHUP|POLLERR))) { - return false; + return 0; } #endif DumpProcSelfMaps(child_in[1]); // what pprof expects on stdin - char pcstr[64]; // enough for a single address - for (SymbolMap::const_iterator iter = symbolization_table->begin(); - iter != symbolization_table->end(); ++iter) { - snprintf(pcstr, sizeof(pcstr), // pprof expects format to be 0xXXXXXX - "0x%" PRIxPTR "\n", iter->first); - // TODO(glider): the number of write()s can be reduced by using - // snprintf() here. - write(child_in[1], pcstr, strlen(pcstr)); + // Allocate 24 bytes = ("0x" + 8 bytes + "\n" + overhead) for each + // address to feed to pprof. + const int kOutBufSize = 24 * symbolization_table_.size(); + char *pprof_buffer = new char[kOutBufSize]; + int written = 0; + for (SymbolMap::const_iterator iter = symbolization_table_.begin(); + iter != symbolization_table_.end(); ++iter) { + written += snprintf(pprof_buffer + written, kOutBufSize - written, + // pprof expects format to be 0xXXXXXX + "0x%"PRIxPTR"\n", reinterpret_cast<uintptr_t>(iter->first)); } + write(child_in[1], pprof_buffer, strlen(pprof_buffer)); close(child_in[1]); // that's all we need to write + const int kSymbolBufferSize = kSymbolSize * symbolization_table_.size(); int total_bytes_read = 0; - memset(out, '\0', out_size); + delete[] symbol_buffer_; + symbol_buffer_ = new char[kSymbolBufferSize]; + memset(symbol_buffer_, '\0', kSymbolBufferSize); while (1) { - int bytes_read = read(child_out[1], out + total_bytes_read, - out_size - total_bytes_read); + int bytes_read = read(child_out[1], symbol_buffer_ + total_bytes_read, + kSymbolBufferSize - total_bytes_read); if (bytes_read < 0) { close(child_out[1]); - return false; + return 0; } else if (bytes_read == 0) { close(child_out[1]); wait(NULL); @@ -159,25 +172,24 @@ extern bool Symbolize(char *out, int out_size, } // We have successfully read the output of pprof into out. Make sure // the last symbol is full (we can tell because it ends with a \n). - // TODO(glider): even when the last symbol is full, the list of symbols - // may be incomplete. We should check for that and return the number of - // symbols we actually get from pprof. - if (total_bytes_read == 0 || out[total_bytes_read - 1] != '\n') - return false; - // make the symbolization_table values point to the output vector - SymbolMap::iterator fill = symbolization_table->begin(); - const char *current_name = out; + if (total_bytes_read == 0 || symbol_buffer_[total_bytes_read - 1] != '\n') + return 0; + // make the symbolization_table_ values point to the output vector + SymbolMap::iterator fill = symbolization_table_.begin(); + int num_symbols = 0; + const char *current_name = symbol_buffer_; for (int i = 0; i < total_bytes_read; i++) { - if (out[i] == '\n') { + if (symbol_buffer_[i] == '\n') { fill->second = current_name; - out[i] = '\0'; - current_name = out + i + 1; + symbol_buffer_[i] = '\0'; + current_name = symbol_buffer_ + i + 1; fill++; + num_symbols++; } } - return true; + return num_symbols; } } - return false; // shouldn't be reachable + return 0; // shouldn't be reachable #endif } diff --git a/src/symbolize.h b/src/symbolize.h index 8fb0366..1ab4ed6 100644 --- a/src/symbolize.h +++ b/src/symbolize.h @@ -35,20 +35,48 @@ #include "config.h" #ifdef HAVE_STDINT_H -#include <stdint.h> // for uintptr_t +#include <stdint.h> // for uintptr_t #endif #include <map> using std::map; -// An average size of memory allocated for a stack trace symbol. -static const int kSymbolSize = 1024; +// SymbolTable encapsulates the address operations necessary for stack trace +// symbolization. A common use-case is to Add() the addresses from one or +// several stack traces to a table, call Symbolize() once and use GetSymbol() +// to get the symbol names for pretty-printing the stack traces. +class SymbolTable { + public: + SymbolTable() + : symbol_buffer_(NULL) {} + ~SymbolTable() { + delete[] symbol_buffer_; + } -// TODO(glider): it's better to make SymbolMap a class that encapsulates the -// address operations and has the Symbolize() method. -typedef map<uintptr_t, const char*> SymbolMap; + // Adds an address to the table. This may overwrite a currently known symbol + // name, so Add() should not generally be called after Symbolize(). + void Add(const void* addr); -extern bool Symbolize(char *out, int out_size, - SymbolMap *symbolization_table); + // Returns the symbol name for addr, if the given address was added before + // the last successful call to Symbolize(). Otherwise may return an empty + // c-string. + const char* GetSymbol(const void* addr); + + // Obtains the symbol names for the addresses stored in the table and returns + // the number of addresses actually symbolized. + int Symbolize(); + + private: + typedef map<const void*, const char*> SymbolMap; + + // An average size of memory allocated for a stack trace symbol. + static const int kSymbolSize = 1024; + + // Map from addresses to symbol names. + SymbolMap symbolization_table_; + + // Pointer to the buffer that stores the symbol names. + char *symbol_buffer_; +}; #endif // TCMALLOC_SYMBOLIZE_H_ diff --git a/src/tcmalloc.cc b/src/tcmalloc.cc index 450c1ab..09c545d 100644 --- a/src/tcmalloc.cc +++ b/src/tcmalloc.cc @@ -360,6 +360,8 @@ extern "C" { // ----------------------- IMPLEMENTATION ------------------------------- +static int tc_new_mode = 0; // See tc_set_new_mode(). + // Routines such as free() and realloc() catch some erroneous pointers // passed to them, and invoke the below when they do. (An erroneous pointer // won't be caught if it's within a valid span or a stale span for which @@ -689,10 +691,7 @@ class TCMallocImplementation : public MallocExtension { virtual void MarkThreadBusy(); // Implemented below - virtual void ReleaseToSystem(ssize_t num_bytes) { - if (num_bytes <= 0) { - return; - } + virtual void ReleaseToSystem(size_t num_bytes) { SpinLockHolder h(Static::pageheap_lock()); if (num_bytes <= extra_bytes_released_) { // We released too much on a prior call, so don't release any @@ -860,6 +859,17 @@ static void ReportLargeAlloc(Length num_pages, void* result) { namespace { +inline void* cpp_alloc(size_t size, bool nothrow); +inline void* do_malloc(size_t size); + +// TODO(willchan): Investigate whether or not lining this much is harmful to +// performance. +// This is equivalent to do_malloc() except when tc_new_mode is set to true. +// Otherwise, it will run the std::new_handler if set. +inline void* do_malloc_or_cpp_alloc(size_t size) { + return tc_new_mode ? cpp_alloc(size, true) : do_malloc(size); +} + // Helper for do_malloc(). inline void* do_malloc_pages(Length num_pages) { Span *span; @@ -910,7 +920,7 @@ inline void* do_calloc(size_t n, size_t elem_size) { const size_t size = n * elem_size; if (elem_size != 0 && size / elem_size != n) return NULL; - void* result = do_malloc(size); + void* result = do_malloc_or_cpp_alloc(size); if (result != NULL) { memset(result, 0, size); } @@ -1019,11 +1029,11 @@ inline void* do_realloc_with_callback( void* new_ptr = NULL; if (new_size > old_size && new_size < lower_bound_to_grow) { - new_ptr = do_malloc(lower_bound_to_grow); + new_ptr = do_malloc_or_cpp_alloc(lower_bound_to_grow); } if (new_ptr == NULL) { // Either new_size is not a tiny increment, or last do_malloc failed. - new_ptr = do_malloc(new_size); + new_ptr = do_malloc_or_cpp_alloc(new_size); } if (new_ptr == NULL) { return NULL; @@ -1241,9 +1251,8 @@ extern "C" PERFTOOLS_DLL_DECL const char* tc_version( // heap-checker.cc depends on this to start a stack trace from // the call to the (de)allocation function. -static int tc_new_mode = 0; // See tc_set_new_mode(). extern "C" PERFTOOLS_DLL_DECL void* tc_malloc(size_t size) __THROW { - void* result = (tc_new_mode ? cpp_alloc(size, false) : do_malloc(size)); + void* result = do_malloc_or_cpp_alloc(size); MallocHook::InvokeNewHook(result, size); return result; } @@ -1268,7 +1277,7 @@ extern "C" PERFTOOLS_DLL_DECL void tc_cfree(void* ptr) __THROW { extern "C" PERFTOOLS_DLL_DECL void* tc_realloc(void* old_ptr, size_t new_size) __THROW { if (old_ptr == NULL) { - void* result = do_malloc(new_size); + void* result = do_malloc_or_cpp_alloc(new_size); MallocHook::InvokeNewHook(result, new_size); return result; } diff --git a/src/tests/atomicops_unittest.cc b/src/tests/atomicops_unittest.cc index 5a620f5..3892b59 100644 --- a/src/tests/atomicops_unittest.cc +++ b/src/tests/atomicops_unittest.cc @@ -60,50 +60,50 @@ static void TestAtomicIncrement() { s.count = 0; s.next_word = next_word_value; - CHECK_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 1), 1); - CHECK_EQ(s.count, 1); - CHECK_EQ(s.prev_word, prev_word_value); - CHECK_EQ(s.next_word, next_word_value); - - CHECK_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 2), 3); - CHECK_EQ(s.count, 3); - CHECK_EQ(s.prev_word, prev_word_value); - CHECK_EQ(s.next_word, next_word_value); - - CHECK_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 3), 6); - CHECK_EQ(s.count, 6); - CHECK_EQ(s.prev_word, prev_word_value); - CHECK_EQ(s.next_word, next_word_value); - - CHECK_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -3), 3); - CHECK_EQ(s.count, 3); - CHECK_EQ(s.prev_word, prev_word_value); - CHECK_EQ(s.next_word, next_word_value); - - CHECK_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -2), 1); - CHECK_EQ(s.count, 1); - CHECK_EQ(s.prev_word, prev_word_value); - CHECK_EQ(s.next_word, next_word_value); - - CHECK_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -1), 0); - CHECK_EQ(s.count, 0); - CHECK_EQ(s.prev_word, prev_word_value); - CHECK_EQ(s.next_word, next_word_value); - - CHECK_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -1), -1); - CHECK_EQ(s.count, -1); - CHECK_EQ(s.prev_word, prev_word_value); - CHECK_EQ(s.next_word, next_word_value); - - CHECK_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, -4), -5); - CHECK_EQ(s.count, -5); - CHECK_EQ(s.prev_word, prev_word_value); - CHECK_EQ(s.next_word, next_word_value); - - CHECK_EQ(base::subtle::NoBarrier_AtomicIncrement(&s.count, 5), 0); - CHECK_EQ(s.count, 0); - CHECK_EQ(s.prev_word, prev_word_value); - CHECK_EQ(s.next_word, next_word_value); + ASSERT_EQ(1, base::subtle::NoBarrier_AtomicIncrement(&s.count, 1)); + ASSERT_EQ(1, s.count); + ASSERT_EQ(prev_word_value, s.prev_word); + ASSERT_EQ(next_word_value, s.next_word); + + ASSERT_EQ(3, base::subtle::NoBarrier_AtomicIncrement(&s.count, 2)); + ASSERT_EQ(3, s.count); + ASSERT_EQ(prev_word_value, s.prev_word); + ASSERT_EQ(next_word_value, s.next_word); + + ASSERT_EQ(6, base::subtle::NoBarrier_AtomicIncrement(&s.count, 3)); + ASSERT_EQ(6, s.count); + ASSERT_EQ(prev_word_value, s.prev_word); + ASSERT_EQ(next_word_value, s.next_word); + + ASSERT_EQ(3, base::subtle::NoBarrier_AtomicIncrement(&s.count, -3)); + ASSERT_EQ(3, s.count); + ASSERT_EQ(prev_word_value, s.prev_word); + ASSERT_EQ(next_word_value, s.next_word); + + ASSERT_EQ(1, base::subtle::NoBarrier_AtomicIncrement(&s.count, -2)); + ASSERT_EQ(1, s.count); + ASSERT_EQ(prev_word_value, s.prev_word); + ASSERT_EQ(next_word_value, s.next_word); + + ASSERT_EQ(0, base::subtle::NoBarrier_AtomicIncrement(&s.count, -1)); + ASSERT_EQ(0, s.count); + ASSERT_EQ(prev_word_value, s.prev_word); + ASSERT_EQ(next_word_value, s.next_word); + + ASSERT_EQ(-1, base::subtle::NoBarrier_AtomicIncrement(&s.count, -1)); + ASSERT_EQ(-1, s.count); + ASSERT_EQ(prev_word_value, s.prev_word); + ASSERT_EQ(next_word_value, s.next_word); + + ASSERT_EQ(-5, base::subtle::NoBarrier_AtomicIncrement(&s.count, -4)); + ASSERT_EQ(-5, s.count); + ASSERT_EQ(prev_word_value, s.prev_word); + ASSERT_EQ(next_word_value, s.next_word); + + ASSERT_EQ(0, base::subtle::NoBarrier_AtomicIncrement(&s.count, 5)); + ASSERT_EQ(0, s.count); + ASSERT_EQ(prev_word_value, s.prev_word); + ASSERT_EQ(next_word_value, s.next_word); } @@ -114,8 +114,8 @@ template <class AtomicType> static void TestCompareAndSwap() { AtomicType value = 0; AtomicType prev = base::subtle::NoBarrier_CompareAndSwap(&value, 0, 1); - CHECK_EQ(1, value); - CHECK_EQ(0, prev); + ASSERT_EQ(1, value); + ASSERT_EQ(0, prev); // Use test value that has non-zero bits in both halves, more for testing // 64-bit implementation on 32-bit platforms. @@ -123,13 +123,13 @@ static void TestCompareAndSwap() { (NUM_BITS(AtomicType) - 2)) + 11; value = k_test_val; prev = base::subtle::NoBarrier_CompareAndSwap(&value, 0, 5); - CHECK_EQ(k_test_val, value); - CHECK_EQ(k_test_val, prev); + ASSERT_EQ(k_test_val, value); + ASSERT_EQ(k_test_val, prev); value = k_test_val; prev = base::subtle::NoBarrier_CompareAndSwap(&value, k_test_val, 5); - CHECK_EQ(5, value); - CHECK_EQ(k_test_val, prev); + ASSERT_EQ(5, value); + ASSERT_EQ(k_test_val, prev); } @@ -137,8 +137,8 @@ template <class AtomicType> static void TestAtomicExchange() { AtomicType value = 0; AtomicType new_value = base::subtle::NoBarrier_AtomicExchange(&value, 1); - CHECK_EQ(1, value); - CHECK_EQ(0, new_value); + ASSERT_EQ(1, value); + ASSERT_EQ(0, new_value); // Use test value that has non-zero bits in both halves, more for testing // 64-bit implementation on 32-bit platforms. @@ -146,13 +146,13 @@ static void TestAtomicExchange() { (NUM_BITS(AtomicType) - 2)) + 11; value = k_test_val; new_value = base::subtle::NoBarrier_AtomicExchange(&value, k_test_val); - CHECK_EQ(k_test_val, value); - CHECK_EQ(k_test_val, new_value); + ASSERT_EQ(k_test_val, value); + ASSERT_EQ(k_test_val, new_value); value = k_test_val; new_value = base::subtle::NoBarrier_AtomicExchange(&value, 5); - CHECK_EQ(5, value); - CHECK_EQ(k_test_val, new_value); + ASSERT_EQ(5, value); + ASSERT_EQ(k_test_val, new_value); } @@ -163,11 +163,11 @@ static void TestAtomicIncrementBounds() { AtomicType test_val = GG_ULONGLONG(1) << (NUM_BITS(AtomicType) / 2); AtomicType value = test_val - 1; AtomicType new_value = base::subtle::NoBarrier_AtomicIncrement(&value, 1); - CHECK_EQ(test_val, value); - CHECK_EQ(value, new_value); + ASSERT_EQ(test_val, value); + ASSERT_EQ(value, new_value); base::subtle::NoBarrier_AtomicIncrement(&value, -1); - CHECK_EQ(test_val - 1, value); + ASSERT_EQ(test_val - 1, value); } // This is a simple sanity check that values are correct. Not testing @@ -180,19 +180,19 @@ static void TestStore() { AtomicType value; base::subtle::NoBarrier_Store(&value, kVal1); - CHECK_EQ(kVal1, value); + ASSERT_EQ(kVal1, value); base::subtle::NoBarrier_Store(&value, kVal2); - CHECK_EQ(kVal2, value); + ASSERT_EQ(kVal2, value); base::subtle::Acquire_Store(&value, kVal1); - CHECK_EQ(kVal1, value); + ASSERT_EQ(kVal1, value); base::subtle::Acquire_Store(&value, kVal2); - CHECK_EQ(kVal2, value); + ASSERT_EQ(kVal2, value); base::subtle::Release_Store(&value, kVal1); - CHECK_EQ(kVal1, value); + ASSERT_EQ(kVal1, value); base::subtle::Release_Store(&value, kVal2); - CHECK_EQ(kVal2, value); + ASSERT_EQ(kVal2, value); } // This is a simple sanity check that values are correct. Not testing @@ -205,19 +205,19 @@ static void TestLoad() { AtomicType value; value = kVal1; - CHECK_EQ(kVal1, base::subtle::NoBarrier_Load(&value)); + ASSERT_EQ(kVal1, base::subtle::NoBarrier_Load(&value)); value = kVal2; - CHECK_EQ(kVal2, base::subtle::NoBarrier_Load(&value)); + ASSERT_EQ(kVal2, base::subtle::NoBarrier_Load(&value)); value = kVal1; - CHECK_EQ(kVal1, base::subtle::Acquire_Load(&value)); + ASSERT_EQ(kVal1, base::subtle::Acquire_Load(&value)); value = kVal2; - CHECK_EQ(kVal2, base::subtle::Acquire_Load(&value)); + ASSERT_EQ(kVal2, base::subtle::Acquire_Load(&value)); value = kVal1; - CHECK_EQ(kVal1, base::subtle::Release_Load(&value)); + ASSERT_EQ(kVal1, base::subtle::Release_Load(&value)); value = kVal2; - CHECK_EQ(kVal2, base::subtle::Release_Load(&value)); + ASSERT_EQ(kVal2, base::subtle::Release_Load(&value)); } template <class AtomicType> diff --git a/src/tests/debugallocation_test.cc b/src/tests/debugallocation_test.cc index 4274b7e..ca00e36 100644 --- a/src/tests/debugallocation_test.cc +++ b/src/tests/debugallocation_test.cc @@ -102,6 +102,28 @@ TEST(DebugAllocationTest, DeallocMismatch) { } } +TEST(DebugAllocationTest, DoubleFree) { + int* pint = new int; + delete pint; + IF_DEBUG_EXPECT_DEATH(delete pint, "has been already deallocated"); +} + +TEST(DebugAllocationTest, StompBefore) { + int* pint = new int; +#ifndef NDEBUG // don't stomp memory if we're not in a position to detect it + pint[-1] = 5; + IF_DEBUG_EXPECT_DEATH(delete pint, "a word before object"); +#endif +} + +TEST(DebugAllocationTest, StompAfter) { + int* pint = new int; +#ifndef NDEBUG // don't stomp memory if we're not in a position to detect it + pint[1] = 5; + IF_DEBUG_EXPECT_DEATH(delete pint, "a word after object"); +#endif +} + TEST(DebugAllocationTest, FreeQueueTest) { // Verify that the allocator doesn't return blocks that were recently freed. int* x = new int; @@ -205,6 +227,31 @@ TEST(DebugAllocationTest, GetAllocatedSizeTest) { free(a); } +TEST(DebugAllocationTest, HugeAlloc) { + const size_t kTooBig = ~static_cast<size_t>(0); + void* a = NULL; + char* b = NULL; + +#ifndef NDEBUG + + a = malloc(kTooBig); + EXPECT_EQ(NULL, a); + b = NULL; + IF_DEBUG_EXPECT_DEATH(b = new char[kTooBig], + "Unable to allocate.*new\\[\\] failed\\."); + EXPECT_EQ(NULL, b); + + // kAlsoTooBig is small enough not to get caught by debugallocation's check, + // but will still fall through to tcmalloc's check. + const size_t kAlsoTooBig = kTooBig - 1024; + + a = malloc(kAlsoTooBig); + EXPECT_EQ(NULL, a); + IF_DEBUG_EXPECT_DEATH(b = new char[kAlsoTooBig], "Unable to allocate.*new failed"); + EXPECT_EQ(NULL, b); +#endif +} + int main(int argc, char** argv) { // If you run without args, we run the non-death parts of the test. // Otherwise, argv[1] should be a number saying which death-test diff --git a/src/tests/malloc_extension_test.cc b/src/tests/malloc_extension_test.cc index 1f00f73..ef76766 100644 --- a/src/tests/malloc_extension_test.cc +++ b/src/tests/malloc_extension_test.cc @@ -43,32 +43,32 @@ int main(int argc, char** argv) { void* a = malloc(1000); size_t cxx_bytes_used, c_bytes_used; - CHECK(MallocExtension::instance()->GetNumericProperty( - "generic.current_allocated_bytes", &cxx_bytes_used)); - CHECK(MallocExtension_GetNumericProperty( - "generic.current_allocated_bytes", &c_bytes_used)); - CHECK_GT(cxx_bytes_used, 1000); - CHECK_EQ(cxx_bytes_used, c_bytes_used); + ASSERT_TRUE(MallocExtension::instance()->GetNumericProperty( + "generic.current_allocated_bytes", &cxx_bytes_used)); + ASSERT_TRUE(MallocExtension_GetNumericProperty( + "generic.current_allocated_bytes", &c_bytes_used)); + ASSERT_GT(cxx_bytes_used, 1000); + ASSERT_EQ(cxx_bytes_used, c_bytes_used); - CHECK(MallocExtension::instance()->VerifyAllMemory()); - CHECK(MallocExtension_VerifyAllMemory()); + ASSERT_TRUE(MallocExtension::instance()->VerifyAllMemory()); + ASSERT_TRUE(MallocExtension_VerifyAllMemory()); - CHECK_GE(MallocExtension::instance()->GetAllocatedSize(a), 1000); + ASSERT_GE(MallocExtension::instance()->GetAllocatedSize(a), 1000); // This is just a sanity check. If we allocated too much, tcmalloc is broken - CHECK_LE(MallocExtension::instance()->GetAllocatedSize(a), 5000); - CHECK_GE(MallocExtension::instance()->GetEstimatedAllocatedSize(1000), 1000); + ASSERT_LE(MallocExtension::instance()->GetAllocatedSize(a), 5000); + ASSERT_GE(MallocExtension::instance()->GetEstimatedAllocatedSize(1000), 1000); for (int i = 0; i < 10; ++i) { void *p = malloc(i); - CHECK_GE(MallocExtension::instance()->GetAllocatedSize(p), + ASSERT_GE(MallocExtension::instance()->GetAllocatedSize(p), MallocExtension::instance()->GetEstimatedAllocatedSize(i)); free(p); } // Check the c-shim version too. - CHECK_GE(MallocExtension_GetAllocatedSize(a), 1000); - CHECK_LE(MallocExtension_GetAllocatedSize(a), 5000); - CHECK_GE(MallocExtension_GetEstimatedAllocatedSize(1000), 1000); + ASSERT_GE(MallocExtension_GetAllocatedSize(a), 1000); + ASSERT_LE(MallocExtension_GetAllocatedSize(a), 5000); + ASSERT_GE(MallocExtension_GetEstimatedAllocatedSize(1000), 1000); free(a); diff --git a/src/tests/profile-handler_unittest.cc b/src/tests/profile-handler_unittest.cc index 1e72b2e..84e035c 100644 --- a/src/tests/profile-handler_unittest.cc +++ b/src/tests/profile-handler_unittest.cc @@ -47,19 +47,41 @@ class Thread { bool joinable_; }; -// timespec of the sleep interval. To ensure a SIGPROF timer interrupt under -// heavy load, this is set to a 20x of ProfileHandler timer interval (i.e 100Hz) -// TODO(nabeelmian) Under very heavy loads, the worker thread may not accumulate -// enough cpu usage to get a profile tick. -const struct timespec sleep_interval = { 0, 200000000 }; // 200 ms +// Sleep interval in nano secs. ITIMER_PROF goes off only afer the specified CPU +// time is consumed. Under heavy load this process may no get scheduled in a +// timely fashion. Therefore, give enough time (20x of ProfileHandle timer +// interval 10ms (100Hz)) for this process to accumulate enought CPU time to get +// a profile tick. +int kSleepInterval = 200000000; + +// Sleep interval in nano secs. To ensure that if the timer has expired it is +// reset. +int kTimerResetInterval = 5000000; // Whether each thread has separate timers. static bool timer_separate_ = false; +static int timer_type_ = ITIMER_PROF; +static int signal_number_ = SIGPROF; + +// Delays processing by the specified number of nano seconds. 'delay_ns' +// must be less than the number of nano seconds in a second (1000000000). +void Delay(int delay_ns) { + static const int kNumNSecInSecond = 1000000000; + EXPECT_LT(delay_ns, kNumNSecInSecond); + struct timespec delay = { 0, delay_ns }; + nanosleep(&delay, 0); +} // Checks whether the profile timer is enabled for the current thread. bool IsTimerEnabled() { itimerval current_timer; - EXPECT_EQ(0, getitimer(ITIMER_PROF, ¤t_timer)); + EXPECT_EQ(0, getitimer(timer_type_, ¤t_timer)); + if ((current_timer.it_value.tv_sec == 0) && + (current_timer.it_value.tv_usec != 0)) { + // May be the timer has expired. Sleep for a bit and check again. + Delay(kTimerResetInterval); + EXPECT_EQ(0, getitimer(timer_type_, ¤t_timer)); + } return (current_timer.it_value.tv_sec != 0 || current_timer.it_value.tv_usec != 0); } @@ -161,11 +183,15 @@ class ProfileHandlerTest { // Determines whether threads have separate timers. static void SetUpTestCase() { + timer_type_ = (getenv("CPUPROFILE_REALTIME") ? ITIMER_REAL : ITIMER_PROF); + signal_number_ = (getenv("CPUPROFILE_REALTIME") ? SIGALRM : SIGPROF); + timer_separate_ = threads_have_separate_timers(); + Delay(kTimerResetInterval); } - // Sets up the profile timers and SIGPROF handler in a known state. It does - // the following: + // Sets up the profile timers and SIGPROF/SIGALRM handler in a known state. + // It does the following: // 1. Unregisters all the callbacks, stops the timer (if shared) and // clears out timer_sharing state in the ProfileHandler. This clears // out any state left behind by the previous test or during module @@ -177,7 +203,7 @@ class ProfileHandlerTest { // Reset the state of ProfileHandler between each test. This unregisters // all callbacks, stops timer (if shared) and clears timer sharing state. ProfileHandlerReset(); - EXPECT_EQ(GetCallbackCount(), 0); + EXPECT_EQ(0, GetCallbackCount()); VerifyDisabled(); // ProfileHandler requires at least two threads to be registerd to determine // whether timers are shared. @@ -214,7 +240,7 @@ class ProfileHandlerTest { busy_worker_->Start(); // Wait for worker to start up and register with the ProfileHandler. // TODO(nabeelmian) This may not work under very heavy load. - nanosleep(&sleep_interval, NULL); + Delay(kSleepInterval); } // Stops the worker thread. @@ -224,10 +250,10 @@ class ProfileHandlerTest { delete busy_worker_; } - // Checks whether SIGPROF signal handler is enabled. + // Checks whether SIGPROF/SIGALRM signal handler is enabled. bool IsSignalEnabled() { struct sigaction sa; - CHECK_EQ(sigaction(SIGPROF, NULL, &sa), 0); + CHECK_EQ(sigaction(signal_number_, NULL, &sa), 0); return ((sa.sa_handler == SIG_IGN) || (sa.sa_handler == SIG_DFL)) ? false : true; } @@ -258,7 +284,7 @@ class ProfileHandlerTest { uint64 interrupts_before = GetInterruptCount(); // Sleep for a bit and check that tick counter is making progress. int old_tick_count = tick_counter; - nanosleep(&sleep_interval, NULL); + Delay(kSleepInterval); int new_tick_count = tick_counter; EXPECT_GT(new_tick_count, old_tick_count); uint64 interrupts_after = GetInterruptCount(); @@ -269,9 +295,9 @@ class ProfileHandlerTest { void VerifyUnregistration(const int& tick_counter) { // Sleep for a bit and check that tick counter is not making progress. int old_tick_count = tick_counter; - nanosleep(&sleep_interval, NULL); + Delay(kSleepInterval); int new_tick_count = tick_counter; - EXPECT_EQ(new_tick_count, old_tick_count); + EXPECT_EQ(old_tick_count, new_tick_count); // If no callbacks, signal handler and shared timer should be disabled. if (GetCallbackCount() == 0) { EXPECT_FALSE(IsSignalEnabled()); @@ -283,13 +309,13 @@ class ProfileHandlerTest { } } - // Verifies that the SIGPROF interrupt handler is disabled and the timer, - // if shared, is disabled. Expects the worker to be running. + // Verifies that the SIGPROF/SIGALRM interrupt handler is disabled and the + // timer, if shared, is disabled. Expects the worker to be running. void VerifyDisabled() { // Check that the signal handler is disabled. EXPECT_FALSE(IsSignalEnabled()); // Check that the callback count is 0. - EXPECT_EQ(GetCallbackCount(), 0); + EXPECT_EQ(0, GetCallbackCount()); // Check that the timer is disabled if shared, enabled otherwise. if (timer_separate_) { EXPECT_TRUE(IsTimerEnabled()); @@ -298,9 +324,25 @@ class ProfileHandlerTest { } // Verify that the ProfileHandler is not accumulating profile ticks. uint64 interrupts_before = GetInterruptCount(); - nanosleep(&sleep_interval, NULL); + Delay(kSleepInterval); uint64 interrupts_after = GetInterruptCount(); - EXPECT_EQ(interrupts_after, interrupts_before); + EXPECT_EQ(interrupts_before, interrupts_after); + } + + // Registers a callback and waits for kTimerResetInterval for timers to get + // reset. + ProfileHandlerToken* RegisterCallback(void* callback_arg) { + ProfileHandlerToken* token = ProfileHandlerRegisterCallback( + TickCounter, callback_arg); + Delay(kTimerResetInterval); + return token; + } + + // Unregisters a callback and waits for kTimerResetInterval for timers to get + // reset. + void UnregisterCallback(ProfileHandlerToken* token) { + ProfileHandlerUnregisterCallback(token); + Delay(kTimerResetInterval); } // Busy worker thread to accumulate cpu usage. @@ -337,10 +379,9 @@ class ProfileHandlerTest { // ProfileHandlerUnregisterCallback. TEST_F(ProfileHandlerTest, RegisterUnregisterCallback) { int tick_count = 0; - ProfileHandlerToken* token = ProfileHandlerRegisterCallback( - TickCounter, &tick_count); + ProfileHandlerToken* token = RegisterCallback(&tick_count); VerifyRegistration(tick_count); - ProfileHandlerUnregisterCallback(token); + UnregisterCallback(token); VerifyUnregistration(tick_count); } @@ -348,31 +389,29 @@ TEST_F(ProfileHandlerTest, RegisterUnregisterCallback) { TEST_F(ProfileHandlerTest, MultipleCallbacks) { // Register first callback. int first_tick_count; - ProfileHandlerToken* token1 = ProfileHandlerRegisterCallback( - TickCounter, &first_tick_count); + ProfileHandlerToken* token1 = RegisterCallback(&first_tick_count); // Check that callback was registered correctly. VerifyRegistration(first_tick_count); - EXPECT_EQ(GetCallbackCount(), 1); + EXPECT_EQ(1, GetCallbackCount()); // Register second callback. int second_tick_count; - ProfileHandlerToken* token2 = ProfileHandlerRegisterCallback( - TickCounter, &second_tick_count); + ProfileHandlerToken* token2 = RegisterCallback(&second_tick_count); // Check that callback was registered correctly. VerifyRegistration(second_tick_count); - EXPECT_EQ(GetCallbackCount(), 2); + EXPECT_EQ(2, GetCallbackCount()); // Unregister first callback. - ProfileHandlerUnregisterCallback(token1); + UnregisterCallback(token1); VerifyUnregistration(first_tick_count); - EXPECT_EQ(GetCallbackCount(), 1); + EXPECT_EQ(1, GetCallbackCount()); // Verify that second callback is still registered. VerifyRegistration(second_tick_count); // Unregister second callback. - ProfileHandlerUnregisterCallback(token2); + UnregisterCallback(token2); VerifyUnregistration(second_tick_count); - EXPECT_EQ(GetCallbackCount(), 0); + EXPECT_EQ(0, GetCallbackCount()); // Verify that the signal handler and timers are correctly disabled. VerifyDisabled(); @@ -383,15 +422,15 @@ TEST_F(ProfileHandlerTest, Reset) { // Verify that the profile timer interrupt is disabled. VerifyDisabled(); int first_tick_count; - ProfileHandlerRegisterCallback(TickCounter, &first_tick_count); + RegisterCallback(&first_tick_count); VerifyRegistration(first_tick_count); - EXPECT_EQ(GetCallbackCount(), 1); + EXPECT_EQ(1, GetCallbackCount()); // Register second callback. int second_tick_count; - ProfileHandlerRegisterCallback(TickCounter, &second_tick_count); + RegisterCallback(&second_tick_count); VerifyRegistration(second_tick_count); - EXPECT_EQ(GetCallbackCount(), 2); + EXPECT_EQ(2, GetCallbackCount()); // Reset the profile handler and verify that callback were correctly // unregistered and timer/signal are disabled. @@ -410,7 +449,7 @@ TEST_F(ProfileHandlerTest, RegisterCallbackBeforeThread) { // the signal handler and reset the timer sharing state in the Profile // Handler. ProfileHandlerReset(); - EXPECT_EQ(GetCallbackCount(), 0); + EXPECT_EQ(0, GetCallbackCount()); VerifyDisabled(); // Start the worker. At this time ProfileHandler doesn't know if timers are @@ -418,14 +457,14 @@ TEST_F(ProfileHandlerTest, RegisterCallbackBeforeThread) { StartWorker(); // Register a callback and check that profile ticks are being delivered. int tick_count; - ProfileHandlerRegisterCallback(TickCounter, &tick_count); - EXPECT_EQ(GetCallbackCount(), 1); + RegisterCallback(&tick_count); + EXPECT_EQ(1, GetCallbackCount()); VerifyRegistration(tick_count); // Register a second thread and verify that timer and signal handler are // correctly enabled. RegisterThread(); - EXPECT_EQ(GetCallbackCount(), 1); + EXPECT_EQ(1, GetCallbackCount()); EXPECT_TRUE(IsTimerEnabled()); EXPECT_TRUE(IsSignalEnabled()); } diff --git a/src/tests/profiler_unittest.sh b/src/tests/profiler_unittest.sh index 73be680..5766f2e 100755 --- a/src/tests/profiler_unittest.sh +++ b/src/tests/profiler_unittest.sh @@ -241,10 +241,16 @@ VerifyIdentical p12 "$PROFILER1_REALNAME" p13 "" || RegisterFailure >"$TMPDIR/p15" 2>/dev/null || RegisterFailure VerifyIdentical p14 "$PROFILER3_REALNAME" p15 "" || RegisterFailure +# Test using ITIMER_REAL instead of ITIMER_PROF. +env CPUPROFILE_REALTIME=1 "$PROFILER3" 5 2 "$TMPDIR/p16" || RegisterFailure +env CPUPROFILE_REALTIME=1 "$PROFILER3" 10 2 "$TMPDIR/p17" || RegisterFailure +VerifySimilar p16 "$PROFILER3_REALNAME" p17 "$PROFILER3_REALNAME" 2 + + # Make sure that when we have a process with a fork, the profiles don't # clobber each other -CPUPROFILE="$TMPDIR/p6" "$PROFILER1" 1 -2 || RegisterFailure -n=`ls $TMPDIR/p6* | wc -l` +CPUPROFILE="$TMPDIR/pfork" "$PROFILER1" 1 -2 || RegisterFailure +n=`ls $TMPDIR/pfork* | wc -l` if [ $n != 3 ]; then echo "FORK test FAILED: expected 3 profiles (for main + 2 children), found $n" num_failures=`expr $num_failures + 1` diff --git a/src/tests/tcmalloc_unittest.cc b/src/tests/tcmalloc_unittest.cc index 8eccb18..cc47ad4 100644 --- a/src/tests/tcmalloc_unittest.cc +++ b/src/tests/tcmalloc_unittest.cc @@ -802,12 +802,14 @@ static void TestRanges() { CheckRangeCallback(b, base::MallocRange::FREE, MB); } +#ifndef DEBUGALLOCATION static size_t GetUnmappedBytes() { size_t bytes; CHECK(MallocExtension::instance()->GetNumericProperty( "tcmalloc.pageheap_unmapped_bytes", &bytes)); return bytes; } +#endif static void TestReleaseToSystem() { // Debug allocation mode adds overhead to each allocation which @@ -833,10 +835,6 @@ static void TestReleaseToSystem() { free(a); - // Negative numbers should be ignored. - MallocExtension::instance()->ReleaseToSystem(-5); - EXPECT_EQ(starting_bytes, GetUnmappedBytes()); - // The span to release should be 1MB. MallocExtension::instance()->ReleaseToSystem(MB/2); EXPECT_EQ(starting_bytes + MB, GetUnmappedBytes()); @@ -871,6 +869,43 @@ static void TestReleaseToSystem() { #endif // #ifndef DEBUGALLOCATION } +bool g_no_memory = false; +std::new_handler g_old_handler = NULL; +static void OnNoMemory() { + g_no_memory = true; + std::set_new_handler(g_old_handler); +} + +static void TestSetNewMode() { + int old_mode = tc_set_new_mode(1); + + // DebugAllocation will try to catch huge allocations. We need to avoid this + // by requesting a smaller malloc block, that still can't be satisfied. + const size_t kHugeRequest = kTooBig - 1024; + + g_old_handler = std::set_new_handler(&OnNoMemory); + g_no_memory = false; + void* ret = malloc(kHugeRequest); + EXPECT_EQ(NULL, ret); + EXPECT_TRUE(g_no_memory); + + g_old_handler = std::set_new_handler(&OnNoMemory); + g_no_memory = false; + ret = calloc(1, kHugeRequest); + EXPECT_EQ(NULL, ret); + EXPECT_TRUE(g_no_memory); + + g_old_handler = std::set_new_handler(&OnNoMemory); + g_no_memory = false; + ret = realloc(NULL, kHugeRequest); + EXPECT_EQ(NULL, ret); + EXPECT_TRUE(g_no_memory); + + g_no_memory = false; + + tc_set_new_mode(old_mode); +} + static int RunAllTests(int argc, char** argv) { // Optional argv[1] is the seed AllocatorState rnd(argc > 1 ? atoi(argv[1]) : 100); @@ -1149,6 +1184,7 @@ static int RunAllTests(int argc, char** argv) { TestHugeThreadCache(); TestRanges(); TestReleaseToSystem(); + TestSetNewMode(); return 0; } diff --git a/src/windows/config.h b/src/windows/config.h index b3a6852..99de82c 100644 --- a/src/windows/config.h +++ b/src/windows/config.h @@ -175,6 +175,9 @@ /* define if your compiler has __attribute__ */ #undef HAVE___ATTRIBUTE__ +/* Define to 1 if compiler supports __environ */ +#undef HAVE___ENVIRON + /* Define to 1 if the system has the type `__int64'. */ #define HAVE___INT64 1 diff --git a/src/windows/google/tcmalloc.h b/src/windows/google/tcmalloc.h index 15a2e19..4b97b15 100644 --- a/src/windows/google/tcmalloc.h +++ b/src/windows/google/tcmalloc.h @@ -90,6 +90,7 @@ extern "C" { #endif #ifdef __cplusplus + PERFTOOLS_DLL_DECL int tc_set_new_mode(int flag) __THROW; PERFTOOLS_DLL_DECL void* tc_new(size_t size); PERFTOOLS_DLL_DECL void tc_delete(void* p) __THROW; PERFTOOLS_DLL_DECL void* tc_newarray(size_t size); |