offline_profiling_info.cc revision a079e3aa62cceb76c1c1811e6e09bcaf75e20289
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/scoped_flock.h"
28#include "base/stl_util.h"
29#include "base/systrace.h"
30#include "base/unix_file/fd_file.h"
31#include "jit/profiling_info.h"
32#include "os.h"
33#include "safe_map.h"
34
35namespace art {
36
37// Transform the actual dex location into relative paths.
38// Note: this is OK because we don't store profiles of different apps into the same file.
39// Apps with split apks don't cause trouble because each split has a different name and will not
40// collide with other entries.
41std::string ProfileCompilationInfo::GetProfileDexFileKey(const std::string& dex_location) {
42  DCHECK(!dex_location.empty());
43  size_t last_sep_index = dex_location.find_last_of('/');
44  if (last_sep_index == std::string::npos) {
45    return dex_location;
46  } else {
47    DCHECK(last_sep_index < dex_location.size());
48    return dex_location.substr(last_sep_index + 1);
49  }
50}
51
52bool ProfileCompilationInfo::SaveProfilingInfo(
53    const std::string& filename,
54    const std::vector<ArtMethod*>& methods,
55    const std::set<DexCacheResolvedClasses>& resolved_classes,
56    uint64_t* bytes_written) {
57  if (methods.empty() && resolved_classes.empty()) {
58    VLOG(profiler) << "No info to save to " << filename;
59    if (bytes_written != nullptr) {
60      *bytes_written = 0;
61    }
62    return true;
63  }
64
65  ScopedTrace trace(__PRETTY_FUNCTION__);
66  ScopedFlock flock;
67  std::string error;
68  if (!flock.Init(filename.c_str(), O_RDWR | O_NOFOLLOW | O_CLOEXEC, /* block */ false, &error)) {
69    LOG(WARNING) << "Couldn't lock the profile file " << filename << ": " << error;
70    return false;
71  }
72
73  int fd = flock.GetFile()->Fd();
74
75  ProfileCompilationInfo info;
76  if (!info.Load(fd)) {
77    LOG(WARNING) << "Could not load previous profile data from file " << filename;
78    return false;
79  }
80  {
81    ScopedObjectAccess soa(Thread::Current());
82    for (ArtMethod* method : methods) {
83      const DexFile* dex_file = method->GetDexFile();
84      if (!info.AddMethodIndex(GetProfileDexFileKey(dex_file->GetLocation()),
85                               dex_file->GetLocationChecksum(),
86                               method->GetDexMethodIndex())) {
87        return false;
88      }
89    }
90    for (const DexCacheResolvedClasses& dex_cache : resolved_classes) {
91      info.AddResolvedClasses(dex_cache);
92    }
93  }
94
95  if (!flock.GetFile()->ClearContent()) {
96    PLOG(WARNING) << "Could not clear profile file: " << filename;
97    return false;
98  }
99
100  // This doesn't need locking because we are trying to lock the file for exclusive
101  // access and fail immediately if we can't.
102  bool result = info.Save(fd);
103  if (result) {
104    VLOG(profiler) << "Successfully saved profile info to " << filename
105        << " Size: " << GetFileSizeBytes(filename);
106    if (bytes_written != nullptr) {
107      *bytes_written = GetFileSizeBytes(filename);
108    }
109  } else {
110    VLOG(profiler) << "Failed to save profile info to " << filename;
111  }
112  return result;
113}
114
115static bool WriteToFile(int fd, const std::ostringstream& os) {
116  std::string data(os.str());
117  const char *p = data.c_str();
118  size_t length = data.length();
119  do {
120    int n = TEMP_FAILURE_RETRY(write(fd, p, length));
121    if (n < 0) {
122      PLOG(WARNING) << "Failed to write to descriptor: " << fd;
123      return false;
124    }
125    p += n;
126    length -= n;
127  } while (length > 0);
128  return true;
129}
130
131static constexpr const char kFieldSeparator = ',';
132static constexpr const char kLineSeparator = '\n';
133static constexpr const char* kClassesMarker = "classes";
134
135/**
136 * Serialization format:
137 *    dex_location1,dex_location_checksum1,method_id11,method_id12...,classes,class_id1,class_id2...
138 *    dex_location2,dex_location_checksum2,method_id21,method_id22...,classes,class_id1,class_id2...
139 * e.g.
140 *    app.apk,131232145,11,23,454,54,classes,1,2,4,1234
141 *    app.apk:classes5.dex,218490184,39,13,49,1
142 **/
143bool ProfileCompilationInfo::Save(int fd) {
144  ScopedTrace trace(__PRETTY_FUNCTION__);
145  DCHECK_GE(fd, 0);
146  // TODO(calin): Profile this and see how much memory it takes. If too much,
147  // write to file directly.
148  std::ostringstream os;
149  for (const auto& it : info_) {
150    const std::string& dex_location = it.first;
151    const DexFileData& dex_data = it.second;
152    if (dex_data.method_set.empty() && dex_data.class_set.empty()) {
153      continue;
154    }
155
156    os << dex_location << kFieldSeparator << dex_data.checksum;
157    for (auto method_it : dex_data.method_set) {
158      os << kFieldSeparator << method_it;
159    }
160    if (!dex_data.class_set.empty()) {
161      os << kFieldSeparator << kClassesMarker;
162      for (auto class_id : dex_data.class_set) {
163        os << kFieldSeparator << class_id;
164      }
165    }
166    os << kLineSeparator;
167  }
168
169  return WriteToFile(fd, os);
170}
171
172// TODO(calin): This a duplicate of Utils::Split fixing the case where the first character
173// is the separator. Merge the fix into Utils::Split once verified that it doesn't break its users.
174static void SplitString(const std::string& s, char separator, std::vector<std::string>* result) {
175  const char* p = s.data();
176  const char* end = p + s.size();
177  // Check if the first character is the separator.
178  if (p != end && *p ==separator) {
179    result->push_back("");
180    ++p;
181  }
182  // Process the rest of the characters.
183  while (p != end) {
184    if (*p == separator) {
185      ++p;
186    } else {
187      const char* start = p;
188      while (++p != end && *p != separator) {
189        // Skip to the next occurrence of the separator.
190      }
191      result->push_back(std::string(start, p - start));
192    }
193  }
194}
195
196ProfileCompilationInfo::DexFileData* ProfileCompilationInfo::GetOrAddDexFileData(
197    const std::string& dex_location,
198    uint32_t checksum) {
199  auto info_it = info_.find(dex_location);
200  if (info_it == info_.end()) {
201    info_it = info_.Put(dex_location, DexFileData(checksum));
202  }
203  if (info_it->second.checksum != checksum) {
204    LOG(WARNING) << "Checksum mismatch for dex " << dex_location;
205    return nullptr;
206  }
207  return &info_it->second;
208}
209
210bool ProfileCompilationInfo::AddResolvedClasses(const DexCacheResolvedClasses& classes) {
211  const std::string dex_location = GetProfileDexFileKey(classes.GetDexLocation());
212  const uint32_t checksum = classes.GetLocationChecksum();
213  DexFileData* const data = GetOrAddDexFileData(dex_location, checksum);
214  if (data == nullptr) {
215    return false;
216  }
217  data->class_set.insert(classes.GetClasses().begin(), classes.GetClasses().end());
218  return true;
219}
220
221bool ProfileCompilationInfo::AddMethodIndex(const std::string& dex_location,
222                                            uint32_t checksum,
223                                            uint16_t method_idx) {
224  DexFileData* const data = GetOrAddDexFileData(dex_location, checksum);
225  if (data == nullptr) {
226    return false;
227  }
228  data->method_set.insert(method_idx);
229  return true;
230}
231
232bool ProfileCompilationInfo::AddClassIndex(const std::string& dex_location,
233                                           uint32_t checksum,
234                                           uint16_t class_idx) {
235  DexFileData* const data = GetOrAddDexFileData(dex_location, checksum);
236  if (data == nullptr) {
237    return false;
238  }
239  data->class_set.insert(class_idx);
240  return true;
241}
242
243bool ProfileCompilationInfo::ProcessLine(const std::string& line) {
244  std::vector<std::string> parts;
245  SplitString(line, kFieldSeparator, &parts);
246  if (parts.size() < 3) {
247    LOG(WARNING) << "Invalid line: " << line;
248    return false;
249  }
250
251  const std::string& dex_location = parts[0];
252  uint32_t checksum;
253  if (!ParseInt(parts[1].c_str(), &checksum)) {
254    return false;
255  }
256
257  for (size_t i = 2; i < parts.size(); i++) {
258    if (parts[i] == kClassesMarker) {
259      ++i;
260      // All of the remaining idx are class def indexes.
261      for (++i; i < parts.size(); ++i) {
262        uint32_t class_def_idx;
263        if (!ParseInt(parts[i].c_str(), &class_def_idx)) {
264          LOG(WARNING) << "Cannot parse class_def_idx " << parts[i];
265          return false;
266        } else if (class_def_idx >= std::numeric_limits<uint16_t>::max()) {
267          LOG(WARNING) << "Class def idx " << class_def_idx << " is larger than uint16_t max";
268          return false;
269        }
270        if (!AddClassIndex(dex_location, checksum, class_def_idx)) {
271          return false;
272        }
273      }
274      break;
275    }
276    uint32_t method_idx;
277    if (!ParseInt(parts[i].c_str(), &method_idx)) {
278      LOG(WARNING) << "Cannot parse method_idx " << parts[i];
279      return false;
280    }
281    if (!AddMethodIndex(dex_location, checksum, method_idx)) {
282      return false;
283    }
284  }
285  return true;
286}
287
288// Parses the buffer (of length n) starting from start_from and identify new lines
289// based on kLineSeparator marker.
290// Returns the first position after kLineSeparator in the buffer (starting from start_from),
291// or -1 if the marker doesn't appear.
292// The processed characters are appended to the given line.
293static int GetLineFromBuffer(char* buffer, int n, int start_from, std::string& line) {
294  if (start_from >= n) {
295    return -1;
296  }
297  int new_line_pos = -1;
298  for (int i = start_from; i < n; i++) {
299    if (buffer[i] == kLineSeparator) {
300      new_line_pos = i;
301      break;
302    }
303  }
304  int append_limit = new_line_pos == -1 ? n : new_line_pos;
305  line.append(buffer + start_from, append_limit - start_from);
306  // Jump over kLineSeparator and return the position of the next character.
307  return new_line_pos == -1 ? new_line_pos : new_line_pos + 1;
308}
309
310bool ProfileCompilationInfo::Load(int fd) {
311  ScopedTrace trace(__PRETTY_FUNCTION__);
312  DCHECK_GE(fd, 0);
313
314  std::string current_line;
315  const int kBufferSize = 1024;
316  char buffer[kBufferSize];
317
318  while (true) {
319    int n = TEMP_FAILURE_RETRY(read(fd, buffer, kBufferSize));
320    if (n < 0) {
321      PLOG(WARNING) << "Error when reading profile file";
322      return false;
323    } else if (n == 0) {
324      break;
325    }
326    // Detect the new lines from the buffer. If we manage to complete a line,
327    // process it. Otherwise append to the current line.
328    int current_start_pos = 0;
329    while (current_start_pos < n) {
330      current_start_pos = GetLineFromBuffer(buffer, n, current_start_pos, current_line);
331      if (current_start_pos == -1) {
332        break;
333      }
334      if (!ProcessLine(current_line)) {
335        return false;
336      }
337      // Reset the current line (we just processed it).
338      current_line.clear();
339    }
340  }
341  return true;
342}
343
344bool ProfileCompilationInfo::Load(const ProfileCompilationInfo& other) {
345  for (const auto& other_it : other.info_) {
346    const std::string& other_dex_location = other_it.first;
347    const DexFileData& other_dex_data = other_it.second;
348
349    auto info_it = info_.find(other_dex_location);
350    if (info_it == info_.end()) {
351      info_it = info_.Put(other_dex_location, DexFileData(other_dex_data.checksum));
352    }
353    if (info_it->second.checksum != other_dex_data.checksum) {
354      LOG(WARNING) << "Checksum mismatch for dex " << other_dex_location;
355      return false;
356    }
357    info_it->second.method_set.insert(other_dex_data.method_set.begin(),
358                                      other_dex_data.method_set.end());
359    info_it->second.class_set.insert(other_dex_data.class_set.begin(),
360                                     other_dex_data.class_set.end());
361  }
362  return true;
363}
364
365bool ProfileCompilationInfo::ContainsMethod(const MethodReference& method_ref) const {
366  auto info_it = info_.find(GetProfileDexFileKey(method_ref.dex_file->GetLocation()));
367  if (info_it != info_.end()) {
368    if (method_ref.dex_file->GetLocationChecksum() != info_it->second.checksum) {
369      return false;
370    }
371    const std::set<uint16_t>& methods = info_it->second.method_set;
372    return methods.find(method_ref.dex_method_index) != methods.end();
373  }
374  return false;
375}
376
377bool ProfileCompilationInfo::ContainsClass(const DexFile& dex_file, uint16_t class_def_idx) const {
378  auto info_it = info_.find(GetProfileDexFileKey(dex_file.GetLocation()));
379  if (info_it != info_.end()) {
380    if (dex_file.GetLocationChecksum() != info_it->second.checksum) {
381      return false;
382    }
383    const std::set<uint16_t>& classes = info_it->second.class_set;
384    return classes.find(class_def_idx) != classes.end();
385  }
386  return false;
387}
388
389uint32_t ProfileCompilationInfo::GetNumberOfMethods() const {
390  uint32_t total = 0;
391  for (const auto& it : info_) {
392    total += it.second.method_set.size();
393  }
394  return total;
395}
396
397std::string ProfileCompilationInfo::DumpInfo(const std::vector<const DexFile*>* dex_files,
398                                             bool print_full_dex_location) const {
399  std::ostringstream os;
400  if (info_.empty()) {
401    return "ProfileInfo: empty";
402  }
403
404  os << "ProfileInfo:";
405
406  const std::string kFirstDexFileKeySubstitute = ":classes.dex";
407  for (const auto& it : info_) {
408    os << "\n";
409    const std::string& location = it.first;
410    const DexFileData& dex_data = it.second;
411    if (print_full_dex_location) {
412      os << location;
413    } else {
414      // Replace the (empty) multidex suffix of the first key with a substitute for easier reading.
415      std::string multidex_suffix = DexFile::GetMultiDexSuffix(location);
416      os << (multidex_suffix.empty() ? kFirstDexFileKeySubstitute : multidex_suffix);
417    }
418    for (const auto method_it : dex_data.method_set) {
419      if (dex_files != nullptr) {
420        const DexFile* dex_file = nullptr;
421        for (size_t i = 0; i < dex_files->size(); i++) {
422          if (location == (*dex_files)[i]->GetLocation()) {
423            dex_file = (*dex_files)[i];
424          }
425        }
426        if (dex_file != nullptr) {
427          os << "\n  " << PrettyMethod(method_it, *dex_file, true);
428        }
429      }
430      os << "\n  " << method_it;
431    }
432  }
433  return os.str();
434}
435
436bool ProfileCompilationInfo::Equals(const ProfileCompilationInfo& other) {
437  return info_.Equals(other.info_);
438}
439
440std::set<DexCacheResolvedClasses> ProfileCompilationInfo::GetResolvedClasses() const {
441  std::set<DexCacheResolvedClasses> ret;
442  for (auto&& pair : info_) {
443    const std::string& profile_key = pair.first;
444    const DexFileData& data = pair.second;
445    DexCacheResolvedClasses classes(profile_key, data.checksum);
446    classes.AddClasses(data.class_set.begin(), data.class_set.end());
447    ret.insert(classes);
448  }
449  return ret;
450}
451
452}  // namespace art
453