offline_profiling_info.cc revision 4d77b6a511659f26fdc711e23825ffa6e7feed7a
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 "offline_profiling_info.h"
18
19#include <fstream>
20#include <vector>
21#include <sys/file.h>
22#include <sys/stat.h>
23#include <sys/uio.h>
24
25#include "art_method-inl.h"
26#include "base/mutex.h"
27#include "base/stl_util.h"
28#include "jit/profiling_info.h"
29#include "safe_map.h"
30
31namespace art {
32
33void OfflineProfilingInfo::SaveProfilingInfo(const std::string& filename,
34                                             const std::vector<ArtMethod*>& methods) {
35  if (methods.empty()) {
36    VLOG(profiler) << "No info to save to " << filename;
37    return;
38  }
39
40  DexFileToMethodsMap info;
41  {
42    ScopedObjectAccess soa(Thread::Current());
43    for (auto it = methods.begin(); it != methods.end(); it++) {
44      AddMethodInfo(*it, &info);
45    }
46  }
47
48  // This doesn't need locking because we are trying to lock the file for exclusive
49  // access and fail immediately if we can't.
50  if (Serialize(filename, info)) {
51    VLOG(profiler) << "Successfully saved profile info to " << filename
52        << " Size: " << GetFileSizeBytes(filename);
53  }
54}
55
56void OfflineProfilingInfo::AddMethodInfo(ArtMethod* method, DexFileToMethodsMap* info) {
57  DCHECK(method != nullptr);
58  const DexFile* dex_file = method->GetDexFile();
59
60  auto info_it = info->find(dex_file);
61  if (info_it == info->end()) {
62    info_it = info->Put(dex_file, std::set<uint32_t>());
63  }
64  info_it->second.insert(method->GetDexMethodIndex());
65}
66
67enum OpenMode {
68  READ,
69  READ_WRITE
70};
71
72static int OpenFile(const std::string& filename, OpenMode open_mode) {
73  int fd = -1;
74  switch (open_mode) {
75    case READ:
76      fd = open(filename.c_str(), O_RDONLY);
77      break;
78    case READ_WRITE:
79      // TODO(calin) allow the shared uid of the app to access the file.
80      fd = open(filename.c_str(),
81                    O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW | O_CLOEXEC,
82                    S_IRUSR | S_IWUSR);
83      break;
84  }
85
86  if (fd < 0) {
87    PLOG(WARNING) << "Failed to open profile file " << filename;
88    return -1;
89  }
90
91  // Lock the file for exclusive access but don't wait if we can't lock it.
92  int err = flock(fd, LOCK_EX | LOCK_NB);
93  if (err < 0) {
94    PLOG(WARNING) << "Failed to lock profile file " << filename;
95    return -1;
96  }
97  return fd;
98}
99
100static bool CloseDescriptorForFile(int fd, const std::string& filename) {
101  // Now unlock the file, allowing another process in.
102  int err = flock(fd, LOCK_UN);
103  if (err < 0) {
104    PLOG(WARNING) << "Failed to unlock profile file " << filename;
105    return false;
106  }
107
108  // Done, close the file.
109  err = ::close(fd);
110  if (err < 0) {
111    PLOG(WARNING) << "Failed to close descriptor for profile file" << filename;
112    return false;
113  }
114
115  return true;
116}
117
118static void WriteToFile(int fd, const std::ostringstream& os) {
119  std::string data(os.str());
120  const char *p = data.c_str();
121  size_t length = data.length();
122  do {
123    int n = ::write(fd, p, length);
124    p += n;
125    length -= n;
126  } while (length > 0);
127}
128
129static constexpr const char kFieldSeparator = ',';
130static constexpr const char kLineSeparator = '\n';
131
132/**
133 * Serialization format:
134 *    dex_location1,dex_location_checksum1,method_id11,method_id12...
135 *    dex_location2,dex_location_checksum2,method_id21,method_id22...
136 * e.g.
137 *    /system/priv-app/app/app.apk,131232145,11,23,454,54
138 *    /system/priv-app/app/app.apk:classes5.dex,218490184,39,13,49,1
139 **/
140bool OfflineProfilingInfo::Serialize(const std::string& filename,
141                                     const DexFileToMethodsMap& info) const {
142  int fd = OpenFile(filename, READ_WRITE);
143  if (fd == -1) {
144    return false;
145  }
146
147  // TODO(calin): Merge with a previous existing profile.
148  // TODO(calin): Profile this and see how much memory it takes. If too much,
149  // write to file directly.
150  std::ostringstream os;
151  for (auto it : info) {
152    const DexFile* dex_file = it.first;
153    const std::set<uint32_t>& method_dex_ids = it.second;
154
155    os << dex_file->GetLocation()
156        << kFieldSeparator
157        << dex_file->GetLocationChecksum();
158    for (auto method_it : method_dex_ids) {
159      os << kFieldSeparator << method_it;
160    }
161    os << kLineSeparator;
162  }
163
164  WriteToFile(fd, os);
165
166  return CloseDescriptorForFile(fd, filename);
167}
168
169// TODO(calin): This a duplicate of Utils::Split fixing the case where the first character
170// is the separator. Merge the fix into Utils::Split once verified that it doesn't break its users.
171static void SplitString(const std::string& s, char separator, std::vector<std::string>* result) {
172  const char* p = s.data();
173  const char* end = p + s.size();
174  // Check if the first character is the separator.
175  if (p != end && *p ==separator) {
176    result->push_back("");
177    ++p;
178  }
179  // Process the rest of the characters.
180  while (p != end) {
181    if (*p == separator) {
182      ++p;
183    } else {
184      const char* start = p;
185      while (++p != end && *p != separator) {
186        // Skip to the next occurrence of the separator.
187      }
188      result->push_back(std::string(start, p - start));
189    }
190  }
191}
192
193bool ProfileCompilationInfo::ProcessLine(const std::string& line,
194                                         const std::vector<const DexFile*>& dex_files) {
195  std::vector<std::string> parts;
196  SplitString(line, kFieldSeparator, &parts);
197  if (parts.size() < 3) {
198    LOG(WARNING) << "Invalid line: " << line;
199    return false;
200  }
201
202  const std::string& dex_location = parts[0];
203  uint32_t checksum;
204  if (!ParseInt(parts[1].c_str(), &checksum)) {
205    return false;
206  }
207
208  const DexFile* current_dex_file = nullptr;
209  for (auto dex_file : dex_files) {
210    if (dex_file->GetLocation() == dex_location) {
211      if (checksum != dex_file->GetLocationChecksum()) {
212        LOG(WARNING) << "Checksum mismatch for "
213            << dex_file->GetLocation() << " when parsing " << filename_;
214        return false;
215      }
216      current_dex_file = dex_file;
217      break;
218    }
219  }
220  if (current_dex_file == nullptr) {
221    return true;
222  }
223
224  for (size_t i = 2; i < parts.size(); i++) {
225    uint32_t method_idx;
226    if (!ParseInt(parts[i].c_str(), &method_idx)) {
227      LOG(WARNING) << "Cannot parse method_idx " << parts[i];
228      return false;
229    }
230    uint16_t class_idx = current_dex_file->GetMethodId(method_idx).class_idx_;
231    auto info_it = info_.find(current_dex_file);
232    if (info_it == info_.end()) {
233      info_it = info_.Put(current_dex_file, ClassToMethodsMap());
234    }
235    ClassToMethodsMap& class_map = info_it->second;
236    auto class_it = class_map.find(class_idx);
237    if (class_it == class_map.end()) {
238      class_it = class_map.Put(class_idx, std::set<uint32_t>());
239    }
240    class_it->second.insert(method_idx);
241  }
242  return true;
243}
244
245// Parses the buffer (of length n) starting from start_from and identify new lines
246// based on kLineSeparator marker.
247// Returns the first position after kLineSeparator in the buffer (starting from start_from),
248// or -1 if the marker doesn't appear.
249// The processed characters are appended to the given line.
250static int GetLineFromBuffer(char* buffer, int n, int start_from, std::string& line) {
251  if (start_from >= n) {
252    return -1;
253  }
254  int new_line_pos = -1;
255  for (int i = start_from; i < n; i++) {
256    if (buffer[i] == kLineSeparator) {
257      new_line_pos = i;
258      break;
259    }
260  }
261  int append_limit = new_line_pos == -1 ? n : new_line_pos;
262  line.append(buffer + start_from, append_limit - start_from);
263  // Jump over kLineSeparator and return the position of the next character.
264  return new_line_pos == -1 ? new_line_pos : new_line_pos + 1;
265}
266
267bool ProfileCompilationInfo::Load(const std::vector<const DexFile*>& dex_files) {
268  if (dex_files.empty()) {
269    return true;
270  }
271  if (kIsDebugBuild) {
272    // In debug builds verify that the locations are unique.
273    std::set<std::string> locations;
274    for (auto dex_file : dex_files) {
275      const std::string& location = dex_file->GetLocation();
276      DCHECK(locations.find(location) == locations.end())
277          << "DexFiles appear to belong to different apks."
278          << " There are multiple dex files with the same location: "
279          << location;
280      locations.insert(location);
281    }
282  }
283  info_.clear();
284
285  int fd = OpenFile(filename_, READ);
286  if (fd == -1) {
287    return false;
288  }
289
290  std::string current_line;
291  const int kBufferSize = 1024;
292  char buffer[kBufferSize];
293  bool success = true;
294
295  while (success) {
296    int n = read(fd, buffer, kBufferSize);
297    if (n < 0) {
298      PLOG(WARNING) << "Error when reading profile file " << filename_;
299      success = false;
300      break;
301    } else if (n == 0) {
302      break;
303    }
304    // Detect the new lines from the buffer. If we manage to complete a line,
305    // process it. Otherwise append to the current line.
306    int current_start_pos = 0;
307    while (current_start_pos < n) {
308      current_start_pos = GetLineFromBuffer(buffer, n, current_start_pos, current_line);
309      if (current_start_pos == -1) {
310        break;
311      }
312      if (!ProcessLine(current_line, dex_files)) {
313        success = false;
314        break;
315      }
316      // Reset the current line (we just processed it).
317      current_line.clear();
318    }
319  }
320  if (!success) {
321    info_.clear();
322  }
323  return CloseDescriptorForFile(fd, filename_) && success;
324}
325
326bool ProfileCompilationInfo::ContainsMethod(const MethodReference& method_ref) const {
327  auto info_it = info_.find(method_ref.dex_file);
328  if (info_it != info_.end()) {
329    uint16_t class_idx = method_ref.dex_file->GetMethodId(method_ref.dex_method_index).class_idx_;
330    const ClassToMethodsMap& class_map = info_it->second;
331    auto class_it = class_map.find(class_idx);
332    if (class_it != class_map.end()) {
333      const std::set<uint32_t>& methods = class_it->second;
334      return methods.find(method_ref.dex_method_index) != methods.end();
335    }
336    return false;
337  }
338  return false;
339}
340
341std::string ProfileCompilationInfo::DumpInfo(bool print_full_dex_location) const {
342  std::ostringstream os;
343  if (info_.empty()) {
344    return "ProfileInfo: empty";
345  }
346
347  os << "ProfileInfo:";
348
349  // Use an additional map to achieve a predefined order based on the dex locations.
350  SafeMap<const std::string, const DexFile*> dex_locations_map;
351  for (auto info_it : info_) {
352    dex_locations_map.Put(info_it.first->GetLocation(), info_it.first);
353  }
354
355  const std::string kFirstDexFileKeySubstitute = ":classes.dex";
356  for (auto dex_file_it : dex_locations_map) {
357    os << "\n";
358    const std::string& location = dex_file_it.first;
359    const DexFile* dex_file = dex_file_it.second;
360    if (print_full_dex_location) {
361      os << location;
362    } else {
363      // Replace the (empty) multidex suffix of the first key with a substitute for easier reading.
364      std::string multidex_suffix = DexFile::GetMultiDexSuffix(location);
365      os << (multidex_suffix.empty() ? kFirstDexFileKeySubstitute : multidex_suffix);
366    }
367    for (auto class_it : info_.find(dex_file)->second) {
368      for (auto method_it : class_it.second) {
369        os << "\n  " << PrettyMethod(method_it, *dex_file, true);
370      }
371    }
372  }
373  return os.str();
374}
375
376}  // namespace art
377