1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <err.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <inttypes.h>
21 #include <malloc.h>
22 #include <stdint.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <unistd.h>
29 
30 #include "Alloc.h"
31 #include "File.h"
32 #include "NativeInfo.h"
33 #include "Pointers.h"
34 #include "Thread.h"
35 #include "Threads.h"
36 
37 constexpr size_t kDefaultMaxThreads = 512;
38 
39 static size_t GetMaxAllocs(const AllocEntry* entries, size_t num_entries) {
40   size_t num_allocs = 0;
41   for (size_t i = 0; i < num_entries; i++) {
42     switch (entries[i].type) {
43       case THREAD_DONE:
44         break;
45       case MALLOC:
46       case CALLOC:
47       case MEMALIGN:
48       case REALLOC:
49         num_allocs++;
50         break;
51       case FREE:
52         num_allocs--;
53         break;
54     }
55   }
56   return num_allocs;
57 }
58 
59 static void ProcessDump(const AllocEntry* entries, size_t num_entries, size_t max_threads) {
60   // Do a pass to get the maximum number of allocations used at one
61   // time to allow a single mmap that can hold the maximum number of
62   // pointers needed at once.
63   size_t max_allocs = GetMaxAllocs(entries, num_entries);
64   Pointers pointers(max_allocs);
65   Threads threads(&pointers, max_threads);
66 
67   NativePrintf("Maximum threads available:   %zu\n", threads.max_threads());
68   NativePrintf("Maximum allocations in dump: %zu\n", max_allocs);
69   NativePrintf("Total pointers available:    %zu\n\n", pointers.max_pointers());
70 
71   NativePrintInfo("Initial ");
72 
73   for (size_t i = 0; i < num_entries; i++) {
74     if (((i + 1) % 100000) == 0) {
75       NativePrintf("  At line %zu:\n", i + 1);
76       NativePrintInfo("    ");
77     }
78     const AllocEntry& entry = entries[i];
79     Thread* thread = threads.FindThread(entry.tid);
80     if (thread == nullptr) {
81       thread = threads.CreateThread(entry.tid);
82     }
83 
84     // Wait for the thread to complete any previous actions before handling
85     // the next action.
86     thread->WaitForReady();
87 
88     thread->SetAllocEntry(&entry);
89 
90     bool does_free = AllocDoesFree(entry);
91     if (does_free) {
92       // Make sure that any other threads doing allocations are complete
93       // before triggering the action. Otherwise, another thread could
94       // be creating the allocation we are going to free.
95       threads.WaitForAllToQuiesce();
96     }
97 
98     // Tell the thread to execute the action.
99     thread->SetPending();
100 
101     if (entries[i].type == THREAD_DONE) {
102       // Wait for the thread to finish and clear the thread entry.
103       threads.Finish(thread);
104     }
105 
106     // Wait for this action to complete. This avoids a race where
107     // another thread could be creating the same allocation where are
108     // trying to free.
109     if (does_free) {
110       thread->WaitForReady();
111     }
112   }
113   // Wait for all threads to stop processing actions.
114   threads.WaitForAllToQuiesce();
115 
116   NativePrintInfo("Final ");
117 
118   // Free any outstanding pointers.
119   // This allows us to run a tool like valgrind to verify that no memory
120   // is leaked and everything is accounted for during a run.
121   threads.FinishAll();
122   pointers.FreeAll();
123 
124   // Print out the total time making all allocation calls.
125   char buffer[256];
126   uint64_t total_nsecs = threads.total_time_nsecs();
127   NativeFormatFloat(buffer, sizeof(buffer), total_nsecs, 1000000000);
128   NativePrintf("Total Allocation/Free Time: %" PRIu64 "ns %ss\n", total_nsecs, buffer);
129 }
130 
131 int main(int argc, char** argv) {
132   if (argc != 2 && argc != 3) {
133     if (argc > 3) {
134       fprintf(stderr, "Only two arguments are expected.\n");
135     } else {
136       fprintf(stderr, "Requires at least one argument.\n");
137     }
138     fprintf(stderr, "Usage: %s MEMORY_LOG_FILE [MAX_THREADS]\n", basename(argv[0]));
139     fprintf(stderr, "  MEMORY_LOG_FILE\n");
140     fprintf(stderr, "    This can either be a text file or a zipped text file.\n");
141     fprintf(stderr, "  MAX_THREADs\n");
142     fprintf(stderr, "    The maximum number of threads in the trace. The default is %zu.\n",
143             kDefaultMaxThreads);
144     fprintf(stderr, "    This pre-allocates the memory for thread data to avoid allocating\n");
145     fprintf(stderr, "    while the trace is being replayed.\n");
146     return 1;
147   }
148 
149 #if defined(__LP64__)
150   NativePrintf("64 bit environment.\n");
151 #else
152   NativePrintf("32 bit environment.\n");
153 #endif
154 
155 #if defined(__BIONIC__)
156   NativePrintf("Setting decay time to 1\n");
157   mallopt(M_DECAY_TIME, 1);
158 #endif
159 
160   size_t max_threads = kDefaultMaxThreads;
161   if (argc == 3) {
162     max_threads = atoi(argv[2]);
163   }
164 
165   AllocEntry* entries;
166   size_t num_entries;
167   GetUnwindInfo(argv[1], &entries, &num_entries);
168 
169   NativePrintf("Processing: %s\n", argv[1]);
170 
171   ProcessDump(entries, num_entries, max_threads);
172 
173   FreeEntries(entries, num_entries);
174 
175   return 0;
176 }
177