Merge "simpleperf: autofdo doc: Change to extbinary format" into main
diff --git a/memory_replay/Alloc.cpp b/memory_replay/Alloc.cpp
index b211218..e97dca0 100644
--- a/memory_replay/Alloc.cpp
+++ b/memory_replay/Alloc.cpp
@@ -19,28 +19,29 @@
#include <stdio.h>
#include <unistd.h>
+#include <memory_trace/MemoryTrace.h>
+
#include "Alloc.h"
-#include "AllocParser.h"
#include "Pointers.h"
#include "Utils.h"
-bool AllocDoesFree(const AllocEntry& entry) {
+bool AllocDoesFree(const memory_trace::Entry& entry) {
switch (entry.type) {
- case MALLOC:
- case CALLOC:
- case MEMALIGN:
- case THREAD_DONE:
+ case memory_trace::MALLOC:
+ case memory_trace::CALLOC:
+ case memory_trace::MEMALIGN:
+ case memory_trace::THREAD_DONE:
return false;
- case FREE:
+ case memory_trace::FREE:
return entry.ptr != 0;
- case REALLOC:
+ case memory_trace::REALLOC:
return entry.u.old_ptr != 0;
}
}
-static uint64_t MallocExecute(const AllocEntry& entry, Pointers* pointers) {
+static uint64_t MallocExecute(const memory_trace::Entry& entry, Pointers* pointers) {
int pagesize = getpagesize();
uint64_t time_nsecs = Nanotime();
void* memory = malloc(entry.size);
@@ -52,7 +53,7 @@
return time_nsecs;
}
-static uint64_t CallocExecute(const AllocEntry& entry, Pointers* pointers) {
+static uint64_t CallocExecute(const memory_trace::Entry& entry, Pointers* pointers) {
int pagesize = getpagesize();
uint64_t time_nsecs = Nanotime();
void* memory = calloc(entry.u.n_elements, entry.size);
@@ -64,7 +65,7 @@
return time_nsecs;
}
-static uint64_t ReallocExecute(const AllocEntry& entry, Pointers* pointers) {
+static uint64_t ReallocExecute(const memory_trace::Entry& entry, Pointers* pointers) {
void* old_memory = nullptr;
if (entry.u.old_ptr != 0) {
old_memory = pointers->Remove(entry.u.old_ptr);
@@ -81,7 +82,7 @@
return time_nsecs;
}
-static uint64_t MemalignExecute(const AllocEntry& entry, Pointers* pointers) {
+static uint64_t MemalignExecute(const memory_trace::Entry& entry, Pointers* pointers) {
int pagesize = getpagesize();
uint64_t time_nsecs = Nanotime();
void* memory = memalign(entry.u.align, entry.size);
@@ -93,7 +94,7 @@
return time_nsecs;
}
-static uint64_t FreeExecute(const AllocEntry& entry, Pointers* pointers) {
+static uint64_t FreeExecute(const memory_trace::Entry& entry, Pointers* pointers) {
if (entry.ptr == 0) {
return 0;
}
@@ -104,17 +105,17 @@
return Nanotime() - time_nsecs;
}
-uint64_t AllocExecute(const AllocEntry& entry, Pointers* pointers) {
+uint64_t AllocExecute(const memory_trace::Entry& entry, Pointers* pointers) {
switch (entry.type) {
- case MALLOC:
+ case memory_trace::MALLOC:
return MallocExecute(entry, pointers);
- case CALLOC:
+ case memory_trace::CALLOC:
return CallocExecute(entry, pointers);
- case REALLOC:
+ case memory_trace::REALLOC:
return ReallocExecute(entry, pointers);
- case MEMALIGN:
+ case memory_trace::MEMALIGN:
return MemalignExecute(entry, pointers);
- case FREE:
+ case memory_trace::FREE:
return FreeExecute(entry, pointers);
default:
return 0;
diff --git a/memory_replay/Alloc.h b/memory_replay/Alloc.h
index f4dcc83..b4c8768 100644
--- a/memory_replay/Alloc.h
+++ b/memory_replay/Alloc.h
@@ -16,11 +16,12 @@
#pragma once
-#include "AllocParser.h"
-
// Forward Declarations.
+namespace memory_trace {
+struct Entry;
+}
class Pointers;
-bool AllocDoesFree(const AllocEntry& entry);
+bool AllocDoesFree(const memory_trace::Entry& entry);
-uint64_t AllocExecute(const AllocEntry& entry, Pointers* pointers);
+uint64_t AllocExecute(const memory_trace::Entry& entry, Pointers* pointers);
diff --git a/memory_replay/AllocParser.cpp b/memory_replay/AllocParser.cpp
deleted file mode 100644
index ac6664a..0000000
--- a/memory_replay/AllocParser.cpp
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2022 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 <stdio.h>
-
-#include "AllocParser.h"
-
-#include <iostream>
-
-void AllocGetData(const std::string& line, AllocEntry* entry) {
- int op_prefix_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,
- &op_prefix_pos) != 3) {
- errx(1, "File Error: Failed to process %s", line.c_str());
- }
- std::string type(name);
- if (type == "thread_done") {
- entry->type = THREAD_DONE;
- } else {
- int args_offset = 0;
- const char* args_beg = &line[op_prefix_pos];
- if (type == "malloc") {
- // Format:
- // TID: malloc POINTER SIZE_OF_ALLOCATION
- if (sscanf(args_beg, "%zu%n", &entry->size, &args_offset) != 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(args_beg, "%" SCNd64 " %zu%n", &entry->u.n_elements, &entry->size,
- &args_offset) != 2) {
- errx(1, "File Error: Failed to read calloc data %s", line.c_str());
- }
- entry->type = CALLOC;
- } else if (type == "realloc") {
- // Format:
- // TID: realloc POINTER OLD_POINTER NEW_SIZE
- if (sscanf(args_beg, "%" SCNx64 " %zu%n", &entry->u.old_ptr, &entry->size,
- &args_offset) != 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(args_beg, "%" SCNd64 " %zu%n", &entry->u.align, &entry->size,
- &args_offset) != 2) {
- errx(1, "File Error: Failed to read memalign data %s", line.c_str());
- }
- entry->type = MEMALIGN;
- } else {
- errx(1, "File Error: Unknown type %s", type.c_str());
- }
-
- const char* timestamps_beg = &args_beg[args_offset];
-
- // Timestamps come after the alloc args if present, for example,
- // TID: malloc POINTER SIZE_OF_ALLOCATION START_TIME END_TIME
- int n_match = sscanf(timestamps_beg, "%" SCNd64 " %" SCNd64, &entry->st, &entry->et);
- if (n_match != EOF && n_match != 2) {
- errx(1, "File Error: Failed to read timestamps %s", line.c_str());
- }
- }
-}
diff --git a/memory_replay/AllocParser.h b/memory_replay/AllocParser.h
deleted file mode 100644
index e58be48..0000000
--- a/memory_replay/AllocParser.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2022 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 <sys/types.h>
-
-#include <string>
-
-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;
- uint64_t st = 0;
- uint64_t et = 0;
-};
-
-void AllocGetData(const std::string& line, AllocEntry* entry);
diff --git a/memory_replay/Android.bp b/memory_replay/Android.bp
index e1f3b68..41adecd 100644
--- a/memory_replay/Android.bp
+++ b/memory_replay/Android.bp
@@ -33,32 +33,46 @@
}
cc_defaults {
- name: "memory_flag_defaults",
- host_supported: false,
+ name: "memory_replay_flag_defaults",
+
+ host_supported: true,
cflags: [
"-Wall",
"-Wextra",
"-Werror",
],
-
- compile_multilib: "both",
-}
-
-cc_library_static {
- name: "liballoc_parser",
- host_supported: true,
- defaults: ["memory_flag_defaults"],
-
- export_include_dirs: ["."],
- srcs: [
- "AllocParser.cpp",
- ],
}
cc_defaults {
name: "memory_replay_defaults",
- defaults: ["memory_flag_defaults"],
+ defaults: ["memory_replay_flag_defaults"],
+
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libziparchive",
+ ],
+}
+
+cc_library_static {
+ name: "libmemory_trace",
+ host_supported: true,
+ defaults: ["memory_replay_flag_defaults"],
+
+ export_include_dirs: ["include"],
+ shared_libs: ["libbase"],
+ srcs: ["MemoryTrace.cpp"],
+
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.runtime",
+ ],
+}
+
+cc_library_static {
+ name: "libmemory_replay",
+ defaults: ["memory_replay_defaults"],
srcs: [
"Alloc.cpp",
@@ -69,24 +83,23 @@
"Threads.cpp",
],
- shared_libs: [
- "libbase",
- "libziparchive",
- ],
-
- static_libs: [
- "liballoc_parser",
+ whole_static_libs: [
+ "libmemory_trace",
],
}
cc_binary {
name: "memory_replay",
defaults: ["memory_replay_defaults"],
+ host_supported: false,
srcs: ["main.cpp"],
- static_libs: ["liblog"],
+ static_libs: [
+ "libmemory_replay",
+ ],
+ compile_multilib: "both",
multilib: {
lib32: {
suffix: "32",
@@ -99,28 +112,27 @@
cc_binary_host {
name: "filter_trace",
-
- cflags: [
- "-Wall",
- "-Wextra",
- "-Werror",
- ],
-
- shared_libs: [
- "libziparchive",
- ],
+ defaults: ["memory_replay_defaults"],
static_libs: [
- "liballoc_parser",
- "libbase",
- "liblog",
+ "libmemory_replay",
],
srcs: [
- "Alloc.cpp",
- "File.cpp",
"FilterTrace.cpp",
- "Pointers.cpp",
+ ],
+}
+
+cc_binary_host {
+ name: "print_trace",
+ defaults: ["memory_replay_defaults"],
+
+ static_libs: [
+ "libmemory_replay",
+ ],
+
+ srcs: [
+ "PrintTrace.cpp",
],
}
@@ -130,8 +142,8 @@
isolated: true,
srcs: [
- "tests/AllocTest.cpp",
"tests/FileTest.cpp",
+ "tests/MemoryTraceTest.cpp",
"tests/NativeInfoTest.cpp",
"tests/PointersTest.cpp",
"tests/ThreadTest.cpp",
@@ -140,35 +152,28 @@
local_include_dirs: ["tests"],
- target: {
- android: {
- test_suites: ["device-tests"],
- },
- },
+ static_libs: [
+ "libmemory_replay",
+ ],
data: [
"tests/test.txt",
"tests/test.zip",
],
+
+ test_suites: ["general-tests"],
}
cc_benchmark {
name: "trace_benchmark",
- defaults: ["memory_flag_defaults"],
+ defaults: ["memory_replay_defaults"],
srcs: [
- "Alloc.cpp",
"TraceBenchmark.cpp",
- "File.cpp",
- ],
-
- shared_libs: [
- "libbase",
- "libziparchive",
],
static_libs: [
- "liballoc_parser",
+ "libmemory_replay",
],
data: [
diff --git a/memory_replay/AndroidTest.xml b/memory_replay/AndroidTest.xml
deleted file mode 100644
index cf3879a..0000000
--- a/memory_replay/AndroidTest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 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.
--->
-<configuration description="Config for memory_replay_tests">
- <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
- <option name="cleanup" value="true" />
- <option name="push" value="memory_replay_tests->/data/local/tmp/memory_replay_tests" />
- </target_preparer>
- <option name="test-suite-tag" value="apct" />
- <test class="com.android.tradefed.testtype.GTest" >
- <option name="native-test-device-path" value="/data/local/tmp" />
- <option name="module-name" value="memory_replay_tests" />
- </test>
-</configuration>
diff --git a/memory_replay/File.cpp b/memory_replay/File.cpp
index e44c500..4983b4d 100644
--- a/memory_replay/File.cpp
+++ b/memory_replay/File.cpp
@@ -28,8 +28,8 @@
#include <android-base/strings.h>
#include <ziparchive/zip_archive.h>
-#include "Alloc.h"
-#include "AllocParser.h"
+#include <memory_trace/MemoryTrace.h>
+
#include "File.h"
std::string ZipGetContents(const char* filename) {
@@ -77,7 +77,7 @@
// This function should not do any memory allocations in the main function.
// Any true allocation should happen in fork'd code.
-void GetUnwindInfo(const char* filename, AllocEntry** entries, size_t* num_entries) {
+void GetUnwindInfo(const char* filename, memory_trace::Entry** 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) {
@@ -123,12 +123,13 @@
*num_entries = *reinterpret_cast<size_t*>(mem);
munmap(mem, sizeof(size_t));
- mem = mmap(nullptr, *num_entries * sizeof(AllocEntry), PROT_READ | PROT_WRITE,
+ mem = mmap(nullptr, *num_entries * sizeof(memory_trace::Entry), 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));
+ err(1, "Unable to allocate a shared map of size %zu",
+ *num_entries * sizeof(memory_trace::Entry));
}
- *entries = reinterpret_cast<AllocEntry*>(mem);
+ *entries = reinterpret_cast<memory_trace::Entry*>(mem);
if ((pid = fork()) == 0) {
std::string contents;
@@ -153,7 +154,11 @@
errx(1, "Too many entries, stopped at entry %zu", entry_idx);
}
contents[end_str] = '\0';
- AllocGetData(&contents[start_str], &(*entries)[entry_idx++]);
+ std::string error;
+ if (!memory_trace::FillInEntryFromString(&contents[start_str], (*entries)[entry_idx++],
+ error)) {
+ errx(1, "%s", error.c_str());
+ }
start_str = end_str + 1;
}
if (entry_idx != *num_entries) {
@@ -167,6 +172,6 @@
WaitPid(pid);
}
-void FreeEntries(AllocEntry* entries, size_t num_entries) {
- munmap(entries, num_entries * sizeof(AllocEntry));
+void FreeEntries(memory_trace::Entry* entries, size_t num_entries) {
+ munmap(entries, num_entries * sizeof(memory_trace::Entry));
}
diff --git a/memory_replay/File.h b/memory_replay/File.h
index c1447bb..e70ca28 100644
--- a/memory_replay/File.h
+++ b/memory_replay/File.h
@@ -21,11 +21,13 @@
#include <string>
// Forward Declarations.
-struct AllocEntry;
+namespace memory_trace {
+struct Entry;
+}
std::string ZipGetContents(const char* filename);
// If filename ends with .zip, treat as a zip file to decompress.
-void GetUnwindInfo(const char* filename, AllocEntry** entries, size_t* num_entries);
+void GetUnwindInfo(const char* filename, memory_trace::Entry** entries, size_t* num_entries);
-void FreeEntries(AllocEntry* entries, size_t num_entries);
+void FreeEntries(memory_trace::Entry* entries, size_t num_entries);
diff --git a/memory_replay/FilterTrace.cpp b/memory_replay/FilterTrace.cpp
index 27f1945..65543f0 100644
--- a/memory_replay/FilterTrace.cpp
+++ b/memory_replay/FilterTrace.cpp
@@ -28,7 +28,8 @@
#include <android-base/parseint.h>
#include <android-base/strings.h>
-#include "AllocParser.h"
+#include <memory_trace/MemoryTrace.h>
+
#include "File.h"
static std::string GetBaseExec() {
@@ -108,42 +109,18 @@
return true;
}
-static void PrintEntry(const AllocEntry& entry, size_t size, bool print_trace_format) {
+static void PrintEntry(const memory_trace::Entry& entry, size_t size, bool print_trace_format) {
if (print_trace_format) {
- switch (entry.type) {
- case REALLOC:
- if (entry.u.old_ptr == 0) {
- // Convert to a malloc since it is functionally the same.
- printf("%d: malloc %p %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr), entry.size);
- } else {
- printf("%d: realloc %p %p %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr),
- reinterpret_cast<void*>(entry.u.old_ptr), entry.size);
- }
- break;
- case MALLOC:
- printf("%d: malloc %p %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr), entry.size);
- break;
- case MEMALIGN:
- printf("%d: memalign %p %zu %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr),
- entry.u.align, entry.size);
- break;
- case CALLOC:
- printf("%d: calloc %p %zu %zu\n", entry.tid, reinterpret_cast<void*>(entry.ptr),
- entry.u.n_elements, entry.size);
- break;
- default:
- errx(1, "Invalid entry type found %d\n", entry.type);
- break;
- }
+ printf("%s\n", memory_trace::CreateStringFromEntry(entry).c_str());
} else {
- printf("%s size %zu\n", entry.type == REALLOC && entry.u.old_ptr != 0 ? "realloc" : "alloc",
- size);
+ printf("%s size %zu\n",
+ entry.type == memory_trace::REALLOC && entry.u.old_ptr != 0 ? "realloc" : "alloc", size);
}
}
static void ProcessTrace(const std::string_view& trace, size_t min_size, size_t max_size,
bool print_trace_format) {
- AllocEntry* entries;
+ memory_trace::Entry* entries;
size_t num_entries;
GetUnwindInfo(trace.data(), &entries, &num_entries);
@@ -159,14 +136,14 @@
size_t total_allocs = 0;
size_t total_reallocs = 0;
for (size_t i = 0; i < num_entries; i++) {
- const AllocEntry& entry = entries[i];
+ const memory_trace::Entry& entry = entries[i];
switch (entry.type) {
- case MALLOC:
- case MEMALIGN:
- case REALLOC:
+ case memory_trace::MALLOC:
+ case memory_trace::MEMALIGN:
+ case memory_trace::REALLOC:
if (entry.size >= min_size && entry.size <= max_size) {
PrintEntry(entry, entry.size, print_trace_format);
- if (entry.type == REALLOC) {
+ if (entry.type == memory_trace::REALLOC) {
total_reallocs++;
} else {
total_allocs++;
@@ -174,15 +151,15 @@
}
break;
- case CALLOC:
+ case memory_trace::CALLOC:
if (size_t size = entry.u.n_elements * entry.size;
size >= min_size && entry.size <= max_size) {
PrintEntry(entry, size, print_trace_format);
}
break;
- case FREE:
- case THREAD_DONE:
+ case memory_trace::FREE:
+ case memory_trace::THREAD_DONE:
default:
break;
}
diff --git a/memory_replay/MemoryTrace.cpp b/memory_replay/MemoryTrace.cpp
new file mode 100644
index 0000000..e6d21df
--- /dev/null
+++ b/memory_replay/MemoryTrace.cpp
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2022 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 <stdio.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/stringprintf.h>
+
+#include <memory_trace/MemoryTrace.h>
+
+namespace memory_trace {
+
+// This is larger than the maximum length of a possible line.
+constexpr size_t kBufferLen = 256;
+
+bool FillInEntryFromString(const std::string& line, Entry& entry, std::string& error) {
+ // All lines have this format:
+ // TID: ALLOCATION_TYPE POINTER [START_TIME_NS END_TIME_NS]
+ // 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
+ // START_TIME_NS is the start time of the operation in nanoseconds.
+ // END_TIME_NS is the end time of the operation in nanoseconds.
+ // The START_TIME_NS and END_TIME_NS are optional parameters, either both
+ // are present are neither are present.
+ int op_prefix_pos = 0;
+ char name[128];
+ if (sscanf(line.c_str(), "%d: %127s %" SCNx64 " %n", &entry.tid, name, &entry.ptr,
+ &op_prefix_pos) != 3) {
+ error = "Failed to process line: " + line;
+ return false;
+ }
+
+ // Handle each individual type of entry type.
+ std::string type(name);
+ if (type == "thread_done") {
+ // TID: thread_done 0x0 [END_TIME_NS]
+ // Where END_TIME_NS is optional.
+ entry.type = THREAD_DONE;
+ entry.start_ns = 0;
+ // Thread done has an optional time which is when the thread ended.
+ // This is the only entry type that has a single timestamp.
+ int n_match = sscanf(&line[op_prefix_pos], " %" SCNd64, &entry.end_ns);
+ entry.start_ns = 0;
+ if (n_match == EOF) {
+ entry.end_ns = 0;
+ } else if (n_match != 1) {
+ error = "Failed to read thread_done end time: " + line;
+ return false;
+ }
+ return true;
+ }
+
+ int args_offset = 0;
+ const char* args_beg = &line[op_prefix_pos];
+ if (type == "malloc") {
+ // Format:
+ // TID: malloc POINTER SIZE_OF_ALLOCATION [START_TIME_NS END_TIME_NS]
+ if (sscanf(args_beg, "%zu%n", &entry.size, &args_offset) != 1) {
+ error = "Failed to read malloc data: " + line;
+ return false;
+ }
+ entry.type = MALLOC;
+ } else if (type == "free") {
+ // Format:
+ // TID: free POINTER [START_TIME_NS END_TIME_NS]
+ entry.type = FREE;
+ } else if (type == "calloc") {
+ // Format:
+ // TID: calloc POINTER ITEM_COUNT ITEM_SIZE [START_TIME_NS END_TIME_NS]
+ if (sscanf(args_beg, "%" SCNd64 " %zu%n", &entry.u.n_elements, &entry.size, &args_offset) !=
+ 2) {
+ error = "Failed to read calloc data: " + line;
+ return false;
+ }
+ entry.type = CALLOC;
+ } else if (type == "realloc") {
+ // Format:
+ // TID: realloc POINTER OLD_POINTER NEW_SIZE [START_TIME_NS END_TIME_NS]
+ if (sscanf(args_beg, "%" SCNx64 " %zu%n", &entry.u.old_ptr, &entry.size, &args_offset) != 2) {
+ error = "Failed to read realloc data: " + line;
+ return false;
+ }
+ entry.type = REALLOC;
+ } else if (type == "memalign") {
+ // Format:
+ // TID: memalign POINTER ALIGNMENT SIZE [START_TIME_NS END_TIME_NS]
+ if (sscanf(args_beg, "%" SCNd64 " %zu%n", &entry.u.align, &entry.size, &args_offset) != 2) {
+ error = "Failed to read memalign data: " + line;
+ return false;
+ }
+ entry.type = MEMALIGN;
+ } else {
+ printf("Unknown type %s: %s\n", type.c_str(), line.c_str());
+ error = "Unknown type " + type + ": " + line;
+ return false;
+ }
+
+ const char* timestamps_beg = &args_beg[args_offset];
+
+ // Get the optional timestamps if they exist.
+ int n_match = sscanf(timestamps_beg, "%" SCNd64 " %" SCNd64, &entry.start_ns, &entry.end_ns);
+ if (n_match == EOF) {
+ entry.start_ns = 0;
+ entry.end_ns = 0;
+ } else if (n_match != 2) {
+ error = "Failed to read timestamps: " + line;
+ return false;
+ }
+ return true;
+}
+
+static const char* TypeToName(const TypeEnum type) {
+ switch (type) {
+ case CALLOC:
+ return "calloc";
+ case FREE:
+ return "free";
+ case MALLOC:
+ return "malloc";
+ case MEMALIGN:
+ return "memalign";
+ case REALLOC:
+ return "realloc";
+ case THREAD_DONE:
+ return "thread_done";
+ }
+ return "unknown";
+}
+
+static size_t FormatEntry(const Entry& entry, char* buffer, size_t buffer_len) {
+ int len = snprintf(buffer, buffer_len, "%d: %s 0x%" PRIx64, entry.tid, TypeToName(entry.type),
+ entry.ptr);
+ if (len < 0) {
+ return 0;
+ }
+ size_t cur_len = len;
+ switch (entry.type) {
+ case FREE:
+ len = 0;
+ break;
+ case CALLOC:
+ len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %" PRIu64 " %zu", entry.u.n_elements,
+ entry.size);
+ break;
+ case MALLOC:
+ len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %zu", entry.size);
+ break;
+ case MEMALIGN:
+ len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %" PRIu64 " %zu", entry.u.align,
+ entry.size);
+ break;
+ case REALLOC:
+ len = snprintf(&buffer[cur_len], buffer_len - cur_len, " 0x%" PRIx64 " %zu", entry.u.old_ptr,
+ entry.size);
+ break;
+ case THREAD_DONE:
+ // Thread done only has a single optional timestamp, end_ns.
+ if (entry.end_ns != 0) {
+ len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %" PRId64, entry.end_ns);
+ if (len < 0) {
+ return 0;
+ }
+ return cur_len + len;
+ }
+ return cur_len;
+ default:
+ return 0;
+ }
+ if (len < 0) {
+ return 0;
+ }
+
+ cur_len += len;
+ if (entry.start_ns == 0) {
+ return cur_len;
+ }
+
+ len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %" PRIu64 " %" PRIu64, entry.start_ns,
+ entry.end_ns);
+ if (len < 0) {
+ return 0;
+ }
+ return cur_len + len;
+}
+
+std::string CreateStringFromEntry(const Entry& entry) {
+ std::string line(kBufferLen, '\0');
+
+ size_t size = FormatEntry(entry, line.data(), line.size());
+ if (size == 0) {
+ return "";
+ }
+ line.resize(size);
+ return line;
+}
+
+bool WriteEntryToFd(int fd, const Entry& entry) {
+ char buffer[kBufferLen];
+ size_t size = FormatEntry(entry, buffer, sizeof(buffer));
+ if (size == 0 || size == sizeof(buffer)) {
+ return false;
+ }
+ buffer[size++] = '\n';
+ buffer[size] = '\0';
+ ssize_t bytes = TEMP_FAILURE_RETRY(write(fd, buffer, size));
+ if (bytes < 0 || static_cast<size_t>(bytes) != size) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace memory_trace
diff --git a/memory_replay/PrintTrace.cpp b/memory_replay/PrintTrace.cpp
new file mode 100644
index 0000000..951c31a
--- /dev/null
+++ b/memory_replay/PrintTrace.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2024 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 <stdio.h>
+
+#include <android-base/file.h>
+
+#include <memory_trace/MemoryTrace.h>
+
+#include "File.h"
+
+static void Usage() {
+ fprintf(stderr, "Usage: %s TRACE_FILE\n",
+ android::base::Basename(android::base::GetExecutablePath()).c_str());
+ fprintf(stderr, " TRACE_FILE\n");
+ fprintf(stderr, " The trace file\n");
+ fprintf(stderr, "\n Print a trace to stdout.\n");
+}
+
+int main(int argc, char** argv) {
+ if (argc != 2) {
+ Usage();
+ return 1;
+ }
+
+ memory_trace::Entry* entries;
+ size_t num_entries;
+ GetUnwindInfo(argv[1], &entries, &num_entries);
+
+ for (size_t i = 0; i < num_entries; i++) {
+ printf("%s\n", memory_trace::CreateStringFromEntry(entries[i]).c_str());
+ }
+
+ FreeEntries(entries, num_entries);
+ return 0;
+}
diff --git a/memory_replay/TEST_MAPPING b/memory_replay/TEST_MAPPING
new file mode 100644
index 0000000..286527a
--- /dev/null
+++ b/memory_replay/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "memory_replay_tests"
+ }
+ ]
+}
diff --git a/memory_replay/Thread.h b/memory_replay/Thread.h
index ffab01b..810bde7 100644
--- a/memory_replay/Thread.h
+++ b/memory_replay/Thread.h
@@ -21,7 +21,9 @@
#include <sys/types.h>
// Forward Declarations.
-struct AllocEntry;
+namespace memory_trace {
+struct Entry;
+}
class Pointers;
class Thread {
@@ -39,8 +41,8 @@
void set_pointers(Pointers* pointers) { pointers_ = pointers; }
Pointers* pointers() { return pointers_; }
- void SetAllocEntry(const AllocEntry* entry) { entry_ = entry; }
- const AllocEntry& GetAllocEntry() { return *entry_; }
+ void SetEntry(const memory_trace::Entry* entry) { entry_ = entry; }
+ const memory_trace::Entry& GetEntry() { return *entry_; }
private:
pthread_mutex_t mutex_ = PTHREAD_MUTEX_INITIALIZER;
@@ -53,7 +55,7 @@
Pointers* pointers_ = nullptr;
- const AllocEntry* entry_;
+ const memory_trace::Entry* entry_;
friend class Threads;
};
diff --git a/memory_replay/Threads.cpp b/memory_replay/Threads.cpp
index 15fc69f..3fcedc0 100644
--- a/memory_replay/Threads.cpp
+++ b/memory_replay/Threads.cpp
@@ -26,6 +26,8 @@
#include <new>
+#include <memory_trace/MemoryTrace.h>
+
#include "Alloc.h"
#include "Pointers.h"
#include "Thread.h"
@@ -35,9 +37,9 @@
Thread* thread = reinterpret_cast<Thread*>(data);
while (true) {
thread->WaitForPending();
- const AllocEntry& entry = thread->GetAllocEntry();
+ const memory_trace::Entry& entry = thread->GetEntry();
thread->AddTimeNsecs(AllocExecute(entry, thread->pointers()));
- bool thread_done = entry.type == THREAD_DONE;
+ bool thread_done = entry.type == memory_trace::THREAD_DONE;
thread->ClearPending();
if (thread_done) {
break;
@@ -143,10 +145,10 @@
}
void Threads::FinishAll() {
- AllocEntry thread_done = {.type = THREAD_DONE};
+ memory_trace::Entry thread_done = {.type = memory_trace::THREAD_DONE};
for (size_t i = 0; i < max_threads_; i++) {
if (threads_[i].tid_ != 0) {
- threads_[i].SetAllocEntry(&thread_done);
+ threads_[i].SetEntry(&thread_done);
threads_[i].SetPending();
Finish(threads_ + i);
}
diff --git a/memory_replay/TraceBenchmark.cpp b/memory_replay/TraceBenchmark.cpp
index a3aad57..1612997 100644
--- a/memory_replay/TraceBenchmark.cpp
+++ b/memory_replay/TraceBenchmark.cpp
@@ -35,12 +35,13 @@
#include <android-base/strings.h>
#include <benchmark/benchmark.h>
-#include "Alloc.h"
+#include <memory_trace/MemoryTrace.h>
+
#include "File.h"
#include "Utils.h"
struct TraceDataType {
- AllocEntry* entries = nullptr;
+ memory_trace::Entry* entries = nullptr;
size_t num_entries = 0;
void** ptrs = nullptr;
size_t num_ptrs = 0;
@@ -99,17 +100,17 @@
std::stack<size_t> free_indices;
std::unordered_map<uint64_t, size_t> ptr_to_index;
for (size_t i = 0; i < trace_data->num_entries; i++) {
- AllocEntry* entry = &trace_data->entries[i];
+ memory_trace::Entry* entry = &trace_data->entries[i];
switch (entry->type) {
- case MALLOC:
- case CALLOC:
- case MEMALIGN: {
+ case memory_trace::MALLOC:
+ case memory_trace::CALLOC:
+ case memory_trace::MEMALIGN: {
size_t idx = GetIndex(free_indices, &trace_data->num_ptrs);
ptr_to_index[entry->ptr] = idx;
entry->ptr = idx;
break;
}
- case REALLOC: {
+ case memory_trace::REALLOC: {
if (entry->u.old_ptr != 0) {
auto idx_entry = ptr_to_index.find(entry->u.old_ptr);
if (idx_entry == ptr_to_index.end()) {
@@ -125,7 +126,7 @@
entry->ptr = idx;
break;
}
- case FREE:
+ case memory_trace::FREE:
if (entry->ptr != 0) {
auto idx_entry = ptr_to_index.find(entry->ptr);
if (idx_entry == ptr_to_index.end()) {
@@ -136,7 +137,7 @@
ptr_to_index.erase(idx_entry);
}
break;
- case THREAD_DONE:
+ case memory_trace::THREAD_DONE:
break;
}
}
@@ -156,9 +157,9 @@
void** ptrs = trace_data->ptrs;
for (size_t i = 0; i < trace_data->num_entries; i++) {
void* ptr;
- const AllocEntry& entry = trace_data->entries[i];
+ const memory_trace::Entry& entry = trace_data->entries[i];
switch (entry.type) {
- case MALLOC:
+ case memory_trace::MALLOC:
start_ns = Nanotime();
ptr = malloc(entry.size);
if (ptr == nullptr) {
@@ -173,7 +174,7 @@
ptrs[entry.ptr] = ptr;
break;
- case CALLOC:
+ case memory_trace::CALLOC:
start_ns = Nanotime();
ptr = calloc(entry.u.n_elements, entry.size);
if (ptr == nullptr) {
@@ -188,7 +189,7 @@
ptrs[entry.ptr] = ptr;
break;
- case MEMALIGN:
+ case memory_trace::MEMALIGN:
start_ns = Nanotime();
ptr = memalign(entry.u.align, entry.size);
if (ptr == nullptr) {
@@ -203,7 +204,7 @@
ptrs[entry.ptr] = ptr;
break;
- case REALLOC:
+ case memory_trace::REALLOC:
start_ns = Nanotime();
if (entry.u.old_ptr == 0) {
ptr = realloc(nullptr, entry.size);
@@ -225,7 +226,7 @@
ptrs[entry.ptr] = ptr;
break;
- case FREE:
+ case memory_trace::FREE:
if (entry.ptr != 0) {
ptr = ptrs[entry.ptr - 1];
ptrs[entry.ptr - 1] = nullptr;
@@ -237,7 +238,7 @@
total_ns += Nanotime() - start_ns;
break;
- case THREAD_DONE:
+ case memory_trace::THREAD_DONE:
break;
}
}
@@ -249,7 +250,8 @@
// Run a trace as if all of the allocations occurred in a single thread.
// This is not completely realistic, but it is a possible worst case that
// could happen in an app.
-static void BenchmarkTrace(benchmark::State& state, const char* filename, bool enable_decay_time) {
+static void BenchmarkTrace(benchmark::State& state, const char* filename,
+ [[maybe_unused]] bool enable_decay_time) {
#if defined(__BIONIC__)
if (enable_decay_time) {
mallopt(M_DECAY_TIME, 1);
diff --git a/memory_replay/include/memory_trace/MemoryTrace.h b/memory_replay/include/memory_trace/MemoryTrace.h
new file mode 100644
index 0000000..108a921
--- /dev/null
+++ b/memory_replay/include/memory_trace/MemoryTrace.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 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 <string>
+
+namespace memory_trace {
+
+enum TypeEnum : uint8_t {
+ MALLOC = 0,
+ CALLOC,
+ MEMALIGN,
+ REALLOC,
+ FREE,
+ THREAD_DONE,
+};
+
+struct Entry {
+ pid_t tid;
+ TypeEnum type;
+ uint64_t ptr = 0;
+ size_t size = 0;
+ union {
+ uint64_t old_ptr = 0;
+ uint64_t n_elements;
+ uint64_t align;
+ } u;
+ uint64_t start_ns = 0;
+ uint64_t end_ns = 0;
+};
+
+bool FillInEntryFromString(const std::string& line, Entry& entry, std::string& error);
+
+std::string CreateStringFromEntry(const Entry& entry);
+
+// Guaranteed not to allocate.
+bool WriteEntryToFd(int fd, const Entry& entry);
+
+} // namespace memory_trace
diff --git a/memory_replay/main.cpp b/memory_replay/main.cpp
index 7d7c538..0f50953 100644
--- a/memory_replay/main.cpp
+++ b/memory_replay/main.cpp
@@ -27,6 +27,8 @@
#include <sys/types.h>
#include <unistd.h>
+#include <memory_trace/MemoryTrace.h>
+
#include "Alloc.h"
#include "File.h"
#include "NativeInfo.h"
@@ -39,28 +41,28 @@
constexpr size_t kDefaultMaxThreads = 512;
-static size_t GetMaxAllocs(const AllocEntry* entries, size_t num_entries) {
+static size_t GetMaxAllocs(const memory_trace::Entry* entries, size_t num_entries) {
size_t max_allocs = 0;
size_t num_allocs = 0;
for (size_t i = 0; i < num_entries; i++) {
switch (entries[i].type) {
- case THREAD_DONE:
+ case memory_trace::THREAD_DONE:
break;
- case MALLOC:
- case CALLOC:
- case MEMALIGN:
+ case memory_trace::MALLOC:
+ case memory_trace::CALLOC:
+ case memory_trace::MEMALIGN:
if (entries[i].ptr != 0) {
num_allocs++;
}
break;
- case REALLOC:
+ case memory_trace::REALLOC:
if (entries[i].ptr == 0 && entries[i].u.old_ptr != 0) {
num_allocs--;
} else if (entries[i].ptr != 0 && entries[i].u.old_ptr == 0) {
num_allocs++;
}
break;
- case FREE:
+ case memory_trace::FREE:
if (entries[i].ptr != 0) {
num_allocs--;
}
@@ -109,7 +111,8 @@
android_logger_list_close(list);
}
-static void ProcessDump(const AllocEntry* entries, size_t num_entries, size_t max_threads) {
+static void ProcessDump(const memory_trace::Entry* 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.
@@ -128,7 +131,7 @@
dprintf(STDOUT_FILENO, " At line %zu:\n", i + 1);
NativePrintInfo(" ");
}
- const AllocEntry& entry = entries[i];
+ const memory_trace::Entry& entry = entries[i];
Thread* thread = threads.FindThread(entry.tid);
if (thread == nullptr) {
thread = threads.CreateThread(entry.tid);
@@ -138,7 +141,7 @@
// the next action.
thread->WaitForReady();
- thread->SetAllocEntry(&entry);
+ thread->SetEntry(&entry);
bool does_free = AllocDoesFree(entry);
if (does_free) {
@@ -151,7 +154,7 @@
// Tell the thread to execute the action.
thread->SetPending();
- if (entries[i].type == THREAD_DONE) {
+ if (entries[i].type == memory_trace::THREAD_DONE) {
// Wait for the thread to finish and clear the thread entry.
threads.Finish(thread);
}
@@ -223,7 +226,7 @@
max_threads = atoi(argv[2]);
}
- AllocEntry* entries;
+ memory_trace::Entry* entries;
size_t num_entries;
GetUnwindInfo(argv[1], &entries, &num_entries);
diff --git a/memory_replay/tests/AllocTest.cpp b/memory_replay/tests/AllocTest.cpp
deleted file mode 100644
index d5dd057..0000000
--- a/memory_replay/tests/AllocTest.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * 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/FileTest.cpp b/memory_replay/tests/FileTest.cpp
index 77c0593..5d92c3d 100644
--- a/memory_replay/tests/FileTest.cpp
+++ b/memory_replay/tests/FileTest.cpp
@@ -22,7 +22,8 @@
#include <android-base/file.h>
#include <gtest/gtest.h>
-#include "Alloc.h"
+#include <memory_trace/MemoryTrace.h>
+
#include "File.h"
static std::string GetTestDirectory() {
@@ -46,7 +47,7 @@
std::string file_name = GetTestZip();
size_t mallinfo_before = mallinfo().uordblks;
- AllocEntry* entries;
+ memory_trace::Entry* entries;
size_t num_entries;
GetUnwindInfo(file_name.c_str(), &entries, &num_entries);
size_t mallinfo_after = mallinfo().uordblks;
@@ -56,13 +57,13 @@
ASSERT_EQ(2U, num_entries);
EXPECT_EQ(12345, entries[0].tid);
- EXPECT_EQ(MALLOC, entries[0].type);
+ EXPECT_EQ(memory_trace::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(memory_trace::FREE, entries[1].type);
EXPECT_EQ(0x1000U, entries[1].ptr);
EXPECT_EQ(0U, entries[1].size);
EXPECT_EQ(0U, entries[1].u.old_ptr);
@@ -71,7 +72,7 @@
}
TEST(FileTest, get_unwind_info_bad_zip_file) {
- AllocEntry* entries;
+ memory_trace::Entry* entries;
size_t num_entries;
EXPECT_DEATH(GetUnwindInfo("/does/not/exist.zip", &entries, &num_entries), "");
}
@@ -81,7 +82,7 @@
std::string file_name = GetTestDirectory() + "/test.txt";
size_t mallinfo_before = mallinfo().uordblks;
- AllocEntry* entries;
+ memory_trace::Entry* entries;
size_t num_entries;
GetUnwindInfo(file_name.c_str(), &entries, &num_entries);
size_t mallinfo_after = mallinfo().uordblks;
@@ -91,13 +92,13 @@
ASSERT_EQ(2U, num_entries);
EXPECT_EQ(98765, entries[0].tid);
- EXPECT_EQ(MEMALIGN, entries[0].type);
+ EXPECT_EQ(memory_trace::MEMALIGN, entries[0].type);
EXPECT_EQ(0xa000U, entries[0].ptr);
EXPECT_EQ(124U, entries[0].size);
EXPECT_EQ(16U, entries[0].u.align);
EXPECT_EQ(98765, entries[1].tid);
- EXPECT_EQ(FREE, entries[1].type);
+ EXPECT_EQ(memory_trace::FREE, entries[1].type);
EXPECT_EQ(0xa000U, entries[1].ptr);
EXPECT_EQ(0U, entries[1].size);
EXPECT_EQ(0U, entries[1].u.old_ptr);
@@ -106,7 +107,7 @@
}
TEST(FileTest, get_unwind_info_bad_file) {
- AllocEntry* entries;
+ memory_trace::Entry* entries;
size_t num_entries;
EXPECT_DEATH(GetUnwindInfo("/does/not/exist", &entries, &num_entries), "");
}
diff --git a/memory_replay/tests/MemoryTraceTest.cpp b/memory_replay/tests/MemoryTraceTest.cpp
new file mode 100644
index 0000000..1cdd3e2
--- /dev/null
+++ b/memory_replay/tests/MemoryTraceTest.cpp
@@ -0,0 +1,371 @@
+/*
+ * 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 <unistd.h>
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <android-base/file.h>
+#include <memory_trace/MemoryTrace.h>
+
+TEST(MemoryTraceReadTest, malloc_valid) {
+ std::string line = "1234: malloc 0xabd0000 20";
+ memory_trace::Entry entry{.start_ns = 1, .end_ns = 1};
+ std::string error;
+ ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+ EXPECT_EQ(memory_trace::MALLOC, entry.type);
+ EXPECT_EQ(1234, entry.tid);
+ EXPECT_EQ(0xabd0000U, entry.ptr);
+ EXPECT_EQ(20U, entry.size);
+ EXPECT_EQ(0U, entry.u.align);
+ EXPECT_EQ(0U, entry.start_ns);
+ EXPECT_EQ(0U, entry.end_ns);
+
+ line += " 1000 1020";
+ ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+ EXPECT_EQ(memory_trace::MALLOC, entry.type);
+ EXPECT_EQ(1234, entry.tid);
+ EXPECT_EQ(0xabd0000U, entry.ptr);
+ EXPECT_EQ(20U, entry.size);
+ EXPECT_EQ(0U, entry.u.align);
+ EXPECT_EQ(1000U, entry.start_ns);
+ EXPECT_EQ(1020U, entry.end_ns);
+}
+
+TEST(MemoryTraceReadTest, malloc_invalid) {
+ // Missing size
+ std::string line = "1234: malloc 0xabd0000";
+ memory_trace::Entry entry;
+ std::string error;
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to read malloc data: 1234: malloc 0xabd0000", error);
+
+ // Missing pointer and size
+ line = "1234: malloc";
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to process line: 1234: malloc", error);
+
+ // Missing end time
+ line = "1234: malloc 0xabd0000 10 100";
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to read timestamps: 1234: malloc 0xabd0000 10 100", error);
+}
+
+TEST(MemoryTraceReadTest, free_valid) {
+ std::string line = "1235: free 0x5000";
+ memory_trace::Entry entry{.start_ns = 1, .end_ns = 1};
+ std::string error;
+ ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+ EXPECT_EQ(memory_trace::FREE, entry.type);
+ EXPECT_EQ(1235, entry.tid);
+ EXPECT_EQ(0x5000U, entry.ptr);
+ EXPECT_EQ(0U, entry.size);
+ EXPECT_EQ(0U, entry.u.align);
+ EXPECT_EQ(0U, entry.start_ns);
+ EXPECT_EQ(0U, entry.end_ns);
+
+ line += " 540 2000";
+ ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+ EXPECT_EQ(memory_trace::FREE, entry.type);
+ EXPECT_EQ(1235, entry.tid);
+ EXPECT_EQ(0x5000U, entry.ptr);
+ EXPECT_EQ(0U, entry.size);
+ EXPECT_EQ(0U, entry.u.align);
+ EXPECT_EQ(540U, entry.start_ns);
+ EXPECT_EQ(2000U, entry.end_ns);
+}
+
+TEST(MemoryTraceReadTest, free_invalid) {
+ // Missing pointer
+ std::string line = "1234: free";
+ memory_trace::Entry entry;
+ std::string error;
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to process line: 1234: free", error);
+
+ // Missing end time
+ line = "1234: free 0x100 100";
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to read timestamps: 1234: free 0x100 100", error);
+}
+
+TEST(MemoryTraceReadTest, calloc_valid) {
+ std::string line = "1236: calloc 0x8000 50 30";
+ memory_trace::Entry entry{.start_ns = 1, .end_ns = 1};
+ std::string error;
+ ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+ EXPECT_EQ(memory_trace::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);
+ EXPECT_EQ(0U, entry.start_ns);
+ EXPECT_EQ(0U, entry.end_ns);
+
+ line += " 700 1000";
+ ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+ EXPECT_EQ(memory_trace::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);
+ EXPECT_EQ(700U, entry.start_ns);
+ EXPECT_EQ(1000U, entry.end_ns);
+}
+
+TEST(MemoryTraceReadTest, calloc_invalid) {
+ // Missing number of elements
+ std::string line = "1236: calloc 0x8000 50";
+ memory_trace::Entry entry;
+ std::string error;
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to read calloc data: 1236: calloc 0x8000 50", error);
+
+ // Missing size and number of elements
+ line = "1236: calloc 0x8000";
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to read calloc data: 1236: calloc 0x8000", error);
+
+ // Missing pointer, size and number of elements
+ line = "1236: calloc";
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to process line: 1236: calloc", error);
+
+ // Missing end time
+ line = "1236: calloc 0x8000 50 20 100";
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to read timestamps: 1236: calloc 0x8000 50 20 100", error);
+}
+
+TEST(MemoryTraceReadTest, realloc_valid) {
+ std::string line = "1237: realloc 0x9000 0x4000 80";
+ memory_trace::Entry entry{.start_ns = 1, .end_ns = 1};
+ std::string error;
+ ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+ EXPECT_EQ(memory_trace::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);
+ EXPECT_EQ(0U, entry.start_ns);
+ EXPECT_EQ(0U, entry.end_ns);
+
+ line += " 3999 10020";
+ ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+ EXPECT_EQ(memory_trace::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);
+ EXPECT_EQ(3999U, entry.start_ns);
+ EXPECT_EQ(10020U, entry.end_ns);
+}
+
+TEST(MemoryTraceReadTest, realloc_invalid) {
+ // Missing size
+ std::string line = "1237: realloc 0x9000 0x4000";
+ memory_trace::Entry entry;
+ std::string error;
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to read realloc data: 1237: realloc 0x9000 0x4000", error);
+
+ // Missing size and old pointer
+ line = "1237: realloc 0x9000";
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to read realloc data: 1237: realloc 0x9000", error);
+
+ // Missing new pointer, size and old pointer
+ line = "1237: realloc";
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to process line: 1237: realloc", error);
+
+ // Missing end time
+ line = "1237: realloc 0x9000 0x4000 10 500";
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to read timestamps: 1237: realloc 0x9000 0x4000 10 500", error);
+}
+
+TEST(MemoryTraceReadTest, memalign_valid) {
+ std::string line = "1238: memalign 0xa000 16 89";
+ memory_trace::Entry entry{.start_ns = 1, .end_ns = 1};
+ std::string error;
+ ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+ EXPECT_EQ(memory_trace::MEMALIGN, entry.type);
+ EXPECT_EQ(1238, entry.tid);
+ EXPECT_EQ(0xa000U, entry.ptr);
+ EXPECT_EQ(89U, entry.size);
+ EXPECT_EQ(16U, entry.u.align);
+ EXPECT_EQ(0U, entry.start_ns);
+ EXPECT_EQ(0U, entry.end_ns);
+
+ line += " 900 1000";
+ ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+ EXPECT_EQ(memory_trace::MEMALIGN, entry.type);
+ EXPECT_EQ(1238, entry.tid);
+ EXPECT_EQ(0xa000U, entry.ptr);
+ EXPECT_EQ(89U, entry.size);
+ EXPECT_EQ(16U, entry.u.align);
+ EXPECT_EQ(900U, entry.start_ns);
+ EXPECT_EQ(1000U, entry.end_ns);
+}
+
+TEST(MemoryTraceReadTest, memalign_invalid) {
+ // Missing size
+ std::string line = "1238: memalign 0xa000 16";
+ memory_trace::Entry entry;
+ std::string error;
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to read memalign data: 1238: memalign 0xa000 16", error);
+
+ // Missing alignment and size
+ line = "1238: memalign 0xa000";
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to read memalign data: 1238: memalign 0xa000", error);
+
+ // Missing pointer, alignment and size
+ line = "1238: memalign";
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to process line: 1238: memalign", error);
+
+ // Missing end time
+ line = "1238: memalign 0xa000 16 10 800";
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to read timestamps: 1238: memalign 0xa000 16 10 800", error);
+}
+
+TEST(MemoryTraceReadTest, thread_done_valid) {
+ std::string line = "1239: thread_done 0x0";
+ memory_trace::Entry entry{.start_ns = 1, .end_ns = 1};
+ std::string error;
+ ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+ EXPECT_EQ(memory_trace::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);
+ EXPECT_EQ(0U, entry.start_ns);
+ EXPECT_EQ(0U, entry.end_ns);
+
+ line += " 290";
+ ASSERT_TRUE(memory_trace::FillInEntryFromString(line, entry, error)) << error;
+ EXPECT_EQ(memory_trace::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);
+ EXPECT_EQ(0U, entry.start_ns);
+ EXPECT_EQ(290U, entry.end_ns);
+}
+
+TEST(MemoryTraceReadTest, thread_done_invalid) {
+ // Missing pointer
+ std::string line = "1240: thread_done";
+ memory_trace::Entry entry;
+ std::string error;
+ EXPECT_FALSE(memory_trace::FillInEntryFromString(line, entry, error));
+ EXPECT_EQ("Failed to process line: 1240: thread_done", error);
+}
+
+class MemoryTraceOutputTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ tmp_file_ = new TemporaryFile();
+ ASSERT_TRUE(tmp_file_->fd != -1);
+ }
+
+ void TearDown() override { delete tmp_file_; }
+
+ void WriteAndReadString(const memory_trace::Entry& entry, std::string& str) {
+ EXPECT_EQ(lseek(tmp_file_->fd, 0, SEEK_SET), 0);
+ EXPECT_TRUE(memory_trace::WriteEntryToFd(tmp_file_->fd, entry));
+ EXPECT_EQ(lseek(tmp_file_->fd, 0, SEEK_SET), 0);
+ EXPECT_TRUE(android::base::ReadFdToString(tmp_file_->fd, &str));
+ }
+
+ std::string WriteAndGetString(const memory_trace::Entry& entry) {
+ std::string str;
+ WriteAndReadString(entry, str);
+ return str;
+ }
+
+ void VerifyEntry(const memory_trace::Entry& entry, const std::string expected) {
+ EXPECT_EQ(expected, memory_trace::CreateStringFromEntry(entry));
+ // The WriteEntryToFd always appends a newline, but string creation doesn't.
+ EXPECT_EQ(expected + "\n", WriteAndGetString(entry));
+ }
+
+ TemporaryFile* tmp_file_ = nullptr;
+};
+
+TEST_F(MemoryTraceOutputTest, malloc_output) {
+ memory_trace::Entry entry{.tid = 123, .type = memory_trace::MALLOC, .ptr = 0x123, .size = 50};
+ VerifyEntry(entry, "123: malloc 0x123 50");
+
+ entry.start_ns = 10;
+ entry.end_ns = 200;
+ VerifyEntry(entry, "123: malloc 0x123 50 10 200");
+}
+
+TEST_F(MemoryTraceOutputTest, calloc_output) {
+ memory_trace::Entry entry{
+ .tid = 123, .type = memory_trace::CALLOC, .ptr = 0x123, .size = 200, .u.n_elements = 400};
+ VerifyEntry(entry, "123: calloc 0x123 400 200");
+
+ entry.start_ns = 15;
+ entry.end_ns = 315;
+ VerifyEntry(entry, "123: calloc 0x123 400 200 15 315");
+}
+
+TEST_F(MemoryTraceOutputTest, memalign_output) {
+ memory_trace::Entry entry{
+ .tid = 123, .type = memory_trace::MEMALIGN, .ptr = 0x123, .size = 1024, .u.align = 0x10};
+ VerifyEntry(entry, "123: memalign 0x123 16 1024");
+
+ entry.start_ns = 23;
+ entry.end_ns = 289;
+ VerifyEntry(entry, "123: memalign 0x123 16 1024 23 289");
+}
+
+TEST_F(MemoryTraceOutputTest, realloc_output) {
+ memory_trace::Entry entry{
+ .tid = 123, .type = memory_trace::REALLOC, .ptr = 0x123, .size = 300, .u.old_ptr = 0x125};
+ VerifyEntry(entry, "123: realloc 0x123 0x125 300");
+
+ entry.start_ns = 45;
+ entry.end_ns = 1000;
+ VerifyEntry(entry, "123: realloc 0x123 0x125 300 45 1000");
+}
+
+TEST_F(MemoryTraceOutputTest, free_output) {
+ memory_trace::Entry entry{.tid = 123, .type = memory_trace::FREE, .ptr = 0x123};
+ VerifyEntry(entry, "123: free 0x123");
+
+ entry.start_ns = 60;
+ entry.end_ns = 2000;
+ VerifyEntry(entry, "123: free 0x123 60 2000");
+}
+
+TEST_F(MemoryTraceOutputTest, thread_done_output) {
+ memory_trace::Entry entry{.tid = 123, .type = memory_trace::THREAD_DONE};
+ VerifyEntry(entry, "123: thread_done 0x0");
+
+ entry.start_ns = 0;
+ entry.end_ns = 2500;
+ VerifyEntry(entry, "123: thread_done 0x0 2500");
+}
diff --git a/memory_replay/tests/ThreadsTest.cpp b/memory_replay/tests/ThreadsTest.cpp
index 990c913..f9516f1 100644
--- a/memory_replay/tests/ThreadsTest.cpp
+++ b/memory_replay/tests/ThreadsTest.cpp
@@ -16,7 +16,8 @@
#include <gtest/gtest.h>
-#include "Alloc.h"
+#include <memory_trace/MemoryTrace.h>
+
#include "Pointers.h"
#include "Thread.h"
#include "Threads.h"
@@ -32,8 +33,8 @@
Thread* found_thread = threads.FindThread(900);
ASSERT_EQ(thread, found_thread);
- AllocEntry thread_done = {.type = THREAD_DONE};
- thread->SetAllocEntry(&thread_done);
+ memory_trace::Entry thread_done = {.type = memory_trace::THREAD_DONE};
+ thread->SetEntry(&thread_done);
thread->SetPending();
@@ -67,10 +68,10 @@
Thread* found_thread3 = threads.FindThread(902);
ASSERT_EQ(thread3, found_thread3);
- AllocEntry thread_done = {.type = THREAD_DONE};
- thread1->SetAllocEntry(&thread_done);
- thread2->SetAllocEntry(&thread_done);
- thread3->SetAllocEntry(&thread_done);
+ memory_trace::Entry thread_done = {.type = memory_trace::THREAD_DONE};
+ thread1->SetEntry(&thread_done);
+ thread2->SetEntry(&thread_done);
+ thread3->SetEntry(&thread_done);
thread1->SetPending();
threads.Finish(thread1);
@@ -96,25 +97,25 @@
// If WaitForAllToQuiesce is not correct, then this should provoke an error
// since we are overwriting the action data while it's being used.
constexpr size_t kAllocEntries = 512;
- std::vector<AllocEntry> mallocs(kAllocEntries);
- std::vector<AllocEntry> frees(kAllocEntries);
+ std::vector<memory_trace::Entry> mallocs(kAllocEntries);
+ std::vector<memory_trace::Entry> frees(kAllocEntries);
for (size_t i = 0; i < kAllocEntries; i++) {
- mallocs[i].type = MALLOC;
+ mallocs[i].type = memory_trace::MALLOC;
mallocs[i].ptr = 0x1234 + i;
mallocs[i].size = 100;
- thread->SetAllocEntry(&mallocs[i]);
+ thread->SetEntry(&mallocs[i]);
thread->SetPending();
threads.WaitForAllToQuiesce();
- frees[i].type = FREE;
+ frees[i].type = memory_trace::FREE;
frees[i].ptr = 0x1234 + i;
- thread->SetAllocEntry(&frees[i]);
+ thread->SetEntry(&frees[i]);
thread->SetPending();
threads.WaitForAllToQuiesce();
}
- AllocEntry thread_done = {.type = THREAD_DONE};
- thread->SetAllocEntry(&thread_done);
+ memory_trace::Entry thread_done = {.type = memory_trace::THREAD_DONE};
+ thread->SetEntry(&thread_done);
thread->SetPending();
threads.Finish(thread);
ASSERT_EQ(0U, threads.num_threads());
diff --git a/mtectrl/OWNERS b/mtectrl/OWNERS
index 79625df..c95d3cf 100644
--- a/mtectrl/OWNERS
+++ b/mtectrl/OWNERS
@@ -1,5 +1,4 @@
[email protected]
[email protected]
[email protected]
[email protected]
diff --git a/profcollectd/libprofcollectd/lib.rs b/profcollectd/libprofcollectd/lib.rs
index 87ce50b..4663e68 100644
--- a/profcollectd/libprofcollectd/lib.rs
+++ b/profcollectd/libprofcollectd/lib.rs
@@ -20,13 +20,8 @@
mod report;
mod scheduler;
mod service;
-mod simpleperf_etm_trace_provider;
-mod simpleperf_lbr_trace_provider;
mod trace_provider;
-#[cfg(feature = "test")]
-mod logging_trace_provider;
-
use anyhow::{Context, Result};
use profcollectd_aidl_interface::aidl::com::android::server::profcollect::IProfCollectd::{
self, BnProfCollectd,
diff --git a/profcollectd/libprofcollectd/trace_provider.rs b/profcollectd/libprofcollectd/trace_provider.rs
index a620743..a57d3ba 100644
--- a/profcollectd/libprofcollectd/trace_provider.rs
+++ b/profcollectd/libprofcollectd/trace_provider.rs
@@ -16,17 +16,23 @@
//! ProfCollect trace provider trait and helper functions.
+mod simpleperf_etm;
+mod simpleperf_lbr;
+
+#[cfg(feature = "test")]
+mod logging;
+
use anyhow::{anyhow, Result};
use chrono::Utc;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use std::time::Duration;
-use crate::simpleperf_etm_trace_provider::SimpleperfEtmTraceProvider;
-use crate::simpleperf_lbr_trace_provider::SimpleperfLbrTraceProvider;
+use simpleperf_etm::SimpleperfEtmTraceProvider;
+use simpleperf_lbr::SimpleperfLbrTraceProvider;
#[cfg(feature = "test")]
-use crate::logging_trace_provider::LoggingTraceProvider;
+use logging::LoggingTraceProvider;
pub trait TraceProvider {
fn get_name(&self) -> &'static str;
diff --git a/profcollectd/libprofcollectd/logging_trace_provider.rs b/profcollectd/libprofcollectd/trace_provider/logging.rs
similarity index 100%
rename from profcollectd/libprofcollectd/logging_trace_provider.rs
rename to profcollectd/libprofcollectd/trace_provider/logging.rs
diff --git a/profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs b/profcollectd/libprofcollectd/trace_provider/simpleperf_etm.rs
similarity index 100%
rename from profcollectd/libprofcollectd/simpleperf_etm_trace_provider.rs
rename to profcollectd/libprofcollectd/trace_provider/simpleperf_etm.rs
diff --git a/profcollectd/libprofcollectd/simpleperf_lbr_trace_provider.rs b/profcollectd/libprofcollectd/trace_provider/simpleperf_lbr.rs
similarity index 100%
rename from profcollectd/libprofcollectd/simpleperf_lbr_trace_provider.rs
rename to profcollectd/libprofcollectd/trace_provider/simpleperf_lbr.rs
diff --git a/simpleperf/BranchListFile.cpp b/simpleperf/BranchListFile.cpp
index 253dcdb..5e2e4d1 100644
--- a/simpleperf/BranchListFile.cpp
+++ b/simpleperf/BranchListFile.cpp
@@ -17,6 +17,7 @@
#include "BranchListFile.h"
#include "ETMDecoder.h"
+#include "ZstdUtil.h"
#include "system/extras/simpleperf/branch_list.pb.h"
namespace simpleperf {
@@ -59,44 +60,11 @@
}
bool ETMBinaryMapToString(const ETMBinaryMap& binary_map, std::string& s) {
- proto::BranchList branch_list_proto;
- branch_list_proto.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
- std::vector<char> branch_buf;
- for (const auto& p : binary_map) {
- const BinaryKey& key = p.first;
- const ETMBinary& binary = p.second;
- auto binary_proto = branch_list_proto.add_etm_data();
-
- binary_proto->set_path(key.path);
- if (!key.build_id.IsEmpty()) {
- binary_proto->set_build_id(key.build_id.ToString().substr(2));
- }
- auto opt_binary_type = ToProtoBinaryType(binary.dso_type);
- if (!opt_binary_type.has_value()) {
- return false;
- }
- binary_proto->set_type(opt_binary_type.value());
-
- for (const auto& addr_p : binary.branch_map) {
- auto addr_proto = binary_proto->add_addrs();
- addr_proto->set_addr(addr_p.first);
-
- for (const auto& branch_p : addr_p.second) {
- const std::vector<bool>& branch = branch_p.first;
- auto branch_proto = addr_proto->add_branches();
-
- branch_proto->set_branch(ETMBranchToProtoString(branch));
- branch_proto->set_branch_size(branch.size());
- branch_proto->set_count(branch_p.second);
- }
- }
-
- if (binary.dso_type == DSO_KERNEL) {
- binary_proto->mutable_kernel_info()->set_kernel_start_addr(key.kernel_start_addr);
- }
+ auto writer = BranchListProtoWriter::CreateForString(&s, false);
+ if (!writer) {
+ return false;
}
- if (!branch_list_proto.SerializeToString(&s)) {
- LOG(ERROR) << "failed to serialize branch list binary map";
+ if (!writer->Write(binary_map)) {
return false;
}
return true;
@@ -116,24 +84,13 @@
}
}
-static UnorderedETMBranchMap BuildUnorderedETMBranchMap(const proto::ETMBinary& binary_proto) {
- UnorderedETMBranchMap branch_map;
- for (size_t i = 0; i < binary_proto.addrs_size(); i++) {
- const auto& addr_proto = binary_proto.addrs(i);
- auto& b_map = branch_map[addr_proto.addr()];
- for (size_t j = 0; j < addr_proto.branches_size(); j++) {
- const auto& branch_proto = addr_proto.branches(j);
- std::vector<bool> branch =
- ProtoStringToETMBranch(branch_proto.branch(), branch_proto.branch_size());
- b_map[branch] = branch_proto.count();
- }
- }
- return branch_map;
-}
-
bool StringToETMBinaryMap(const std::string& s, ETMBinaryMap& binary_map) {
LBRData lbr_data;
- return ParseBranchListData(s, binary_map, lbr_data);
+ auto reader = BranchListProtoReader::CreateForString(s);
+ if (!reader) {
+ return false;
+ }
+ return reader->Read(binary_map, lbr_data);
}
class ETMThreadTreeWhenRecording : public ETMThreadTree {
@@ -359,79 +316,415 @@
ETMBranchListGenerator::~ETMBranchListGenerator() {}
bool LBRDataToString(const LBRData& data, std::string& s) {
- proto::BranchList branch_list_proto;
- branch_list_proto.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
- auto lbr_proto = branch_list_proto.mutable_lbr_data();
- for (const LBRSample& sample : data.samples) {
- auto sample_proto = lbr_proto->add_samples();
- sample_proto->set_binary_id(sample.binary_id);
- sample_proto->set_vaddr_in_file(sample.vaddr_in_file);
- for (const LBRBranch& branch : sample.branches) {
- auto branch_proto = sample_proto->add_branches();
- branch_proto->set_from_binary_id(branch.from_binary_id);
- branch_proto->set_to_binary_id(branch.to_binary_id);
- branch_proto->set_from_vaddr_in_file(branch.from_vaddr_in_file);
- branch_proto->set_to_vaddr_in_file(branch.to_vaddr_in_file);
- }
+ auto writer = BranchListProtoWriter::CreateForString(&s, false);
+ if (!writer) {
+ return false;
}
- for (const BinaryKey& binary : data.binaries) {
- auto binary_proto = lbr_proto->add_binaries();
- binary_proto->set_path(binary.path);
- binary_proto->set_build_id(binary.build_id.ToString().substr(2));
- }
- if (!branch_list_proto.SerializeToString(&s)) {
- LOG(ERROR) << "failed to serialize lbr data";
+ if (!writer->Write(data)) {
return false;
}
return true;
}
-bool ParseBranchListData(const std::string& s, ETMBinaryMap& etm_data, LBRData& lbr_data) {
- proto::BranchList branch_list_proto;
- if (!branch_list_proto.ParseFromString(s)) {
+std::unique_ptr<BranchListProtoWriter> BranchListProtoWriter::CreateForFile(
+ const std::string& output_filename, bool compress, size_t max_branches_per_message) {
+ auto writer = std::unique_ptr<BranchListProtoWriter>(
+ new BranchListProtoWriter(output_filename, nullptr, compress, max_branches_per_message));
+ if (!writer->WriteHeader()) {
+ return nullptr;
+ }
+ return writer;
+}
+
+std::unique_ptr<BranchListProtoWriter> BranchListProtoWriter::CreateForString(
+ std::string* output_str, bool compress, size_t max_branches_per_message) {
+ auto writer = std::unique_ptr<BranchListProtoWriter>(
+ new BranchListProtoWriter("", output_str, compress, max_branches_per_message));
+ if (!writer->WriteHeader()) {
+ return nullptr;
+ }
+ return writer;
+}
+
+bool BranchListProtoWriter::Write(const ETMBinaryMap& etm_data) {
+ if (!output_fp_ && !WriteHeader()) {
+ return false;
+ }
+ std::unique_ptr<proto::BranchList> proto_branch_list = std::make_unique<proto::BranchList>();
+ proto::ETMBinary* proto_binary = nullptr;
+ proto::ETMBinary_Address* proto_addr = nullptr;
+ size_t branch_count = 0;
+
+ auto add_proto_binary = [&](const BinaryKey& key, const ETMBinary& binary) {
+ proto_binary = proto_branch_list->add_etm_data();
+ proto_binary->set_path(key.path);
+ if (!key.build_id.IsEmpty()) {
+ proto_binary->set_build_id(key.build_id.ToString().substr(2));
+ }
+ auto opt_binary_type = ToProtoBinaryType(binary.dso_type);
+ if (!opt_binary_type.has_value()) {
+ return false;
+ }
+ proto_binary->set_type(opt_binary_type.value());
+ if (binary.dso_type == DSO_KERNEL) {
+ proto_binary->mutable_kernel_info()->set_kernel_start_addr(key.kernel_start_addr);
+ }
+ return true;
+ };
+
+ auto add_proto_addr = [&](uint64_t addr) {
+ proto_addr = proto_binary->add_addrs();
+ proto_addr->set_addr(addr);
+ };
+
+ for (const auto& [key, binary] : etm_data) {
+ if (!add_proto_binary(key, binary)) {
+ return false;
+ }
+ for (const auto& [addr, branch_map] : binary.branch_map) {
+ add_proto_addr(addr);
+ size_t new_branch_count = 0;
+ for (const auto& [branch, _] : branch_map) {
+ new_branch_count += branch.size();
+ }
+ if (branch_count + new_branch_count > max_branches_per_message_) {
+ if (!WriteProtoBranchList(*proto_branch_list)) {
+ return false;
+ }
+ proto_branch_list.reset(new proto::BranchList);
+ if (!add_proto_binary(key, binary)) {
+ return false;
+ }
+ add_proto_addr(addr);
+ branch_count = 0;
+ }
+ branch_count += new_branch_count;
+ for (const auto& [branch, count] : branch_map) {
+ proto::ETMBinary_Address_Branch* proto_branch = proto_addr->add_branches();
+ proto_branch->set_branch(ETMBranchToProtoString(branch));
+ proto_branch->set_branch_size(branch.size());
+ proto_branch->set_count(count);
+ }
+ }
+ }
+ return WriteProtoBranchList(*proto_branch_list);
+}
+
+bool BranchListProtoWriter::Write(const LBRData& lbr_data) {
+ if (!output_fp_ && !WriteHeader()) {
+ return false;
+ }
+ proto::BranchList proto_branch_list;
+ proto_branch_list.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC);
+ auto proto_lbr = proto_branch_list.mutable_lbr_data();
+ for (const LBRSample& sample : lbr_data.samples) {
+ auto proto_sample = proto_lbr->add_samples();
+ proto_sample->set_binary_id(sample.binary_id);
+ proto_sample->set_vaddr_in_file(sample.vaddr_in_file);
+ for (const LBRBranch& branch : sample.branches) {
+ auto proto_branch = proto_sample->add_branches();
+ proto_branch->set_from_binary_id(branch.from_binary_id);
+ proto_branch->set_to_binary_id(branch.to_binary_id);
+ proto_branch->set_from_vaddr_in_file(branch.from_vaddr_in_file);
+ proto_branch->set_to_vaddr_in_file(branch.to_vaddr_in_file);
+ }
+ }
+ for (const BinaryKey& binary : lbr_data.binaries) {
+ auto proto_binary = proto_lbr->add_binaries();
+ proto_binary->set_path(binary.path);
+ proto_binary->set_build_id(binary.build_id.ToString().substr(2));
+ }
+ return WriteProtoBranchList(proto_branch_list);
+}
+
+bool BranchListProtoWriter::WriteHeader() {
+ if (!output_filename_.empty()) {
+ output_fp_.reset(fopen(output_filename_.c_str(), "wbe"));
+ if (!output_fp_) {
+ PLOG(ERROR) << "failed to open " << output_filename_;
+ return false;
+ }
+ } else {
+ output_str_->clear();
+ }
+ if (!WriteData(ETM_BRANCH_LIST_PROTO_MAGIC, strlen(ETM_BRANCH_LIST_PROTO_MAGIC))) {
+ return false;
+ }
+ uint32_t version = 1;
+ if (!WriteData(&version, sizeof(version))) {
+ return false;
+ }
+ uint8_t compress = compress_ ? 1 : 0;
+ if (!WriteData(&compress, sizeof(compress))) {
+ return false;
+ }
+ return true;
+}
+
+bool BranchListProtoWriter::WriteProtoBranchList(proto::BranchList& branch_list) {
+ std::string s;
+ if (!branch_list.SerializeToString(&s)) {
+ LOG(ERROR) << "failed to serialize branch list binary map";
+ return false;
+ }
+ if (compress_ && !ZstdCompress(s.data(), s.size(), s)) {
+ return false;
+ }
+ uint32_t msg_size = s.size();
+ return WriteData(&msg_size, sizeof(msg_size)) && WriteData(s.data(), s.size());
+}
+
+bool BranchListProtoWriter::WriteData(const void* data, size_t size) {
+ if (output_fp_) {
+ if (fwrite(data, size, 1, output_fp_.get()) != 1) {
+ LOG(ERROR) << "failed to write to " << output_filename_;
+ return false;
+ }
+ } else {
+ output_str_->insert(output_str_->size(), static_cast<const char*>(data), size);
+ }
+ return true;
+}
+
+std::unique_ptr<BranchListProtoReader> BranchListProtoReader::CreateForFile(
+ const std::string& input_filename) {
+ return std::unique_ptr<BranchListProtoReader>(new BranchListProtoReader(input_filename, ""));
+}
+
+std::unique_ptr<BranchListProtoReader> BranchListProtoReader::CreateForString(
+ const std::string& input_str) {
+ return std::unique_ptr<BranchListProtoReader>(new BranchListProtoReader("", input_str));
+}
+
+bool BranchListProtoReader::Read(ETMBinaryMap& etm_data, LBRData& lbr_data) {
+ if (!input_filename_.empty()) {
+ input_fp_.reset(fopen(input_filename_.c_str(), "rbe"));
+ if (!input_fp_) {
+ PLOG(ERROR) << "failed to open " << input_filename_;
+ return false;
+ }
+ }
+ char magic[24];
+ if (!ReadData(magic, sizeof(magic)) ||
+ memcmp(magic, ETM_BRANCH_LIST_PROTO_MAGIC, sizeof(magic)) != 0) {
+ return ReadOldFileFormat(etm_data, lbr_data);
+ }
+ uint32_t version;
+ if (!ReadData(&version, sizeof(version)) && version != 1) {
+ LOG(ERROR) << "unsupported version in " << input_filename_;
+ return false;
+ }
+ uint8_t compress;
+ if (!ReadData(&compress, sizeof(compress))) {
+ return false;
+ }
+ compress_ = compress == 1;
+ long file_offset = ftell(input_fp_.get());
+ if (file_offset == -1) {
+ PLOG(ERROR) << "failed to call ftell";
+ return false;
+ }
+ uint64_t file_size = GetFileSize(input_filename_);
+ while (file_offset < file_size) {
+ uint32_t msg_size;
+ if (!ReadData(&msg_size, sizeof(msg_size))) {
+ return false;
+ }
+ proto::BranchList proto_branch_list;
+ if (!ReadProtoBranchList(msg_size, proto_branch_list)) {
+ return false;
+ }
+ for (size_t i = 0; i < proto_branch_list.etm_data_size(); i++) {
+ const proto::ETMBinary& proto_binary = proto_branch_list.etm_data(i);
+ if (!AddETMBinary(proto_binary, etm_data)) {
+ return false;
+ }
+ }
+ if (proto_branch_list.has_lbr_data()) {
+ AddLBRData(proto_branch_list.lbr_data(), lbr_data);
+ }
+ file_offset += 4 + msg_size;
+ }
+ return true;
+}
+
+bool BranchListProtoReader::AddETMBinary(const proto::ETMBinary& proto_binary,
+ ETMBinaryMap& etm_data) {
+ BinaryKey key(proto_binary.path(), BuildId(proto_binary.build_id()));
+ if (proto_binary.has_kernel_info()) {
+ key.kernel_start_addr = proto_binary.kernel_info().kernel_start_addr();
+ }
+ ETMBinary& binary = etm_data[key];
+ auto dso_type = ToDsoType(proto_binary.type());
+ if (!dso_type) {
+ LOG(ERROR) << "invalid binary type " << proto_binary.type();
+ return false;
+ }
+ binary.dso_type = dso_type.value();
+ auto& branch_map = binary.branch_map;
+ for (size_t i = 0; i < proto_binary.addrs_size(); i++) {
+ const auto& proto_addr = proto_binary.addrs(i);
+ auto& b_map = branch_map[proto_addr.addr()];
+ for (size_t j = 0; j < proto_addr.branches_size(); j++) {
+ const auto& proto_branch = proto_addr.branches(j);
+ std::vector<bool> branch =
+ ProtoStringToETMBranch(proto_branch.branch(), proto_branch.branch_size());
+ b_map[branch] = proto_branch.count();
+ }
+ }
+ return true;
+}
+
+void BranchListProtoReader::AddLBRData(const proto::LBRData& proto_lbr_data, LBRData& lbr_data) {
+ for (size_t i = 0; i < proto_lbr_data.samples_size(); ++i) {
+ const auto& proto_sample = proto_lbr_data.samples(i);
+ lbr_data.samples.resize(lbr_data.samples.size() + 1);
+ LBRSample& sample = lbr_data.samples.back();
+ sample.binary_id = proto_sample.binary_id();
+ sample.vaddr_in_file = proto_sample.vaddr_in_file();
+ sample.branches.resize(proto_sample.branches_size());
+ for (size_t j = 0; j < proto_sample.branches_size(); ++j) {
+ const auto& proto_branch = proto_sample.branches(j);
+ LBRBranch& branch = sample.branches[j];
+ branch.from_binary_id = proto_branch.from_binary_id();
+ branch.to_binary_id = proto_branch.to_binary_id();
+ branch.from_vaddr_in_file = proto_branch.from_vaddr_in_file();
+ branch.to_vaddr_in_file = proto_branch.to_vaddr_in_file();
+ }
+ }
+ for (size_t i = 0; i < proto_lbr_data.binaries_size(); ++i) {
+ const auto& proto_binary = proto_lbr_data.binaries(i);
+ lbr_data.binaries.emplace_back(proto_binary.path(), BuildId(proto_binary.build_id()));
+ }
+}
+
+bool BranchListProtoReader::ReadProtoBranchList(uint32_t size,
+ proto::BranchList& proto_branch_list) {
+ std::string s;
+ s.resize(size);
+ if (!ReadData(s.data(), size)) {
+ return false;
+ }
+ if (compress_ && !ZstdDecompress(s.data(), s.size(), s)) {
+ return false;
+ }
+ if (!proto_branch_list.ParseFromString(s)) {
PLOG(ERROR) << "failed to read ETMBranchList msg";
return false;
}
- if (branch_list_proto.magic() != ETM_BRANCH_LIST_PROTO_MAGIC) {
- PLOG(ERROR) << "not in etm branch list format in branch_list.proto";
- return false;
- }
- for (size_t i = 0; i < branch_list_proto.etm_data_size(); i++) {
- const auto& binary_proto = branch_list_proto.etm_data(i);
- BinaryKey key(binary_proto.path(), BuildId(binary_proto.build_id()));
- if (binary_proto.has_kernel_info()) {
- key.kernel_start_addr = binary_proto.kernel_info().kernel_start_addr();
- }
- ETMBinary& binary = etm_data[key];
- auto dso_type = ToDsoType(binary_proto.type());
- if (!dso_type) {
- LOG(ERROR) << "invalid binary type " << binary_proto.type();
+ return true;
+}
+
+bool BranchListProtoReader::ReadData(void* data, size_t size) {
+ if (input_fp_) {
+ if (fread(data, size, 1, input_fp_.get()) != 1) {
+ PLOG(ERROR) << "failed to read " << input_filename_;
return false;
}
- binary.dso_type = dso_type.value();
- binary.branch_map = BuildUnorderedETMBranchMap(binary_proto);
+ } else {
+ if (input_str_pos_ + size > input_str_.size()) {
+ LOG(ERROR) << "failed to read BranchList from string";
+ return false;
+ }
+ memcpy(data, &input_str_[input_str_pos_], size);
+ input_str_pos_ += size;
}
- if (branch_list_proto.has_lbr_data()) {
- const auto& lbr_data_proto = branch_list_proto.lbr_data();
- lbr_data.samples.resize(lbr_data_proto.samples_size());
- for (size_t i = 0; i < lbr_data_proto.samples_size(); ++i) {
- const auto& sample_proto = lbr_data_proto.samples(i);
- LBRSample& sample = lbr_data.samples[i];
- sample.binary_id = sample_proto.binary_id();
- sample.vaddr_in_file = sample_proto.vaddr_in_file();
- sample.branches.resize(sample_proto.branches_size());
- for (size_t j = 0; j < sample_proto.branches_size(); ++j) {
- const auto& branch_proto = sample_proto.branches(j);
- LBRBranch& branch = sample.branches[j];
- branch.from_binary_id = branch_proto.from_binary_id();
- branch.to_binary_id = branch_proto.to_binary_id();
- branch.from_vaddr_in_file = branch_proto.from_vaddr_in_file();
- branch.to_vaddr_in_file = branch_proto.to_vaddr_in_file();
+ return true;
+}
+
+bool BranchListProtoReader::ReadOldFileFormat(ETMBinaryMap& etm_data, LBRData& lbr_data) {
+ size_t size = 0;
+ if (!input_filename_.empty()) {
+ size = static_cast<size_t>(GetFileSize(input_filename_));
+ if (android::base::EndsWith(input_filename_, ".zst")) {
+ compress_ = true;
+ }
+ } else {
+ size = input_str_.size();
+ }
+ proto::BranchList proto_branch_list;
+ if (!ReadProtoBranchList(size, proto_branch_list)) {
+ return false;
+ }
+ if (proto_branch_list.magic() != ETM_BRANCH_LIST_PROTO_MAGIC) {
+ PLOG(ERROR) << "not in format of branch_list.proto";
+ }
+ for (size_t i = 0; i < proto_branch_list.etm_data_size(); i++) {
+ const proto::ETMBinary& proto_binary = proto_branch_list.etm_data(i);
+ if (!AddETMBinary(proto_binary, etm_data)) {
+ return false;
+ }
+ }
+ if (proto_branch_list.has_lbr_data()) {
+ AddLBRData(proto_branch_list.lbr_data(), lbr_data);
+ }
+ return true;
+}
+
+bool DumpBranchListFile(std::string filename) {
+ ETMBinaryMap etm_data;
+ LBRData lbr_data;
+ auto reader = BranchListProtoReader::CreateForFile(filename);
+ if (!reader || !reader->Read(etm_data, lbr_data)) {
+ return false;
+ }
+
+ if (!etm_data.empty()) {
+ std::vector<BinaryKey> sorted_keys;
+ for (const auto& [key, _] : etm_data) {
+ sorted_keys.emplace_back(key);
+ }
+ std::sort(sorted_keys.begin(), sorted_keys.end(),
+ [](const BinaryKey& key1, const BinaryKey& key2) { return key1.path < key2.path; });
+ PrintIndented(0, "etm_data:\n");
+ for (size_t i = 0; i < sorted_keys.size(); ++i) {
+ const auto& key = sorted_keys[i];
+ const auto& binary = etm_data[key];
+ PrintIndented(1, "binary[%zu].path: %s\n", i, key.path.c_str());
+ PrintIndented(1, "binary[%zu].build_id: %s\n", i, key.build_id.ToString().c_str());
+ PrintIndented(1, "binary[%zu].binary_type: %s\n", i, DsoTypeToString(binary.dso_type));
+ if (binary.dso_type == DSO_KERNEL) {
+ PrintIndented(1, "binary[%zu].kernel_start_addr: 0x%" PRIx64 "\n", i,
+ key.kernel_start_addr);
+ }
+ PrintIndented(1, "binary[%zu].addrs:\n", i);
+ size_t addr_id = 0;
+ for (const auto& [addr, branches] : binary.GetOrderedBranchMap()) {
+ PrintIndented(2, "addr[%zu]: 0x%" PRIx64 "\n", addr_id++, addr);
+ size_t branch_id = 0;
+ for (const auto& [branch, count] : branches) {
+ std::string s = "0b";
+ for (auto it = branch.rbegin(); it != branch.rend(); ++it) {
+ s.push_back(*it ? '1' : '0');
+ }
+ PrintIndented(3, "branch[%zu].branch: %s\n", branch_id, s.c_str());
+ PrintIndented(3, "branch[%zu].count: %" PRIu64 "\n", branch_id, count);
+ ++branch_id;
+ }
}
}
- for (size_t i = 0; i < lbr_data_proto.binaries_size(); ++i) {
- const auto& binary_proto = lbr_data_proto.binaries(i);
- lbr_data.binaries.emplace_back(binary_proto.path(), BuildId(binary_proto.build_id()));
+ }
+ if (!lbr_data.samples.empty()) {
+ PrintIndented(0, "lbr_data:\n");
+ for (size_t i = 0; i < lbr_data.samples.size(); ++i) {
+ const auto& sample = lbr_data.samples[i];
+ PrintIndented(1, "sample[%zu].binary_id: %u\n", i, sample.binary_id);
+ PrintIndented(1, "sample[%zu].vaddr_in_file: 0x%" PRIx64 "\n", i, sample.vaddr_in_file);
+ PrintIndented(1, "sample[%zu].branches:\n", i);
+ for (size_t j = 0; j < sample.branches.size(); ++j) {
+ const auto& branch = sample.branches[j];
+ PrintIndented(2, "branch[%zu].from_binary_id: %u\n", j, branch.from_binary_id);
+ PrintIndented(2, "branch[%zu].from_vaddr_in_file: 0x%" PRIx64 "\n", j,
+ branch.from_vaddr_in_file);
+ PrintIndented(2, "branch[%zu].to_binary_id: %u\n", j, branch.to_binary_id);
+ PrintIndented(2, "branch[%zu].to_vaddr_in_file: 0x%" PRIx64 "\n", j,
+ branch.to_vaddr_in_file);
+ }
+ }
+ for (size_t i = 0; i < lbr_data.binaries.size(); ++i) {
+ const auto& binary = lbr_data.binaries[i];
+ PrintIndented(1, "binary[%zu].path: %s\n", i, binary.path.c_str());
+ PrintIndented(1, "binary[%zu].build_id: %s\n", i, binary.build_id.ToString().c_str());
}
}
return true;
diff --git a/simpleperf/BranchListFile.h b/simpleperf/BranchListFile.h
index 7200908..2b9c07c 100644
--- a/simpleperf/BranchListFile.h
+++ b/simpleperf/BranchListFile.h
@@ -170,7 +170,72 @@
};
bool LBRDataToString(const LBRData& data, std::string& s);
-bool ParseBranchListData(const std::string& s, ETMBinaryMap& etm_data, LBRData& lbr_data);
+
+namespace proto {
+class BranchList;
+class ETMBinary;
+class LBRData;
+} // namespace proto
+
+class BranchListProtoWriter {
+ private:
+ // This value is choosen to prevent exceeding the 2GB size limit for a protobuf message.
+ static constexpr size_t kMaxBranchesPerMessage = 100000000;
+
+ public:
+ static std::unique_ptr<BranchListProtoWriter> CreateForFile(
+ const std::string& output_filename, bool compress,
+ size_t max_branches_per_message = kMaxBranchesPerMessage);
+ static std::unique_ptr<BranchListProtoWriter> CreateForString(
+ std::string* output_str, bool compress,
+ size_t max_branches_per_message = kMaxBranchesPerMessage);
+
+ bool Write(const ETMBinaryMap& etm_data);
+ bool Write(const LBRData& lbr_data);
+
+ private:
+ BranchListProtoWriter(const std::string& output_filename, std::string* output_str, bool compress,
+ size_t max_branches_per_message)
+ : output_filename_(output_filename),
+ compress_(compress),
+ max_branches_per_message_(max_branches_per_message),
+ output_fp_(nullptr, fclose),
+ output_str_(output_str) {}
+
+ bool WriteHeader();
+ bool WriteProtoBranchList(proto::BranchList& branch_list);
+ bool WriteData(const void* data, size_t size);
+
+ const std::string output_filename_;
+ const bool compress_;
+ const size_t max_branches_per_message_;
+ std::unique_ptr<FILE, decltype(&fclose)> output_fp_;
+ std::string* output_str_;
+};
+
+class BranchListProtoReader {
+ public:
+ static std::unique_ptr<BranchListProtoReader> CreateForFile(const std::string& input_filename);
+ static std::unique_ptr<BranchListProtoReader> CreateForString(const std::string& input_str);
+ bool Read(ETMBinaryMap& etm_data, LBRData& lbr_data);
+
+ private:
+ BranchListProtoReader(const std::string& input_filename, const std::string& input_str)
+ : input_filename_(input_filename), input_fp_(nullptr, fclose), input_str_(input_str) {}
+ bool ReadProtoBranchList(uint32_t size, proto::BranchList& proto_branch_list);
+ bool AddETMBinary(const proto::ETMBinary& proto_binary, ETMBinaryMap& etm_data);
+ void AddLBRData(const proto::LBRData& proto_lbr_data, LBRData& lbr_data);
+ bool ReadData(void* data, size_t size);
+ bool ReadOldFileFormat(ETMBinaryMap& etm_data, LBRData& lbr_data);
+
+ const std::string input_filename_;
+ std::unique_ptr<FILE, decltype(&fclose)> input_fp_;
+ const std::string& input_str_;
+ size_t input_str_pos_ = 0;
+ bool compress_ = false;
+};
+
+bool DumpBranchListFile(std::string filename);
// for testing
std::string ETMBranchToProtoString(const std::vector<bool>& branch);
diff --git a/simpleperf/BranchListFile_test.cpp b/simpleperf/BranchListFile_test.cpp
index d7f9a6a..af278a0 100644
--- a/simpleperf/BranchListFile_test.cpp
+++ b/simpleperf/BranchListFile_test.cpp
@@ -34,3 +34,118 @@
ASSERT_EQ(branch, branch2);
}
}
+
+static bool IsETMDataEqual(ETMBinaryMap& data1, ETMBinaryMap& data2) {
+ if (data1.size() != data2.size()) {
+ return false;
+ }
+ for (const auto& [key, binary1] : data1) {
+ auto it = data2.find(key);
+ if (it == data2.end()) {
+ return false;
+ }
+ ETMBinary& binary2 = it->second;
+ if (binary1.dso_type != binary2.dso_type) {
+ return false;
+ }
+ const UnorderedETMBranchMap& branch_map1 = binary1.branch_map;
+ const UnorderedETMBranchMap& branch_map2 = binary2.branch_map;
+ if (branch_map1.size() != branch_map2.size()) {
+ return false;
+ }
+ for (const auto& [addr, b_map1] : branch_map1) {
+ auto it2 = branch_map2.find(addr);
+ if (it2 == branch_map2.end()) {
+ return false;
+ }
+ const auto& b_map2 = it2->second;
+ if (b_map1.size() != b_map2.size()) {
+ return false;
+ }
+ for (const auto& [branch, count1] : b_map1) {
+ auto it3 = b_map2.find(branch);
+ if (it3 == b_map2.end()) {
+ return false;
+ }
+ if (count1 != it3->second) {
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+}
+
+static bool IsLBRDataEqual(const LBRData& data1, const LBRData& data2) {
+ if (data1.samples.size() != data2.samples.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < data1.samples.size(); i++) {
+ const LBRSample& sample1 = data1.samples[i];
+ const LBRSample& sample2 = data2.samples[i];
+ if (sample1.binary_id != sample2.binary_id) {
+ return false;
+ }
+ if (sample1.vaddr_in_file != sample2.vaddr_in_file) {
+ return false;
+ }
+ if (sample1.branches.size() != sample2.branches.size()) {
+ return false;
+ }
+ for (size_t j = 0; j < sample1.branches.size(); j++) {
+ const LBRBranch& b1 = sample1.branches[j];
+ const LBRBranch& b2 = sample2.branches[j];
+ if (b1.from_binary_id != b2.from_binary_id || b1.to_binary_id != b2.to_binary_id ||
+ b1.from_vaddr_in_file != b2.from_vaddr_in_file ||
+ b1.to_vaddr_in_file != b2.to_vaddr_in_file) {
+ return false;
+ }
+ }
+ }
+ return data1.binaries == data2.binaries;
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(BranchListProtoReaderWriter, smoke) {
+ ETMBinaryMap etm_data;
+ ETMBinary& binary = etm_data[BinaryKey("fake_binary", BuildId())];
+ binary.dso_type = DSO_ELF_FILE;
+ UnorderedETMBranchMap& branch_map = binary.branch_map;
+ for (size_t addr = 0; addr <= 1024; addr++) {
+ auto& b_map = branch_map[addr];
+ std::vector<bool> branch1 = {true};
+ b_map[branch1] = 1;
+ std::vector<bool> branch2 = {true, false};
+ b_map[branch2] = 2;
+ }
+ LBRData lbr_data;
+ lbr_data.binaries.emplace_back(BinaryKey("binary1", BuildId()));
+ lbr_data.binaries.emplace_back(BinaryKey("binary2", BuildId()));
+ for (uint64_t from_addr = 0; from_addr <= 10; from_addr++) {
+ for (uint64_t to_addr = 100; to_addr <= 110; to_addr++) {
+ LBRBranch branch = {0, 1, from_addr, to_addr};
+ LBRSample sample = {0, from_addr, {branch}};
+ lbr_data.samples.emplace_back(sample);
+ }
+ }
+
+ TemporaryFile tmpfile;
+ close(tmpfile.fd);
+ for (size_t max_branches_per_message : {100, 100000000}) {
+ for (bool compress : {false, true}) {
+ auto writer =
+ BranchListProtoWriter::CreateForFile(tmpfile.path, compress, max_branches_per_message);
+ ASSERT_TRUE(writer);
+ ASSERT_TRUE(writer->Write(etm_data));
+ ASSERT_TRUE(writer->Write(lbr_data));
+ writer = nullptr;
+ auto reader = BranchListProtoReader::CreateForFile(tmpfile.path);
+ ASSERT_TRUE(reader);
+ ETMBinaryMap new_etm_data;
+ LBRData new_lbr_data;
+ ASSERT_TRUE(reader->Read(new_etm_data, new_lbr_data));
+ ASSERT_TRUE(IsETMDataEqual(etm_data, new_etm_data));
+ ASSERT_TRUE(IsLBRDataEqual(lbr_data, new_lbr_data));
+ }
+ }
+}
diff --git a/simpleperf/branch_list.proto b/simpleperf/branch_list.proto
index 6d6bfb1..706f411 100644
--- a/simpleperf/branch_list.proto
+++ b/simpleperf/branch_list.proto
@@ -14,8 +14,18 @@
* limitations under the License.
*/
-// The branch list file format is generated by the inject command. It contains
-// a single BranchList message.
+// The branch list file format is generated by the inject command. The new format is:
+// struct BranchListFile {
+// char magic[24] = "simpleperf:EtmBranchList";
+// uint32 version = 1;
+// uint8 compressed; // 1 if compressed, otherwise 0
+// struct {
+// uint32 msg_size;
+// message BranchList msg;
+// } msgs[];
+// };
+// The old format is a single BranchList message.
+//
syntax = "proto3";
diff --git a/simpleperf/cmd_inject.cpp b/simpleperf/cmd_inject.cpp
index 181b4ae..380b646 100644
--- a/simpleperf/cmd_inject.cpp
+++ b/simpleperf/cmd_inject.cpp
@@ -221,14 +221,17 @@
class ETMThreadTreeWithFilter : public ETMThreadTree {
public:
- ETMThreadTreeWithFilter(ThreadTree& thread_tree, std::optional<int>& exclude_pid)
- : thread_tree_(thread_tree), exclude_pid_(exclude_pid) {}
+ ETMThreadTreeWithFilter(ThreadTree& thread_tree, std::optional<int>& exclude_pid,
+ const std::vector<std::unique_ptr<RegEx>>& exclude_process_names)
+ : thread_tree_(thread_tree),
+ exclude_pid_(exclude_pid),
+ exclude_process_names_(exclude_process_names) {}
void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); }
const ThreadEntry* FindThread(int tid) override {
const ThreadEntry* thread = thread_tree_.FindThread(tid);
- if (thread != nullptr && exclude_pid_ && thread->pid == exclude_pid_) {
+ if (thread != nullptr && ShouldExcludePid(thread->pid)) {
return nullptr;
}
return thread;
@@ -237,18 +240,37 @@
const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); }
private:
+ bool ShouldExcludePid(int pid) {
+ if (exclude_pid_ && pid == exclude_pid_) {
+ return true;
+ }
+ if (!exclude_process_names_.empty()) {
+ const ThreadEntry* process = thread_tree_.FindThread(pid);
+ if (process != nullptr) {
+ for (const auto& regex : exclude_process_names_) {
+ if (regex->Search(process->comm)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
ThreadTree& thread_tree_;
std::optional<int>& exclude_pid_;
+ const std::vector<std::unique_ptr<RegEx>>& exclude_process_names_;
};
// Read perf.data with ETM data and generate AutoFDO or branch list data.
class ETMPerfDataReader : public PerfDataReader {
public:
ETMPerfDataReader(std::unique_ptr<RecordFileReader> reader, bool exclude_perf,
+ const std::vector<std::unique_ptr<RegEx>>& exclude_process_names,
const RegEx* binary_name_regex, ETMDumpOption etm_dump_option)
: PerfDataReader(std::move(reader), exclude_perf, binary_name_regex),
etm_dump_option_(etm_dump_option),
- etm_thread_tree_(thread_tree_, exclude_pid_) {}
+ etm_thread_tree_(thread_tree_, exclude_pid_, exclude_process_names) {}
bool Read() override {
if (reader_->HasFeature(PerfFileFormat::FEAT_ETM_BRANCH_LIST)) {
@@ -522,20 +544,13 @@
void AddCallback(const LBRDataCallback& callback) { lbr_data_callback_ = callback; }
bool Read() {
- std::string s;
- if (!android::base::ReadFileToString(filename_, &s)) {
- PLOG(ERROR) << "failed to read " << filename_;
+ auto reader = BranchListProtoReader::CreateForFile(filename_);
+ if (!reader) {
return false;
}
- if (android::base::EndsWith(filename_, ".zst")) {
- if (!ZstdDecompress(s.data(), s.size(), s)) {
- return false;
- }
- }
ETMBinaryMap etm_data;
LBRData lbr_data;
- if (!ParseBranchListData(s, etm_data, lbr_data)) {
- PLOG(ERROR) << "file is in wrong format: " << filename_;
+ if (!reader->Read(etm_data, lbr_data)) {
return false;
}
if (etm_binary_callback_ && !etm_data.empty()) {
@@ -985,28 +1000,19 @@
// Write branch lists to a protobuf file specified by branch_list.proto.
static bool WriteBranchListFile(const std::string& output_filename, const ETMBinaryMap& etm_data,
const LBRData& lbr_data, bool compress) {
- std::string s;
+ auto writer = BranchListProtoWriter::CreateForFile(output_filename, compress);
+ if (!writer) {
+ return false;
+ }
if (!etm_data.empty()) {
- if (!ETMBinaryMapToString(etm_data, s)) {
- return false;
- }
- } else if (!lbr_data.samples.empty()) {
- if (!LBRDataToString(lbr_data, s)) {
- return false;
- }
- } else {
- // Don't produce empty output file.
- LOG(INFO) << "Skip empty output file.";
- unlink(output_filename.c_str());
- return true;
+ return writer->Write(etm_data);
}
- if (compress && !ZstdCompress(s.data(), s.size(), s)) {
- return false;
+ if (!lbr_data.samples.empty()) {
+ return writer->Write(lbr_data);
}
- if (!android::base::WriteStringToFile(s, output_filename)) {
- PLOG(ERROR) << "failed to write to " << output_filename;
- return false;
- }
+ // Don't produce empty output file.
+ LOG(INFO) << "Skip empty output file.";
+ unlink(output_filename.c_str());
return true;
}
@@ -1030,10 +1036,13 @@
" Default is autofdo.\n"
"--dump-etm type1,type2,... Dump etm data. A type is one of raw, packet and element.\n"
"--exclude-perf Exclude trace data for the recording process.\n"
+"--exclude-process-name process_name_regex Exclude data for processes with name containing\n"
+" the regular expression.\n"
"--symdir <dir> Look for binaries in a directory recursively.\n"
"--allow-mismatched-build-id Allow mismatched build ids when searching for debug binaries.\n"
"-j <jobs> Use multiple threads to process branch list files.\n"
"-z Compress branch-list output\n"
+"--dump <file> Dump a branch list file.\n"
"\n"
"Examples:\n"
"1. Generate autofdo text output.\n"
@@ -1050,6 +1059,9 @@
if (!ParseOptions(args)) {
return false;
}
+ if (!dump_branch_list_file_.empty()) {
+ return DumpBranchListFile(dump_branch_list_file_);
+ }
CHECK(!input_filenames_.empty());
if (IsPerfDataFile(input_filenames_[0])) {
@@ -1078,8 +1090,10 @@
const OptionFormatMap option_formats = {
{"--allow-mismatched-build-id", {OptionValueType::NONE, OptionType::SINGLE}},
{"--binary", {OptionValueType::STRING, OptionType::SINGLE}},
+ {"--dump", {OptionValueType::STRING, OptionType::SINGLE}},
{"--dump-etm", {OptionValueType::STRING, OptionType::SINGLE}},
{"--exclude-perf", {OptionValueType::NONE, OptionType::SINGLE}},
+ {"--exclude-process-name", {OptionValueType::STRING, OptionType::MULTIPLE}},
{"-i", {OptionValueType::STRING, OptionType::MULTIPLE}},
{"-j", {OptionValueType::UINT, OptionType::SINGLE}},
{"-o", {OptionValueType::STRING, OptionType::SINGLE}},
@@ -1103,12 +1117,20 @@
return false;
}
}
+ options.PullStringValue("--dump", &dump_branch_list_file_);
if (auto value = options.PullValue("--dump-etm"); value) {
if (!ParseEtmDumpOption(value->str_value, &etm_dump_option_)) {
return false;
}
}
exclude_perf_ = options.PullBoolValue("--exclude-perf");
+ for (const std::string& value : options.PullStringValues("--exclude-process-name")) {
+ std::unique_ptr<RegEx> regex = RegEx::Create(value);
+ if (regex == nullptr) {
+ return false;
+ }
+ exclude_process_names_.emplace_back(std::move(regex));
+ }
for (const OptionValue& value : options.PullValues("-i")) {
std::vector<std::string> files = android::base::Split(value.str_value, ",");
@@ -1155,11 +1177,6 @@
}
compress_ = options.PullBoolValue("-z");
CHECK(options.values.empty());
-
- if (compress_ && !android::base::EndsWith(output_filename_, ".zst")) {
- LOG(ERROR) << "When -z is used, output filename should has a .zst suffix";
- return false;
- }
return true;
}
@@ -1195,7 +1212,8 @@
std::unique_ptr<PerfDataReader> reader;
if (data_type == "etm") {
reader.reset(new ETMPerfDataReader(std::move(file_reader), exclude_perf_,
- binary_name_regex_.get(), etm_dump_option_));
+ exclude_process_names_, binary_name_regex_.get(),
+ etm_dump_option_));
} else if (data_type == "lbr") {
reader.reset(
new LBRPerfDataReader(std::move(file_reader), exclude_perf_, binary_name_regex_.get()));
@@ -1306,6 +1324,7 @@
std::unique_ptr<RegEx> binary_name_regex_;
bool exclude_perf_ = false;
+ std::vector<std::unique_ptr<RegEx>> exclude_process_names_;
std::vector<std::string> input_filenames_;
std::string output_filename_ = "perf_inject.data";
OutputFormat output_format_ = OutputFormat::AutoFDO;
@@ -1313,6 +1332,7 @@
bool compress_ = false;
bool allow_mismatched_build_id_ = false;
size_t jobs_ = 1;
+ std::string dump_branch_list_file_;
std::unique_ptr<Dso> placeholder_dso_;
};
diff --git a/simpleperf/cmd_inject_test.cpp b/simpleperf/cmd_inject_test.cpp
index 4fc84fc..8ecdec2 100644
--- a/simpleperf/cmd_inject_test.cpp
+++ b/simpleperf/cmd_inject_test.cpp
@@ -111,13 +111,10 @@
TEST(cmd_inject, compress_option) {
TemporaryFile tmpfile;
close(tmpfile.release());
- ASSERT_FALSE(RunInjectCmd({"--output", "branch-list", "-z", "-o", tmpfile.path}));
- std::string tmp_zstd_path = std::string(tmpfile.path) + ".zst";
- ASSERT_TRUE(RunInjectCmd({"--output", "branch-list", "-z", "-o", tmp_zstd_path}));
+ ASSERT_TRUE(RunInjectCmd({"--output", "branch-list", "-z", "-o", tmpfile.path}));
std::string autofdo_data;
- ASSERT_TRUE(RunInjectCmd({"-i", tmp_zstd_path.c_str(), "--output", "autofdo"}, &autofdo_data));
+ ASSERT_TRUE(RunInjectCmd({"-i", tmpfile.path, "--output", "autofdo"}, &autofdo_data));
CheckMatchingExpectedData("perf_inject.data", autofdo_data);
- unlink(tmp_zstd_path.c_str());
}
// @CddTest = 6.1/C-0-2
@@ -340,3 +337,36 @@
{"-i", std::string(tmpfile.path) + "," + tmpfile.path, "--output", "autofdo", "-j", "0"},
&autofdo_data));
}
+
+// @CddTest = 6.1/C-0-2
+TEST(cmd_inject, dump_option) {
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(RunInjectCmd({"--output", "branch-list", "-o", tmpfile.path}));
+
+ CaptureStdout capture;
+ ASSERT_TRUE(capture.Start());
+ ASSERT_TRUE(InjectCmd()->Run({"--dump", tmpfile.path}));
+ std::string data = capture.Finish();
+ ASSERT_NE(data.find("binary[0].build_id: 0x0c9a20bf9c009d0e4e8bbf9fad0300ae00000000"),
+ std::string::npos);
+
+ ASSERT_TRUE(RunInjectCmd(
+ {"--output", "branch-list", "-o", tmpfile.path, "-i", GetTestData("lbr/perf_lbr.data")}));
+
+ ASSERT_TRUE(capture.Start());
+ ASSERT_TRUE(InjectCmd()->Run({"--dump", tmpfile.path}));
+ data = capture.Finish();
+ ASSERT_NE(data.find("binary[0].path: /home/yabinc/lbr_test_loop"), std::string::npos);
+}
+
+// @CddTest = 6.1/C-0-2
+TEST(cmd_inject, exclude_process_name_option) {
+ TemporaryFile tmpfile;
+ close(tmpfile.release());
+ ASSERT_TRUE(RunInjectCmd(
+ {"--output", "branch-list", "--exclude-process-name", "etm_test_loop", "-o", tmpfile.path}));
+ struct stat st;
+ ASSERT_EQ(stat(tmpfile.path, &st), -1);
+ ASSERT_EQ(errno, ENOENT);
+}
diff --git a/simpleperf/doc/README.md b/simpleperf/doc/README.md
index 2efa323..b4f991b 100644
--- a/simpleperf/doc/README.md
+++ b/simpleperf/doc/README.md
@@ -263,6 +263,7 @@
2) Generate binary_cache, containing elf files with debug information. Use -lib option to add
libs with debug info. Do it with
`binary_cache_builder.py -i perf.data -lib <dir_of_lib_with_debug_info>`.
+ For Android platform, we can add debug binaries as in [Android platform profiling](android_platform_profiling.md#general-tips).
3) Use report_html.py to generate report.html with annotated source code and disassembly,
as described [here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/doc/scripts_reference.md#report_html_py).
@@ -271,6 +272,11 @@
2) Use pprof_proto_generator.py to generate pprof proto file. `pprof_proto_generator.py`.
3) Use pprof to report a function with annotated source code, as described [here](https://android.googlesource.com/platform/system/extras/+/main/simpleperf/doc/scripts_reference.md#pprof_proto_generator_py).
+3. Through Continuous PProf UI.
+ 1) Generate pprof proto file as above.
+ 2) Upload pprof.profile to pprof/. It can show source file path and line numbers for each symbol.
+ An example is [here](https://pprof.corp.google.com/?id=f5588600d3a225737a1901cb28f3f5b1).
+
### Reduce lost samples and samples with truncated stack
diff --git a/simpleperf/doc/android_platform_profiling.md b/simpleperf/doc/android_platform_profiling.md
index 52bccda..16c967c 100644
--- a/simpleperf/doc/android_platform_profiling.md
+++ b/simpleperf/doc/android_platform_profiling.md
@@ -33,12 +33,24 @@
# Collect unstripped binaries from $ANDROID_PRODUCT_OUT/symbols to binary_cache/.
$ ./binary_cache_builder.py -lib $ANDROID_PRODUCT_OUT/symbols
-# Report source code and disassembly. Disassembling all binaries is slow, so it's better to add
-# --binary_filter option to only disassemble selected binaries.
+# Collect unstripped binaries from symbol file downloaded from builder server to binary_cache/.
+$ unzip comet-symbols-12488474.zip
+$ ./binary_cache_builder.py -lib out
+
+# To verify that the binaries in binary_cache/ include debug sections, you can perform a manual
+# check.
+
+# Generate an HTML report with source code and disassembly.
+# Disassembling all binaries can be slow, so you can use the --binary_filter
+# option to disassemble only specific binaries, like surfaceflinger.so in this example.
$ ./report_html.py --add_source_code --source_dirs $ANDROID_BUILD_TOP --add_disassembly \
--binary_filter surfaceflinger.so
```
+For a comprehensive guide to displaying source code and disassembly, see
+[Show Annotated Source Code and Disassembly](README.md#show-annotated-source-code-and-disassembly).
+
+
## Start simpleperf from system_server process
Sometimes we want to profile a process/system-wide when a special situation happens. In this case,
diff --git a/simpleperf/scripts/simpleperf_utils.py b/simpleperf/scripts/simpleperf_utils.py
index 19b8554..93cd76d 100644
--- a/simpleperf/scripts/simpleperf_utils.py
+++ b/simpleperf/scripts/simpleperf_utils.py
@@ -367,27 +367,27 @@
def get_android_version(self) -> int:
""" Get Android version on device, like 7 is for Android N, 8 is for Android O."""
- android_version = 0
-
- def parse_version(s: str):
+ def parse_version(s: str) -> int:
if not s:
- return
+ return 0
if s[0].isdigit():
i = 1
while i < len(s) and s[i].isdigit():
i += 1
- android_version = int(s[:i])
+ return int(s[:i])
else:
c = s[0].upper()
if c.isupper() and 'L' <= c <= 'V':
- android_version = ord(c) - ord('L') + 5
+ return ord(c) - ord('L') + 5
+ return 0
+ android_version = 0
s = self.get_property('ro.build.version.codename')
if s != 'REL':
- parse_version(s)
+ android_version = parse_version(s)
if android_version == 0:
s = self.get_property('ro.build.version.release')
- parse_version(s)
+ android_version = parse_version(s)
if android_version == 0:
s = self.get_property('ro.build.version.sdk')
if int(s) >= 35: