blob: 8edd992f31c5d754b05fd4699c4cf5b1bd379b9e [file] [log] [blame]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "platform/globals.h"
#if defined(DART_USE_TCMALLOC) && defined(DEBUG)
#include "platform/assert.h"
#include "vm/globals.h"
#include "vm/malloc_hooks.h"
#include "vm/os.h"
#include "vm/profiler.h"
#include "vm/profiler_service.h"
#include "vm/unit_test.h"
namespace dart {
static void MallocHookTestBufferInitializer(volatile char* buffer,
uintptr_t size) {
// Run through the buffer and do something. If we don't do this and the memory
// in buffer isn't touched, the tcmalloc hooks won't be called.
for (uintptr_t i = 0; i < size; ++i) {
buffer[i] = i;
}
}
class EnableMallocHooksScope : public ValueObject {
public:
EnableMallocHooksScope() {
OSThread::Current(); // Ensure not allocated during test.
saved_enable_malloc_hooks_ = FLAG_profiler_native_memory;
FLAG_profiler_native_memory = true;
MallocHooks::Init();
MallocHooks::ResetStats();
}
~EnableMallocHooksScope() {
MallocHooks::Cleanup();
FLAG_profiler_native_memory = saved_enable_malloc_hooks_;
}
private:
bool saved_enable_malloc_hooks_;
};
class EnableMallocHooksAndStacksScope : public EnableMallocHooksScope {
public:
EnableMallocHooksAndStacksScope() {
OSThread::Current(); // Ensure not allocated during test.
saved_enable_stack_traces_ = MallocHooks::stack_trace_collection_enabled();
MallocHooks::set_stack_trace_collection_enabled(true);
if (!FLAG_profiler) {
FLAG_profiler = true;
Profiler::Init();
}
MallocHooks::ResetStats();
}
~EnableMallocHooksAndStacksScope() {
MallocHooks::set_stack_trace_collection_enabled(saved_enable_stack_traces_);
}
private:
bool saved_enable_stack_traces_;
};
UNIT_TEST_CASE(BasicMallocHookTest) {
EnableMallocHooksScope scope;
EXPECT_EQ(0L, MallocHooks::allocation_count());
EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes());
const intptr_t buffer_size = 10;
char* buffer = new char[buffer_size];
MallocHookTestBufferInitializer(buffer, buffer_size);
EXPECT_EQ(1L, MallocHooks::allocation_count());
EXPECT_EQ(static_cast<intptr_t>(sizeof(char) * buffer_size),
MallocHooks::heap_allocated_memory_in_bytes());
delete[] buffer;
EXPECT_EQ(0L, MallocHooks::allocation_count());
EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes());
}
UNIT_TEST_CASE(FreeUnseenMemoryMallocHookTest) {
EnableMallocHooksScope scope;
const intptr_t pre_hook_buffer_size = 3;
char* pre_hook_buffer = new char[pre_hook_buffer_size];
MallocHookTestBufferInitializer(pre_hook_buffer, pre_hook_buffer_size);
MallocHooks::ResetStats();
EXPECT_EQ(0L, MallocHooks::allocation_count());
EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes());
const intptr_t buffer_size = 10;
char* buffer = new char[buffer_size];
MallocHookTestBufferInitializer(buffer, buffer_size);
EXPECT_EQ(1L, MallocHooks::allocation_count());
EXPECT_EQ(static_cast<intptr_t>(sizeof(char) * buffer_size),
MallocHooks::heap_allocated_memory_in_bytes());
delete[] pre_hook_buffer;
EXPECT_EQ(1L, MallocHooks::allocation_count());
EXPECT_EQ(static_cast<intptr_t>(sizeof(char) * buffer_size),
MallocHooks::heap_allocated_memory_in_bytes());
delete[] buffer;
EXPECT_EQ(0L, MallocHooks::allocation_count());
EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes());
}
VM_UNIT_TEST_CASE(StackTraceMallocHookSimpleTest) {
EnableMallocHooksAndStacksScope scope;
char* var = static_cast<char*>(malloc(16 * sizeof(char)));
Sample* sample = MallocHooks::GetSample(var);
EXPECT(sample != NULL);
free(var);
sample = MallocHooks::GetSample(var);
EXPECT(sample == NULL);
}
static char* DART_NOINLINE StackTraceLengthHelper(uintptr_t* end_address) {
char* var = static_cast<char*>(malloc(16 * sizeof(char)));
*end_address = OS::GetProgramCounter();
return var;
}
VM_UNIT_TEST_CASE(StackTraceMallocHookLengthTest) {
EnableMallocHooksAndStacksScope scope;
uintptr_t test_start_address =
reinterpret_cast<uintptr_t>(Dart_TestStackTraceMallocHookLengthTest);
uintptr_t helper_start_address =
reinterpret_cast<uintptr_t>(StackTraceLengthHelper);
uintptr_t helper_end_address = 0;
char* var = StackTraceLengthHelper(&helper_end_address);
Sample* sample = MallocHooks::GetSample(var);
EXPECT(sample != NULL);
uintptr_t test_end_address = OS::GetProgramCounter();
// Ensure that all stack frames are where we expect them to be in the sample.
// If they aren't, the kSkipCount constant in malloc_hooks.cc is likely
// incorrect.
uword address = sample->At(0);
bool first_result =
(helper_start_address <= address) && (helper_end_address >= address);
EXPECT(first_result);
address = sample->At(1);
bool second_result =
(test_start_address <= address) && (test_end_address >= address);
EXPECT(second_result);
if (!(first_result && second_result)) {
OS::PrintErr(
"If this test is failing, it's likely that the value set for"
" the number of frames to skip in malloc_hooks.cc is "
"incorrect for this configuration/platform. This value can be"
" found in malloc_hooks.cc in the AllocationInfo class, and "
"is stored in the kSkipCount constant.\n");
OS::PrintErr("First result: %d Second Result: %d\n", first_result,
second_result);
OS::PrintErr("Dumping sample stack trace:\n");
sample->DumpStackTrace();
}
free(var);
}
ISOLATE_UNIT_TEST_CASE(StackTraceMallocHookSimpleJSONTest) {
EnableMallocHooksAndStacksScope scope;
ClearProfileVisitor cpv(Isolate::Current());
Profiler::sample_buffer()->VisitSamples(&cpv);
char* var = static_cast<char*>(malloc(16 * sizeof(char)));
JSONStream js;
ProfilerService::PrintNativeAllocationJSON(&js, -1, -1, false);
const char* json = js.ToCString();
// Check that all the stack frames from the current down to main are actually
// present in the profile. This is just a simple sanity check to make sure
// that the ProfileTrie has a representation of the stack trace collected when
// var is allocated. More intense testing is already done in profiler_test.cc.
// This is brittle: inlining and ICF in the C compiler and linker will affect
// the frames we see.
EXPECT_SUBSTRING("\"dart::Dart_TestStackTraceMallocHookSimpleJSONTest()\"",
json);
EXPECT_SUBSTRING("\"dart::TestCase::Run()\"", json);
EXPECT_SUBSTRING("\"dart::TestCaseBase::RunTest()\"", json);
EXPECT_SUBSTRING("\"main\"", json);
free(var);
}
}; // namespace dart
#endif // defined(DART_USE_TCMALLOC) && !defined(DEBUG)