diff options
author | Christopher Ferris <cferris@google.com> | 2019-08-30 11:17:55 -0700 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2019-08-30 11:17:55 -0700 |
commit | 2077beb1a9f11742e5273bd68807d084ab75474c (patch) | |
tree | 16d4d05908a3ac824f2d0f911d966724ee964e68 | |
parent | 64146b2a17afb2550cf4d305dd06d87d087cec13 (diff) | |
parent | 7ac993ce67420cc2e2b06c8b8f97e99f1a8e7d03 (diff) | |
download | extras-2077beb1a9f11742e5273bd68807d084ab75474c.tar.gz |
Merge "Refactor code with single trace parser."
am: 7ac993ce67
Change-Id: Iff4618349bce7e22456bf004b861daafcc0328f6
28 files changed, 930 insertions, 1091 deletions
diff --git a/memory_replay/Action.cpp b/memory_replay/Action.cpp deleted file mode 100644 index 7f15012f..00000000 --- a/memory_replay/Action.cpp +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <inttypes.h> -#include <malloc.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <sys/param.h> -#include <time.h> -#include <unistd.h> - -#include <new> - -#include "Action.h" -#include "Pointers.h" -#include "Threads.h" - -static __always_inline uint64_t Nanotime() { - struct timespec t; - t.tv_sec = t.tv_nsec = 0; - clock_gettime(CLOCK_MONOTONIC, &t); - return static_cast<uint64_t>(t.tv_sec) * 1000000000LL + t.tv_nsec; -} - -static __always_inline void MakeAllocationResident(void* ptr, size_t nbytes, int pagesize) { - uint8_t* data = reinterpret_cast<uint8_t*>(ptr); - for (size_t i = 0; i < nbytes; i += pagesize) { - data[i] = 1; - } -} - -class EndThreadAction : public Action { - public: - EndThreadAction() {} - - bool EndThread() override { return true; } - - uint64_t Execute(Pointers*) override { return 0; } -}; - -class AllocAction : public Action { - public: - explicit AllocAction(uintptr_t key_pointer) : key_pointer_(key_pointer) {} - - protected: - uintptr_t key_pointer_ = 0; - size_t size_ = 0; -}; - -class MallocAction : public AllocAction { - public: - MallocAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) { - if (sscanf(line, "%zu", &size_) != 1) { - is_error_ = true; - } - } - - uint64_t Execute(Pointers* pointers) override { - int pagesize = getpagesize(); - uint64_t time_nsecs = Nanotime(); - void* memory = malloc(size_); - MakeAllocationResident(memory, size_, pagesize); - time_nsecs = Nanotime() - time_nsecs; - - pointers->Add(key_pointer_, memory); - - return time_nsecs; - } -}; - -class CallocAction : public AllocAction { - public: - CallocAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) { - if (sscanf(line, "%zu %zu", &n_elements_, &size_) != 2) { - is_error_ = true; - } - } - - uint64_t Execute(Pointers* pointers) override { - int pagesize = getpagesize(); - uint64_t time_nsecs = Nanotime(); - void* memory = calloc(n_elements_, size_); - MakeAllocationResident(memory, n_elements_ * size_, pagesize); - time_nsecs = Nanotime() - time_nsecs; - - pointers->Add(key_pointer_, memory); - - return time_nsecs; - } - - private: - size_t n_elements_ = 0; -}; - -class ReallocAction : public AllocAction { - public: - ReallocAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) { - if (sscanf(line, "%" SCNxPTR " %zu", &old_pointer_, &size_) != 2) { - is_error_ = true; - } - } - - bool DoesFree() override { return old_pointer_ != 0; } - - uint64_t Execute(Pointers* pointers) override { - void* old_memory = nullptr; - if (old_pointer_ != 0) { - old_memory = pointers->Remove(old_pointer_); - } - - int pagesize = getpagesize(); - uint64_t time_nsecs = Nanotime(); - void* memory = realloc(old_memory, size_); - MakeAllocationResident(memory, size_, pagesize); - time_nsecs = Nanotime() - time_nsecs; - - pointers->Add(key_pointer_, memory); - - return time_nsecs; - } - - private: - uintptr_t old_pointer_ = 0; -}; - -class MemalignAction : public AllocAction { - public: - MemalignAction(uintptr_t key_pointer, const char* line) : AllocAction(key_pointer) { - if (sscanf(line, "%zu %zu", &align_, &size_) != 2) { - is_error_ = true; - } - } - - uint64_t Execute(Pointers* pointers) override { - int pagesize = getpagesize(); - uint64_t time_nsecs = Nanotime(); - void* memory = memalign(align_, size_); - MakeAllocationResident(memory, size_, pagesize); - time_nsecs = Nanotime() - time_nsecs; - - pointers->Add(key_pointer_, memory); - - return time_nsecs; - } - - private: - size_t align_ = 0; -}; - -class FreeAction : public AllocAction { - public: - explicit FreeAction(uintptr_t key_pointer) : AllocAction(key_pointer) {} - - bool DoesFree() override { return key_pointer_ != 0; } - - uint64_t Execute(Pointers* pointers) override { - if (key_pointer_) { - void* memory = pointers->Remove(key_pointer_); - uint64_t time_nsecs = Nanotime(); - free(memory); - return Nanotime() - time_nsecs; - } - return 0; - } -}; - -size_t Action::MaxActionSize() { - size_t max = MAX(sizeof(EndThreadAction), sizeof(MallocAction)); - max = MAX(max, sizeof(CallocAction)); - max = MAX(max, sizeof(ReallocAction)); - max = MAX(max, sizeof(MemalignAction)); - return MAX(max, sizeof(FreeAction)); -} - -Action* Action::CreateAction(uintptr_t key_pointer, const char* type, const char* line, - void* action_memory) { - Action* action = nullptr; - if (strcmp(type, "malloc") == 0) { - action = new (action_memory) MallocAction(key_pointer, line); - } else if (strcmp(type, "free") == 0) { - action = new (action_memory) FreeAction(key_pointer); - } else if (strcmp(type, "calloc") == 0) { - action = new (action_memory) CallocAction(key_pointer, line); - } else if (strcmp(type, "realloc") == 0) { - action = new (action_memory) ReallocAction(key_pointer, line); - } else if (strcmp(type, "memalign") == 0) { - action = new (action_memory) MemalignAction(key_pointer, line); - } else if (strcmp(type, "thread_done") == 0) { - action = new (action_memory) EndThreadAction(); - } - - if (action == nullptr || action->IsError()) { - return nullptr; - } - return action; -} diff --git a/memory_replay/Action.h b/memory_replay/Action.h deleted file mode 100644 index f498f526..00000000 --- a/memory_replay/Action.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef _MEMORY_REPLAY_ACTION_H -#define _MEMORY_REPLAY_ACTION_H - -#include <stdint.h> - -class Pointers; - -class Action { - public: - Action() {} - virtual ~Action() {} - - virtual uint64_t Execute(Pointers* pointers) = 0; - - bool IsError() { return is_error_; }; - - virtual bool EndThread() { return false; } - - virtual bool DoesFree() { return false; } - - static size_t MaxActionSize(); - static Action* CreateAction(uintptr_t key_pointer, const char* type, - const char* line, void* action_memory); - - protected: - bool is_error_ = false; -}; - -#endif // _MEMORY_REPLAY_ACTION_H diff --git a/memory_replay/Alloc.cpp b/memory_replay/Alloc.cpp new file mode 100644 index 00000000..af94ee5d --- /dev/null +++ b/memory_replay/Alloc.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <err.h> +#include <inttypes.h> +#include <stdint.h> +#include <stdio.h> +#include <unistd.h> + +#include <string> + +#include "Alloc.h" +#include "Pointers.h" +#include "Utils.h" + +void AllocGetData(const std::string& line, AllocEntry* entry) { + int line_pos = 0; + char name[128]; + // All lines have this format: + // TID: ALLOCATION_TYPE POINTER + // where + // TID is the thread id of the thread doing the operation. + // ALLOCATION_TYPE is one of malloc, calloc, memalign, realloc, free, thread_done + // POINTER is the hex value of the actual pointer + if (sscanf(line.c_str(), "%d: %127s %" SCNx64 " %n", &entry->tid, name, &entry->ptr, &line_pos) != + 3) { + errx(1, "File Error: Failed to process %s", line.c_str()); + } + const char* line_end = &line[line_pos]; + std::string type(name); + if (type == "malloc") { + // Format: + // TID: malloc POINTER SIZE_OF_ALLOCATION + if (sscanf(line_end, "%zu", &entry->size) != 1) { + errx(1, "File Error: Failed to read malloc data %s", line.c_str()); + } + entry->type = MALLOC; + } else if (type == "free") { + // Format: + // TID: free POINTER + entry->type = FREE; + } else if (type == "calloc") { + // Format: + // TID: calloc POINTER ITEM_COUNT ITEM_SIZE + if (sscanf(line_end, "%" SCNd64 " %zu", &entry->u.n_elements, &entry->size) != 2) { + errx(1, "File Error: Failed to read calloc data %s", line.c_str()); + } + entry->type = CALLOC; + } else if (type == "realloc") { + // Format: + // TID: calloc POINTER NEW_SIZE OLD_POINTER + if (sscanf(line_end, "%" SCNx64 " %zu", &entry->u.old_ptr, &entry->size) != 2) { + errx(1, "File Error: Failed to read realloc data %s", line.c_str()); + } + entry->type = REALLOC; + } else if (type == "memalign") { + // Format: + // TID: memalign POINTER ALIGNMENT SIZE + if (sscanf(line_end, "%" SCNd64 " %zu", &entry->u.align, &entry->size) != 2) { + errx(1, "File Error: Failed to read memalign data %s", line.c_str()); + } + entry->type = MEMALIGN; + } else if (type == "thread_done") { + entry->type = THREAD_DONE; + } else { + errx(1, "File Error: Unknown type %s", type.c_str()); + } +} + +bool AllocDoesFree(const AllocEntry& entry) { + switch (entry.type) { + case MALLOC: + case CALLOC: + case MEMALIGN: + case THREAD_DONE: + return false; + + case FREE: + return entry.ptr != 0; + + case REALLOC: + return entry.u.old_ptr != 0; + } +} + +static uint64_t MallocExecute(const AllocEntry& entry, Pointers* pointers) { + int pagesize = getpagesize(); + uint64_t time_nsecs = Nanotime(); + void* memory = malloc(entry.size); + MakeAllocationResident(memory, entry.size, pagesize); + time_nsecs = Nanotime() - time_nsecs; + + pointers->Add(entry.ptr, memory); + + return time_nsecs; +} + +static uint64_t CallocExecute(const AllocEntry& entry, Pointers* pointers) { + int pagesize = getpagesize(); + uint64_t time_nsecs = Nanotime(); + void* memory = calloc(entry.u.n_elements, entry.size); + MakeAllocationResident(memory, entry.u.n_elements * entry.size, pagesize); + time_nsecs = Nanotime() - time_nsecs; + + pointers->Add(entry.ptr, memory); + + return time_nsecs; +} + +static uint64_t ReallocExecute(const AllocEntry& entry, Pointers* pointers) { + void* old_memory = nullptr; + if (entry.u.old_ptr != 0) { + old_memory = pointers->Remove(entry.u.old_ptr); + } + + int pagesize = getpagesize(); + uint64_t time_nsecs = Nanotime(); + void* memory = realloc(old_memory, entry.size); + MakeAllocationResident(memory, entry.size, pagesize); + time_nsecs = Nanotime() - time_nsecs; + + pointers->Add(entry.ptr, memory); + + return time_nsecs; +} + +static uint64_t MemalignExecute(const AllocEntry& entry, Pointers* pointers) { + int pagesize = getpagesize(); + uint64_t time_nsecs = Nanotime(); + void* memory = memalign(entry.u.align, entry.size); + MakeAllocationResident(memory, entry.size, pagesize); + time_nsecs = Nanotime() - time_nsecs; + + pointers->Add(entry.ptr, memory); + + return time_nsecs; +} + +static uint64_t FreeExecute(const AllocEntry& entry, Pointers* pointers) { + if (entry.ptr == 0) { + return 0; + } + + void* memory = pointers->Remove(entry.ptr); + uint64_t time_nsecs = Nanotime(); + free(memory); + return Nanotime() - time_nsecs; +} + +uint64_t AllocExecute(const AllocEntry& entry, Pointers* pointers) { + switch (entry.type) { + case MALLOC: + return MallocExecute(entry, pointers); + case CALLOC: + return CallocExecute(entry, pointers); + case REALLOC: + return ReallocExecute(entry, pointers); + case MEMALIGN: + return MemalignExecute(entry, pointers); + case FREE: + return FreeExecute(entry, pointers); + default: + return 0; + } +} diff --git a/memory_replay/Alloc.h b/memory_replay/Alloc.h new file mode 100644 index 00000000..d590fcba --- /dev/null +++ b/memory_replay/Alloc.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <string> + +// Forward Declarations. +class Pointers; + +enum AllocEnum : uint8_t { + MALLOC = 0, + CALLOC, + MEMALIGN, + REALLOC, + FREE, + THREAD_DONE, +}; + +struct AllocEntry { + pid_t tid; + AllocEnum type; + uint64_t ptr = 0; + size_t size = 0; + union { + uint64_t old_ptr = 0; + uint64_t n_elements; + uint64_t align; + } u; +}; + +void AllocGetData(const std::string& line, AllocEntry* entry); + +bool AllocDoesFree(const AllocEntry& entry); + +uint64_t AllocExecute(const AllocEntry& entry, Pointers* pointers); diff --git a/memory_replay/Android.bp b/memory_replay/Android.bp index d8217d69..c5cd3cd5 100644 --- a/memory_replay/Android.bp +++ b/memory_replay/Android.bp @@ -16,7 +16,7 @@ cc_defaults { name: "memory_flag_defaults", - host_supported: true, + host_supported: false, cflags: [ "-Wall", @@ -24,12 +24,6 @@ cc_defaults { "-Werror", ], - target: { - darwin: { - enabled: false, - }, - }, - compile_multilib: "both", } @@ -38,15 +32,22 @@ cc_defaults { defaults: ["memory_flag_defaults"], srcs: [ - "Action.cpp", - "LineBuffer.cpp", + "Alloc.cpp", "NativeInfo.cpp", "Pointers.cpp", "Thread.cpp", "Threads.cpp", + "Zip.cpp", ], - shared_libs: ["libbase"], + shared_libs: [ + "libbase", + "libziparchive", + ], + + static_libs: [ + "libasync_safe", + ], } cc_binary { @@ -71,12 +72,12 @@ cc_test { isolated: true, srcs: [ - "tests/ActionTest.cpp", - "tests/LineBufferTest.cpp", + "tests/AllocTest.cpp", "tests/NativeInfoTest.cpp", "tests/PointersTest.cpp", "tests/ThreadTest.cpp", "tests/ThreadsTest.cpp", + "tests/ZipTest.cpp", ], local_include_dirs: ["tests"], @@ -86,6 +87,10 @@ cc_test { test_suites: ["device-tests"], }, }, + + data: [ + "tests/test.zip", + ], } cc_benchmark { @@ -93,7 +98,9 @@ cc_benchmark { defaults: ["memory_flag_defaults"], srcs: [ + "Alloc.cpp", "TraceBenchmark.cpp", + "Zip.cpp", ], shared_libs: [ diff --git a/memory_replay/LineBuffer.cpp b/memory_replay/LineBuffer.cpp deleted file mode 100644 index 5e65ad62..00000000 --- a/memory_replay/LineBuffer.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <errno.h> -#include <string.h> -#include <unistd.h> - -#include "LineBuffer.h" - -LineBuffer::LineBuffer(int fd, char* buffer, size_t buffer_len) : fd_(fd), buffer_(buffer), buffer_len_(buffer_len) { -} - -bool LineBuffer::GetLine(char** line, size_t* line_len) { - while (true) { - if (bytes_ > 0) { - char* newline = reinterpret_cast<char*>(memchr(buffer_ + start_, '\n', bytes_)); - if (newline != nullptr) { - *newline = '\0'; - *line = buffer_ + start_; - start_ = newline - buffer_ + 1; - bytes_ -= newline - *line + 1; - *line_len = newline - *line; - return true; - } - } - if (start_ > 0) { - // Didn't find anything, copy the current to the front of the buffer. - memmove(buffer_, buffer_ + start_, bytes_); - start_ = 0; - } - ssize_t bytes = TEMP_FAILURE_RETRY(read(fd_, buffer_ + bytes_, buffer_len_ - bytes_ - 1)); - if (bytes <= 0) { - if (bytes_ > 0) { - // The read data might not contain a nul terminator, so add one. - buffer_[bytes_] = '\0'; - *line = buffer_ + start_; - *line_len = bytes_; - bytes_ = 0; - start_ = 0; - return true; - } - return false; - } - bytes_ += bytes; - } -} diff --git a/memory_replay/NativeInfo.cpp b/memory_replay/NativeInfo.cpp index 2db653cb..8d0e4945 100644 --- a/memory_replay/NativeInfo.cpp +++ b/memory_replay/NativeInfo.cpp @@ -18,50 +18,87 @@ #include <errno.h> #include <fcntl.h> #include <inttypes.h> +#include <stdarg.h> #include <stdint.h> #include <stdio.h> #include <string.h> -#include <sys/types.h> #include <sys/stat.h> +#include <sys/types.h> #include <unistd.h> #include <android-base/unique_fd.h> +#include <async_safe/log.h> -#include "LineBuffer.h" #include "NativeInfo.h" +void NativePrintf(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + char buffer[512]; + int buffer_len = async_safe_format_buffer_va_list(buffer, sizeof(buffer), fmt, args); + va_end(args); + + (void)write(STDOUT_FILENO, buffer, buffer_len); +} + +void NativeFormatFloat(char* buffer, size_t buffer_len, uint64_t value, uint64_t divisor) { + uint64_t hundreds = ((((value % divisor) * 1000) / divisor) + 5) / 10; + async_safe_format_buffer(buffer, buffer_len, "%" PRIu64 ".%02" PRIu64, value / divisor, hundreds); +} + // This function is not re-entrant since it uses a static buffer for // the line data. -void GetNativeInfo(int smaps_fd, size_t* rss_bytes, size_t* va_bytes) { - static char map_buffer[65535]; - LineBuffer line_buf(smaps_fd, map_buffer, sizeof(map_buffer)); - char* line; +void NativeGetInfo(int smaps_fd, size_t* rss_bytes, size_t* va_bytes) { size_t total_rss_bytes = 0; size_t total_va_bytes = 0; - size_t line_len; bool native_map = false; - while (line_buf.GetLine(&line, &line_len)) { - uintptr_t start, end; - int name_pos; - size_t native_rss_kB; - if (sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %*4s %*x %*x:%*x %*d %n", - &start, &end, &name_pos) == 2) { - if (strcmp(line + name_pos, "[anon:libc_malloc]") == 0 || - strcmp(line + name_pos, "[heap]") == 0) { - total_va_bytes += end - start; - native_map = true; - } else { - native_map = false; + + char buf[1024]; + size_t buf_start = 0; + size_t buf_bytes = 0; + while (true) { + ssize_t bytes = + TEMP_FAILURE_RETRY(read(smaps_fd, buf + buf_bytes, sizeof(buf) - buf_bytes - 1)); + if (bytes <= 0) { + break; + } + buf_bytes += bytes; + while (buf_bytes > 0) { + char* newline = reinterpret_cast<char*>(memchr(&buf[buf_start], '\n', buf_bytes)); + if (newline == nullptr) { + break; + } + *newline = '\0'; + uintptr_t start, end; + int name_pos; + size_t native_rss_kB; + if (sscanf(&buf[buf_start], "%" SCNxPTR "-%" SCNxPTR " %*4s %*x %*x:%*x %*d %n", &start, &end, + &name_pos) == 2) { + char* map_name = &buf[buf_start + name_pos]; + if (strcmp(map_name, "[anon:libc_malloc]") == 0 || strcmp(map_name, "[heap]") == 0) { + total_va_bytes += end - start; + native_map = true; + } else { + native_map = false; + } + } else if (native_map && sscanf(&buf[buf_start], "Rss: %zu", &native_rss_kB) == 1) { + total_rss_bytes += native_rss_kB * 1024; } - } else if (native_map && sscanf(line, "Rss: %zu", &native_rss_kB) == 1) { - total_rss_bytes += native_rss_kB * 1024; + buf_bytes -= newline - &buf[buf_start] + 1; + buf_start = newline - buf + 1; + } + if (buf_start > 0) { + if (buf_bytes > 0) { + memmove(buf, &buf[buf_start], buf_bytes); + } + buf_start = 0; } } *rss_bytes = total_rss_bytes; *va_bytes = total_va_bytes; } -void PrintNativeInfo(const char* preamble) { +void NativePrintInfo(const char* preamble) { size_t rss_bytes; size_t va_bytes; @@ -70,8 +107,12 @@ void PrintNativeInfo(const char* preamble) { err(1, "Cannot open /proc/self/smaps: %s\n", strerror(errno)); } - GetNativeInfo(smaps_fd, &rss_bytes, &va_bytes); - printf("%sNative RSS: %zu bytes %0.2fMB\n", preamble, rss_bytes, rss_bytes/(1024*1024.0)); - printf("%sNative VA Space: %zu bytes %0.2fMB\n", preamble, va_bytes, va_bytes/(1024*1024.0)); - fflush(stdout); + NativeGetInfo(smaps_fd, &rss_bytes, &va_bytes); + + // Avoid any allocations, so use special non-allocating printfs. + char buffer[256]; + NativeFormatFloat(buffer, sizeof(buffer), rss_bytes, 1024 * 1024); + NativePrintf("%sNative RSS: %zu bytes %sMB\n", preamble, rss_bytes, buffer); + NativeFormatFloat(buffer, sizeof(buffer), va_bytes, 1024 * 1024); + NativePrintf("%sNative VA Space: %zu bytes %sMB\n", preamble, va_bytes, buffer); } diff --git a/memory_replay/NativeInfo.h b/memory_replay/NativeInfo.h index 40a16f2d..c91eec29 100644 --- a/memory_replay/NativeInfo.h +++ b/memory_replay/NativeInfo.h @@ -14,13 +14,14 @@ * limitations under the License. */ -#ifndef _MEMORY_REPLAY_NATIVE_INFO_H -#define _MEMORY_REPLAY_NATIVE_INFO_H +#pragma once -// This function is not re-entrant. -void GetNativeInfo(int smaps_fd, size_t* rss_bytes, size_t* va_bytes); +void NativeGetInfo(int smaps_fd, size_t* rss_bytes, size_t* va_bytes); -// This function is not re-entrant. -void PrintNativeInfo(const char* preamble); +void NativePrintInfo(const char* preamble); -#endif // _MEMORY_REPLAY_NATIVE_INFO_H +// Does not support any floating point specifiers. +void NativePrintf(const char* fmt, ...) __printflike(1, 2); + +// Fill buffer as if %0.2f was chosen for value / divisor. +void NativeFormatFloat(char* buffer, size_t buffer_len, uint64_t value, uint64_t divisor); diff --git a/memory_replay/Pointers.cpp b/memory_replay/Pointers.cpp index e9eebadb..6335dc2c 100644 --- a/memory_replay/Pointers.cpp +++ b/memory_replay/Pointers.cpp @@ -32,12 +32,12 @@ Pointers::Pointers(size_t max_allocs) { // Align to a page. pointers_size_ = (max_allocs * 4 * sizeof(pointer_data) + pagesize - 1) & ~(pagesize - 1); max_pointers_ = pointers_size_ / sizeof(pointer_data); - void* memory = mmap(nullptr, pointers_size_, PROT_READ | PROT_WRITE, - MAP_ANON | MAP_PRIVATE, -1, 0); + void* memory = + mmap(nullptr, pointers_size_, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (memory == MAP_FAILED) { err(1, "Unable to allocate data for pointer hash: %zu total_allocs\n", max_allocs); } - // Make sure that all of the RSS for this is counted right away. + // Set all of the pointers to be empty. memset(memory, 0, pointers_size_); pointers_ = reinterpret_cast<pointer_data*>(memory); } @@ -74,7 +74,7 @@ void* Pointers::Remove(uintptr_t key_pointer) { return pointer; } -pointer_data* Pointers::Find(uintptr_t key_pointer) { +Pointers::pointer_data* Pointers::Find(uintptr_t key_pointer) { size_t index = GetHash(key_pointer); for (size_t entries = max_pointers_; entries != 0; entries--) { if (atomic_load(&pointers_[index].key_pointer) == key_pointer) { @@ -87,7 +87,7 @@ pointer_data* Pointers::Find(uintptr_t key_pointer) { return nullptr; } -pointer_data* Pointers::FindEmpty(uintptr_t key_pointer) { +Pointers::pointer_data* Pointers::FindEmpty(uintptr_t key_pointer) { size_t index = GetHash(key_pointer); for (size_t entries = 0; entries < max_pointers_; entries++) { uintptr_t empty = 0; diff --git a/memory_replay/Pointers.h b/memory_replay/Pointers.h index c7cd825a..040027bf 100644 --- a/memory_replay/Pointers.h +++ b/memory_replay/Pointers.h @@ -14,19 +14,18 @@ * limitations under the License. */ -#ifndef _MEMORY_REPLAY_POINTERS_H -#define _MEMORY_REPLAY_POINTERS_H +#pragma once #include <stdatomic.h> #include <stdint.h> -struct pointer_data { - std::atomic_uintptr_t key_pointer; - void* pointer; -}; - class Pointers { public: + struct pointer_data { + std::atomic_uintptr_t key_pointer; + void* pointer; + }; + explicit Pointers(size_t max_allocs); virtual ~Pointers(); @@ -47,5 +46,3 @@ class Pointers { size_t pointers_size_ = 0; size_t max_pointers_ = 0; }; - -#endif // _MEMORY_REPLAY_POINTERS_H diff --git a/memory_replay/Thread.cpp b/memory_replay/Thread.cpp index 497b288e..4f0a6d54 100644 --- a/memory_replay/Thread.cpp +++ b/memory_replay/Thread.cpp @@ -16,7 +16,6 @@ #include <pthread.h> -#include "Action.h" #include "Thread.h" Thread::Thread() { @@ -56,7 +55,3 @@ void Thread::ClearPending() { pthread_mutex_unlock(&mutex_); pthread_cond_signal(&cond_); } - -Action* Thread::CreateAction(uintptr_t key_pointer, const char* type, const char* line) { - return Action::CreateAction(key_pointer, type, line, action_memory_); -} diff --git a/memory_replay/Thread.h b/memory_replay/Thread.h index 7724c12e..ffab01b4 100644 --- a/memory_replay/Thread.h +++ b/memory_replay/Thread.h @@ -14,18 +14,16 @@ * limitations under the License. */ -#ifndef _MEMORY_REPLAY_THREAD_H -#define _MEMORY_REPLAY_THREAD_H +#pragma once #include <pthread.h> #include <stdint.h> #include <sys/types.h> -class Action; +// Forward Declarations. +struct AllocEntry; class Pointers; -constexpr size_t ACTION_MEMORY_SIZE = 128; - class Thread { public: Thread(); @@ -36,13 +34,13 @@ class Thread { void SetPending(); void ClearPending(); - Action* CreateAction(uintptr_t key_pointer, const char* type, const char* line); void AddTimeNsecs(uint64_t nsecs) { total_time_nsecs_ += nsecs; } void set_pointers(Pointers* pointers) { pointers_ = pointers; } Pointers* pointers() { return pointers_; } - Action* GetAction() { return reinterpret_cast<Action*>(action_memory_); } + void SetAllocEntry(const AllocEntry* entry) { entry_ = entry; } + const AllocEntry& GetAllocEntry() { return *entry_; } private: pthread_mutex_t mutex_ = PTHREAD_MUTEX_INITIALIZER; @@ -55,12 +53,7 @@ class Thread { Pointers* pointers_ = nullptr; - // Per thread memory for an Action. Only one action can be processed. - // at a time. - static constexpr size_t ACTION_SIZE = 128; - uint8_t action_memory_[ACTION_SIZE]; + const AllocEntry* entry_; friend class Threads; }; - -#endif // _MEMORY_REPLAY_THREAD_H diff --git a/memory_replay/Threads.cpp b/memory_replay/Threads.cpp index 03c5d9cb..61f950ee 100644 --- a/memory_replay/Threads.cpp +++ b/memory_replay/Threads.cpp @@ -26,7 +26,8 @@ #include <new> -#include "Action.h" +#include "Alloc.h" +#include "Pointers.h" #include "Thread.h" #include "Threads.h" @@ -34,11 +35,11 @@ void* ThreadRunner(void* data) { Thread* thread = reinterpret_cast<Thread*>(data); while (true) { thread->WaitForPending(); - Action* action = thread->GetAction(); - thread->AddTimeNsecs(action->Execute(thread->pointers())); - bool end_thread = action->EndThread(); + const AllocEntry& entry = thread->GetAllocEntry(); + thread->AddTimeNsecs(AllocExecute(entry, thread->pointers())); + bool thread_done = entry.type == THREAD_DONE; thread->ClearPending(); - if (end_thread) { + if (thread_done) { break; } } @@ -57,11 +58,6 @@ Threads::Threads(Pointers* pointers, size_t max_threads) data_size_, max_threads_); } - if (Thread::ACTION_SIZE < Action::MaxActionSize()) { - err(1, "Thread action size is too small: ACTION_SIZE %zu, max size %zu\n", - Thread::ACTION_SIZE, Action::MaxActionSize()); - } - threads_ = new (memory) Thread[max_threads_]; } @@ -149,9 +145,10 @@ void Threads::Finish(Thread* thread) { } void Threads::FinishAll() { + AllocEntry thread_done = {.type = THREAD_DONE}; for (size_t i = 0; i < max_threads_; i++) { if (threads_[i].tid_ != 0) { - threads_[i].CreateAction(0, "thread_done", nullptr); + threads_[i].SetAllocEntry(&thread_done); threads_[i].SetPending(); Finish(threads_ + i); } diff --git a/memory_replay/Threads.h b/memory_replay/Threads.h index 4778bff3..3bb4a320 100644 --- a/memory_replay/Threads.h +++ b/memory_replay/Threads.h @@ -14,12 +14,12 @@ * limitations under the License. */ -#ifndef _MEMORY_REPLAY_THREADS_H -#define _MEMORY_REPLAY_THREADS_H +#pragma once #include <stdint.h> #include <sys/types.h> +// Forward Declarations. class Pointers; class Thread; @@ -53,5 +53,3 @@ class Threads { friend Thread; }; - -#endif // _MEMORY_REPLAY_THREADS_H diff --git a/memory_replay/TraceBenchmark.cpp b/memory_replay/TraceBenchmark.cpp index 38eb69ae..b8f2bc8b 100644 --- a/memory_replay/TraceBenchmark.cpp +++ b/memory_replay/TraceBenchmark.cpp @@ -22,11 +22,9 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <time.h> #include <unistd.h> #include <algorithm> -#include <memory> #include <stack> #include <string> #include <unordered_map> @@ -35,62 +33,39 @@ #include <android-base/file.h> #include <android-base/strings.h> #include <benchmark/benchmark.h> -#include <ziparchive/zip_archive.h> - -enum AllocEnum : uint8_t { - MALLOC = 0, // arg2 not used - CALLOC, // size = item_count, arg2 = item_size - MEMALIGN, // arg2 = alignment - REALLOC, // if arg2 = 0, ptr arg is nullptr, else arg2 = old pointer index + 1 - FREE, // size not used, arg2 not used -}; -struct MallocEntry { - MallocEntry(AllocEnum type, size_t idx, size_t size, size_t arg2) - : type(type), idx(idx), size(size), arg2(arg2) {} +#include "Alloc.h" +#include "Utils.h" +#include "Zip.h" + +struct TraceAllocEntry { + TraceAllocEntry(AllocEnum type, size_t idx, size_t size, size_t last_arg) + : type(type), idx(idx), size(size) { + u.old_idx = last_arg; + } AllocEnum type; size_t idx; size_t size; - size_t arg2; + union { + size_t old_idx = 0; + size_t align; + size_t n_elements; + } u; }; -static std::string GetZipContents(const char* filename) { - ZipArchiveHandle archive; - if (OpenArchive(filename, &archive) != 0) { - return ""; - } - - std::string contents; - void* cookie; - if (StartIteration(archive, &cookie) == 0) { - ZipEntry entry; - std::string name; - if (Next(cookie, &entry, &name) == 0) { - contents.resize(entry.uncompressed_length); - if (ExtractToMemory(archive, &entry, reinterpret_cast<uint8_t*>(contents.data()), - entry.uncompressed_length) != 0) { - contents = ""; - } - } - } - - CloseArchive(archive); - return contents; -} - -static size_t GetIndex(std::stack<size_t>& indices, size_t* max_index) { - if (indices.empty()) { +static size_t GetIndex(std::stack<size_t>& free_indices, size_t* max_index) { + if (free_indices.empty()) { return (*max_index)++; } - size_t index = indices.top(); - indices.pop(); + size_t index = free_indices.top(); + free_indices.pop(); return index; } -static std::vector<MallocEntry>* GetTraceData(const char* filename, size_t* max_ptrs) { +static std::vector<TraceAllocEntry>* GetTraceData(const char* filename, size_t* max_ptrs) { // Only keep last trace encountered cached. static std::string cached_filename; - static std::vector<MallocEntry> cached_entries; + static std::vector<TraceAllocEntry> cached_entries; static size_t cached_max_ptrs; if (cached_filename == filename) { @@ -102,7 +77,7 @@ static std::vector<MallocEntry>* GetTraceData(const char* filename, size_t* max_ cached_max_ptrs = 0; cached_filename = filename; - std::string content(GetZipContents(filename)); + std::string content(ZipGetContents(filename)); if (content.empty()) { errx(1, "Internal Error: Empty zip file %s", filename); } @@ -110,94 +85,64 @@ static std::vector<MallocEntry>* GetTraceData(const char* filename, size_t* max_ *max_ptrs = 0; std::stack<size_t> free_indices; - std::unordered_map<uintptr_t, size_t> indices; - std::vector<MallocEntry>* entries = &cached_entries; + std::unordered_map<uint64_t, size_t> ptr_to_index; + std::vector<TraceAllocEntry>* entries = &cached_entries; for (const std::string& line : lines) { if (line.empty()) { continue; } - pid_t tid; - int line_pos = 0; - char name[128]; - uintptr_t pointer; - // All lines have this format: - // TID: ALLOCATION_TYPE POINTER - // where - // TID is the thread id of the thread doing the operation. - // ALLOCATION_TYPE is one of malloc, calloc, memalign, realloc, free, thread_done - // POINTER is the hex value of the actual pointer - if (sscanf(line.c_str(), "%d: %127s %" SCNxPTR " %n", &tid, name, &pointer, &line_pos) != 3) { - errx(1, "Internal Error: Failed to process %s", line.c_str()); - } - const char* line_end = &line[line_pos]; - std::string type(name); - if (type == "malloc") { - // Format: - // TID: malloc POINTER SIZE_OF_ALLOCATION - size_t size; - if (sscanf(line_end, "%zu", &size) != 1) { - errx(1, "Internal Error: Failed to read malloc data %s", line.c_str()); - } - size_t idx = GetIndex(free_indices, max_ptrs); - indices[pointer] = idx; - entries->emplace_back(MALLOC, idx, size, 0); - } else if (type == "free") { - // Format: - // TID: free POINTER - if (pointer != 0) { - auto entry = indices.find(pointer); - if (entry == indices.end()) { - errx(1, "Internal Error: Unable to find free pointer %" PRIuPTR, pointer); - } - free_indices.push(entry->second); - entries->emplace_back(FREE, entry->second + 1, 0, 0); - } else { - entries->emplace_back(FREE, 0, 0, 0); + AllocEntry entry; + AllocGetData(line, &entry); + + switch (entry.type) { + case MALLOC: { + size_t idx = GetIndex(free_indices, max_ptrs); + ptr_to_index[entry.ptr] = idx; + entries->emplace_back(MALLOC, idx, entry.size, 0); + break; } - } else if (type == "calloc") { - // Format: - // TID: calloc POINTER ITEM_SIZE ITEM_COUNT - size_t n_elements; - size_t size; - if (sscanf(line_end, "%zu %zu", &n_elements, &size) != 2) { - errx(1, "Internal Error: Failed to read calloc data %s", line.c_str()); + case CALLOC: { + size_t idx = GetIndex(free_indices, max_ptrs); + ptr_to_index[entry.ptr] = idx; + entries->emplace_back(CALLOC, idx, entry.u.n_elements, entry.size); + break; } - size_t idx = GetIndex(free_indices, max_ptrs); - indices[pointer] = idx; - entries->emplace_back(CALLOC, idx, size, n_elements); - } else if (type == "realloc") { - // Format: - // TID: calloc POINTER NEW_SIZE OLD_POINTER - uintptr_t old_pointer; - size_t size; - if (sscanf(line_end, "%" SCNxPTR " %zu", &old_pointer, &size) != 2) { - errx(1, "Internal Error: Failed to read realloc data %s", line.c_str()); + case MEMALIGN: { + size_t idx = GetIndex(free_indices, max_ptrs); + ptr_to_index[entry.ptr] = idx; + entries->emplace_back(MEMALIGN, idx, entry.size, entry.u.align); + break; } - size_t old_pointer_idx = 0; - if (old_pointer != 0) { - auto entry = indices.find(old_pointer); - if (entry == indices.end()) { - errx(1, "Internal Error: Failed to find realloc pointer %" PRIuPTR, old_pointer); + case REALLOC: { + size_t old_pointer_idx = 0; + if (entry.u.old_ptr != 0) { + auto idx_entry = ptr_to_index.find(entry.u.old_ptr); + if (idx_entry == ptr_to_index.end()) { + errx(1, "File Error: Failed to find realloc pointer %" PRIu64, entry.u.old_ptr); + } + old_pointer_idx = idx_entry->second; + free_indices.push(old_pointer_idx); } - old_pointer_idx = entry->second; - free_indices.push(old_pointer_idx); - } - size_t idx = GetIndex(free_indices, max_ptrs); - indices[pointer] = idx; - entries->emplace_back(REALLOC, idx, size, old_pointer_idx + 1); - } else if (type == "memalign") { - // Format: - // TID: memalign POINTER SIZE ALIGNMENT - size_t align; - size_t size; - if (sscanf(line_end, "%zu %zu", &align, &size) != 2) { - errx(1, "Internal Error: Failed to read memalign data %s", line.c_str()); + size_t idx = GetIndex(free_indices, max_ptrs); + ptr_to_index[entry.ptr] = idx; + entries->emplace_back(REALLOC, idx, entry.size, old_pointer_idx + 1); + break; } - size_t idx = GetIndex(free_indices, max_ptrs); - indices[pointer] = idx; - entries->emplace_back(MEMALIGN, idx, size, align); - } else if (type != "thread_done") { - errx(1, "Internal Error: Unknown type %s", line.c_str()); + case FREE: + if (entry.ptr != 0) { + auto idx_entry = ptr_to_index.find(entry.ptr); + if (idx_entry == ptr_to_index.end()) { + errx(1, "File Error: Unable to find free pointer %" PRIu64, entry.ptr); + } + free_indices.push(idx_entry->second); + entries->emplace_back(FREE, idx_entry->second + 1, 0, 0); + } else { + entries->emplace_back(FREE, 0, 0, 0); + } + break; + case THREAD_DONE: + // Ignore these. + break; } } @@ -205,21 +150,8 @@ static std::vector<MallocEntry>* GetTraceData(const char* filename, size_t* max_ return entries; } -static __always_inline uint64_t Nanotime() { - struct timespec t; - t.tv_sec = t.tv_nsec = 0; - clock_gettime(CLOCK_MONOTONIC, &t); - return static_cast<uint64_t>(t.tv_sec) * 1000000000LL + t.tv_nsec; -} - -static __always_inline void MakeAllocationResident(void* ptr, size_t nbytes, int pagesize) { - uint8_t* data = reinterpret_cast<uint8_t*>(ptr); - for (size_t i = 0; i < nbytes; i += pagesize) { - data[i] = 1; - } -} - -static void RunTrace(benchmark::State& state, std::vector<MallocEntry>& entries, size_t max_ptrs) { +static void RunTrace(benchmark::State& state, std::vector<TraceAllocEntry>& entries, + size_t max_ptrs) { std::vector<void*> ptrs(max_ptrs, nullptr); int pagesize = getpagesize(); @@ -245,7 +177,7 @@ static void RunTrace(benchmark::State& state, std::vector<MallocEntry>& entries, case CALLOC: start_ns = Nanotime(); - ptr = calloc(entry.arg2, entry.size); + ptr = calloc(entry.u.n_elements, entry.size); if (ptr == nullptr) { errx(1, "calloc returned nullptr"); } @@ -260,7 +192,7 @@ static void RunTrace(benchmark::State& state, std::vector<MallocEntry>& entries, case MEMALIGN: start_ns = Nanotime(); - ptr = memalign(entry.arg2, entry.size); + ptr = memalign(entry.u.align, entry.size); if (ptr == nullptr) { errx(1, "memalign returned nullptr"); } @@ -275,11 +207,11 @@ static void RunTrace(benchmark::State& state, std::vector<MallocEntry>& entries, case REALLOC: start_ns = Nanotime(); - if (entry.arg2 == 0) { + if (entry.u.old_idx == 0) { ptr = realloc(nullptr, entry.size); } else { - ptr = realloc(ptrs[entry.arg2 - 1], entry.size); - ptrs[entry.arg2 - 1] = nullptr; + ptr = realloc(ptrs[entry.u.old_idx - 1], entry.size); + ptrs[entry.u.old_idx - 1] = nullptr; } if (entry.size > 0) { if (ptr == nullptr) { @@ -306,6 +238,9 @@ static void RunTrace(benchmark::State& state, std::vector<MallocEntry>& entries, free(ptr); total_ns += Nanotime() - start_ns; break; + + case THREAD_DONE: + break; } } state.SetIterationTime(total_ns / double(1000000000.0)); @@ -319,7 +254,7 @@ static void RunTrace(benchmark::State& state, std::vector<MallocEntry>& entries, static void BenchmarkTrace(benchmark::State& state, const char* filename) { std::string full_filename(android::base::GetExecutableDirectory() + "/traces/" + filename); size_t max_ptrs; - std::vector<MallocEntry>* entries = GetTraceData(full_filename.c_str(), &max_ptrs); + std::vector<TraceAllocEntry>* entries = GetTraceData(full_filename.c_str(), &max_ptrs); if (entries == nullptr) { errx(1, "ERROR: Failed to get trace data for %s.", full_filename.c_str()); } diff --git a/memory_replay/Utils.h b/memory_replay/Utils.h new file mode 100644 index 00000000..44ce93ac --- /dev/null +++ b/memory_replay/Utils.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <stdint.h> +#include <time.h> + +static __always_inline uint64_t Nanotime() { + struct timespec t = {}; + clock_gettime(CLOCK_MONOTONIC, &t); + return static_cast<uint64_t>(t.tv_sec) * 1000000000LL + t.tv_nsec; +} + +static __always_inline void MakeAllocationResident(void* ptr, size_t nbytes, int pagesize) { + uint8_t* data = reinterpret_cast<uint8_t*>(ptr); + for (size_t i = 0; i < nbytes; i += pagesize) { + data[i] = 1; + } +} diff --git a/memory_replay/Zip.cpp b/memory_replay/Zip.cpp new file mode 100644 index 00000000..8e2edf0a --- /dev/null +++ b/memory_replay/Zip.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <err.h> +#include <errno.h> +#include <stdint.h> +#include <sys/mman.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include <string> + +#include <ziparchive/zip_archive.h> + +#include "Alloc.h" +#include "Zip.h" + +std::string ZipGetContents(const char* filename) { + ZipArchiveHandle archive; + if (OpenArchive(filename, &archive) != 0) { + return ""; + } + + // It is assumed that the archive contains only a single entry. + void* cookie; + std::string contents; + if (StartIteration(archive, &cookie) == 0) { + ZipEntry entry; + std::string name; + if (Next(cookie, &entry, &name) == 0) { + contents.resize(entry.uncompressed_length); + if (ExtractToMemory(archive, &entry, reinterpret_cast<uint8_t*>(contents.data()), + entry.uncompressed_length) != 0) { + contents = ""; + } + } + } + + CloseArchive(archive); + return contents; +} + +void WaitPid(pid_t pid) { + int wstatus; + pid_t wait_pid = TEMP_FAILURE_RETRY(waitpid(pid, &wstatus, 0)); + if (wait_pid != pid) { + if (wait_pid == -1) { + err(1, "waitpid() failed"); + } else { + errx(1, "Unexpected pid from waitpid(): expected %d, returned %d", pid, wait_pid); + } + } + if (!WIFEXITED(wstatus)) { + errx(1, "Forked process did not terminate with exit() call"); + } + if (WEXITSTATUS(wstatus) != 0) { + errx(1, "Bad exit value from forked process: returned %d", WEXITSTATUS(wstatus)); + } +} + +// This function should not do any memory allocations in the main function. +// Any true allocation should happen in fork'd code. +void ZipGetUnwindInfo(const char* filename, AllocEntry** entries, size_t* num_entries) { + void* mem = + mmap(nullptr, sizeof(size_t), PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0); + if (mem == MAP_FAILED) { + err(1, "Unable to allocate a shared map of size %zu", sizeof(size_t)); + } + *reinterpret_cast<size_t*>(mem) = 0; + + pid_t pid; + if ((pid = fork()) == 0) { + // First get the number of lines in the trace file. It is assumed + // that there are no blank lines, and every line contains a valid + // allocation operation. + std::string contents = ZipGetContents(filename); + if (contents.empty()) { + errx(1, "Unable to get contents of %s", filename); + } + size_t lines = 0; + size_t index = 0; + while (true) { + index = contents.find('\n', index); + if (index == std::string::npos) { + break; + } + index++; + lines++; + } + if (contents[contents.size() - 1] != '\n') { + // Add one since the last line doesn't end in '\n'. + lines++; + } + *reinterpret_cast<size_t*>(mem) = lines; + _exit(0); + } else if (pid == -1) { + err(1, "fork() call failed"); + } + WaitPid(pid); + *num_entries = *reinterpret_cast<size_t*>(mem); + munmap(mem, sizeof(size_t)); + + mem = mmap(nullptr, *num_entries * sizeof(AllocEntry), PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_SHARED, -1, 0); + if (mem == MAP_FAILED) { + err(1, "Unable to allocate a shared map of size %zu", *num_entries * sizeof(AllocEntry)); + } + *entries = reinterpret_cast<AllocEntry*>(mem); + + if ((pid = fork()) == 0) { + std::string contents = ZipGetContents(filename); + if (contents.empty()) { + errx(1, "Contents of zip file %s is empty.", filename); + } + size_t entry_idx = 0; + size_t start_str = 0; + size_t end_str = 0; + while (true) { + end_str = contents.find('\n', start_str); + if (end_str == std::string::npos) { + break; + } + if (entry_idx == *num_entries) { + errx(1, "Too many entries, stopped at entry %zu", entry_idx); + } + contents[end_str] = '\0'; + AllocGetData(&contents[start_str], &(*entries)[entry_idx++]); + start_str = end_str + 1; + } + if (entry_idx != *num_entries) { + errx(1, "Mismatched number of entries found: expected %zu, found %zu", *num_entries, + entry_idx); + } + _exit(0); + } else if (pid == -1) { + err(1, "fork() call failed"); + } + WaitPid(pid); +} + +void ZipFreeEntries(AllocEntry* entries, size_t num_entries) { + munmap(entries, num_entries * sizeof(AllocEntry)); +} diff --git a/memory_replay/LineBuffer.h b/memory_replay/Zip.h index 934d3021..6224e86b 100644 --- a/memory_replay/LineBuffer.h +++ b/memory_replay/Zip.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 The Android Open Source Project + * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,23 +14,17 @@ * limitations under the License. */ -#ifndef _MEMORY_REPLAY_LINE_BUFFER_H -#define _MEMORY_REPLAY_LINE_BUFFER_H +#pragma once #include <stdint.h> -class LineBuffer { - public: - LineBuffer(int fd, char* buffer, size_t buffer_len); +#include <string> - bool GetLine(char** line, size_t* line_len); +// Forward Declarations. +struct AllocEntry; - private: - int fd_; - char* buffer_ = nullptr; - size_t buffer_len_ = 0; - size_t start_ = 0; - size_t bytes_ = 0; -}; +std::string ZipGetContents(const char* filename); -#endif // _MEMORY_REPLAY_LINE_BUFFER_H +void ZipGetUnwindInfo(const char* filename, AllocEntry** entries, size_t* num_entries); + +void ZipFreeEntries(AllocEntry* entries, size_t num_entries); diff --git a/memory_replay/main.cpp b/memory_replay/main.cpp index 2ffc53d0..ff8c1afa 100644 --- a/memory_replay/main.cpp +++ b/memory_replay/main.cpp @@ -27,96 +27,67 @@ #include <sys/types.h> #include <unistd.h> -#include "Action.h" -#include "LineBuffer.h" +#include "Alloc.h" #include "NativeInfo.h" #include "Pointers.h" #include "Thread.h" #include "Threads.h" +#include "Zip.h" -static char g_buffer[65535]; +constexpr size_t DEFAULT_MAX_THREADS = 512; -size_t GetMaxAllocs(int fd) { - lseek(fd, 0, SEEK_SET); - LineBuffer line_buf(fd, g_buffer, sizeof(g_buffer)); - char* line; - size_t line_len; +static size_t GetMaxAllocs(const AllocEntry* entries, size_t num_entries) { size_t num_allocs = 0; - while (line_buf.GetLine(&line, &line_len)) { - char* word = reinterpret_cast<char*>(memchr(line, ':', line_len)); - if (word == nullptr) { - continue; - } - - word++; - while (*word++ == ' ') - ; - // This will treat a realloc as an allocation, even if it frees - // another allocation. Since reallocs are relatively rare, this - // shouldn't inflate the numbers that much. - if (*word == 'f') { - // Check if this is a free of zero. - uintptr_t pointer; - if (sscanf(word, "free %" SCNxPTR, &pointer) == 1 && pointer != 0) { + for (size_t i = 0; i < num_entries; i++) { + switch (entries[i].type) { + case THREAD_DONE: + break; + case MALLOC: + case CALLOC: + case MEMALIGN: + case REALLOC: + num_allocs++; + break; + case FREE: num_allocs--; - } - } else if (*word != 't') { - // Skip the thread_done message. - num_allocs++; + break; } } return num_allocs; } -void ProcessDump(int fd, size_t max_allocs, size_t max_threads) { - lseek(fd, 0, SEEK_SET); +static void ProcessDump(const AllocEntry* entries, size_t num_entries, size_t max_threads) { + // Do a pass to get the maximum number of allocations used at one + // time to allow a single mmap that can hold the maximum number of + // pointers needed at once. + size_t max_allocs = GetMaxAllocs(entries, num_entries); Pointers pointers(max_allocs); Threads threads(&pointers, max_threads); - printf("Maximum threads available: %zu\n", threads.max_threads()); - printf("Maximum allocations in dump: %zu\n", max_allocs); - printf("Total pointers available: %zu\n", pointers.max_pointers()); - printf("\n"); - - PrintNativeInfo("Initial "); - - LineBuffer line_buf(fd, g_buffer, sizeof(g_buffer)); - char* line; - size_t line_len; - size_t line_number = 0; - while (line_buf.GetLine(&line, &line_len)) { - pid_t tid; - int line_pos = 0; - char type[128]; - uintptr_t key_pointer; - - // Every line is of this format: - // <tid>: <action_type> <pointer> - // Some actions have extra arguments which will be used and verified - // when creating the Action object. - if (sscanf(line, "%d: %s %" SCNxPTR " %n", &tid, type, &key_pointer, &line_pos) != 3) { - err(1, "Unparseable line found: %s\n", line); - } - line_number++; - if ((line_number % 100000) == 0) { - printf(" At line %zu:\n", line_number); - PrintNativeInfo(" "); + NativePrintf("Maximum threads available: %zu\n", threads.max_threads()); + NativePrintf("Maximum allocations in dump: %zu\n", max_allocs); + NativePrintf("Total pointers available: %zu\n\n", pointers.max_pointers()); + + NativePrintInfo("Initial "); + + for (size_t i = 0; i < num_entries; i++) { + if (((i + 1) % 100000) == 0) { + NativePrintf(" At line %zu:\n", i + 1); + NativePrintInfo(" "); } - Thread* thread = threads.FindThread(tid); + const AllocEntry& entry = entries[i]; + Thread* thread = threads.FindThread(entry.tid); if (thread == nullptr) { - thread = threads.CreateThread(tid); + thread = threads.CreateThread(entry.tid); } // Wait for the thread to complete any previous actions before handling // the next action. thread->WaitForReady(); - Action* action = thread->CreateAction(key_pointer, type, line + line_pos); - if (action == nullptr) { - err(1, "Cannot create action from line: %s\n", line); - } + thread->SetAllocEntry(&entry); - bool does_free = action->DoesFree(); + bool does_free = AllocDoesFree(entry); if (does_free) { // Make sure that any other threads doing allocations are complete // before triggering the action. Otherwise, another thread could @@ -127,7 +98,7 @@ void ProcessDump(int fd, size_t max_allocs, size_t max_threads) { // Tell the thread to execute the action. thread->SetPending(); - if (action->EndThread()) { + if (entries[i].type == THREAD_DONE) { // Wait for the thread to finish and clear the thread entry. threads.Finish(thread); } @@ -142,7 +113,7 @@ void ProcessDump(int fd, size_t max_allocs, size_t max_threads) { // Wait for all threads to stop processing actions. threads.WaitForAllToQuiesce(); - PrintNativeInfo("Final "); + NativePrintInfo("Final "); // Free any outstanding pointers. // This allows us to run a tool like valgrind to verify that no memory @@ -151,12 +122,12 @@ void ProcessDump(int fd, size_t max_allocs, size_t max_threads) { pointers.FreeAll(); // Print out the total time making all allocation calls. - printf("Total Allocation/Free Time: %" PRIu64 "ns %0.2fs\n", threads.total_time_nsecs(), - threads.total_time_nsecs() / 1000000000.0); + char buffer[256]; + uint64_t total_nsecs = threads.total_time_nsecs(); + NativeFormatFloat(buffer, sizeof(buffer), total_nsecs, 1000000000); + NativePrintf("Total Allocation/Free Time: %" PRIu64 "ns %ss\n", total_nsecs, buffer); } -constexpr size_t DEFAULT_MAX_THREADS = 512; - int main(int argc, char** argv) { if (argc != 2 && argc != 3) { if (argc > 3) { @@ -168,8 +139,14 @@ int main(int argc, char** argv) { return 1; } +#if defined(__LP64__) + NativePrintf("64 bit environment.\n"); +#else + NativePrintf("32 bit environment.\n"); +#endif + #if defined(__BIONIC__) - printf("Setting decay time to 1\n"); + NativePrintf("Setting decay time to 1\n"); mallopt(M_DECAY_TIME, 1); #endif @@ -178,21 +155,15 @@ int main(int argc, char** argv) { max_threads = atoi(argv[2]); } - int dump_fd = open(argv[1], O_RDONLY); - if (dump_fd == -1) { - fprintf(stderr, "Failed to open %s: %s\n", argv[1], strerror(errno)); - return 1; - } + AllocEntry* entries; + size_t num_entries; + ZipGetUnwindInfo(argv[1], &entries, &num_entries); - printf("Processing: %s\n", argv[1]); + NativePrintf("Processing: %s\n", argv[1]); - // Do a first pass to get the total number of allocations used at one - // time to allow a single mmap that can hold the maximum number of - // pointers needed at once. - size_t max_allocs = GetMaxAllocs(dump_fd); - ProcessDump(dump_fd, max_allocs, max_threads); + ProcessDump(entries, num_entries, max_threads); - close(dump_fd); + ZipFreeEntries(entries, num_entries); return 0; } diff --git a/memory_replay/tests/ActionTest.cpp b/memory_replay/tests/ActionTest.cpp deleted file mode 100644 index cd72c24e..00000000 --- a/memory_replay/tests/ActionTest.cpp +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <gtest/gtest.h> -#include <stdint.h> -#include <string.h> - -#include "Action.h" -#include "Pointers.h" - -TEST(ActionTest, malloc) { - uint8_t memory[Action::MaxActionSize()]; - const char* line = "1024"; - Action* action = Action::CreateAction(0x1234, "malloc", line, memory); - ASSERT_TRUE(action != NULL); - ASSERT_FALSE(action->DoesFree()); - ASSERT_FALSE(action->EndThread()); - - Pointers pointers(1); - action->Execute(&pointers); - void* pointer = pointers.Remove(0x1234); - ASSERT_TRUE(pointer != nullptr); - free(pointer); -} - -TEST(ActionTest, malloc_malformed) { - uint8_t memory[128]; - const char* line = ""; - Action* action = Action::CreateAction(0x1234, "malloc", line, memory); - ASSERT_FALSE(action != NULL); -} - -TEST(ActionTest, free) { - uint8_t memory[128]; - const char* line = ""; - Action* action = Action::CreateAction(0x1234, "free", line, memory); - ASSERT_TRUE(action != NULL); - ASSERT_TRUE(action->DoesFree()); - ASSERT_FALSE(action->EndThread()); - - Pointers pointers(1); - pointers.Add(0x1234, malloc(10)); - action->Execute(&pointers); -} - -TEST(ActionTest, calloc) { - uint8_t memory[128]; - const char* line = "100 10"; - Action* action = Action::CreateAction(0x1234, "calloc", line, memory); - ASSERT_TRUE(action != NULL); - ASSERT_FALSE(action->DoesFree()); - ASSERT_FALSE(action->EndThread()); - - Pointers pointers(1); - action->Execute(&pointers); - void* pointer = pointers.Remove(0x1234); - ASSERT_TRUE(pointer != nullptr); - free(pointer); -} - -TEST(ActionTest, free_zero) { - uint8_t memory[128]; - const char* line = ""; - Action* action = Action::CreateAction(0, "free", line, memory); - ASSERT_TRUE(action != NULL); - ASSERT_FALSE(action->DoesFree()); - ASSERT_FALSE(action->EndThread()); - // Should be a nop. - action->Execute(nullptr); -} - -TEST(ActionTest, calloc_malformed) { - uint8_t memory[128]; - const char* line1 = "100"; - Action* action = Action::CreateAction(0x1234, "calloc", line1, memory); - ASSERT_FALSE(action != NULL); - - const char* line2 = ""; - action = Action::CreateAction(0x1234, "calloc", line2, memory); - ASSERT_FALSE(action != NULL); -} - -TEST(ActionTest, realloc) { - uint8_t memory[128]; - const char* line = "0xabcd 100"; - Action* action = Action::CreateAction(0x1234, "realloc", line, memory); - ASSERT_TRUE(action != NULL); - ASSERT_TRUE(action->DoesFree()); - ASSERT_FALSE(action->EndThread()); - - Pointers pointers(1); - pointers.Add(0xabcd, malloc(10)); - action->Execute(&pointers); - void* pointer = pointers.Remove(0x1234); - ASSERT_TRUE(pointer != nullptr); - free(pointer); - - const char* null_line = "0x0 100"; - action = Action::CreateAction(0x1234, "realloc", null_line, memory); - ASSERT_FALSE(action->DoesFree()); - ASSERT_FALSE(action->EndThread()); - - action->Execute(&pointers); - pointer = pointers.Remove(0x1234); - ASSERT_TRUE(pointer != nullptr); - free(pointer); -} - -TEST(ActionTest, realloc_malformed) { - uint8_t memory[128]; - const char* line1 = "0x100"; - Action* action = Action::CreateAction(0x1234, "realloc", line1, memory); - ASSERT_FALSE(action != NULL); - - const char* line2 = ""; - action = Action::CreateAction(0x1234, "realloc", line2, memory); - ASSERT_FALSE(action != NULL); -} - -TEST(ActionTest, memalign) { - uint8_t memory[128]; - const char* line = "16 300"; - Action* action = Action::CreateAction(0x1234, "memalign", line, memory); - ASSERT_TRUE(action != NULL); - ASSERT_FALSE(action->DoesFree()); - ASSERT_FALSE(action->EndThread()); - - Pointers pointers(1); - action->Execute(&pointers); - void* pointer = pointers.Remove(0x1234); - ASSERT_TRUE(pointer != nullptr); - free(pointer); -} - -TEST(ActionTest, memalign_malformed) { - uint8_t memory[128]; - const char* line1 = "100"; - Action* action = Action::CreateAction(0x1234, "memalign", line1, memory); - ASSERT_FALSE(action != NULL); - - const char* line2 = ""; - action = Action::CreateAction(0x1234, "memalign", line2, memory); - ASSERT_FALSE(action != NULL); -} - -TEST(ActionTest, endthread) { - uint8_t memory[128]; - const char* line = ""; - Action* action = Action::CreateAction(0x0, "thread_done", line, memory); - ASSERT_TRUE(action != NULL); - ASSERT_FALSE(action->DoesFree()); - ASSERT_TRUE(action->EndThread()); - - action->Execute(nullptr); -} diff --git a/memory_replay/tests/AllocTest.cpp b/memory_replay/tests/AllocTest.cpp new file mode 100644 index 00000000..d5dd0573 --- /dev/null +++ b/memory_replay/tests/AllocTest.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <stdint.h> + +#include <string> + +#include <gtest/gtest.h> + +#include "Alloc.h" + +TEST(AllocTest, malloc_valid) { + std::string line = "1234: malloc 0xabd0000 20"; + AllocEntry entry; + AllocGetData(line, &entry); + EXPECT_EQ(MALLOC, entry.type); + EXPECT_EQ(1234, entry.tid); + EXPECT_EQ(0xabd0000U, entry.ptr); + EXPECT_EQ(20U, entry.size); + EXPECT_EQ(0U, entry.u.align); +} + +TEST(AllocTest, malloc_invalid) { + std::string line = "1234: malloc 0xabd0000"; + AllocEntry entry; + EXPECT_DEATH(AllocGetData(line, &entry), ""); + + line = "1234: malloc"; + EXPECT_DEATH(AllocGetData(line, &entry), ""); +} + +TEST(AllocTest, free_valid) { + std::string line = "1235: free 0x5000"; + AllocEntry entry; + AllocGetData(line, &entry); + EXPECT_EQ(FREE, entry.type); + EXPECT_EQ(1235, entry.tid); + EXPECT_EQ(0x5000U, entry.ptr); + EXPECT_EQ(0U, entry.size); + EXPECT_EQ(0U, entry.u.align); +} + +TEST(AllocTest, free_invalid) { + std::string line = "1234: free"; + AllocEntry entry; + EXPECT_DEATH(AllocGetData(line, &entry), ""); +} + +TEST(AllocTest, calloc_valid) { + std::string line = "1236: calloc 0x8000 50 30"; + AllocEntry entry; + AllocGetData(line, &entry); + EXPECT_EQ(CALLOC, entry.type); + EXPECT_EQ(1236, entry.tid); + EXPECT_EQ(0x8000U, entry.ptr); + EXPECT_EQ(30U, entry.size); + EXPECT_EQ(50U, entry.u.n_elements); +} + +TEST(AllocTest, calloc_invalid) { + std::string line = "1236: calloc 0x8000 50"; + AllocEntry entry; + EXPECT_DEATH(AllocGetData(line, &entry), ""); + + line = "1236: calloc 0x8000"; + EXPECT_DEATH(AllocGetData(line, &entry), ""); + + line = "1236: calloc"; + EXPECT_DEATH(AllocGetData(line, &entry), ""); +} + +TEST(AllocTest, realloc_valid) { + std::string line = "1237: realloc 0x9000 0x4000 80"; + AllocEntry entry; + AllocGetData(line, &entry); + EXPECT_EQ(REALLOC, entry.type); + EXPECT_EQ(1237, entry.tid); + EXPECT_EQ(0x9000U, entry.ptr); + EXPECT_EQ(80U, entry.size); + EXPECT_EQ(0x4000U, entry.u.old_ptr); +} + +TEST(AllocTest, realloc_invalid) { + std::string line = "1237: realloc 0x9000 0x4000"; + AllocEntry entry; + EXPECT_DEATH(AllocGetData(line, &entry), ""); + + line = "1237: realloc 0x9000"; + EXPECT_DEATH(AllocGetData(line, &entry), ""); + + line = "1237: realloc"; + EXPECT_DEATH(AllocGetData(line, &entry), ""); +} + +TEST(AllocTest, memalign_valid) { + std::string line = "1238: memalign 0xa000 16 89"; + AllocEntry entry; + AllocGetData(line, &entry); + EXPECT_EQ(MEMALIGN, entry.type); + EXPECT_EQ(1238, entry.tid); + EXPECT_EQ(0xa000U, entry.ptr); + EXPECT_EQ(89U, entry.size); + EXPECT_EQ(16U, entry.u.align); +} + +TEST(AllocTest, memalign_invalid) { + std::string line = "1238: memalign 0xa000 16"; + AllocEntry entry; + EXPECT_DEATH(AllocGetData(line, &entry), ""); + + line = "1238: memalign 0xa000"; + EXPECT_DEATH(AllocGetData(line, &entry), ""); + + line = "1238: memalign"; + EXPECT_DEATH(AllocGetData(line, &entry), ""); +} + +TEST(AllocTest, thread_done_valid) { + std::string line = "1239: thread_done 0x0"; + AllocEntry entry; + AllocGetData(line, &entry); + EXPECT_EQ(THREAD_DONE, entry.type); + EXPECT_EQ(1239, entry.tid); + EXPECT_EQ(0U, entry.ptr); + EXPECT_EQ(0U, entry.size); + EXPECT_EQ(0U, entry.u.old_ptr); +} + +TEST(AllocTest, thread_done_invalid) { + std::string line = "1240: thread_done"; + AllocEntry entry; + EXPECT_DEATH(AllocGetData(line, &entry), ""); +} diff --git a/memory_replay/tests/LineBufferTest.cpp b/memory_replay/tests/LineBufferTest.cpp deleted file mode 100644 index 1a310226..00000000 --- a/memory_replay/tests/LineBufferTest.cpp +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <gtest/gtest.h> - -#include <string> - -#include <android-base/file.h> - -#include "LineBuffer.h" - -class LineBufferTest : public ::testing::Test { - protected: - void SetUp() override { - tmp_file_ = new TemporaryFile(); - ASSERT_TRUE(tmp_file_->fd != -1); - } - - void TearDown() override { - delete tmp_file_; - } - - TemporaryFile* tmp_file_ = nullptr; -}; - -TEST_F(LineBufferTest, single_line) { - std::string line_data; - line_data += "Single line with newline.\n"; - ASSERT_TRUE(TEMP_FAILURE_RETRY( - write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1); - ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1)); - - char buffer[100]; - LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer)); - - char* line; - size_t line_len; - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("Single line with newline.", line); - ASSERT_EQ(sizeof("Single line with newline.") - 1, line_len); - - ASSERT_FALSE(line_buf.GetLine(&line, &line_len)); -} - -TEST_F(LineBufferTest, single_line_no_newline) { - std::string line_data; - line_data += "Single line with no newline."; - ASSERT_TRUE(TEMP_FAILURE_RETRY( - write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1); - ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1)); - - char buffer[100]; - LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer)); - - char* line; - size_t line_len; - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("Single line with no newline.", line); - ASSERT_EQ(sizeof("Single line with no newline.") - 1, line_len); - - ASSERT_FALSE(line_buf.GetLine(&line, &line_len)); -} - -TEST_F(LineBufferTest, single_read) { - std::string line_data; - line_data += "The first line.\n"; - line_data += "Second line here.\n"; - line_data += "Third line is last.\n"; - ASSERT_TRUE(TEMP_FAILURE_RETRY( - write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1); - ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1)); - - char buffer[100]; - LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer)); - - char* line; - size_t line_len; - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("The first line.", line); - ASSERT_EQ(sizeof("The first line.") - 1, line_len); - - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("Second line here.", line); - ASSERT_EQ(sizeof("Second line here.") - 1, line_len); - - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("Third line is last.", line); - ASSERT_EQ(sizeof("Third line is last.") - 1, line_len); - - ASSERT_FALSE(line_buf.GetLine(&line, &line_len)); -} - -TEST_F(LineBufferTest, single_read_no_end_newline) { - std::string line_data; - line_data += "The first line.\n"; - line_data += "Second line here.\n"; - line_data += "Third line is last no newline."; - ASSERT_TRUE(TEMP_FAILURE_RETRY( - write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1); - ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1)); - - char buffer[100]; - LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer)); - - char* line; - size_t line_len; - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("The first line.", line); - ASSERT_EQ(sizeof("The first line.") - 1, line_len); - - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("Second line here.", line); - ASSERT_EQ(sizeof("Second line here.") - 1, line_len); - - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("Third line is last no newline.", line); - ASSERT_EQ(sizeof("Third line is last no newline.") - 1, line_len); - - ASSERT_FALSE(line_buf.GetLine(&line, &line_len)); -} - -TEST_F(LineBufferTest, one_line_per_read) { - std::string line_data; - line_data += "The first line.\n"; - line_data += "Second line here.\n"; - line_data += "Third line is last.\n"; - line_data += "The fourth line.\n"; - ASSERT_TRUE(TEMP_FAILURE_RETRY( - write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1); - ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1)); - - char buffer[24]; - LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer)); - - char* line; - size_t line_len; - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("The first line.", line); - ASSERT_EQ(sizeof("The first line.") - 1, line_len); - - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("Second line here.", line); - ASSERT_EQ(sizeof("Second line here.") - 1, line_len); - - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("Third line is last.", line); - ASSERT_EQ(sizeof("Third line is last.") - 1, line_len); - - line_data += "The fourth line.\n"; - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("The fourth line.", line); - ASSERT_EQ(sizeof("The fourth line.") - 1, line_len); - - ASSERT_FALSE(line_buf.GetLine(&line, &line_len)); -} - -TEST_F(LineBufferTest, multiple_line_per_read_multiple_reads) { - std::string line_data; - line_data += "The first line.\n"; - line_data += "Second line here.\n"; - line_data += "Third line is last.\n"; - line_data += "The fourth line.\n"; - ASSERT_TRUE(TEMP_FAILURE_RETRY( - write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1); - ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1)); - - char buffer[60]; - LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer)); - - char* line; - size_t line_len; - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("The first line.", line); - ASSERT_EQ(sizeof("The first line.") - 1, line_len); - - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("Second line here.", line); - ASSERT_EQ(sizeof("Second line here.") - 1, line_len); - - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("Third line is last.", line); - ASSERT_EQ(sizeof("Third line is last.") - 1, line_len); - - line_data += "The fourth line.\n"; - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("The fourth line.", line); - ASSERT_EQ(sizeof("The fourth line.") - 1, line_len); - - ASSERT_FALSE(line_buf.GetLine(&line, &line_len)); -} - -TEST_F(LineBufferTest, line_larger_than_buffer) { - std::string line_data; - line_data += "The first line.\n"; - line_data += "Second line here.\n"; - line_data += "This is a really, really, really, kind of long.\n"; - line_data += "The fourth line.\n"; - ASSERT_TRUE(TEMP_FAILURE_RETRY( - write(tmp_file_->fd, line_data.c_str(), line_data.size())) != -1); - ASSERT_TRUE(lseek(tmp_file_->fd, 0, SEEK_SET) != off_t(-1)); - - char buffer[25]; - LineBuffer line_buf(tmp_file_->fd, buffer, sizeof(buffer)); - - char* line; - size_t line_len; - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("The first line.", line); - ASSERT_EQ(sizeof("The first line.") - 1, line_len); - - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("Second line here.", line); - ASSERT_EQ(sizeof("Second line here.") - 1, line_len); - - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("This is a really, really", line); - ASSERT_EQ(sizeof(buffer) - 1, line_len); - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ(", really, kind of long.", line); - ASSERT_EQ(sizeof(", really, kind of long.") - 1, line_len); - - line_data += "The fourth line.\n"; - ASSERT_TRUE(line_buf.GetLine(&line, &line_len)); - ASSERT_STREQ("The fourth line.", line); - ASSERT_EQ(sizeof("The fourth line.") - 1, line_len); - - ASSERT_FALSE(line_buf.GetLine(&line, &line_len)); -} diff --git a/memory_replay/tests/NativeInfoTest.cpp b/memory_replay/tests/NativeInfoTest.cpp index 44c87704..e09f0658 100644 --- a/memory_replay/tests/NativeInfoTest.cpp +++ b/memory_replay/tests/NativeInfoTest.cpp @@ -14,12 +14,12 @@ * limitations under the License. */ -#include <gtest/gtest.h> #include <stdint.h> #include <string> #include <android-base/file.h> +#include <gtest/gtest.h> #include "NativeInfo.h" @@ -61,7 +61,7 @@ TEST_F(NativeInfoTest, no_matching) { size_t rss_bytes = 1; size_t va_bytes = 1; - GetNativeInfo(tmp_file_->fd, &rss_bytes, &va_bytes); + NativeGetInfo(tmp_file_->fd, &rss_bytes, &va_bytes); ASSERT_EQ(0U, rss_bytes); ASSERT_EQ(0U, va_bytes); } @@ -122,7 +122,7 @@ TEST_F(NativeInfoTest, multiple_anons) { size_t rss_bytes = 1; size_t va_bytes = 1; - GetNativeInfo(tmp_file_->fd, &rss_bytes, &va_bytes); + NativeGetInfo(tmp_file_->fd, &rss_bytes, &va_bytes); ASSERT_EQ(32768U, rss_bytes); ASSERT_EQ(12288U, va_bytes); } @@ -183,7 +183,7 @@ TEST_F(NativeInfoTest, multiple_heaps) { size_t rss_bytes = 1; size_t va_bytes = 1; - GetNativeInfo(tmp_file_->fd, &rss_bytes, &va_bytes); + NativeGetInfo(tmp_file_->fd, &rss_bytes, &va_bytes); ASSERT_EQ(45056U, rss_bytes); ASSERT_EQ(12288U, va_bytes); } @@ -260,7 +260,7 @@ TEST_F(NativeInfoTest, mix_heap_anon) { size_t rss_bytes = 1; size_t va_bytes = 1; - GetNativeInfo(tmp_file_->fd, &rss_bytes, &va_bytes); + NativeGetInfo(tmp_file_->fd, &rss_bytes, &va_bytes); ASSERT_EQ(73728U, rss_bytes); ASSERT_EQ(12288U, va_bytes); } diff --git a/memory_replay/tests/ThreadTest.cpp b/memory_replay/tests/ThreadTest.cpp index 72492905..4cecf189 100644 --- a/memory_replay/tests/ThreadTest.cpp +++ b/memory_replay/tests/ThreadTest.cpp @@ -14,13 +14,13 @@ * limitations under the License. */ -#include <gtest/gtest.h> #include <pthread.h> #include <unistd.h> #include <utility> -#include "Action.h" +#include <gtest/gtest.h> + #include "Pointers.h" #include "Thread.h" @@ -99,16 +99,3 @@ TEST(ThreadTest, pointers) { thread.set_pointers(&pointers); ASSERT_TRUE(thread.pointers() == &pointers); } - -TEST(ThreadTest, action) { - Thread thread; - - Action* action = thread.CreateAction(0x1234, "thread_done", ""); - ASSERT_EQ(action, thread.GetAction()); - - // Verify the action object is not garbage. - action->Execute(nullptr); - - ASSERT_TRUE(action->EndThread()); - ASSERT_FALSE(action->DoesFree()); -} diff --git a/memory_replay/tests/ThreadsTest.cpp b/memory_replay/tests/ThreadsTest.cpp index c2ba023c..990c9130 100644 --- a/memory_replay/tests/ThreadsTest.cpp +++ b/memory_replay/tests/ThreadsTest.cpp @@ -16,7 +16,7 @@ #include <gtest/gtest.h> -#include "Action.h" +#include "Alloc.h" #include "Pointers.h" #include "Thread.h" #include "Threads.h" @@ -32,7 +32,8 @@ TEST(ThreadsTest, single_thread) { Thread* found_thread = threads.FindThread(900); ASSERT_EQ(thread, found_thread); - thread->CreateAction(0x1234, "thread_done", ""); + AllocEntry thread_done = {.type = THREAD_DONE}; + thread->SetAllocEntry(&thread_done); thread->SetPending(); @@ -66,9 +67,10 @@ TEST(ThreadsTest, multiple_threads) { Thread* found_thread3 = threads.FindThread(902); ASSERT_EQ(thread3, found_thread3); - thread1->CreateAction(0x1234, "thread_done", ""); - thread2->CreateAction(0x1235, "thread_done", ""); - thread3->CreateAction(0x1236, "thread_done", ""); + AllocEntry thread_done = {.type = THREAD_DONE}; + thread1->SetAllocEntry(&thread_done); + thread2->SetAllocEntry(&thread_done); + thread3->SetAllocEntry(&thread_done); thread1->SetPending(); threads.Finish(thread1); @@ -93,17 +95,26 @@ TEST(ThreadsTest, verify_quiesce) { // If WaitForAllToQuiesce is not correct, then this should provoke an error // since we are overwriting the action data while it's being used. - for (size_t i = 0; i < 512; i++) { - thread->CreateAction(0x1234 + i, "malloc", "100"); + constexpr size_t kAllocEntries = 512; + std::vector<AllocEntry> mallocs(kAllocEntries); + std::vector<AllocEntry> frees(kAllocEntries); + for (size_t i = 0; i < kAllocEntries; i++) { + mallocs[i].type = MALLOC; + mallocs[i].ptr = 0x1234 + i; + mallocs[i].size = 100; + thread->SetAllocEntry(&mallocs[i]); thread->SetPending(); threads.WaitForAllToQuiesce(); - thread->CreateAction(0x1234 + i, "free", ""); + frees[i].type = FREE; + frees[i].ptr = 0x1234 + i; + thread->SetAllocEntry(&frees[i]); thread->SetPending(); threads.WaitForAllToQuiesce(); } - thread->CreateAction(0x1236, "thread_done", ""); + AllocEntry thread_done = {.type = THREAD_DONE}; + thread->SetAllocEntry(&thread_done); thread->SetPending(); threads.Finish(thread); ASSERT_EQ(0U, threads.num_threads()); diff --git a/memory_replay/tests/ZipTest.cpp b/memory_replay/tests/ZipTest.cpp new file mode 100644 index 00000000..e5302d55 --- /dev/null +++ b/memory_replay/tests/ZipTest.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <malloc.h> +#include <stdint.h> + +#include <string> + +#include <android-base/file.h> +#include <gtest/gtest.h> + +#include "Alloc.h" +#include "Zip.h" + +std::string GetTestZip() { + return android::base::GetExecutableDirectory() + "/tests/test.zip"; +} + +TEST(ZipTest, zip_get_contents) { + EXPECT_EQ("12345: malloc 0x1000 16\n12345: free 0x1000\n", ZipGetContents(GetTestZip().c_str())); +} + +TEST(ZipTest, zip_get_contents_bad_file) { + EXPECT_EQ("", ZipGetContents("/does/not/exist")); +} + +TEST(ZipTest, zip_get_unwind_info) { + // This will allocate, so do it before getting mallinfo. + std::string file_name = GetTestZip(); + + size_t mallinfo_before = mallinfo().uordblks; + AllocEntry* entries; + size_t num_entries; + ZipGetUnwindInfo(file_name.c_str(), &entries, &num_entries); + size_t mallinfo_after = mallinfo().uordblks; + + // Verify no memory is allocated. + EXPECT_EQ(mallinfo_after, mallinfo_before); + + ASSERT_EQ(2U, num_entries); + EXPECT_EQ(12345, entries[0].tid); + EXPECT_EQ(MALLOC, entries[0].type); + EXPECT_EQ(0x1000U, entries[0].ptr); + EXPECT_EQ(16U, entries[0].size); + EXPECT_EQ(0U, entries[0].u.old_ptr); + + EXPECT_EQ(12345, entries[1].tid); + EXPECT_EQ(FREE, entries[1].type); + EXPECT_EQ(0x1000U, entries[1].ptr); + EXPECT_EQ(0U, entries[1].size); + EXPECT_EQ(0U, entries[1].u.old_ptr); + + ZipFreeEntries(entries, num_entries); +} + +TEST(ZipTest, zip_get_unwind_info_bad_file) { + AllocEntry* entries; + size_t num_entries; + EXPECT_DEATH(ZipGetUnwindInfo("/does/not/exist", &entries, &num_entries), ""); +} diff --git a/memory_replay/tests/test.zip b/memory_replay/tests/test.zip Binary files differnew file mode 100644 index 00000000..baef5597 --- /dev/null +++ b/memory_replay/tests/test.zip diff --git a/memory_replay/traces/README b/memory_replay/traces/README index d306b9a4..88b7b59b 100644 --- a/memory_replay/traces/README +++ b/memory_replay/traces/README @@ -11,7 +11,7 @@ Format of dumps: <tid> The pid_t value that is the gettid() value recorded during the run. -<action_name> +<action_name> One of: malloc - Allocate memory using the malloc function. calloc - Allocate memory using the calloc function. |