diff options
author | dmikurube@chromium.org <dmikurube@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-26 05:17:25 +0000 |
---|---|---|
committer | dmikurube@chromium.org <dmikurube@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-26 05:17:25 +0000 |
commit | 7ce58b29d323d6da838330661a80c67b7871734b (patch) | |
tree | 94b8367342219267b3fc3c91c82b0cbde532b784 /base/allocator | |
parent | 75693935c15c7d02e3dc3ebe92d8d6a32f44e711 (diff) | |
download | chromium_src-7ce58b29d323d6da838330661a80c67b7871734b.zip chromium_src-7ce58b29d323d6da838330661a80c67b7871734b.tar.gz chromium_src-7ce58b29d323d6da838330661a80c67b7871734b.tar.bz2 |
Type profiler by intercepting 'new' and 'delete' expressions.
It stores mapping between object's starting addresses and their
allocated types when a build option 'clang_type_profiler=1' is
specified. It enables information like
"an object at 0x37f3c88 is an instance of std::string."
Nothing is changed when the option is not specified.
It depends on a modified version of the LLVM/Clang compiler
introduced at deps/third_party/llvm-allocated-type.
BUG=123758
TEST=build with clang_type_profiler=1 and run type_profiler_unittests and type_profiler_map_unittests manually.
Review URL: https://chromiumcodereview.appspot.com/10411047
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@158752 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/allocator')
-rw-r--r-- | base/allocator/allocator.gyp | 111 | ||||
-rw-r--r-- | base/allocator/type_profiler.cc | 63 | ||||
-rw-r--r-- | base/allocator/type_profiler.h | 40 | ||||
-rw-r--r-- | base/allocator/type_profiler_control.cc | 38 | ||||
-rw-r--r-- | base/allocator/type_profiler_control.h | 31 | ||||
-rw-r--r-- | base/allocator/type_profiler_map_unittests.cc | 99 | ||||
-rw-r--r-- | base/allocator/type_profiler_tcmalloc.cc | 37 | ||||
-rw-r--r-- | base/allocator/type_profiler_tcmalloc.h | 29 | ||||
-rw-r--r-- | base/allocator/type_profiler_unittests.cc | 189 |
9 files changed, 637 insertions, 0 deletions
diff --git a/base/allocator/allocator.gyp b/base/allocator/allocator.gyp index db95863..67cf692 100644 --- a/base/allocator/allocator.gyp +++ b/base/allocator/allocator.gyp @@ -303,6 +303,19 @@ }, }, 'conditions': [ + ['OS=="linux" and clang_type_profiler==1', { + 'dependencies': [ + 'type_profiler_tcmalloc', + ], + # It is undoing dependencies and cflags_cc for type_profiler which + # build/common.gypi injects into all targets. + 'dependencies!': [ + 'type_profiler', + ], + 'cflags_cc!': [ + '-fintercept-allocation-functions', + ], + }], ['OS=="win"', { 'defines': [ 'PERFTOOLS_DLL_DECL=', @@ -467,6 +480,18 @@ 'include_dirs': [ '../../' ], + 'conditions': [ + ['OS=="linux" and clang_type_profiler==1', { + # It is undoing dependencies and cflags_cc for type_profiler which + # build/common.gypi injects into all targets. + 'dependencies!': [ + 'type_profiler', + ], + 'cflags_cc!': [ + '-fintercept-allocation-functions', + ], + }], + ], }, ], 'conditions': [ @@ -549,5 +574,91 @@ }, ], }], + ['OS=="linux" and clang_type_profiler==1', { + # Some targets in this section undo dependencies and cflags_cc for + # type_profiler which build/common.gypi injects into all targets. + 'targets': [ + { + 'target_name': 'type_profiler', + 'type': 'static_library', + 'dependencies!': [ + 'type_profiler', + ], + 'cflags_cc!': [ + '-fintercept-allocation-functions', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'type_profiler.cc', + 'type_profiler.h', + 'type_profiler_control.h', + ], + }, + { + 'target_name': 'type_profiler_tcmalloc', + 'type': 'static_library', + 'dependencies!': [ + 'type_profiler', + ], + 'cflags_cc!': [ + '-fintercept-allocation-functions', + ], + 'include_dirs': [ + '<(tcmalloc_dir)/src', + '../..', + ], + 'sources': [ + 'type_profiler_tcmalloc.cc', + 'type_profiler_tcmalloc.h', + '<(tcmalloc_dir)/src/gperftools/type_profiler_map.h', + '<(tcmalloc_dir)/src/type_profiler_map.cc', + ], + }, + { + 'target_name': 'type_profiler_unittests', + 'type': 'executable', + 'dependencies': [ + '../../testing/gtest.gyp:gtest', + '../base.gyp:base', + 'allocator', + 'type_profiler_tcmalloc', + ], + 'include_dirs': [ + '../..', + ], + 'sources': [ + 'type_profiler_control.cc', + 'type_profiler_control.h', + 'type_profiler_unittests.cc', + ], + }, + { + 'target_name': 'type_profiler_map_unittests', + 'type': 'executable', + 'dependencies': [ + '../../testing/gtest.gyp:gtest', + '../base.gyp:base', + 'allocator', + ], + 'dependencies!': [ + 'type_profiler', + ], + 'cflags_cc!': [ + '-fintercept-allocation-functions', + ], + 'include_dirs': [ + '<(tcmalloc_dir)/src', + '../..', + ], + 'sources': [ + 'type_profiler_map_unittests.cc', + '<(tcmalloc_dir)/src/gperftools/type_profiler_map.h', + '<(tcmalloc_dir)/src/type_profiler_map.cc', + ], + }, + ], + }], ], } diff --git a/base/allocator/type_profiler.cc b/base/allocator/type_profiler.cc new file mode 100644 index 0000000..635fbcf --- /dev/null +++ b/base/allocator/type_profiler.cc @@ -0,0 +1,63 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if defined(TYPE_PROFILING) + +#include "base/allocator/type_profiler.h" + +#include <assert.h> + +namespace { + +void* NopIntercept(void* ptr, size_t size, const std::type_info& type) { + return ptr; +} + +base::type_profiler::InterceptFunction* g_new_intercept = NopIntercept; +base::type_profiler::InterceptFunction* g_delete_intercept = NopIntercept; + +} + +void* __op_new_intercept__(void* ptr, + size_t size, + const std::type_info& type) { + return g_new_intercept(ptr, size, type); +} + +void* __op_delete_intercept__(void* ptr, + size_t size, + const std::type_info& type) { + return g_delete_intercept(ptr, size, type); +} + +namespace base { +namespace type_profiler { + +// static +void InterceptFunctions::SetFunctions(InterceptFunction* new_intercept, + InterceptFunction* delete_intercept) { + // Don't use DCHECK, as this file is injected into targets + // that do not and should not depend on base/base.gyp:base + assert(g_new_intercept == NopIntercept); + assert(g_delete_intercept == NopIntercept); + + g_new_intercept = new_intercept; + g_delete_intercept = delete_intercept; +} + +// static +void InterceptFunctions::ResetFunctions() { + g_new_intercept = NopIntercept; + g_delete_intercept = NopIntercept; +} + +// static +bool InterceptFunctions::IsAvailable() { + return g_new_intercept != NopIntercept || g_delete_intercept != NopIntercept; +} + +} // namespace type_profiler +} // namespace base + +#endif // defined(TYPE_PROFILING) diff --git a/base/allocator/type_profiler.h b/base/allocator/type_profiler.h new file mode 100644 index 0000000..86b5711 --- /dev/null +++ b/base/allocator/type_profiler.h @@ -0,0 +1,40 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ALLOCATOR_TYPE_PROFILER_H_ +#define BASE_ALLOCATOR_TYPE_PROFILER_H_ + +#if defined(TYPE_PROFILING) + +#include <stddef.h> // for size_t +#include <typeinfo> // for std::typeinfo + +namespace base { +namespace type_profiler { + +typedef void* InterceptFunction(void*, size_t, const std::type_info&); + +class InterceptFunctions { + public: + // It must be called only once in a process while it is in single-thread. + // For now, ContentMainRunnerImpl::Initialize is the only supposed caller + // of this function except for single-threaded unit tests. + static void SetFunctions(InterceptFunction* new_intercept, + InterceptFunction* delete_intercept); + + private: + friend class TypeProfilerTest; + + // These functions are not thread safe. + // They must be used only from single-threaded unit tests. + static void ResetFunctions(); + static bool IsAvailable(); +}; + +} // namespace type_profiler +} // namespace base + +#endif // defined(TYPE_PROFILING) + +#endif // BASE_ALLOCATOR_TYPE_PROFILER_H_ diff --git a/base/allocator/type_profiler_control.cc b/base/allocator/type_profiler_control.cc new file mode 100644 index 0000000..6be7984 --- /dev/null +++ b/base/allocator/type_profiler_control.cc @@ -0,0 +1,38 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/allocator/type_profiler_control.h" + +namespace base { +namespace type_profiler { + +namespace { + +#if defined(TYPE_PROFILING) +const bool kTypeProfilingEnabled = true; +#else +const bool kTypeProfilingEnabled = false; +#endif + +bool g_enable_intercept = kTypeProfilingEnabled; + +} // namespace + +// static +void Controller::Stop() { + g_enable_intercept = false; +} + +// static +bool Controller::IsProfiling() { + return kTypeProfilingEnabled && g_enable_intercept; +} + +// static +void Controller::Restart() { + g_enable_intercept = kTypeProfilingEnabled; +} + +} // namespace type_profiler +} // namespace base diff --git a/base/allocator/type_profiler_control.h b/base/allocator/type_profiler_control.h new file mode 100644 index 0000000..17cf5b6 --- /dev/null +++ b/base/allocator/type_profiler_control.h @@ -0,0 +1,31 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ALLOCATOR_TYPE_PROFILER_CONTROL_H_ +#define BASE_ALLOCATOR_TYPE_PROFILER_CONTROL_H_ + +#include "base/gtest_prod_util.h" + +namespace base { +namespace type_profiler { + +class Controller { + public: + static void Stop(); + static bool IsProfiling(); + + private: + FRIEND_TEST_ALL_PREFIXES(TypeProfilerTest, + TestProfileNewWithoutProfiledDelete); + + // It must be used only from allowed unit tests. The following is only + // allowed for use in unit tests. Profiling should never be restarted in + // regular use. + static void Restart(); +}; + +} // namespace type_profiler +} // namespace base + +#endif // BASE_ALLOCATOR_TYPE_PROFILER_CONTROL_H_ diff --git a/base/allocator/type_profiler_map_unittests.cc b/base/allocator/type_profiler_map_unittests.cc new file mode 100644 index 0000000..5ac5dd0 --- /dev/null +++ b/base/allocator/type_profiler_map_unittests.cc @@ -0,0 +1,99 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is a unittest set for type_profiler_map in third_party/tcmalloc. It is +// independent from other tests and executed manually like allocator_unittests +// since type_profiler_map is a singleton (like TCMalloc's heap-profiler), and +// it requires RTTI and different compiling/linking options from others. + +#if defined(TYPE_PROFILING) + +#include "base/memory/scoped_ptr.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/tcmalloc/chromium/src/gperftools/type_profiler_map.h" + +namespace base { +namespace type_profiler { + +static const void* const g_const_null = static_cast<const void*>(NULL); + +TEST(TypeProfilerMapTest, NormalOperation) { + // Allocate an object just to get a valid address. + // This 'new' is not profiled by type_profiler. + scoped_ptr<int> dummy(new int(48)); + const std::type_info* type; + + type = LookupType(dummy.get()); + EXPECT_EQ(g_const_null, type); + + InsertType(dummy.get(), 12, typeid(int)); + type = LookupType(dummy.get()); + ASSERT_NE(g_const_null, type); + EXPECT_STREQ(typeid(int).name(), type->name()); + + EraseType(dummy.get()); + type = LookupType(dummy.get()); + EXPECT_EQ(g_const_null, type); +} + +TEST(TypeProfilerMapTest, EraseWithoutInsert) { + scoped_ptr<int> dummy(new int(48)); + const std::type_info* type; + + for (int i = 0; i < 10; ++i) { + EraseType(dummy.get()); + type = LookupType(dummy.get()); + EXPECT_EQ(g_const_null, type); + } +} + +TEST(TypeProfilerMapTest, InsertThenMultipleErase) { + scoped_ptr<int> dummy(new int(48)); + const std::type_info* type; + + InsertType(dummy.get(), 12, typeid(int)); + type = LookupType(dummy.get()); + ASSERT_NE(g_const_null, type); + EXPECT_STREQ(typeid(int).name(), type->name()); + + for (int i = 0; i < 10; ++i) { + EraseType(dummy.get()); + type = LookupType(dummy.get()); + EXPECT_EQ(g_const_null, type); + } +} + +TEST(TypeProfilerMapTest, MultipleInsertWithoutErase) { + scoped_ptr<int> dummy(new int(48)); + const std::type_info* type; + + InsertType(dummy.get(), 12, typeid(int)); + type = LookupType(dummy.get()); + ASSERT_NE(g_const_null, type); + EXPECT_STREQ(typeid(int).name(), type->name()); + + InsertType(dummy.get(), 5, typeid(char)); + type = LookupType(dummy.get()); + ASSERT_NE(g_const_null, type); + EXPECT_STREQ(typeid(char).name(), type->name()); + + InsertType(dummy.get(), 129, typeid(long)); + type = LookupType(dummy.get()); + ASSERT_NE(g_const_null, type); + EXPECT_STREQ(typeid(long).name(), type->name()); + + EraseType(dummy.get()); + type = LookupType(dummy.get()); + EXPECT_EQ(g_const_null, type); +} + +} // namespace type_profiler +} // namespace base + +#endif // defined(TYPE_PROFILING) + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/base/allocator/type_profiler_tcmalloc.cc b/base/allocator/type_profiler_tcmalloc.cc new file mode 100644 index 0000000..e5e10e0 --- /dev/null +++ b/base/allocator/type_profiler_tcmalloc.cc @@ -0,0 +1,37 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if defined(TYPE_PROFILING) + +#include "base/allocator/type_profiler_tcmalloc.h" + +#include "base/allocator/type_profiler_control.h" +#include "third_party/tcmalloc/chromium/src/gperftools/heap-profiler.h" +#include "third_party/tcmalloc/chromium/src/gperftools/type_profiler_map.h" + +namespace base { +namespace type_profiler { + +void* NewInterceptForTCMalloc(void* ptr, + size_t size, + const std::type_info& type) { + if (Controller::IsProfiling()) + InsertType(ptr, size, type); + + return ptr; +} + +void* DeleteInterceptForTCMalloc(void* ptr, + size_t size, + const std::type_info& type) { + if (Controller::IsProfiling()) + EraseType(ptr); + + return ptr; +} + +} // namespace type_profiler +} // namespace base + +#endif // defined(TYPE_PROFILING) diff --git a/base/allocator/type_profiler_tcmalloc.h b/base/allocator/type_profiler_tcmalloc.h new file mode 100644 index 0000000..ac55995 --- /dev/null +++ b/base/allocator/type_profiler_tcmalloc.h @@ -0,0 +1,29 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_ALLOCATOR_TYPE_PROFILER_TCMALLOC_H_ +#define BASE_ALLOCATOR_TYPE_PROFILER_TCMALLOC_H_ + +#if defined(TYPE_PROFILING) + +#include <cstddef> // for size_t +#include <typeinfo> // for std::type_info + +namespace base { +namespace type_profiler { + +void* NewInterceptForTCMalloc(void* ptr, + size_t size, + const std::type_info& type); + +void* DeleteInterceptForTCMalloc(void* ptr, + size_t size, + const std::type_info& type); + +} // namespace type_profiler +} // namespace base + +#endif // defined(TYPE_PROFILING) + +#endif // BASE_ALLOCATOR_TYPE_PROFILER_TCMALLOC_H_ diff --git a/base/allocator/type_profiler_unittests.cc b/base/allocator/type_profiler_unittests.cc new file mode 100644 index 0000000..e8f06ed --- /dev/null +++ b/base/allocator/type_profiler_unittests.cc @@ -0,0 +1,189 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is a unittest set for type_profiler. It is independent from other +// tests and executed manually like allocator_unittests since type_profiler_map +// used in type_profiler is a singleton (like TCMalloc's heap-profiler), and +// it requires RTTI and different compiling/linking options from others +// +// It tests that the profiler doesn't fail in suspicous cases. For example, +// 'new' is not profiled, but 'delete' for the created object is profiled. + +#if defined(TYPE_PROFILING) + +#include "base/allocator/type_profiler.h" +#include "base/allocator/type_profiler_control.h" +#include "base/allocator/type_profiler_tcmalloc.h" +#include "base/basictypes.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/tcmalloc/chromium/src/gperftools/type_profiler_map.h" + +namespace base { +namespace type_profiler { + +class TypeProfilerTest : public testing::Test { + public: + TypeProfilerTest() {} + + void SetInterceptFunctions() { + InterceptFunctions::SetFunctions(NewInterceptForTCMalloc, + DeleteInterceptForTCMalloc); + } + + void ResetInterceptFunctions() { + InterceptFunctions::ResetFunctions(); + } + + void SetUp() { + SetInterceptFunctions(); + } + + void TearDown() { + ResetInterceptFunctions(); + } + + protected: + static const size_t kDummyArraySize; + static const void* const kConstNull; + + private: + DISALLOW_COPY_AND_ASSIGN(TypeProfilerTest); +}; + +const size_t TypeProfilerTest::kDummyArraySize = 10; +const void* const TypeProfilerTest::kConstNull = static_cast<const void*>(NULL); + +TEST_F(TypeProfilerTest, TestNormalProfiling) { + int* dummy = new int(48); + const std::type_info* type; + + type = LookupType(dummy); + ASSERT_NE(kConstNull, type); + EXPECT_STREQ(typeid(int).name(), type->name()); + delete dummy; + + type = LookupType(dummy); + EXPECT_EQ(kConstNull, type); +} + +TEST_F(TypeProfilerTest, TestNormalArrayProfiling) { + int* dummy = new int[kDummyArraySize]; + const std::type_info* type; + + type = LookupType(dummy); + ASSERT_NE(kConstNull, type); + // For an array, the profiler remembers its base type. + EXPECT_STREQ(typeid(int).name(), type->name()); + delete[] dummy; + + type = LookupType(dummy); + EXPECT_EQ(kConstNull, type); +} + +TEST_F(TypeProfilerTest, TestRepeatedNewAndDelete) { + int *dummy[kDummyArraySize]; + const std::type_info* type; + for (int i = 0; i < kDummyArraySize; ++i) + dummy[i] = new int(i); + + for (int i = 0; i < kDummyArraySize; ++i) { + type = LookupType(dummy[i]); + ASSERT_NE(kConstNull, type); + EXPECT_STREQ(typeid(int).name(), type->name()); + } + + for (int i = 0; i < kDummyArraySize; ++i) { + delete dummy[i]; + type = LookupType(dummy[i]); + ASSERT_EQ(kConstNull, type); + } +} + +TEST_F(TypeProfilerTest, TestMultipleNewWithDroppingDelete) { + static const size_t large_size = 256 * 1024; + + char* dummy_char = new char[large_size / sizeof(*dummy_char)]; + const std::type_info* type; + + type = LookupType(dummy_char); + ASSERT_NE(kConstNull, type); + EXPECT_STREQ(typeid(char).name(), type->name()); + + // Call "::operator delete" directly to drop __op_delete_intercept__. + ::operator delete[](dummy_char); + + type = LookupType(dummy_char); + ASSERT_NE(kConstNull, type); + EXPECT_STREQ(typeid(char).name(), type->name()); + + // Allocates a little different size. + int* dummy_int = new int[large_size / sizeof(*dummy_int) - 1]; + + // We expect that tcmalloc returns the same address for these large (over 32k) + // allocation calls. It usually happens, but maybe probablistic. + ASSERT_EQ(static_cast<void*>(dummy_char), static_cast<void*>(dummy_int)) << + "two new (malloc) calls didn't return the same address; retry it."; + + type = LookupType(dummy_int); + ASSERT_NE(kConstNull, type); + EXPECT_STREQ(typeid(int).name(), type->name()); + + delete[] dummy_int; + + type = LookupType(dummy_int); + EXPECT_EQ(kConstNull, type); +} + +TEST_F(TypeProfilerTest, TestProfileDeleteWithoutProfiledNew) { + // 'dummy' should be new'ed in this test before intercept functions are set. + ResetInterceptFunctions(); + + int* dummy = new int(48); + const std::type_info* type; + + // Set intercept functions again after 'dummy' is new'ed. + SetInterceptFunctions(); + + delete dummy; + + type = LookupType(dummy); + EXPECT_EQ(kConstNull, type); + + ResetInterceptFunctions(); +} + +TEST_F(TypeProfilerTest, TestProfileNewWithoutProfiledDelete) { + int* dummy = new int(48); + const std::type_info* type; + + EXPECT_TRUE(Controller::IsProfiling()); + + // Stop profiling before deleting 'dummy'. + Controller::Stop(); + EXPECT_FALSE(Controller::IsProfiling()); + + delete dummy; + + // NOTE: We accept that a profile entry remains when a profiled object is + // deleted after Controller::Stop(). + type = LookupType(dummy); + ASSERT_NE(kConstNull, type); + EXPECT_STREQ(typeid(int).name(), type->name()); + + Controller::Restart(); + EXPECT_TRUE(Controller::IsProfiling()); + + // Remove manually since 'dummy' is not removed from type_profiler_map. + EraseType(dummy); +} + +} // namespace type_profiler +} // namespace base + +#endif // defined(TYPE_PROFILING) + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} |