OSDN Git Service

Tool to replay memory allocation recordings.
authorChristopher Ferris <cferris@google.com>
Fri, 11 Sep 2015 22:34:23 +0000 (15:34 -0700)
committerChristopher Ferris <cferris@google.com>
Tue, 15 Sep 2015 21:33:50 +0000 (14:33 -0700)
Included with the tool are a set of app memory recordings that
can be used to judge the abilities of the native memory
allocator.

Verified the full flow by running valgrind on the host version on all
the current dumps and verifying that there are no leak, and no errors.

Change-Id: I756224991f04b61ba26c692a30a37aad0c9fb163

28 files changed:
memory_replay/Action.cpp [new file with mode: 0644]
memory_replay/Action.h [new file with mode: 0644]
memory_replay/Android.mk [new file with mode: 0644]
memory_replay/LineBuffer.cpp [new file with mode: 0644]
memory_replay/LineBuffer.h [new file with mode: 0644]
memory_replay/NativeInfo.cpp [new file with mode: 0644]
memory_replay/NativeInfo.h [new file with mode: 0644]
memory_replay/Pointers.cpp [new file with mode: 0644]
memory_replay/Pointers.h [new file with mode: 0644]
memory_replay/Thread.cpp [new file with mode: 0644]
memory_replay/Thread.h [new file with mode: 0644]
memory_replay/Threads.cpp [new file with mode: 0644]
memory_replay/Threads.h [new file with mode: 0644]
memory_replay/dumps/README [new file with mode: 0644]
memory_replay/dumps/camera.zip [new file with mode: 0644]
memory_replay/dumps/gmail.zip [new file with mode: 0644]
memory_replay/dumps/maps.zip [new file with mode: 0644]
memory_replay/dumps/surfaceflinger.zip [new file with mode: 0644]
memory_replay/dumps/system_server.zip [new file with mode: 0644]
memory_replay/dumps/systemui.zip [new file with mode: 0644]
memory_replay/dumps/youtube.zip [new file with mode: 0644]
memory_replay/main.cpp [new file with mode: 0644]
memory_replay/tests/ActionTest.cpp [new file with mode: 0644]
memory_replay/tests/LineBufferTest.cpp [new file with mode: 0644]
memory_replay/tests/NativeInfoTest.cpp [new file with mode: 0644]
memory_replay/tests/PointersTest.cpp [new file with mode: 0644]
memory_replay/tests/ThreadTest.cpp [new file with mode: 0644]
memory_replay/tests/ThreadsTest.cpp [new file with mode: 0644]

diff --git a/memory_replay/Action.cpp b/memory_replay/Action.cpp
new file mode 100644 (file)
index 0000000..3c97c43
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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 <new>
+
+#include "Action.h"
+#include "Threads.h"
+#include "Pointers.h"
+
+class EndThreadAction : public Action {
+ public:
+  EndThreadAction() {}
+
+  bool EndThread() override { return true; }
+
+  void Execute(Pointers*) override {}
+};
+
+class AllocAction : public Action {
+ public:
+  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;
+    }
+  }
+
+  void Execute(Pointers* pointers) override {
+    void* memory = malloc(size_);
+    memset(memory, 1, size_);
+    pointers->Add(key_pointer_, memory);
+  }
+};
+
+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;
+    }
+  }
+
+  void Execute(Pointers* pointers) override {
+    void* memory = calloc(n_elements_, size_);
+    memset(memory, 0, n_elements_ * size_);
+    pointers->Add(key_pointer_, memory);
+  }
+
+ 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; }
+
+  void Execute(Pointers* pointers) override {
+    void* old_memory = nullptr;
+    if (old_pointer_ != 0) {
+      old_memory = pointers->Remove(old_pointer_);
+    }
+    void* memory = realloc(old_memory, size_);
+    memset(memory, 1, size_);
+    pointers->Add(key_pointer_, memory);
+  }
+
+ 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;
+    }
+  }
+
+  void Execute(Pointers* pointers) override {
+    void* memory = memalign(align_, size_);
+    memset(memory, 1, size_);
+    pointers->Add(key_pointer_, memory);
+  }
+
+ private:
+  size_t align_ = 0;
+};
+
+class FreeAction : public AllocAction {
+ public:
+  FreeAction(uintptr_t key_pointer) : AllocAction(key_pointer) {
+  }
+
+  bool DoesFree() override { return key_pointer_ != 0; }
+
+  void Execute(Pointers* pointers) override {
+    if (key_pointer_) {
+      void* memory = pointers->Remove(key_pointer_);
+      free(memory);
+    }
+  }
+};
+
+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
new file mode 100644 (file)
index 0000000..0f010aa
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * 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 void 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/Android.mk b/memory_replay/Android.mk
new file mode 100644 (file)
index 0000000..b01fa86
--- /dev/null
@@ -0,0 +1,72 @@
+LOCAL_PATH := $(call my-dir)
+
+memory_replay_src_files := \
+       Action.cpp \
+       LineBuffer.cpp \
+       NativeInfo.cpp \
+       Pointers.cpp \
+       Thread.cpp \
+       Threads.cpp \
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(memory_replay_src_files) main.cpp
+LOCAL_CFLAGS := -Wall -Wextra -Werror
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := memory_replay
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(memory_replay_src_files) main.cpp
+LOCAL_CFLAGS := -Wall -Wextra -Werror
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := memory_replay
+include $(BUILD_HOST_EXECUTABLE)
+
+memory_replay_test_src_files := \
+       tests/ActionTest.cpp \
+       tests/LineBufferTest.cpp \
+       tests/NativeInfoTest.cpp \
+       tests/PointersTest.cpp \
+       tests/ThreadTest.cpp \
+       tests/ThreadsTest.cpp \
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+       $(memory_replay_src_files) \
+       $(memory_replay_test_src_files) \
+
+LOCAL_CFLAGS := -Wall -Wextra -Werror
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/tests
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := memory_replay_tests
+
+LOCAL_SHARED_LIBRARIES := libbase
+
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+include $(BUILD_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+       $(memory_replay_src_files) \
+       $(memory_replay_test_src_files) \
+
+LOCAL_CFLAGS := -Wall -Wextra -Werror
+LOCAL_C_INCLUDES := $(LOCAL_PATH)/tests
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := memory_replay_tests
+
+LOCAL_SHARED_LIBRARIES := libbase
+
+include $(BUILD_HOST_NATIVE_TEST)
+
+memory_replay_src_files :=
+memory_replay_test_src_files :=
diff --git a/memory_replay/LineBuffer.cpp b/memory_replay/LineBuffer.cpp
new file mode 100644 (file)
index 0000000..5e65ad6
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * 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/LineBuffer.h b/memory_replay/LineBuffer.h
new file mode 100644 (file)
index 0000000..934d302
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * 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_LINE_BUFFER_H
+#define _MEMORY_REPLAY_LINE_BUFFER_H
+
+#include <stdint.h>
+
+class LineBuffer {
+ public:
+  LineBuffer(int fd, char* buffer, size_t buffer_len);
+
+  bool GetLine(char** line, size_t* line_len);
+
+ private:
+  int fd_;
+  char* buffer_ = nullptr;
+  size_t buffer_len_ = 0;
+  size_t start_ = 0;
+  size_t bytes_ = 0;
+};
+
+#endif // _MEMORY_REPLAY_LINE_BUFFER_H
diff --git a/memory_replay/NativeInfo.cpp b/memory_replay/NativeInfo.cpp
new file mode 100644 (file)
index 0000000..b88eaf6
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2014 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 <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "LineBuffer.h"
+#include "NativeInfo.h"
+
+// This function is not re-entrant since it uses a static buffer for
+// the line data.
+void GetNativeInfo(int smaps_fd, size_t* pss_bytes, size_t* va_bytes) {
+  static char map_buffer[65535];
+  LineBuffer line_buf(smaps_fd, map_buffer, sizeof(map_buffer));
+  char* line;
+  size_t total_pss_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_pss_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;
+      }
+    } else if (native_map && sscanf(line, "Pss: %zu", &native_pss_kB) == 1) {
+      total_pss_bytes += native_pss_kB * 1024;
+    }
+  }
+  close(smaps_fd);
+  *pss_bytes = total_pss_bytes;
+  *va_bytes = total_va_bytes;
+}
+
+void PrintNativeInfo(const char* preamble) {
+  size_t pss_bytes;
+  size_t va_bytes;
+
+  int smaps_fd = open("/proc/self/smaps", O_RDONLY);
+  if (smaps_fd == -1) {
+    err(1, "Cannot open /proc/self/smaps: %s\n", strerror(errno));
+  }
+
+  GetNativeInfo(smaps_fd, &pss_bytes, &va_bytes);
+  printf("%sNative PSS: %zu bytes %0.2fMB\n", preamble, pss_bytes, pss_bytes/(1024*1024.0));
+  printf("%sNative VA Space: %zu bytes %0.2fMB\n", preamble, va_bytes, va_bytes/(1024*1024.0));
+  fflush(stdout);
+
+  close(smaps_fd);
+}
diff --git a/memory_replay/NativeInfo.h b/memory_replay/NativeInfo.h
new file mode 100644 (file)
index 0000000..5953695
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2014 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_NATIVE_INFO_H
+#define _MEMORY_REPLAY_NATIVE_INFO_H
+
+// This function is not re-entrant.
+void GetNativeInfo(int smaps_fd, size_t* pss_bytes, size_t* va_bytes);
+
+// This function is not re-entrant.
+void PrintNativeInfo(const char* preamble);
+
+#endif // _MEMORY_REPLAY_NATIVE_INFO_H
diff --git a/memory_replay/Pointers.cpp b/memory_replay/Pointers.cpp
new file mode 100644 (file)
index 0000000..b9604f0
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * 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 <err.h>
+#include <inttypes.h>
+#include <stdatomic.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "err.h"
+#include "Pointers.h"
+
+Pointers::Pointers(size_t max_allocs) {
+  size_t pagesize = getpagesize();
+  // Create a mmap that contains a 4:1 ratio of allocations to entries.
+  // 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);
+  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 PSS for this is counted right away.
+  memset(memory, 0, pointers_size_);
+  pointers_ = reinterpret_cast<pointer_data*>(memory);
+}
+
+Pointers::~Pointers() {
+  if (pointers_ != nullptr) {
+    munmap(pointers_, pointers_size_);
+    pointers_ = nullptr;
+  }
+}
+
+void Pointers::Add(uintptr_t key_pointer, void* pointer) {
+  pointer_data* data = FindEmpty(key_pointer);
+  if (data == nullptr) {
+    err(1, "No empty entry found for 0x%" PRIxPTR "\n", key_pointer);
+  }
+  atomic_store(&data->key_pointer, key_pointer);
+  data->pointer = pointer;
+}
+
+void* Pointers::Remove(uintptr_t key_pointer) {
+  if (key_pointer == 0) {
+    err(1, "Illegal zero value passed to Remove\n");
+  }
+
+  pointer_data* data = Find(key_pointer);
+  if (data == nullptr) {
+    err(1, "No pointer value found for 0x%" PRIxPTR "\n", key_pointer);
+  }
+
+  void* pointer = data->pointer;
+  atomic_store(&data->key_pointer, uintptr_t(0));
+
+  return pointer;
+}
+
+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) {
+      return pointers_ + index;
+    }
+    if (++index == max_pointers_) {
+      index = 0;
+    }
+  }
+  return nullptr;
+}
+
+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;
+    if (atomic_compare_exchange_strong(&pointers_[index].key_pointer, &empty,
+        uintptr_t(1))) {
+      return pointers_ + index;
+    }
+    if (++index == max_pointers_) {
+      index = 0;
+    }
+  }
+  return nullptr;
+}
+
+size_t Pointers::GetHash(uintptr_t key_pointer) {
+  return key_pointer % max_pointers_;
+}
+
+void Pointers::FreeAll() {
+  for (size_t i = 0; i < max_pointers_; i++) {
+    if (atomic_load(&pointers_[i].key_pointer) != 0) {
+      free(pointers_[i].pointer);
+    }
+  }
+}
diff --git a/memory_replay/Pointers.h b/memory_replay/Pointers.h
new file mode 100644 (file)
index 0000000..2491716
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * 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_POINTERS_H
+#define _MEMORY_REPLAY_POINTERS_H
+
+#include <stdatomic.h>
+#include <stdint.h>
+
+struct pointer_data {
+  std::atomic_uintptr_t key_pointer;
+  void* pointer;
+};
+
+class Pointers {
+ public:
+  Pointers(size_t max_allocs);
+  virtual ~Pointers();
+
+  void Add(uintptr_t key_pointer, void* pointer);
+
+  void* Remove(uintptr_t key_pointer);
+
+  size_t max_pointers() { return max_pointers_; }
+
+  void FreeAll();
+
+ private:
+  pointer_data* FindEmpty(uintptr_t key_pointer);
+  pointer_data* Find(uintptr_t key_pointer);
+  size_t GetHash(uintptr_t key_pointer);
+
+  pointer_data* pointers_ = nullptr;
+  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
new file mode 100644 (file)
index 0000000..497b288
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * 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 <pthread.h>
+
+#include "Action.h"
+#include "Thread.h"
+
+Thread::Thread() {
+  pthread_cond_init(&cond_, nullptr);
+}
+
+Thread::~Thread() {
+  pthread_cond_destroy(&cond_);
+}
+
+void Thread::WaitForReady() {
+  pthread_mutex_lock(&mutex_);
+  while (pending_) {
+    pthread_cond_wait(&cond_, &mutex_);
+  }
+  pthread_mutex_unlock(&mutex_);
+}
+
+void Thread::WaitForPending() {
+  pthread_mutex_lock(&mutex_);
+  while (!pending_) {
+    pthread_cond_wait(&cond_, &mutex_);
+  }
+  pthread_mutex_unlock(&mutex_);
+}
+
+void Thread::SetPending() {
+  pthread_mutex_lock(&mutex_);
+  pending_ = true;
+  pthread_mutex_unlock(&mutex_);
+  pthread_cond_signal(&cond_);
+}
+
+void Thread::ClearPending() {
+  pthread_mutex_lock(&mutex_);
+  pending_ = false;
+  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
new file mode 100644 (file)
index 0000000..6622654
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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_THREAD_H
+#define _MEMORY_REPLAY_THREAD_H
+
+#include <pthread.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+class Action;
+class Pointers;
+
+constexpr size_t ACTION_MEMORY_SIZE = 128;
+
+class Thread {
+ public:
+  Thread();
+  virtual ~Thread();
+
+  void WaitForReady();
+  void WaitForPending();
+  void SetPending();
+  void ClearPending();
+
+  Action* CreateAction(uintptr_t key_pointer, const char* type, const char* line);
+
+  void set_pointers(Pointers* pointers) { pointers_ = pointers; }
+  Pointers* pointers() { return pointers_; }
+
+  Action* GetAction() { return reinterpret_cast<Action*>(action_memory_); }
+
+ private:
+  pthread_mutex_t mutex_ = PTHREAD_MUTEX_INITIALIZER;
+  pthread_cond_t cond_;
+  bool pending_ = false;
+
+  pthread_t thread_id_;
+  pid_t tid_ = 0;
+
+  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];
+
+  friend class Threads;
+};
+
+#endif // _MEMORY_REPLAY_THREAD_H
diff --git a/memory_replay/Threads.cpp b/memory_replay/Threads.cpp
new file mode 100644 (file)
index 0000000..24835e9
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * 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 <err.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <new>
+
+#include "Action.h"
+#include "Thread.h"
+#include "Threads.h"
+
+void* ThreadRunner(void* data) {
+  Thread* thread = reinterpret_cast<Thread*>(data);
+  while (true) {
+    thread->WaitForPending();
+    Action* action = thread->GetAction();
+    action->Execute(thread->pointers());
+    bool end_thread = action->EndThread();
+    thread->ClearPending();
+    if (end_thread) {
+      break;
+    }
+  }
+  return nullptr;
+}
+
+Threads::Threads(Pointers* pointers, size_t max_threads)
+    : pointers_(pointers), max_threads_(max_threads) {
+  size_t pagesize = getpagesize();
+  data_size_ = (max_threads_ * sizeof(Thread) + pagesize - 1) & ~(pagesize - 1);
+  max_threads_ = data_size_ / sizeof(Thread);
+
+  void* memory = mmap(nullptr, data_size_, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
+  if (memory == MAP_FAILED) {
+    err(1, "Failed to map in memory for Threads: map size %zu, max threads %zu\n",
+        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_];
+}
+
+Threads::~Threads() {
+  if (threads_) {
+    munmap(threads_, data_size_);
+    threads_ = nullptr;
+    data_size_ = 0;
+  }
+}
+
+Thread* Threads::CreateThread(pid_t tid) {
+  if (num_threads_ == max_threads_) {
+    err(1, "Too many threads created, current max %zu.\n", num_threads_);
+  }
+  Thread* thread = FindEmptyEntry(tid);
+  if (thread == nullptr) {
+    err(1, "No empty entries found, current max %zu, num threads %zu\n",
+          max_threads_, num_threads_);
+  }
+  thread->tid_ = tid;
+  thread->pointers_ = pointers_;
+  if (pthread_create(&thread->thread_id_, nullptr, ThreadRunner, thread) == -1) {
+    err(1, "Failed to create thread %d: %s\n", tid, strerror(errno));
+  }
+
+  num_threads_++;
+  return thread;
+}
+
+Thread* Threads::FindThread(pid_t tid) {
+  size_t index = GetHashEntry(tid);
+  for (size_t entries = num_threads_; entries != 0; ) {
+    pid_t cur_tid = threads_[index].tid_;
+    if (cur_tid == tid) {
+      return threads_ + index;
+    }
+    if (cur_tid != 0) {
+      entries--;
+    }
+    if (++index == max_threads_) {
+      index = 0;
+    }
+  }
+  return nullptr;
+}
+
+void Threads::WaitForAllToQuiesce() {
+  for (size_t i = 0, threads = 0; threads < num_threads_; i++) {
+    pid_t cur_tid = threads_[i].tid_;
+    if (cur_tid != 0) {
+      threads++;
+      threads_[i].WaitForReady();
+    }
+  }
+}
+
+size_t Threads::GetHashEntry(pid_t tid) {
+  return tid % max_threads_;
+}
+
+Thread* Threads::FindEmptyEntry(pid_t tid) {
+  size_t index = GetHashEntry(tid);
+  for (size_t entries = 0; entries < max_threads_; entries++) {
+    if (threads_[index].tid_ == 0) {
+      return threads_ + index;
+    }
+    if (++index == max_threads_) {
+      index = 0;
+    }
+  }
+  return nullptr;
+}
+
+void Threads::Finish(Thread* thread) {
+  pthread_join(thread->thread_id_, nullptr);
+  thread->tid_ = 0;
+  num_threads_--;
+}
+
+void Threads::FinishAll() {
+  for (size_t i = 0; i < max_threads_; i++) {
+    if (threads_[i].tid_ != 0) {
+      threads_[i].CreateAction(0, "thread_done", nullptr);
+      threads_[i].SetPending();
+      Finish(threads_ + i);
+    }
+  }
+}
diff --git a/memory_replay/Threads.h b/memory_replay/Threads.h
new file mode 100644 (file)
index 0000000..af4815c
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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_THREADS_H
+#define _MEMORY_REPLAY_THREADS_H
+
+#include <stdint.h>
+#include <sys/types.h>
+
+class Pointers;
+class Thread;
+
+class Threads {
+ public:
+  Threads(Pointers* pointers, size_t max_threads);
+  virtual ~Threads();
+
+  Thread* CreateThread(pid_t tid);
+  Thread* FindThread(pid_t tid);
+  void WaitForAllToQuiesce();
+  void Finish(Thread* thread);
+  void FinishAll();
+
+  size_t num_threads() { return num_threads_; }
+  size_t max_threads() { return max_threads_; }
+
+ private:
+  Pointers* pointers_ = nullptr;
+  Thread* threads_ = nullptr;
+  size_t data_size_ = 0;
+  size_t max_threads_ = 0;
+  size_t num_threads_= 0;
+
+  Thread* FindEmptyEntry(pid_t tid);
+  size_t GetHashEntry(pid_t tid);
+
+  void ClearData();
+
+  friend Thread;
+};
+
+#endif // _MEMORY_REPLAY_THREADS_H
diff --git a/memory_replay/dumps/README b/memory_replay/dumps/README
new file mode 100644 (file)
index 0000000..d306b9a
--- /dev/null
@@ -0,0 +1,71 @@
+The files in this directory are a collection of recordings of
+the memory allocations of a set of apps.
+
+In order to run these files through the tool, they will need to be placed
+unzipped on the device.
+
+Format of dumps:
+
+<tid>: <action_name> <ptr> [<optional_arguments>]
+
+<tid>
+  The pid_t value that is the gettid() value recorded during the run.
+
+<action_name> 
+  One of:
+    malloc - Allocate memory using the malloc function.
+    calloc - Allocate memory using the calloc function.
+    memalign - Allocate memory using the memalign function. This is used
+               during recording for either memalign or posix_memalign.
+    realloc - Allocate memory using the realloc function.
+    free - Free memory allocated using one of the above actions.
+    thread_done - Terminate the thread with the given tid.
+
+Format of each action:
+
+<tid>: malloc <ptr> <size>
+ Allocation made by malloc(<size>).  <ptr> is the value returned by malloc.
+
+Example:
+
+100: malloc 0xb48390a0 48
+
+<tid>: calloc <ptr> <nmemb> <size>
+  Allocation made by calloc(<nmemb>, <size>. <ptr> is the value returned
+  by calloc.
+
+Example:
+
+200: calloc 0xb48c1100 32 8
+
+<tid>:realloc <new_ptr> <old_ptr> <size>
+  Allocation made by realloc(<old_ptr>, <size>). <old_ptr> can be 0x0
+  to indicate a realloc with a nullptr. <new_ptr> is the value returned
+  by realloc.
+
+Example:
+
+300: realloc 0x96b90920 0x93605280 150
+
+<tid>:memalign <ptr> <alignment> <size>
+  Allocation made by memalign(<alignment>, <size>). <ptr> is the value
+  returned by memalign.
+
+Example:
+
+400: memalign 0xae42d080 16 104
+
+<tid>: free <ptr>
+  Find a previously allocated pointer <ptr> and call free(<ptr>).
+  <ptr> can be 0x0 to indicate the freeing of a nullptr.
+
+Example:
+
+500: free 0xb4827400
+
+<tid>: thread_done 0x0
+  Indicates that the thread <tid> has completed.
+
+Example:
+
+600: thread_done 0x0
diff --git a/memory_replay/dumps/camera.zip b/memory_replay/dumps/camera.zip
new file mode 100644 (file)
index 0000000..b9d1fb1
Binary files /dev/null and b/memory_replay/dumps/camera.zip differ
diff --git a/memory_replay/dumps/gmail.zip b/memory_replay/dumps/gmail.zip
new file mode 100644 (file)
index 0000000..45e0d04
Binary files /dev/null and b/memory_replay/dumps/gmail.zip differ
diff --git a/memory_replay/dumps/maps.zip b/memory_replay/dumps/maps.zip
new file mode 100644 (file)
index 0000000..17cf4e7
Binary files /dev/null and b/memory_replay/dumps/maps.zip differ
diff --git a/memory_replay/dumps/surfaceflinger.zip b/memory_replay/dumps/surfaceflinger.zip
new file mode 100644 (file)
index 0000000..495f2f2
Binary files /dev/null and b/memory_replay/dumps/surfaceflinger.zip differ
diff --git a/memory_replay/dumps/system_server.zip b/memory_replay/dumps/system_server.zip
new file mode 100644 (file)
index 0000000..bb43baf
Binary files /dev/null and b/memory_replay/dumps/system_server.zip differ
diff --git a/memory_replay/dumps/systemui.zip b/memory_replay/dumps/systemui.zip
new file mode 100644 (file)
index 0000000..8909fac
Binary files /dev/null and b/memory_replay/dumps/systemui.zip differ
diff --git a/memory_replay/dumps/youtube.zip b/memory_replay/dumps/youtube.zip
new file mode 100644 (file)
index 0000000..bea68ab
Binary files /dev/null and b/memory_replay/dumps/youtube.zip differ
diff --git a/memory_replay/main.cpp b/memory_replay/main.cpp
new file mode 100644 (file)
index 0000000..ce5fcdf
--- /dev/null
@@ -0,0 +1,187 @@
+/*
+ * 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 <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "Action.h"
+#include "LineBuffer.h"
+#include "NativeInfo.h"
+#include "Pointers.h"
+#include "Thread.h"
+#include "Threads.h"
+
+static char g_buffer[65535];
+
+size_t GetMaxAllocs(int fd) {
+  lseek(fd, SEEK_SET, 0);
+  LineBuffer line_buf(fd, g_buffer, sizeof(g_buffer));
+  char* line;
+  size_t line_len;
+  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) {
+        num_allocs--;
+      }
+    } else if (*word != 't') {
+      // Skip the thread_done message.
+      num_allocs++;
+    }
+  }
+  return num_allocs;
+}
+
+void ProcessDump(int fd, size_t max_allocs, size_t max_threads) {
+  lseek(fd, SEEK_SET, 0);
+  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("    ");
+    }
+    Thread* thread = threads.FindThread(tid);
+    if (thread == nullptr) {
+      thread = threads.CreateThread(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);
+    }
+
+    bool does_free = action->DoesFree();
+    if (does_free) {
+      // Make sure that any other threads doing allocations are complete
+      // before triggering the action. Otherwise, another thread could
+      // be creating the allocation we are going to free.
+      threads.WaitForAllToQuiesce();
+    }
+
+    // Tell the thread to execute the action.
+    thread->SetPending();
+
+    if (action->EndThread()) {
+      // Wait for the thread to finish and clear the thread entry.
+      threads.Finish(thread);
+    }
+
+    // Wait for this action to complete. This avoids a race where
+    // another thread could be creating the same allocation where are
+    // trying to free.
+    if (does_free) {
+      thread->WaitForReady();
+    }
+  }
+  // Wait for all threads to stop processing actions.
+  threads.WaitForAllToQuiesce();
+
+  PrintNativeInfo("Final ");
+
+  // Free any outstanding pointers.
+  // This allows us to run a tool like valgrind to verify that no memory
+  // is leaked and everything is accounted for during a run.
+  threads.FinishAll();
+  pointers.FreeAll();
+}
+
+constexpr size_t DEFAULT_MAX_THREADS = 512;
+
+int main(int argc, char** argv) {
+  if (argc != 2 && argc != 3) {
+    if (argc > 3) {
+      fprintf(stderr, "Only two arguments are expected.\n");
+    } else {
+      fprintf(stderr, "Requires at least one argument.\n");
+    }
+    fprintf(stderr, "Usage: %s MEMORY_LOG_FILE [MAX_THREADS]\n", basename(argv[0]));
+    return 1;
+  }
+
+  size_t max_threads = DEFAULT_MAX_THREADS;
+  if (argc == 3) {
+    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;
+  }
+
+  printf("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);
+
+  close(dump_fd);
+
+  return 0;
+}
diff --git a/memory_replay/tests/ActionTest.cpp b/memory_replay/tests/ActionTest.cpp
new file mode 100644 (file)
index 0000000..cd72c24
--- /dev/null
@@ -0,0 +1,168 @@
+/*
+ * 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/LineBufferTest.cpp b/memory_replay/tests/LineBufferTest.cpp
new file mode 100644 (file)
index 0000000..8e9976e
--- /dev/null
@@ -0,0 +1,241 @@
+/*
+ * 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 <base/test_utils.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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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, SEEK_SET, 0) != 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
new file mode 100644 (file)
index 0000000..89f314e
--- /dev/null
@@ -0,0 +1,266 @@
+/*
+ * 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>
+
+#include <base/test_utils.h>
+
+#include "NativeInfo.h"
+
+class NativeInfoTest : 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(NativeInfoTest, no_matching) {
+  std::string smaps_data =
+      "b6f1a000-b6f1c000 rw-p 00000000 00:00 0          [anon:thread signal stack]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   12 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:thread signal stack]\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, SEEK_SET, 0) != off_t(-1));
+
+  size_t pss_bytes = 1;
+  size_t va_bytes = 1;
+  GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+  ASSERT_EQ(0U, pss_bytes);
+  ASSERT_EQ(0U, va_bytes);
+}
+
+TEST_F(NativeInfoTest, multiple_anons) {
+  std::string smaps_data =
+      "b6f1a000-b6f1c000 rw-p 00000000 00:00 0          [anon:libc_malloc]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   12 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:libc_malloc]\n"
+      "b6f1e000-b6f1f000 rw-p 00000000 00:00 0          [anon:libc_malloc]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   20 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:libc_malloc]\n"
+      "b6f2e000-b6f2f000 rw-p 00000000 00:00 0\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   24 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, SEEK_SET, 0) != off_t(-1));
+
+  size_t pss_bytes = 1;
+  size_t va_bytes = 1;
+  GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+  ASSERT_EQ(32768U, pss_bytes);
+  ASSERT_EQ(12288U, va_bytes);
+}
+
+TEST_F(NativeInfoTest, multiple_heaps) {
+  std::string smaps_data =
+      "b6f1a000-b6f1c000 rw-p 00000000 00:00 0          [heap]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   24 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [heap]\n"
+      "b6f1e000-b6f1f000 rw-p 00000000 00:00 0          [heap]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   20 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [heap]\n"
+      "b6f2e000-b6f2f000 rw-p 00000000 00:00 0\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   24 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, SEEK_SET, 0) != off_t(-1));
+
+  size_t pss_bytes = 1;
+  size_t va_bytes = 1;
+  GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+  ASSERT_EQ(45056U, pss_bytes);
+  ASSERT_EQ(12288U, va_bytes);
+}
+
+TEST_F(NativeInfoTest, mix_heap_anon) {
+  std::string smaps_data =
+      "b6f1a000-b6f1c000 rw-p 00000000 00:00 0          [heap]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   32 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [heap]\n"
+      "b6f1e000-b6f1f000 rw-p 00000000 00:00 0          [anon:skip]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   32 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:skip]\n"
+      "b6f2e000-b6f2f000 rw-p 00000000 00:00 0          [anon:libc_malloc]\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   40 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:           [anon:libc_malloc]\n"
+      "b6f3e000-b6f3f000 rw-p 00000000 00:00 0\n"
+      "Size:                  8 kB\n"
+      "Rss:                   0 kB\n"
+      "Pss:                   24 kB\n"
+      "Shared_Clean:          0 kB\n"
+      "Shared_Dirty:          0 kB\n"
+      "Private_Clean:         0 kB\n"
+      "Private_Dirty:         0 kB\n"
+      "Referenced:            0 kB\n"
+      "Anonymous:             0 kB\n"
+      "AnonHugePages:         0 kB\n"
+      "Swap:                  0 kB\n"
+      "KernelPageSize:        4 kB\n"
+      "MMUPageSize:           4 kB\n"
+      "Locked:                0 kB\n"
+      "Name:\n";
+  ASSERT_TRUE(TEMP_FAILURE_RETRY(
+      write(tmp_file_->fd, smaps_data.c_str(), smaps_data.size())) != -1);
+  ASSERT_TRUE(lseek(tmp_file_->fd, SEEK_SET, 0) != off_t(-1));
+
+  size_t pss_bytes = 1;
+  size_t va_bytes = 1;
+  GetNativeInfo(tmp_file_->fd, &pss_bytes, &va_bytes);
+  ASSERT_EQ(73728U, pss_bytes);
+  ASSERT_EQ(12288U, va_bytes);
+}
diff --git a/memory_replay/tests/PointersTest.cpp b/memory_replay/tests/PointersTest.cpp
new file mode 100644 (file)
index 0000000..a39f018
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * 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 "Pointers.h"
+
+TEST(PointersTest, smoke) {
+  Pointers pointers(1);
+
+  pointers.Add(0x1234, reinterpret_cast<void*>(0xabcd));
+  void* memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcd), memory_pointer);
+}
+
+TEST(PointersTest, readd_pointer) {
+  Pointers pointers(1);
+
+  pointers.Add(0x1234, reinterpret_cast<void*>(0xabcd));
+  void* memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcd), memory_pointer);
+  pointers.Add(0x1234, reinterpret_cast<void*>(0x5555));
+  memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0x5555), memory_pointer);
+}
+
+
+TEST(PointersTest, expect_collision) {
+  Pointers pointers(2);
+
+  // This assumes the simple hash being used will result in a collision
+  // hitting the same entry.
+  pointers.Add(0x1234, reinterpret_cast<void*>(0xabcd));
+  pointers.Add(0x11234, reinterpret_cast<void*>(0xabcf));
+  void* memory_pointer = pointers.Remove(0x11234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcf), memory_pointer);
+  memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcd), memory_pointer);
+}
+
+TEST(PointersTest, multiple_add_removes) {
+  Pointers pointers(4);
+
+  pointers.Add(0x1234, reinterpret_cast<void*>(0xabcd));
+  pointers.Add(0x1235, reinterpret_cast<void*>(0xabcf));
+  pointers.Add(0x1236, reinterpret_cast<void*>(0xabc1));
+  pointers.Add(0x1237, reinterpret_cast<void*>(0xabc2));
+
+  void* memory_pointer = pointers.Remove(0x1236);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabc1), memory_pointer);
+
+  pointers.Add(0x2349, reinterpret_cast<void*>(0x2abcd));
+
+  memory_pointer = pointers.Remove(0x1234);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabcd), memory_pointer);
+  memory_pointer = pointers.Remove(0x1237);
+  ASSERT_EQ(reinterpret_cast<void*>(0xabc2), memory_pointer);
+
+  pointers.Add(0x3500, reinterpret_cast<void*>(0x3abcd));
+
+  memory_pointer = pointers.Remove(0x3500);
+  ASSERT_EQ(reinterpret_cast<void*>(0x3abcd), memory_pointer);
+  memory_pointer = pointers.Remove(0x2349);
+  ASSERT_EQ(reinterpret_cast<void*>(0x2abcd), memory_pointer);
+}
+
+static void TestNoEntriesLeft() {
+  Pointers pointers(1);
+
+  // Even though we've requested only one pointer, we get more due
+  // to the way the data is allocated.
+  for (size_t i = 0; i <= pointers.max_pointers(); i++) {
+    pointers.Add(0x1234 + i, reinterpret_cast<void*>(0xabcd + i));
+  }
+}
+
+TEST(PointersTest_DeathTest, no_entries_left) {
+  ASSERT_EXIT(TestNoEntriesLeft(), ::testing::ExitedWithCode(1), "");
+}
+
+static void TestFindNoPointer() {
+  Pointers pointers(1);
+
+  pointers.Remove(0x1234);
+}
+
+TEST(PointersTest_DeathTest, find_no_pointer) {
+  ASSERT_EXIT(TestFindNoPointer(), ::testing::ExitedWithCode(1), "");
+}
+
+static void TestRemoveZeroValue() {
+  Pointers pointers(1);
+
+  void* memory = pointers.Remove(0);
+  if (memory) {}
+}
+
+TEST(PointersTest_DeathTest, remove_zero_value) {
+  ASSERT_EXIT(TestRemoveZeroValue(), ::testing::ExitedWithCode(1), "");
+}
diff --git a/memory_replay/tests/ThreadTest.cpp b/memory_replay/tests/ThreadTest.cpp
new file mode 100644 (file)
index 0000000..7249290
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * 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 <pthread.h>
+#include <unistd.h>
+
+#include <utility>
+
+#include "Action.h"
+#include "Pointers.h"
+#include "Thread.h"
+
+typedef std::pair<Thread*, volatile bool*> thread_data_t;
+
+TEST(ThreadTest, ready) {
+  Thread thread;
+
+  // A thread should be ready immediately. If not, this will hang forever.
+  thread.WaitForReady();
+}
+
+void* ThreadWaitForReady(void* data) {
+  thread_data_t* thread_data = reinterpret_cast<thread_data_t*>(data);
+  Thread* thread = thread_data->first;
+  volatile bool* finish = thread_data->second;
+
+  thread->WaitForReady();
+  *finish = true;
+
+  return nullptr;
+}
+
+TEST(ThreadTest, ready_thread) {
+  Thread thread;
+  volatile bool finish = false;
+  thread_data_t thread_data = std::make_pair(&thread, &finish);
+
+  thread.SetPending();
+
+  pthread_t thread_id;
+  ASSERT_TRUE(pthread_create(&thread_id, nullptr, ThreadWaitForReady, &thread_data) == 0);
+
+  ASSERT_FALSE(finish);
+  sleep(1);
+  ASSERT_FALSE(finish);
+
+  thread.ClearPending();
+  ASSERT_TRUE(pthread_join(thread_id, nullptr) == 0);
+  ASSERT_TRUE(finish);
+}
+
+void* ThreadWaitForPending(void* data) {
+  thread_data_t* thread_data = reinterpret_cast<thread_data_t*>(data);
+  Thread* thread = thread_data->first;
+  volatile bool* finish = thread_data->second;
+
+  thread->WaitForPending();
+  *finish = true;
+
+  return nullptr;
+}
+
+TEST(ThreadTest, pending) {
+  Thread thread;
+  volatile bool finish = false;
+  thread_data_t thread_data = std::make_pair(&thread, &finish);
+
+  pthread_t thread_id;
+  ASSERT_TRUE(pthread_create(&thread_id, nullptr, ThreadWaitForPending, &thread_data) == 0);
+
+  ASSERT_FALSE(finish);
+  sleep(1);
+  ASSERT_FALSE(finish);
+
+  thread.SetPending();
+  ASSERT_TRUE(pthread_join(thread_id, nullptr) == 0);
+  ASSERT_TRUE(finish);
+}
+
+TEST(ThreadTest, pointers) {
+  Pointers pointers(2);
+  Thread thread;
+
+  ASSERT_TRUE(thread.pointers() == nullptr);
+  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
new file mode 100644 (file)
index 0000000..c2ba023
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * 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 "Action.h"
+#include "Pointers.h"
+#include "Thread.h"
+#include "Threads.h"
+
+TEST(ThreadsTest, single_thread) {
+  Pointers pointers(2);
+
+  Threads threads(&pointers, 1);
+  Thread* thread = threads.CreateThread(900);
+  ASSERT_TRUE(thread != nullptr);
+  ASSERT_EQ(1U, threads.num_threads());
+
+  Thread* found_thread = threads.FindThread(900);
+  ASSERT_EQ(thread, found_thread);
+
+  thread->CreateAction(0x1234, "thread_done", "");
+
+  thread->SetPending();
+
+  threads.Finish(thread);
+
+  ASSERT_EQ(0U, threads.num_threads());
+}
+
+TEST(ThreadsTest, multiple_threads) {
+  Pointers pointers(4);
+
+  Threads threads(&pointers, 1);
+  Thread* thread1 = threads.CreateThread(900);
+  ASSERT_TRUE(thread1 != nullptr);
+  ASSERT_EQ(1U, threads.num_threads());
+
+  Thread* thread2 = threads.CreateThread(901);
+  ASSERT_TRUE(thread2 != nullptr);
+  ASSERT_EQ(2U, threads.num_threads());
+
+  Thread* thread3 = threads.CreateThread(902);
+  ASSERT_TRUE(thread3 != nullptr);
+  ASSERT_EQ(3U, threads.num_threads());
+
+  Thread* found_thread1 = threads.FindThread(900);
+  ASSERT_EQ(thread1, found_thread1);
+
+  Thread* found_thread2 = threads.FindThread(901);
+  ASSERT_EQ(thread2, found_thread2);
+
+  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", "");
+
+  thread1->SetPending();
+  threads.Finish(thread1);
+  ASSERT_EQ(2U, threads.num_threads());
+
+  thread3->SetPending();
+  threads.Finish(thread3);
+  ASSERT_EQ(1U, threads.num_threads());
+
+  thread2->SetPending();
+  threads.Finish(thread2);
+  ASSERT_EQ(0U, threads.num_threads());
+}
+
+TEST(ThreadsTest, verify_quiesce) {
+  Pointers pointers(4);
+
+  Threads threads(&pointers, 1);
+  Thread* thread = threads.CreateThread(900);
+  ASSERT_TRUE(thread != nullptr);
+  ASSERT_EQ(1U, threads.num_threads());
+
+  // 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");
+    thread->SetPending();
+    threads.WaitForAllToQuiesce();
+
+    thread->CreateAction(0x1234 + i, "free", "");
+    thread->SetPending();
+    threads.WaitForAllToQuiesce();
+  }
+
+  thread->CreateAction(0x1236, "thread_done", "");
+  thread->SetPending();
+  threads.Finish(thread);
+  ASSERT_EQ(0U, threads.num_threads());
+}
+
+static void TestTooManyThreads() {
+  Pointers pointers(4);
+
+  Threads threads(&pointers, 1);
+  for (size_t i = 0; i <= threads.max_threads(); i++) {
+    Thread* thread = threads.CreateThread(900+i);
+    ASSERT_EQ(thread, threads.FindThread(900+i));
+  }
+}
+
+TEST(ThreadsTest, too_many_threads) {
+  ASSERT_EXIT(TestTooManyThreads(), ::testing::ExitedWithCode(1), "");
+}