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 <stdint.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <sys/stat.h>
26#include <sys/types.h>
27#include <unistd.h>
28
29#include "Action.h"
30#include "LineBuffer.h"
31#include "NativeInfo.h"
32#include "Pointers.h"
33#include "Thread.h"
34#include "Threads.h"
35
36static char g_buffer[65535];
37
38size_t GetMaxAllocs(int fd) {
39  lseek(fd, 0, SEEK_SET);
40  LineBuffer line_buf(fd, g_buffer, sizeof(g_buffer));
41  char* line;
42  size_t line_len;
43  size_t num_allocs = 0;
44  while (line_buf.GetLine(&line, &line_len)) {
45    char* word = reinterpret_cast<char*>(memchr(line, ':', line_len));
46    if (word == nullptr) {
47      continue;
48    }
49
50    word++;
51    while (*word++ == ' ');
52    // This will treat a realloc as an allocation, even if it frees
53    // another allocation. Since reallocs are relatively rare, this
54    // shouldn't inflate the numbers that much.
55    if (*word == 'f') {
56      // Check if this is a free of zero.
57      uintptr_t pointer;
58      if (sscanf(word, "free %" SCNxPTR, &pointer) == 1 && pointer != 0) {
59        num_allocs--;
60      }
61    } else if (*word != 't') {
62      // Skip the thread_done message.
63      num_allocs++;
64    }
65  }
66  return num_allocs;
67}
68
69void ProcessDump(int fd, size_t max_allocs, size_t max_threads) {
70  lseek(fd, 0, SEEK_SET);
71  Pointers pointers(max_allocs);
72  Threads threads(&pointers, max_threads);
73
74  printf("Maximum threads available:   %zu\n", threads.max_threads());
75  printf("Maximum allocations in dump: %zu\n", max_allocs);
76  printf("Total pointers available:    %zu\n", pointers.max_pointers());
77  printf("\n");
78
79  PrintNativeInfo("Initial ");
80
81  LineBuffer line_buf(fd, g_buffer, sizeof(g_buffer));
82  char* line;
83  size_t line_len;
84  size_t line_number = 0;
85  while (line_buf.GetLine(&line, &line_len)) {
86    pid_t tid;
87    int line_pos = 0;
88    char type[128];
89    uintptr_t key_pointer;
90
91    // Every line is of this format:
92    //   <tid>: <action_type> <pointer>
93    // Some actions have extra arguments which will be used and verified
94    // when creating the Action object.
95    if (sscanf(line, "%d: %s %" SCNxPTR " %n", &tid, type, &key_pointer, &line_pos) != 3) {
96      err(1, "Unparseable line found: %s\n", line);
97    }
98    line_number++;
99    if ((line_number % 100000) == 0) {
100      printf("  At line %zu:\n", line_number);
101      PrintNativeInfo("    ");
102    }
103    Thread* thread = threads.FindThread(tid);
104    if (thread == nullptr) {
105      thread = threads.CreateThread(tid);
106    }
107
108    // Wait for the thread to complete any previous actions before handling
109    // the next action.
110    thread->WaitForReady();
111
112    Action* action = thread->CreateAction(key_pointer, type, line + line_pos);
113    if (action == nullptr) {
114      err(1, "Cannot create action from line: %s\n", line);
115    }
116
117    bool does_free = action->DoesFree();
118    if (does_free) {
119      // Make sure that any other threads doing allocations are complete
120      // before triggering the action. Otherwise, another thread could
121      // be creating the allocation we are going to free.
122      threads.WaitForAllToQuiesce();
123    }
124
125    // Tell the thread to execute the action.
126    thread->SetPending();
127
128    if (action->EndThread()) {
129      // Wait for the thread to finish and clear the thread entry.
130      threads.Finish(thread);
131    }
132
133    // Wait for this action to complete. This avoids a race where
134    // another thread could be creating the same allocation where are
135    // trying to free.
136    if (does_free) {
137      thread->WaitForReady();
138    }
139  }
140  // Wait for all threads to stop processing actions.
141  threads.WaitForAllToQuiesce();
142
143  PrintNativeInfo("Final ");
144
145  // Free any outstanding pointers.
146  // This allows us to run a tool like valgrind to verify that no memory
147  // is leaked and everything is accounted for during a run.
148  threads.FinishAll();
149  pointers.FreeAll();
150
151  // Print out the total time making all allocation calls.
152  printf("Total Allocation/Free Time: %" PRIu64 "ns %0.2fs\n",
153         threads.total_time_nsecs(), threads.total_time_nsecs()/1000000000.0);
154}
155
156constexpr size_t DEFAULT_MAX_THREADS = 512;
157
158int main(int argc, char** argv) {
159  if (argc != 2 && argc != 3) {
160    if (argc > 3) {
161      fprintf(stderr, "Only two arguments are expected.\n");
162    } else {
163      fprintf(stderr, "Requires at least one argument.\n");
164    }
165    fprintf(stderr, "Usage: %s MEMORY_LOG_FILE [MAX_THREADS]\n", basename(argv[0]));
166    return 1;
167  }
168
169  size_t max_threads = DEFAULT_MAX_THREADS;
170  if (argc == 3) {
171    max_threads = atoi(argv[2]);
172  }
173
174  int dump_fd = open(argv[1], O_RDONLY);
175  if (dump_fd == -1) {
176    fprintf(stderr, "Failed to open %s: %s\n", argv[1], strerror(errno));
177    return 1;
178  }
179
180  printf("Processing: %s\n", argv[1]);
181
182  // Do a first pass to get the total number of allocations used at one
183  // time to allow a single mmap that can hold the maximum number of
184  // pointers needed at once.
185  size_t max_allocs = GetMaxAllocs(dump_fd);
186  ProcessDump(dump_fd, max_allocs, max_threads);
187
188  close(dump_fd);
189
190  return 0;
191}
192