summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--profman/profile_assistant.cc2
-rw-r--r--profman/profile_assistant_test.cc10
-rw-r--r--profman/profman.cc50
-rw-r--r--runtime/jit/jit.cc7
-rw-r--r--runtime/jit/jit.h4
-rw-r--r--runtime/jit/jit_code_cache.cc12
-rw-r--r--runtime/jit/jit_code_cache.h7
-rw-r--r--runtime/jit/offline_profiling_info.cc561
-rw-r--r--runtime/jit/offline_profiling_info.h101
-rw-r--r--runtime/jit/profile_compilation_info_test.cc200
-rw-r--r--runtime/jit/profile_saver.cc308
-rw-r--r--runtime/jit/profile_saver.h52
-rw-r--r--runtime/oat_file_manager.cc14
-rw-r--r--runtime/oat_file_manager.h5
-rw-r--r--runtime/utils.cc8
-rw-r--r--runtime/utils.h1
16 files changed, 1025 insertions, 317 deletions
diff --git a/profman/profile_assistant.cc b/profman/profile_assistant.cc
index 58e8a3a7d1..ac1865785e 100644
--- a/profman/profile_assistant.cc
+++ b/profman/profile_assistant.cc
@@ -54,7 +54,7 @@ ProfileAssistant::ProcessingResult ProfileAssistant::ProcessProfilesInternal(
for (size_t i = 0; i < new_info.size(); i++) {
// Merge all data into a single object.
- if (!info.Load(new_info[i])) {
+ if (!info.MergeWith(new_info[i])) {
LOG(WARNING) << "Could not merge profile data at index " << i;
return kErrorBadProfiles;
}
diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc
index b0d5df2b3b..157ffc4c71 100644
--- a/profman/profile_assistant_test.cc
+++ b/profman/profile_assistant_test.cc
@@ -102,8 +102,8 @@ TEST_F(ProfileAssistantTest, AdviseCompilationEmptyReferences) {
ASSERT_TRUE(result.Load(reference_profile_fd));
ProfileCompilationInfo expected;
- ASSERT_TRUE(expected.Load(info1));
- ASSERT_TRUE(expected.Load(info2));
+ ASSERT_TRUE(expected.MergeWith(info1));
+ ASSERT_TRUE(expected.MergeWith(info2));
ASSERT_TRUE(expected.Equals(result));
// The information from profiles must remain the same.
@@ -145,9 +145,9 @@ TEST_F(ProfileAssistantTest, AdviseCompilationNonEmptyReferences) {
ASSERT_TRUE(result.Load(reference_profile_fd));
ProfileCompilationInfo expected;
- ASSERT_TRUE(expected.Load(info1));
- ASSERT_TRUE(expected.Load(info2));
- ASSERT_TRUE(expected.Load(reference_info));
+ ASSERT_TRUE(expected.MergeWith(info1));
+ ASSERT_TRUE(expected.MergeWith(info2));
+ ASSERT_TRUE(expected.MergeWith(reference_info));
ASSERT_TRUE(expected.Equals(result));
// The information from profiles must remain the same.
diff --git a/profman/profman.cc b/profman/profman.cc
index 7c9e449ed5..3e632bce8b 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -14,12 +14,14 @@
* limitations under the License.
*/
+#include "errno.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <unistd.h>
+#include <iostream>
#include <string>
#include <vector>
@@ -68,6 +70,9 @@ NO_RETURN static void Usage(const char *fmt, ...) {
UsageError("Command: %s", CommandLine().c_str());
UsageError("Usage: profman [options]...");
UsageError("");
+ UsageError(" --dump-info-for=<filename>: dumps the content of the profile file");
+ UsageError(" to standard output in a human readable form.");
+ UsageError("");
UsageError(" --profile-file=<filename>: specify profiler output file to use for compilation.");
UsageError(" Can be specified multiple time, in which case the data from the different");
UsageError(" profiles will be aggregated.");
@@ -117,9 +122,11 @@ class ProfMan FINAL {
const StringPiece option(argv[i]);
const bool log_options = false;
if (log_options) {
- LOG(INFO) << "patchoat: option[" << i << "]=" << argv[i];
+ LOG(INFO) << "profman: option[" << i << "]=" << argv[i];
}
- if (option.starts_with("--profile-file=")) {
+ if (option.starts_with("--dump-info-for=")) {
+ dump_info_for_ = option.substr(strlen("--dump-info-for=")).ToString();
+ } else if (option.starts_with("--profile-file=")) {
profile_files_.push_back(option.substr(strlen("--profile-file=")).ToString());
} else if (option.starts_with("--profile-file-fd=")) {
ParseFdForCollection(option, "--profile-file-fd", &profile_files_fd_);
@@ -132,13 +139,23 @@ class ProfMan FINAL {
}
}
- if (profile_files_.empty() && profile_files_fd_.empty()) {
+ bool has_profiles = !profile_files_.empty() || !profile_files_fd_.empty();
+ bool has_reference_profile = !reference_profile_file_.empty() ||
+ (reference_profile_file_fd_ != -1);
+
+ if (!dump_info_for_.empty()) {
+ if (has_profiles || has_reference_profile) {
+ Usage("dump-info-for cannot be specified together with other options");
+ }
+ return;
+ }
+ if (!has_profiles) {
Usage("No profile files specified.");
}
if (!profile_files_.empty() && !profile_files_fd_.empty()) {
Usage("Profile files should not be specified with both --profile-file-fd and --profile-file");
}
- if (!reference_profile_file_.empty() && (reference_profile_file_fd_ != -1)) {
+ if (!has_reference_profile) {
Usage("--reference-profile-file-fd should only be supplied with --profile-file-fd");
}
if (reference_profile_file_.empty() && (reference_profile_file_fd_ == -1)) {
@@ -160,6 +177,27 @@ class ProfMan FINAL {
return result;
}
+ int DumpProfileInfo() {
+ int fd = open(dump_info_for_.c_str(), O_RDWR);
+ if (fd < 0) {
+ std::cerr << "Cannot open " << dump_info_for_ << strerror(errno);
+ return -1;
+ }
+ ProfileCompilationInfo info;
+ if (!info.Load(fd)) {
+ std::cerr << "Cannot load profile info from " << dump_info_for_;
+ return -1;
+ }
+ std::string dump = info.DumpInfo(/*dex_files*/ nullptr);
+ info.Save(fd);
+ std::cout << dump << "\n";
+ return 0;
+ }
+
+ bool ShouldOnlyDumpProfile() {
+ return !dump_info_for_.empty();
+ }
+
private:
static void ParseFdForCollection(const StringPiece& option,
const char* arg_name,
@@ -186,6 +224,7 @@ class ProfMan FINAL {
std::string reference_profile_file_;
int reference_profile_file_fd_;
uint64_t start_ns_;
+ std::string dump_info_for_;
};
// See ProfileAssistant::ProcessingResult for return codes.
@@ -195,6 +234,9 @@ static int profman(int argc, char** argv) {
// Parse arguments. Argument mistakes will lead to exit(EXIT_FAILURE) in UsageError.
profman.ParseArgs(argc, argv);
+ if (profman.ShouldOnlyDumpProfile()) {
+ return profman.DumpProfileInfo();
+ }
// Process profile information and assess if we need to do a profile guided compilation.
// This operation involves I/O.
return profman.ProcessProfiles();
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index c2bfe16069..c36543f1f3 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -119,6 +119,11 @@ void Jit::DumpInfo(std::ostream& os) {
memory_use_.PrintMemoryUse(os);
}
+void Jit::DumpForSigQuit(std::ostream& os) {
+ DumpInfo(os);
+ ProfileSaver::DumpInstanceInfo(os);
+}
+
void Jit::AddTimingLogger(const TimingLogger& logger) {
cumulative_timings_.AddLogger(logger);
}
@@ -297,7 +302,7 @@ void Jit::StartProfileSaver(const std::string& filename,
void Jit::StopProfileSaver() {
if (save_profiling_info_ && ProfileSaver::IsStarted()) {
- ProfileSaver::Stop();
+ ProfileSaver::Stop(dump_info_on_shutdown_);
}
}
diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h
index ff3acf62c8..8198c18ebb 100644
--- a/runtime/jit/jit.h
+++ b/runtime/jit/jit.h
@@ -127,9 +127,7 @@ class Jit {
const std::string& app_dir);
void StopProfileSaver();
- void DumpForSigQuit(std::ostream& os) REQUIRES(!lock_) {
- DumpInfo(os);
- }
+ void DumpForSigQuit(std::ostream& os) REQUIRES(!lock_);
static void NewTypeLoadedIfUsingJit(mirror::Class* type)
SHARED_REQUIRES(Locks::mutator_lock_);
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 752d4ba180..6b6f5a5c15 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -887,13 +887,15 @@ void* JitCodeCache::MoreCore(const void* mspace, intptr_t increment) NO_THREAD_S
}
}
-void JitCodeCache::GetCompiledArtMethods(const std::set<std::string>& dex_base_locations,
- std::vector<ArtMethod*>& methods) {
+void JitCodeCache::GetProfiledMethods(const std::set<std::string>& dex_base_locations,
+ std::vector<MethodReference>& methods) {
ScopedTrace trace(__FUNCTION__);
MutexLock mu(Thread::Current(), lock_);
- for (auto it : method_code_map_) {
- if (ContainsElement(dex_base_locations, it.second->GetDexFile()->GetBaseLocation())) {
- methods.push_back(it.second);
+ for (const ProfilingInfo* info : profiling_infos_) {
+ ArtMethod* method = info->GetMethod();
+ const DexFile* dex_file = method->GetDexFile();
+ if (ContainsElement(dex_base_locations, dex_file->GetBaseLocation())) {
+ methods.emplace_back(dex_file, method->GetDexMethodIndex());
}
}
}
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index f31cc51d51..4df6762517 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -26,6 +26,7 @@
#include "gc/accounting/bitmap.h"
#include "gc_root.h"
#include "jni.h"
+#include "method_reference.h"
#include "oat_file.h"
#include "object_callbacks.h"
#include "safe_map.h"
@@ -165,9 +166,9 @@ class JitCodeCache {
void* MoreCore(const void* mspace, intptr_t increment);
- // Adds to `methods` all the compiled ArtMethods which are part of any of the given dex locations.
- void GetCompiledArtMethods(const std::set<std::string>& dex_base_locations,
- std::vector<ArtMethod*>& methods)
+ // Adds to `methods` all profiled methods which are part of any of the given dex locations.
+ void GetProfiledMethods(const std::set<std::string>& dex_base_locations,
+ std::vector<MethodReference>& methods)
REQUIRES(!lock_)
SHARED_REQUIRES(Locks::mutator_lock_);
diff --git a/runtime/jit/offline_profiling_info.cc b/runtime/jit/offline_profiling_info.cc
index f181ca3467..a79bcf05ae 100644
--- a/runtime/jit/offline_profiling_info.cc
+++ b/runtime/jit/offline_profiling_info.cc
@@ -16,7 +16,8 @@
#include "offline_profiling_info.h"
-#include <fstream>
+#include "errno.h"
+#include <limits.h>
#include <vector>
#include <sys/file.h>
#include <sys/stat.h>
@@ -34,6 +35,11 @@
namespace art {
+const uint8_t ProfileCompilationInfo::kProfileMagic[] = { 'p', 'r', 'o', '\0' };
+const uint8_t ProfileCompilationInfo::kProfileVersion[] = { '0', '0', '1', '\0' };
+
+static constexpr uint16_t kMaxDexFileKeyLength = PATH_MAX;
+
// Transform the actual dex location into relative paths.
// Note: this is OK because we don't store profiles of different apps into the same file.
// Apps with split apks don't cause trouble because each split has a different name and will not
@@ -49,15 +55,27 @@ std::string ProfileCompilationInfo::GetProfileDexFileKey(const std::string& dex_
}
}
-bool ProfileCompilationInfo::SaveProfilingInfo(
- const std::string& filename,
- const std::vector<ArtMethod*>& methods,
+bool ProfileCompilationInfo::AddMethodsAndClasses(
+ const std::vector<MethodReference>& methods,
const std::set<DexCacheResolvedClasses>& resolved_classes) {
- if (methods.empty() && resolved_classes.empty()) {
- VLOG(profiler) << "No info to save to " << filename;
- return true;
+ for (const MethodReference& method : methods) {
+ if (!AddMethodIndex(GetProfileDexFileKey(method.dex_file->GetLocation()),
+ method.dex_file->GetLocationChecksum(),
+ method.dex_method_index)) {
+ return false;
+ }
}
+ for (const DexCacheResolvedClasses& dex_cache : resolved_classes) {
+ if (!AddResolvedClasses(dex_cache)) {
+ return false;
+ }
+ }
+ return true;
+}
+bool ProfileCompilationInfo::MergeAndSave(const std::string& filename,
+ uint64_t* bytes_written,
+ bool force) {
ScopedTrace trace(__PRETTY_FUNCTION__);
ScopedFlock flock;
std::string error;
@@ -68,26 +86,37 @@ bool ProfileCompilationInfo::SaveProfilingInfo(
int fd = flock.GetFile()->Fd();
- ProfileCompilationInfo info;
- if (!info.Load(fd)) {
- LOG(WARNING) << "Could not load previous profile data from file " << filename;
- return false;
- }
- {
- ScopedObjectAccess soa(Thread::Current());
- for (ArtMethod* method : methods) {
- const DexFile* dex_file = method->GetDexFile();
- if (!info.AddMethodIndex(GetProfileDexFileKey(dex_file->GetLocation()),
- dex_file->GetLocationChecksum(),
- method->GetDexMethodIndex())) {
+ // Load the file but keep a copy around to be able to infer if the content has changed.
+ ProfileCompilationInfo fileInfo;
+ ProfileLoadSatus status = fileInfo.LoadInternal(fd, &error);
+ if (status == kProfileLoadSuccess) {
+ // Merge the content of file into the current object.
+ if (MergeWith(fileInfo)) {
+ // If after the merge we have the same data as what is the file there's no point
+ // in actually doing the write. The file will be exactly the same as before.
+ if (Equals(fileInfo)) {
+ if (bytes_written != nullptr) {
+ *bytes_written = 0;
+ }
+ return true;
+ }
+ } else {
+ LOG(WARNING) << "Could not merge previous profile data from file " << filename;
+ if (!force) {
return false;
}
}
- for (const DexCacheResolvedClasses& dex_cache : resolved_classes) {
- info.AddResolvedClasses(dex_cache);
- }
+ } else if (force &&
+ ((status == kProfileLoadVersionMismatch) || (status == kProfileLoadBadData))) {
+ // Log a warning but don't return false. We will clear the profile anyway.
+ LOG(WARNING) << "Clearing bad or obsolete profile data from file "
+ << filename << ": " << error;
+ } else {
+ LOG(WARNING) << "Could not load profile data from file " << filename << ": " << error;
+ return false;
}
+ // We need to clear the data because we don't support appending to the profiles yet.
if (!flock.GetFile()->ClearContent()) {
PLOG(WARNING) << "Could not clear profile file: " << filename;
return false;
@@ -95,95 +124,118 @@ bool ProfileCompilationInfo::SaveProfilingInfo(
// This doesn't need locking because we are trying to lock the file for exclusive
// access and fail immediately if we can't.
- bool result = info.Save(fd);
+ bool result = Save(fd);
if (result) {
VLOG(profiler) << "Successfully saved profile info to " << filename
<< " Size: " << GetFileSizeBytes(filename);
+ if (bytes_written != nullptr) {
+ *bytes_written = GetFileSizeBytes(filename);
+ }
} else {
VLOG(profiler) << "Failed to save profile info to " << filename;
}
return result;
}
-static bool WriteToFile(int fd, const std::ostringstream& os) {
- std::string data(os.str());
- const char *p = data.c_str();
- size_t length = data.length();
- do {
- int n = TEMP_FAILURE_RETRY(write(fd, p, length));
- if (n < 0) {
- PLOG(WARNING) << "Failed to write to descriptor: " << fd;
+// Returns true if all the bytes were successfully written to the file descriptor.
+static bool WriteBuffer(int fd, const uint8_t* buffer, size_t byte_count) {
+ while (byte_count > 0) {
+ int bytes_written = TEMP_FAILURE_RETRY(write(fd, buffer, byte_count));
+ if (bytes_written == -1) {
return false;
}
- p += n;
- length -= n;
- } while (length > 0);
+ byte_count -= bytes_written; // Reduce the number of remaining bytes.
+ buffer += bytes_written; // Move the buffer forward.
+ }
return true;
}
-static constexpr const char kFieldSeparator = ',';
-static constexpr const char kLineSeparator = '\n';
-static constexpr const char* kClassesMarker = "classes";
+// Add the string bytes to the buffer.
+static void AddStringToBuffer(std::vector<uint8_t>* buffer, const std::string& value) {
+ buffer->insert(buffer->end(), value.begin(), value.end());
+}
+
+// Insert each byte, from low to high into the buffer.
+template <typename T>
+static void AddUintToBuffer(std::vector<uint8_t>* buffer, T value) {
+ for (size_t i = 0; i < sizeof(T); i++) {
+ buffer->push_back((value >> (i * kBitsPerByte)) & 0xff);
+ }
+}
+
+static constexpr size_t kLineHeaderSize =
+ 3 * sizeof(uint16_t) + // method_set.size + class_set.size + dex_location.size
+ sizeof(uint32_t); // checksum
/**
* Serialization format:
- * dex_location1,dex_location_checksum1,method_id11,method_id12...,classes,class_id1,class_id2...
- * dex_location2,dex_location_checksum2,method_id21,method_id22...,classes,class_id1,class_id2...
- * e.g.
- * app.apk,131232145,11,23,454,54,classes,1,2,4,1234
- * app.apk:classes5.dex,218490184,39,13,49,1
+ * magic,version,number_of_lines
+ * dex_location1,number_of_methods1,number_of_classes1,dex_location_checksum1, \
+ * method_id11,method_id12...,class_id1,class_id2...
+ * dex_location2,number_of_methods2,number_of_classes2,dex_location_checksum2, \
+ * method_id21,method_id22...,,class_id1,class_id2...
+ * .....
**/
bool ProfileCompilationInfo::Save(int fd) {
ScopedTrace trace(__PRETTY_FUNCTION__);
DCHECK_GE(fd, 0);
- // TODO(calin): Profile this and see how much memory it takes. If too much,
- // write to file directly.
- std::ostringstream os;
+
+ // Cache at most 5KB before writing.
+ static constexpr size_t kMaxSizeToKeepBeforeWriting = 5 * KB;
+ // Use a vector wrapper to avoid keeping track of offsets when we add elements.
+ std::vector<uint8_t> buffer;
+ WriteBuffer(fd, kProfileMagic, sizeof(kProfileMagic));
+ WriteBuffer(fd, kProfileVersion, sizeof(kProfileVersion));
+ AddUintToBuffer(&buffer, static_cast<uint16_t>(info_.size()));
+
for (const auto& it : info_) {
+ if (buffer.size() > kMaxSizeToKeepBeforeWriting) {
+ if (!WriteBuffer(fd, buffer.data(), buffer.size())) {
+ return false;
+ }
+ buffer.clear();
+ }
const std::string& dex_location = it.first;
const DexFileData& dex_data = it.second;
if (dex_data.method_set.empty() && dex_data.class_set.empty()) {
continue;
}
- os << dex_location << kFieldSeparator << dex_data.checksum;
- for (auto method_it : dex_data.method_set) {
- os << kFieldSeparator << method_it;
- }
- if (!dex_data.class_set.empty()) {
- os << kFieldSeparator << kClassesMarker;
- for (auto class_id : dex_data.class_set) {
- os << kFieldSeparator << class_id;
- }
+ if (dex_location.size() >= kMaxDexFileKeyLength) {
+ LOG(WARNING) << "DexFileKey exceeds allocated limit";
+ return false;
}
- os << kLineSeparator;
- }
- return WriteToFile(fd, os);
-}
+ // Make sure that the buffer has enough capacity to avoid repeated resizings
+ // while we add data.
+ size_t required_capacity = buffer.size() +
+ kLineHeaderSize +
+ dex_location.size() +
+ sizeof(uint16_t) * (dex_data.class_set.size() + dex_data.method_set.size());
-// TODO(calin): This a duplicate of Utils::Split fixing the case where the first character
-// is the separator. Merge the fix into Utils::Split once verified that it doesn't break its users.
-static void SplitString(const std::string& s, char separator, std::vector<std::string>* result) {
- const char* p = s.data();
- const char* end = p + s.size();
- // Check if the first character is the separator.
- if (p != end && *p ==separator) {
- result->push_back("");
- ++p;
- }
- // Process the rest of the characters.
- while (p != end) {
- if (*p == separator) {
- ++p;
- } else {
- const char* start = p;
- while (++p != end && *p != separator) {
- // Skip to the next occurrence of the separator.
- }
- result->push_back(std::string(start, p - start));
+ buffer.reserve(required_capacity);
+
+ DCHECK_LE(dex_location.size(), std::numeric_limits<uint16_t>::max());
+ DCHECK_LE(dex_data.method_set.size(), std::numeric_limits<uint16_t>::max());
+ DCHECK_LE(dex_data.class_set.size(), std::numeric_limits<uint16_t>::max());
+ AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_location.size()));
+ AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_data.method_set.size()));
+ AddUintToBuffer(&buffer, static_cast<uint16_t>(dex_data.class_set.size()));
+ AddUintToBuffer(&buffer, dex_data.checksum); // uint32_t
+
+ AddStringToBuffer(&buffer, dex_location);
+
+ for (auto method_it : dex_data.method_set) {
+ AddUintToBuffer(&buffer, method_it);
+ }
+ for (auto class_id : dex_data.class_set) {
+ AddUintToBuffer(&buffer, class_id);
}
+ DCHECK_EQ(required_capacity, buffer.size())
+ << "Failed to add the expected number of bytes in the buffer";
}
+
+ return WriteBuffer(fd, buffer.data(), buffer.size());
}
ProfileCompilationInfo::DexFileData* ProfileCompilationInfo::GetOrAddDexFileData(
@@ -233,120 +285,259 @@ bool ProfileCompilationInfo::AddClassIndex(const std::string& dex_location,
return true;
}
-bool ProfileCompilationInfo::ProcessLine(const std::string& line) {
- std::vector<std::string> parts;
- SplitString(line, kFieldSeparator, &parts);
- if (parts.size() < 3) {
- LOG(WARNING) << "Invalid line: " << line;
- return false;
+bool ProfileCompilationInfo::ProcessLine(SafeBuffer& line_buffer,
+ uint16_t method_set_size,
+ uint16_t class_set_size,
+ uint32_t checksum,
+ const std::string& dex_location) {
+ for (uint16_t i = 0; i < method_set_size; i++) {
+ uint16_t method_idx = line_buffer.ReadUintAndAdvance<uint16_t>();
+ if (!AddMethodIndex(dex_location, checksum, method_idx)) {
+ return false;
+ }
}
- const std::string& dex_location = parts[0];
- uint32_t checksum;
- if (!ParseInt(parts[1].c_str(), &checksum)) {
+ for (uint16_t i = 0; i < class_set_size; i++) {
+ uint16_t class_def_idx = line_buffer.ReadUintAndAdvance<uint16_t>();
+ if (!AddClassIndex(dex_location, checksum, class_def_idx)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Tests for EOF by trying to read 1 byte from the descriptor.
+// Returns:
+// 0 if the descriptor is at the EOF,
+// -1 if there was an IO error
+// 1 if the descriptor has more content to read
+static int testEOF(int fd) {
+ uint8_t buffer[1];
+ return TEMP_FAILURE_RETRY(read(fd, buffer, 1));
+}
+
+// Reads an uint value previously written with AddUintToBuffer.
+template <typename T>
+T ProfileCompilationInfo::SafeBuffer::ReadUintAndAdvance() {
+ static_assert(std::is_unsigned<T>::value, "Type is not unsigned");
+ CHECK_LE(ptr_current_ + sizeof(T), ptr_end_);
+ T value = 0;
+ for (size_t i = 0; i < sizeof(T); i++) {
+ value += ptr_current_[i] << (i * kBitsPerByte);
+ }
+ ptr_current_ += sizeof(T);
+ return value;
+}
+
+bool ProfileCompilationInfo::SafeBuffer::CompareAndAdvance(const uint8_t* data, size_t data_size) {
+ if (ptr_current_ + data_size > ptr_end_) {
return false;
}
+ if (memcmp(ptr_current_, data, data_size) == 0) {
+ ptr_current_ += data_size;
+ return true;
+ }
+ return false;
+}
- for (size_t i = 2; i < parts.size(); i++) {
- if (parts[i] == kClassesMarker) {
- ++i;
- // All of the remaining idx are class def indexes.
- for (++i; i < parts.size(); ++i) {
- uint32_t class_def_idx;
- if (!ParseInt(parts[i].c_str(), &class_def_idx)) {
- LOG(WARNING) << "Cannot parse class_def_idx " << parts[i];
- return false;
- } else if (class_def_idx >= std::numeric_limits<uint16_t>::max()) {
- LOG(WARNING) << "Class def idx " << class_def_idx << " is larger than uint16_t max";
- return false;
- }
- if (!AddClassIndex(dex_location, checksum, class_def_idx)) {
- return false;
- }
- }
- break;
- }
- uint32_t method_idx;
- if (!ParseInt(parts[i].c_str(), &method_idx)) {
- LOG(WARNING) << "Cannot parse method_idx " << parts[i];
- return false;
- }
- if (!AddMethodIndex(dex_location, checksum, method_idx)) {
- return false;
+ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::SafeBuffer::FillFromFd(
+ int fd,
+ const std::string& source,
+ /*out*/std::string* error) {
+ size_t byte_count = ptr_end_ - ptr_current_;
+ uint8_t* buffer = ptr_current_;
+ while (byte_count > 0) {
+ int bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, byte_count));
+ if (bytes_read == 0) {
+ *error += "Profile EOF reached prematurely for " + source;
+ return kProfileLoadBadData;
+ } else if (bytes_read < 0) {
+ *error += "Profile IO error for " + source + strerror(errno);
+ return kProfileLoadIOError;
}
+ byte_count -= bytes_read;
+ buffer += bytes_read;
}
- return true;
+ return kProfileLoadSuccess;
}
-// Parses the buffer (of length n) starting from start_from and identify new lines
-// based on kLineSeparator marker.
-// Returns the first position after kLineSeparator in the buffer (starting from start_from),
-// or -1 if the marker doesn't appear.
-// The processed characters are appended to the given line.
-static int GetLineFromBuffer(char* buffer, int n, int start_from, std::string& line) {
- if (start_from >= n) {
- return -1;
- }
- int new_line_pos = -1;
- for (int i = start_from; i < n; i++) {
- if (buffer[i] == kLineSeparator) {
- new_line_pos = i;
- break;
+ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileHeader(
+ int fd,
+ /*out*/uint16_t* number_of_lines,
+ /*out*/std::string* error) {
+ // Read magic and version
+ const size_t kMagicVersionSize =
+ sizeof(kProfileMagic) +
+ sizeof(kProfileVersion) +
+ sizeof(uint16_t); // number of lines
+
+ SafeBuffer safe_buffer(kMagicVersionSize);
+
+ ProfileLoadSatus status = safe_buffer.FillFromFd(fd, "ReadProfileHeader", error);
+ if (status != kProfileLoadSuccess) {
+ return status;
+ }
+
+ if (!safe_buffer.CompareAndAdvance(kProfileMagic, sizeof(kProfileMagic))) {
+ *error = "Profile missing magic";
+ return kProfileLoadVersionMismatch;
+ }
+ if (!safe_buffer.CompareAndAdvance(kProfileVersion, sizeof(kProfileVersion))) {
+ *error = "Profile version mismatch";
+ return kProfileLoadVersionMismatch;
+ }
+ *number_of_lines = safe_buffer.ReadUintAndAdvance<uint16_t>();
+ return kProfileLoadSuccess;
+}
+
+ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLineHeader(
+ int fd,
+ /*out*/ProfileLineHeader* line_header,
+ /*out*/std::string* error) {
+ SafeBuffer header_buffer(kLineHeaderSize);
+ ProfileLoadSatus status = header_buffer.FillFromFd(fd, "ReadProfileHeader", error);
+ if (status != kProfileLoadSuccess) {
+ return status;
+ }
+
+ uint16_t dex_location_size = header_buffer.ReadUintAndAdvance<uint16_t>();
+ line_header->method_set_size = header_buffer.ReadUintAndAdvance<uint16_t>();
+ line_header->class_set_size = header_buffer.ReadUintAndAdvance<uint16_t>();
+ line_header->checksum = header_buffer.ReadUintAndAdvance<uint32_t>();
+
+ if (dex_location_size == 0 || dex_location_size > kMaxDexFileKeyLength) {
+ *error = "DexFileKey has an invalid size: " + std::to_string(dex_location_size);
+ return kProfileLoadBadData;
+ }
+
+ SafeBuffer location_buffer(dex_location_size);
+ status = location_buffer.FillFromFd(fd, "ReadProfileHeaderDexLocation", error);
+ if (status != kProfileLoadSuccess) {
+ return status;
+ }
+ line_header->dex_location.assign(
+ reinterpret_cast<char*>(location_buffer.Get()), dex_location_size);
+ return kProfileLoadSuccess;
+}
+
+ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::ReadProfileLine(
+ int fd,
+ const ProfileLineHeader& line_header,
+ /*out*/std::string* error) {
+ // Make sure that we don't try to read everything in memory (in case the profile if full).
+ // Split readings in chunks of at most 10kb.
+ static constexpr uint16_t kMaxNumberOfEntriesToRead = 5120;
+ uint16_t methods_left_to_read = line_header.method_set_size;
+ uint16_t classes_left_to_read = line_header.class_set_size;
+
+ while ((methods_left_to_read > 0) || (classes_left_to_read > 0)) {
+ uint16_t methods_to_read = std::min(kMaxNumberOfEntriesToRead, methods_left_to_read);
+ uint16_t max_classes_to_read = kMaxNumberOfEntriesToRead - methods_to_read;
+ uint16_t classes_to_read = std::min(max_classes_to_read, classes_left_to_read);
+
+ size_t line_size = sizeof(uint16_t) * (methods_to_read + classes_to_read);
+ SafeBuffer line_buffer(line_size);
+
+ ProfileLoadSatus status = line_buffer.FillFromFd(fd, "ReadProfileLine", error);
+ if (status != kProfileLoadSuccess) {
+ return status;
}
+ if (!ProcessLine(line_buffer,
+ methods_to_read,
+ classes_to_read,
+ line_header.checksum,
+ line_header.dex_location)) {
+ *error = "Error when reading profile file line";
+ return kProfileLoadBadData;
+ }
+ methods_left_to_read -= methods_to_read;
+ classes_left_to_read -= classes_to_read;
}
- int append_limit = new_line_pos == -1 ? n : new_line_pos;
- line.append(buffer + start_from, append_limit - start_from);
- // Jump over kLineSeparator and return the position of the next character.
- return new_line_pos == -1 ? new_line_pos : new_line_pos + 1;
+ return kProfileLoadSuccess;
}
bool ProfileCompilationInfo::Load(int fd) {
+ std::string error;
+ ProfileLoadSatus status = LoadInternal(fd, &error);
+
+ if (status == kProfileLoadSuccess) {
+ return true;
+ } else {
+ PLOG(WARNING) << "Error when reading profile " << error;
+ return false;
+ }
+}
+
+ProfileCompilationInfo::ProfileLoadSatus ProfileCompilationInfo::LoadInternal(
+ int fd, std::string* error) {
ScopedTrace trace(__PRETTY_FUNCTION__);
DCHECK_GE(fd, 0);
- std::string current_line;
- const int kBufferSize = 1024;
- char buffer[kBufferSize];
+ struct stat stat_buffer;
+ if (fstat(fd, &stat_buffer) != 0) {
+ return kProfileLoadIOError;
+ }
+ // We allow empty profile files.
+ // Profiles may be created by ActivityManager or installd before we manage to
+ // process them in the runtime or profman.
+ if (stat_buffer.st_size == 0) {
+ return kProfileLoadSuccess;
+ }
+ // Read profile header: magic + version + number_of_lines.
+ uint16_t number_of_lines;
+ ProfileLoadSatus status = ReadProfileHeader(fd, &number_of_lines, error);
+ if (status != kProfileLoadSuccess) {
+ return status;
+ }
- while (true) {
- int n = TEMP_FAILURE_RETRY(read(fd, buffer, kBufferSize));
- if (n < 0) {
- PLOG(WARNING) << "Error when reading profile file";
- return false;
- } else if (n == 0) {
- break;
+ while (number_of_lines > 0) {
+ ProfileLineHeader line_header;
+ // First, read the line header to get the amount of data we need to read.
+ status = ReadProfileLineHeader(fd, &line_header, error);
+ if (status != kProfileLoadSuccess) {
+ return status;
}
- // Detect the new lines from the buffer. If we manage to complete a line,
- // process it. Otherwise append to the current line.
- int current_start_pos = 0;
- while (current_start_pos < n) {
- current_start_pos = GetLineFromBuffer(buffer, n, current_start_pos, current_line);
- if (current_start_pos == -1) {
- break;
- }
- if (!ProcessLine(current_line)) {
- return false;
- }
- // Reset the current line (we just processed it).
- current_line.clear();
+
+ // Now read the actual profile line.
+ status = ReadProfileLine(fd, line_header, error);
+ if (status != kProfileLoadSuccess) {
+ return status;
}
+ number_of_lines--;
+ }
+
+ // Check that we read everything and that profiles don't contain junk data.
+ int result = testEOF(fd);
+ if (result == 0) {
+ return kProfileLoadSuccess;
+ } else if (result < 0) {
+ return kProfileLoadIOError;
+ } else {
+ *error = "Unexpected content in the profile file";
+ return kProfileLoadBadData;
}
- return true;
}
-bool ProfileCompilationInfo::Load(const ProfileCompilationInfo& other) {
+bool ProfileCompilationInfo::MergeWith(const ProfileCompilationInfo& other) {
+ // First verify that all checksums match. This will avoid adding garbage to
+ // the current profile info.
+ // Note that the number of elements should be very small, so this should not
+ // be a performance issue.
+ for (const auto& other_it : other.info_) {
+ auto info_it = info_.find(other_it.first);
+ if ((info_it != info_.end()) && (info_it->second.checksum != other_it.second.checksum)) {
+ LOG(WARNING) << "Checksum mismatch for dex " << other_it.first;
+ return false;
+ }
+ }
+ // All checksums match. Import the data.
for (const auto& other_it : other.info_) {
const std::string& other_dex_location = other_it.first;
const DexFileData& other_dex_data = other_it.second;
-
auto info_it = info_.find(other_dex_location);
if (info_it == info_.end()) {
info_it = info_.Put(other_dex_location, DexFileData(other_dex_data.checksum));
}
- if (info_it->second.checksum != other_dex_data.checksum) {
- LOG(WARNING) << "Checksum mismatch for dex " << other_dex_location;
- return false;
- }
info_it->second.method_set.insert(other_dex_data.method_set.begin(),
other_dex_data.method_set.end());
info_it->second.class_set.insert(other_dex_data.class_set.begin(),
@@ -387,6 +578,14 @@ uint32_t ProfileCompilationInfo::GetNumberOfMethods() const {
return total;
}
+uint32_t ProfileCompilationInfo::GetNumberOfResolvedClasses() const {
+ uint32_t total = 0;
+ for (const auto& it : info_) {
+ total += it.second.class_set.size();
+ }
+ return total;
+}
+
std::string ProfileCompilationInfo::DumpInfo(const std::vector<const DexFile*>* dex_files,
bool print_full_dex_location) const {
std::ostringstream os;
@@ -408,19 +607,29 @@ std::string ProfileCompilationInfo::DumpInfo(const std::vector<const DexFile*>*
std::string multidex_suffix = DexFile::GetMultiDexSuffix(location);
os << (multidex_suffix.empty() ? kFirstDexFileKeySubstitute : multidex_suffix);
}
- for (const auto method_it : dex_data.method_set) {
- if (dex_files != nullptr) {
- const DexFile* dex_file = nullptr;
- for (size_t i = 0; i < dex_files->size(); i++) {
- if (location == (*dex_files)[i]->GetLocation()) {
- dex_file = (*dex_files)[i];
- }
- }
- if (dex_file != nullptr) {
- os << "\n " << PrettyMethod(method_it, *dex_file, true);
+ const DexFile* dex_file = nullptr;
+ if (dex_files != nullptr) {
+ for (size_t i = 0; i < dex_files->size(); i++) {
+ if (location == (*dex_files)[i]->GetLocation()) {
+ dex_file = (*dex_files)[i];
}
}
- os << "\n " << method_it;
+ }
+ os << "\n\tmethods: ";
+ for (const auto method_it : dex_data.method_set) {
+ if (dex_file != nullptr) {
+ os << "\n\t\t" << PrettyMethod(method_it, *dex_file, true);
+ } else {
+ os << method_it << ",";
+ }
+ }
+ os << "\n\tclasses: ";
+ for (const auto class_it : dex_data.class_set) {
+ if (dex_file != nullptr) {
+ os << "\n\t\t" << PrettyType(class_it, *dex_file);
+ } else {
+ os << class_it << ",";
+ }
}
}
return os.str();
@@ -442,4 +651,10 @@ std::set<DexCacheResolvedClasses> ProfileCompilationInfo::GetResolvedClasses() c
return ret;
}
+void ProfileCompilationInfo::ClearResolvedClasses() {
+ for (auto& pair : info_) {
+ pair.second.class_set.clear();
+ }
+}
+
} // namespace art
diff --git a/runtime/jit/offline_profiling_info.h b/runtime/jit/offline_profiling_info.h
index df03244779..5a07da79a1 100644
--- a/runtime/jit/offline_profiling_info.h
+++ b/runtime/jit/offline_profiling_info.h
@@ -28,9 +28,6 @@
namespace art {
-class ArtMethod;
-class DexCacheProfileData;
-
// TODO: rename file.
/**
* Profile information in a format suitable to be queried by the compiler and
@@ -41,21 +38,29 @@ class DexCacheProfileData;
*/
class ProfileCompilationInfo {
public:
- // Saves profile information about the given methods in the given file.
- // Note that the saving proceeds only if the file can be locked for exclusive access.
- // If not (the locking is not blocking), the function does not save and returns false.
- static bool SaveProfilingInfo(const std::string& filename,
- const std::vector<ArtMethod*>& methods,
- const std::set<DexCacheResolvedClasses>& resolved_classes);
+ static const uint8_t kProfileMagic[];
+ static const uint8_t kProfileVersion[];
+ // Add the given methods and classes to the current profile object.
+ bool AddMethodsAndClasses(const std::vector<MethodReference>& methods,
+ const std::set<DexCacheResolvedClasses>& resolved_classes);
// Loads profile information from the given file descriptor.
bool Load(int fd);
- // Loads the data from another ProfileCompilationInfo object.
- bool Load(const ProfileCompilationInfo& info);
+ // Merge the data from another ProfileCompilationInfo into the current object.
+ bool MergeWith(const ProfileCompilationInfo& info);
// Saves the profile data to the given file descriptor.
bool Save(int fd);
+ // Loads and merges profile information from the given file into the current
+ // object and tries to save it back to disk.
+ // If `force` is true then the save will go through even if the given file
+ // has bad data or its version does not match. In this cases the profile content
+ // is ignored.
+ bool MergeAndSave(const std::string& filename, uint64_t* bytes_written, bool force);
+
// Returns the number of methods that were profiled.
uint32_t GetNumberOfMethods() const;
+ // Returns the number of resolved classes that were profiled.
+ uint32_t GetNumberOfResolvedClasses() const;
// Returns true if the method reference is present in the profiling info.
bool ContainsMethod(const MethodReference& method_ref) const;
@@ -70,8 +75,8 @@ class ProfileCompilationInfo {
std::string DumpInfo(const std::vector<const DexFile*>* dex_files,
bool print_full_dex_location = true) const;
- // For testing purposes.
bool Equals(const ProfileCompilationInfo& other);
+
static std::string GetProfileDexFileKey(const std::string& dex_location);
// Returns the class descriptors for all of the classes in the profiles' class sets.
@@ -79,7 +84,17 @@ class ProfileCompilationInfo {
// profile info stuff to generate a map back to the dex location.
std::set<DexCacheResolvedClasses> GetResolvedClasses() const;
+ // Clears the resolved classes from the current object.
+ void ClearResolvedClasses();
+
private:
+ enum ProfileLoadSatus {
+ kProfileLoadIOError,
+ kProfileLoadVersionMismatch,
+ kProfileLoadBadData,
+ kProfileLoadSuccess
+ };
+
struct DexFileData {
explicit DexFileData(uint32_t location_checksum) : checksum(location_checksum) {}
uint32_t checksum;
@@ -96,9 +111,65 @@ class ProfileCompilationInfo {
DexFileData* GetOrAddDexFileData(const std::string& dex_location, uint32_t checksum);
bool AddMethodIndex(const std::string& dex_location, uint32_t checksum, uint16_t method_idx);
bool AddClassIndex(const std::string& dex_location, uint32_t checksum, uint16_t class_idx);
- bool AddResolvedClasses(const DexCacheResolvedClasses& classes)
- SHARED_REQUIRES(Locks::mutator_lock_);
- bool ProcessLine(const std::string& line);
+ bool AddResolvedClasses(const DexCacheResolvedClasses& classes);
+
+ // Parsing functionality.
+
+ struct ProfileLineHeader {
+ std::string dex_location;
+ uint16_t method_set_size;
+ uint16_t class_set_size;
+ uint32_t checksum;
+ };
+
+ // A helper structure to make sure we don't read past our buffers in the loops.
+ struct SafeBuffer {
+ public:
+ explicit SafeBuffer(size_t size) : storage_(new uint8_t[size]) {
+ ptr_current_ = storage_.get();
+ ptr_end_ = ptr_current_ + size;
+ }
+
+ // Reads the content of the descriptor at the current position.
+ ProfileLoadSatus FillFromFd(int fd,
+ const std::string& source,
+ /*out*/std::string* error);
+
+ // Reads an uint value (high bits to low bits) and advances the current pointer
+ // with the number of bits read.
+ template <typename T> T ReadUintAndAdvance();
+
+ // Compares the given data with the content current pointer. If the contents are
+ // equal it advances the current pointer by data_size.
+ bool CompareAndAdvance(const uint8_t* data, size_t data_size);
+
+ // Get the underlying raw buffer.
+ uint8_t* Get() { return storage_.get(); }
+
+ private:
+ std::unique_ptr<uint8_t> storage_;
+ uint8_t* ptr_current_;
+ uint8_t* ptr_end_;
+ };
+
+ ProfileLoadSatus LoadInternal(int fd, std::string* error);
+
+ ProfileLoadSatus ReadProfileHeader(int fd,
+ /*out*/uint16_t* number_of_lines,
+ /*out*/std::string* error);
+
+ ProfileLoadSatus ReadProfileLineHeader(int fd,
+ /*out*/ProfileLineHeader* line_header,
+ /*out*/std::string* error);
+ ProfileLoadSatus ReadProfileLine(int fd,
+ const ProfileLineHeader& line_header,
+ /*out*/std::string* error);
+
+ bool ProcessLine(SafeBuffer& line_buffer,
+ uint16_t method_set_size,
+ uint16_t class_set_size,
+ uint32_t checksum,
+ const std::string& dex_location);
friend class ProfileCompilationInfoTest;
friend class CompilerDriverProfileTest;
diff --git a/runtime/jit/profile_compilation_info_test.cc b/runtime/jit/profile_compilation_info_test.cc
index fdd8c6e600..c8f4d94c74 100644
--- a/runtime/jit/profile_compilation_info_test.cc
+++ b/runtime/jit/profile_compilation_info_test.cc
@@ -21,6 +21,7 @@
#include "class_linker-inl.h"
#include "common_runtime_test.h"
#include "dex_file.h"
+#include "method_reference.h"
#include "mirror/class-inl.h"
#include "mirror/class_loader.h"
#include "handle_scope-inl.h"
@@ -49,16 +50,44 @@ class ProfileCompilationInfoTest : public CommonRuntimeTest {
return methods;
}
- bool AddData(const std::string& dex_location,
- uint32_t checksum,
- uint16_t method_index,
- ProfileCompilationInfo* info) {
+ bool AddMethod(const std::string& dex_location,
+ uint32_t checksum,
+ uint16_t method_index,
+ ProfileCompilationInfo* info) {
return info->AddMethodIndex(dex_location, checksum, method_index);
}
+ bool AddClass(const std::string& dex_location,
+ uint32_t checksum,
+ uint16_t class_index,
+ ProfileCompilationInfo* info) {
+ return info->AddMethodIndex(dex_location, checksum, class_index);
+ }
+
uint32_t GetFd(const ScratchFile& file) {
return static_cast<uint32_t>(file.GetFd());
}
+
+ bool SaveProfilingInfo(
+ const std::string& filename,
+ const std::vector<ArtMethod*>& methods,
+ const std::set<DexCacheResolvedClasses>& resolved_classes) {
+ ProfileCompilationInfo info;
+ std::vector<MethodReference> method_refs;
+ ScopedObjectAccess soa(Thread::Current());
+ for (ArtMethod* method : methods) {
+ method_refs.emplace_back(method->GetDexFile(), method->GetDexMethodIndex());
+ }
+ if (!info.AddMethodsAndClasses(method_refs, resolved_classes)) {
+ return false;
+ }
+ return info.MergeAndSave(filename, nullptr, false);
+ }
+
+ // Cannot sizeof the actual arrays so hardcode the values here.
+ // They should not change anyway.
+ static constexpr int kProfileMagicSize = 4;
+ static constexpr int kProfileVersionSize = 4;
};
TEST_F(ProfileCompilationInfoTest, SaveArtMethods) {
@@ -75,9 +104,7 @@ TEST_F(ProfileCompilationInfoTest, SaveArtMethods) {
// Save virtual methods from Main.
std::set<DexCacheResolvedClasses> resolved_classes;
std::vector<ArtMethod*> main_methods = GetVirtualMethods(class_loader, "LMain;");
- ASSERT_TRUE(ProfileCompilationInfo::SaveProfilingInfo(profile.GetFilename(),
- main_methods,
- resolved_classes));
+ ASSERT_TRUE(SaveProfilingInfo(profile.GetFilename(), main_methods, resolved_classes));
// Check that what we saved is in the profile.
ProfileCompilationInfo info1;
@@ -92,9 +119,7 @@ TEST_F(ProfileCompilationInfoTest, SaveArtMethods) {
// Save virtual methods from Second.
std::vector<ArtMethod*> second_methods = GetVirtualMethods(class_loader, "LSecond;");
- ASSERT_TRUE(ProfileCompilationInfo::SaveProfilingInfo(profile.GetFilename(),
- second_methods,
- resolved_classes));
+ ASSERT_TRUE(SaveProfilingInfo(profile.GetFilename(), second_methods, resolved_classes));
// Check that what we saved is in the profile (methods form Main and Second).
ProfileCompilationInfo info2;
@@ -118,8 +143,8 @@ TEST_F(ProfileCompilationInfoTest, SaveFd) {
ProfileCompilationInfo saved_info;
// Save a few methods.
for (uint16_t i = 0; i < 10; i++) {
- ASSERT_TRUE(AddData("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info));
- ASSERT_TRUE(AddData("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info));
+ ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info));
+ ASSERT_TRUE(AddMethod("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info));
}
ASSERT_TRUE(saved_info.Save(GetFd(profile)));
ASSERT_EQ(0, profile.GetFile()->Flush());
@@ -132,9 +157,9 @@ TEST_F(ProfileCompilationInfoTest, SaveFd) {
// Save more methods.
for (uint16_t i = 0; i < 100; i++) {
- ASSERT_TRUE(AddData("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info));
- ASSERT_TRUE(AddData("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info));
- ASSERT_TRUE(AddData("dex_location3", /* checksum */ 3, /* method_idx */ i, &saved_info));
+ ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info));
+ ASSERT_TRUE(AddMethod("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info));
+ ASSERT_TRUE(AddMethod("dex_location3", /* checksum */ 3, /* method_idx */ i, &saved_info));
}
ASSERT_TRUE(profile.GetFile()->ResetOffset());
ASSERT_TRUE(saved_info.Save(GetFd(profile)));
@@ -147,25 +172,156 @@ TEST_F(ProfileCompilationInfoTest, SaveFd) {
ASSERT_TRUE(loaded_info2.Equals(saved_info));
}
-TEST_F(ProfileCompilationInfoTest, AddDataFail) {
+TEST_F(ProfileCompilationInfoTest, AddMethodsAndClassesFail) {
ScratchFile profile;
ProfileCompilationInfo info;
- ASSERT_TRUE(AddData("dex_location", /* checksum */ 1, /* method_idx */ 1, &info));
+ ASSERT_TRUE(AddMethod("dex_location", /* checksum */ 1, /* method_idx */ 1, &info));
// Trying to add info for an existing file but with a different checksum.
- ASSERT_FALSE(AddData("dex_location", /* checksum */ 2, /* method_idx */ 2, &info));
+ ASSERT_FALSE(AddMethod("dex_location", /* checksum */ 2, /* method_idx */ 2, &info));
}
-TEST_F(ProfileCompilationInfoTest, LoadFail) {
+TEST_F(ProfileCompilationInfoTest, MergeFail) {
ScratchFile profile;
ProfileCompilationInfo info1;
- ASSERT_TRUE(AddData("dex_location", /* checksum */ 1, /* method_idx */ 1, &info1));
+ ASSERT_TRUE(AddMethod("dex_location", /* checksum */ 1, /* method_idx */ 1, &info1));
// Use the same file, change the checksum.
ProfileCompilationInfo info2;
- ASSERT_TRUE(AddData("dex_location", /* checksum */ 2, /* method_idx */ 2, &info2));
+ ASSERT_TRUE(AddMethod("dex_location", /* checksum */ 2, /* method_idx */ 2, &info2));
+
+ ASSERT_FALSE(info1.MergeWith(info2));
+}
+
+TEST_F(ProfileCompilationInfoTest, SaveMaxMethods) {
+ ScratchFile profile;
+
+ ProfileCompilationInfo saved_info;
+ // Save the maximum number of methods
+ for (uint16_t i = 0; i < std::numeric_limits<uint16_t>::max(); i++) {
+ ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info));
+ ASSERT_TRUE(AddMethod("dex_location2", /* checksum */ 2, /* method_idx */ i, &saved_info));
+ }
+ // Save the maximum number of classes
+ for (uint16_t i = 0; i < std::numeric_limits<uint16_t>::max(); i++) {
+ ASSERT_TRUE(AddClass("dex_location1", /* checksum */ 1, /* class_idx */ i, &saved_info));
+ ASSERT_TRUE(AddClass("dex_location2", /* checksum */ 2, /* class_idx */ i, &saved_info));
+ }
+
+ ASSERT_TRUE(saved_info.Save(GetFd(profile)));
+ ASSERT_EQ(0, profile.GetFile()->Flush());
+
+ // Check that we get back what we saved.
+ ProfileCompilationInfo loaded_info;
+ ASSERT_TRUE(profile.GetFile()->ResetOffset());
+ ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
+ ASSERT_TRUE(loaded_info.Equals(saved_info));
+}
+
+TEST_F(ProfileCompilationInfoTest, SaveEmpty) {
+ ScratchFile profile;
+
+ ProfileCompilationInfo saved_info;
+ ASSERT_TRUE(saved_info.Save(GetFd(profile)));
+ ASSERT_EQ(0, profile.GetFile()->Flush());
+
+ // Check that we get back what we saved.
+ ProfileCompilationInfo loaded_info;
+ ASSERT_TRUE(profile.GetFile()->ResetOffset());
+ ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
+ ASSERT_TRUE(loaded_info.Equals(saved_info));
+}
+
+TEST_F(ProfileCompilationInfoTest, LoadEmpty) {
+ ScratchFile profile;
+
+ ProfileCompilationInfo empyt_info;
+
+ ProfileCompilationInfo loaded_info;
+ ASSERT_TRUE(profile.GetFile()->ResetOffset());
+ ASSERT_TRUE(loaded_info.Load(GetFd(profile)));
+ ASSERT_TRUE(loaded_info.Equals(empyt_info));
+}
+
+TEST_F(ProfileCompilationInfoTest, BadMagic) {
+ ScratchFile profile;
+ uint8_t buffer[] = { 1, 2, 3, 4 };
+ ASSERT_TRUE(profile.GetFile()->WriteFully(buffer, sizeof(buffer)));
+ ProfileCompilationInfo loaded_info;
+ ASSERT_TRUE(profile.GetFile()->ResetOffset());
+ ASSERT_FALSE(loaded_info.Load(GetFd(profile)));
+}
+
+TEST_F(ProfileCompilationInfoTest, BadVersion) {
+ ScratchFile profile;
+
+ ASSERT_TRUE(profile.GetFile()->WriteFully(
+ ProfileCompilationInfo::kProfileMagic, kProfileMagicSize));
+ uint8_t version[] = { 'v', 'e', 'r', 's', 'i', 'o', 'n' };
+ ASSERT_TRUE(profile.GetFile()->WriteFully(version, sizeof(version)));
+ ASSERT_EQ(0, profile.GetFile()->Flush());
+
+ ProfileCompilationInfo loaded_info;
+ ASSERT_TRUE(profile.GetFile()->ResetOffset());
+ ASSERT_FALSE(loaded_info.Load(GetFd(profile)));
+}
- ASSERT_FALSE(info1.Load(info2));
+TEST_F(ProfileCompilationInfoTest, Incomplete) {
+ ScratchFile profile;
+ ASSERT_TRUE(profile.GetFile()->WriteFully(
+ ProfileCompilationInfo::kProfileMagic, kProfileMagicSize));
+ ASSERT_TRUE(profile.GetFile()->WriteFully(
+ ProfileCompilationInfo::kProfileVersion, kProfileVersionSize));
+ // Write that we have at least one line.
+ uint8_t line_number[] = { 0, 1 };
+ ASSERT_TRUE(profile.GetFile()->WriteFully(line_number, sizeof(line_number)));
+ ASSERT_EQ(0, profile.GetFile()->Flush());
+
+ ProfileCompilationInfo loaded_info;
+ ASSERT_TRUE(profile.GetFile()->ResetOffset());
+ ASSERT_FALSE(loaded_info.Load(GetFd(profile)));
+}
+
+TEST_F(ProfileCompilationInfoTest, TooLongDexLocation) {
+ ScratchFile profile;
+ ASSERT_TRUE(profile.GetFile()->WriteFully(
+ ProfileCompilationInfo::kProfileMagic, kProfileMagicSize));
+ ASSERT_TRUE(profile.GetFile()->WriteFully(
+ ProfileCompilationInfo::kProfileVersion, kProfileVersionSize));
+ // Write that we have at least one line.
+ uint8_t line_number[] = { 0, 1 };
+ ASSERT_TRUE(profile.GetFile()->WriteFully(line_number, sizeof(line_number)));
+
+ // dex_location_size, methods_size, classes_size, checksum.
+ // Dex location size is too big and should be rejected.
+ uint8_t line[] = { 255, 255, 0, 1, 0, 1, 0, 0, 0, 0 };
+ ASSERT_TRUE(profile.GetFile()->WriteFully(line, sizeof(line)));
+ ASSERT_EQ(0, profile.GetFile()->Flush());
+
+ ProfileCompilationInfo loaded_info;
+ ASSERT_TRUE(profile.GetFile()->ResetOffset());
+ ASSERT_FALSE(loaded_info.Load(GetFd(profile)));
+}
+
+TEST_F(ProfileCompilationInfoTest, UnexpectedContent) {
+ ScratchFile profile;
+
+ ProfileCompilationInfo saved_info;
+ // Save the maximum number of methods
+ for (uint16_t i = 0; i < 10; i++) {
+ ASSERT_TRUE(AddMethod("dex_location1", /* checksum */ 1, /* method_idx */ i, &saved_info));
+ }
+ ASSERT_TRUE(saved_info.Save(GetFd(profile)));
+
+ uint8_t random_data[] = { 1, 2, 3};
+ ASSERT_TRUE(profile.GetFile()->WriteFully(random_data, sizeof(random_data)));
+
+ ASSERT_EQ(0, profile.GetFile()->Flush());
+
+ // Check that we fail because of unexpected data at the end of the file.
+ ProfileCompilationInfo loaded_info;
+ ASSERT_TRUE(profile.GetFile()->ResetOffset());
+ ASSERT_FALSE(loaded_info.Load(GetFd(profile)));
}
} // namespace art
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index 6fe17dbe15..7a9d2506dc 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -22,25 +22,25 @@
#include "art_method-inl.h"
#include "base/systrace.h"
-#include "scoped_thread_state_change.h"
+#include "base/time_utils.h"
+#include "compiler_filter.h"
#include "oat_file_manager.h"
+#include "scoped_thread_state_change.h"
-namespace art {
-// An arbitrary value to throttle save requests. Set to 2s for now.
-static constexpr const uint64_t kMilisecondsToNano = 1000000;
-static constexpr const uint64_t kMinimumTimeBetweenCodeCacheUpdatesNs = 2000 * kMilisecondsToNano;
+namespace art {
// TODO: read the constants from ProfileOptions,
// Add a random delay each time we go to sleep so that we don't hammer the CPU
// with all profile savers running at the same time.
-static constexpr const uint64_t kRandomDelayMaxMs = 20 * 1000; // 20 seconds
-static constexpr const uint64_t kMaxBackoffMs = 5 * 60 * 1000; // 5 minutes
-static constexpr const uint64_t kSavePeriodMs = 10 * 1000; // 10 seconds
-static constexpr const uint64_t kInitialDelayMs = 2 * 1000; // 2 seconds
-static constexpr const double kBackoffCoef = 1.5;
+static constexpr const uint64_t kRandomDelayMaxMs = 30 * 1000; // 30 seconds
+static constexpr const uint64_t kMaxBackoffMs = 10 * 60 * 1000; // 10 minutes
+static constexpr const uint64_t kSavePeriodMs = 20 * 1000; // 20 seconds
+static constexpr const uint64_t kSaveResolvedClassesDelayMs = 2 * 1000; // 2 seconds
+static constexpr const double kBackoffCoef = 2.0;
-static constexpr const uint32_t kMinimumNrOrMethodsToSave = 10;
+static constexpr const uint32_t kMinimumNumberOfMethodsToSave = 10;
+static constexpr const uint32_t kMinimumNumberOfClassesToSave = 10;
ProfileSaver* ProfileSaver::instance_ = nullptr;
pthread_t ProfileSaver::profiler_pthread_ = 0U;
@@ -52,13 +52,21 @@ ProfileSaver::ProfileSaver(const std::string& output_filename,
const std::string& app_data_dir)
: jit_code_cache_(jit_code_cache),
foreign_dex_profile_path_(foreign_dex_profile_path),
- code_cache_last_update_time_ns_(0),
shutting_down_(false),
- first_profile_(true),
+ last_save_number_of_methods_(0),
+ last_save_number_of_classes_(0),
wait_lock_("ProfileSaver wait lock"),
- period_condition_("ProfileSaver period condition", wait_lock_) {
- AddTrackedLocations(output_filename, code_paths);
- app_data_dir_ = "";
+ period_condition_("ProfileSaver period condition", wait_lock_),
+ total_bytes_written_(0),
+ total_number_of_writes_(0),
+ total_number_of_code_cache_queries_(0),
+ total_number_of_skipped_writes_(0),
+ total_number_of_failed_writes_(0),
+ total_ms_of_sleep_(0),
+ total_ns_of_work_(0),
+ total_number_of_foreign_dex_marks_(0),
+ max_number_of_profile_entries_cached_(0) {
+ AddTrackedLocations(output_filename, app_data_dir, code_paths);
if (!app_data_dir.empty()) {
// The application directory is used to determine which dex files are owned by app.
// Since it could be a symlink (e.g. /data/data instead of /data/user/0), and we
@@ -66,9 +74,9 @@ ProfileSaver::ProfileSaver(const std::string& output_filename,
// store it's canonical form to be sure we use the same base when comparing.
UniqueCPtr<const char[]> app_data_dir_real_path(realpath(app_data_dir.c_str(), nullptr));
if (app_data_dir_real_path != nullptr) {
- app_data_dir_.assign(app_data_dir_real_path.get());
+ app_data_dirs_.emplace(app_data_dir_real_path.get());
} else {
- LOG(WARNING) << "Failed to get the real path for app dir: " << app_data_dir_
+ LOG(WARNING) << "Failed to get the real path for app dir: " << app_data_dir
<< ". The app dir will not be used to determine which dex files belong to the app";
}
}
@@ -80,14 +88,13 @@ void ProfileSaver::Run() {
uint64_t save_period_ms = kSavePeriodMs;
VLOG(profiler) << "Save profiling information every " << save_period_ms << " ms";
-
- bool first_iteration = true;
+ bool cache_resolved_classes = true;
while (!ShuttingDown(self)) {
uint64_t sleep_time_ms;
- if (first_iteration) {
+ if (cache_resolved_classes) {
// Sleep less long for the first iteration since we want to record loaded classes shortly
// after app launch.
- sleep_time_ms = kInitialDelayMs;
+ sleep_time_ms = kSaveResolvedClassesDelayMs;
} else {
const uint64_t random_sleep_delay_ms = rand() % kRandomDelayMaxMs;
sleep_time_ms = save_period_ms + random_sleep_delay_ms;
@@ -96,76 +103,146 @@ void ProfileSaver::Run() {
MutexLock mu(self, wait_lock_);
period_condition_.TimedWait(self, sleep_time_ms, 0);
}
-
+ total_ms_of_sleep_ += sleep_time_ms;
if (ShuttingDown(self)) {
break;
}
- if (!ProcessProfilingInfo() && save_period_ms < kMaxBackoffMs) {
- // If we don't need to save now it is less likely that we will need to do
- // so in the future. Increase the time between saves according to the
- // kBackoffCoef, but make it no larger than kMaxBackoffMs.
- save_period_ms = static_cast<uint64_t>(kBackoffCoef * save_period_ms);
+ uint64_t start = NanoTime();
+ if (cache_resolved_classes) {
+ // TODO(calin) This only considers the case of the primary profile file.
+ // Anything that gets loaded in the same VM will not have their resolved
+ // classes save (unless they started before the initial saving was done).
+ FetchAndCacheResolvedClasses();
} else {
- // Reset the period to the initial value as it's highly likely to JIT again.
- save_period_ms = kSavePeriodMs;
+ bool profile_saved_to_disk = ProcessProfilingInfo();
+ if (profile_saved_to_disk) {
+ // Reset the period to the initial value as it's highly likely to JIT again.
+ save_period_ms = kSavePeriodMs;
+ VLOG(profiler) << "Profile saver: saved something, period reset to: " << save_period_ms;
+ } else {
+ // If we don't need to save now it is less likely that we will need to do
+ // so in the future. Increase the time between saves according to the
+ // kBackoffCoef, but make it no larger than kMaxBackoffMs.
+ save_period_ms = std::min(kMaxBackoffMs,
+ static_cast<uint64_t>(kBackoffCoef * save_period_ms));
+ VLOG(profiler) << "Profile saver: nothing to save, delaying period to: " << save_period_ms;
+ }
}
- first_iteration = false;
+ cache_resolved_classes = false;
+
+ total_ns_of_work_ += (NanoTime() - start);
}
}
-bool ProfileSaver::ProcessProfilingInfo() {
+ProfileCompilationInfo* ProfileSaver::GetCachedProfiledInfo(const std::string& filename) {
+ auto info_it = profile_cache_.find(filename);
+ if (info_it == profile_cache_.end()) {
+ info_it = profile_cache_.Put(filename, ProfileCompilationInfo());
+ }
+ return &info_it->second;
+}
+
+void ProfileSaver::FetchAndCacheResolvedClasses() {
ScopedTrace trace(__PRETTY_FUNCTION__);
- uint64_t last_update_time_ns = jit_code_cache_->GetLastUpdateTimeNs();
- if (!first_profile_ && last_update_time_ns - code_cache_last_update_time_ns_
- < kMinimumTimeBetweenCodeCacheUpdatesNs) {
- VLOG(profiler) << "Not enough time has passed since the last code cache update."
- << "Last update: " << last_update_time_ns
- << " Last save: " << code_cache_last_update_time_ns_;
- return false;
+
+ ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+ std::set<DexCacheResolvedClasses> resolved_classes =
+ class_linker->GetResolvedClasses(/*ignore boot classes*/ true);
+ MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
+ uint64_t total_number_of_profile_entries_cached = 0;
+ for (const auto& it : tracked_dex_base_locations_) {
+ std::set<DexCacheResolvedClasses> resolved_classes_for_location;
+ const std::string& filename = it.first;
+ const std::set<std::string>& locations = it.second;
+
+ for (const DexCacheResolvedClasses& classes : resolved_classes) {
+ if (locations.find(classes.GetDexLocation()) != locations.end()) {
+ resolved_classes_for_location.insert(classes);
+ }
+ }
+ ProfileCompilationInfo* info = GetCachedProfiledInfo(filename);
+ info->AddMethodsAndClasses(std::vector<MethodReference>(), resolved_classes_for_location);
+ total_number_of_profile_entries_cached += resolved_classes_for_location.size();
}
+ max_number_of_profile_entries_cached_ = std::max(
+ max_number_of_profile_entries_cached_,
+ total_number_of_profile_entries_cached);
+}
- uint64_t start = NanoTime();
- code_cache_last_update_time_ns_ = last_update_time_ns;
+bool ProfileSaver::ProcessProfilingInfo() {
+ ScopedTrace trace(__PRETTY_FUNCTION__);
SafeMap<std::string, std::set<std::string>> tracked_locations;
{
// Make a copy so that we don't hold the lock while doing I/O.
MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
tracked_locations = tracked_dex_base_locations_;
}
+
+ bool profile_file_saved = false;
+ uint64_t total_number_of_profile_entries_cached = 0;
for (const auto& it : tracked_locations) {
if (ShuttingDown(Thread::Current())) {
return true;
}
const std::string& filename = it.first;
const std::set<std::string>& locations = it.second;
- std::vector<ArtMethod*> methods;
+ std::vector<MethodReference> methods;
{
ScopedObjectAccess soa(Thread::Current());
- jit_code_cache_->GetCompiledArtMethods(locations, methods);
- }
- // Always save for the first one for loaded classes profile.
- if (methods.size() < kMinimumNrOrMethodsToSave && !first_profile_) {
- VLOG(profiler) << "Not enough information to save to: " << filename
- <<" Nr of methods: " << methods.size();
- return false;
+ jit_code_cache_->GetProfiledMethods(locations, methods);
+ total_number_of_code_cache_queries_++;
}
- std::set<DexCacheResolvedClasses> resolved_classes;
- if (first_profile_) {
- ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
- resolved_classes = class_linker->GetResolvedClasses(/*ignore boot classes*/true);
+ ProfileCompilationInfo* cached_info = GetCachedProfiledInfo(filename);
+ cached_info->AddMethodsAndClasses(methods, std::set<DexCacheResolvedClasses>());
+ int64_t delta_number_of_methods =
+ cached_info->GetNumberOfMethods() -
+ static_cast<int64_t>(last_save_number_of_methods_);
+ int64_t delta_number_of_classes =
+ cached_info->GetNumberOfResolvedClasses() -
+ static_cast<int64_t>(last_save_number_of_classes_);
+
+ if (delta_number_of_methods < kMinimumNumberOfMethodsToSave &&
+ delta_number_of_classes < kMinimumNumberOfClassesToSave) {
+ VLOG(profiler) << "Not enough information to save to: " << filename
+ << " Nr of methods: " << delta_number_of_methods
+ << " Nr of classes: " << delta_number_of_classes;
+ total_number_of_skipped_writes_++;
+ continue;
}
-
- if (!ProfileCompilationInfo::SaveProfilingInfo(filename, methods, resolved_classes)) {
+ uint64_t bytes_written;
+ // Force the save. In case the profile data is corrupted or the the profile
+ // has the wrong version this will "fix" the file to the correct format.
+ if (cached_info->MergeAndSave(filename, &bytes_written, /*force*/ true)) {
+ last_save_number_of_methods_ = cached_info->GetNumberOfMethods();
+ last_save_number_of_classes_ = cached_info->GetNumberOfResolvedClasses();
+ // Clear resolved classes. No need to store them around as
+ // they don't change after the first write.
+ cached_info->ClearResolvedClasses();
+ if (bytes_written > 0) {
+ total_number_of_writes_++;
+ total_bytes_written_ += bytes_written;
+ profile_file_saved = true;
+ } else {
+ // At this point we could still have avoided the write.
+ // We load and merge the data from the file lazily at its first ever
+ // save attempt. So, whatever we are trying to save could already be
+ // in the file.
+ total_number_of_skipped_writes_++;
+ }
+ } else {
LOG(WARNING) << "Could not save profiling info to " << filename;
- return false;
+ total_number_of_failed_writes_++;
}
-
- VLOG(profiler) << "Profile process time: " << PrettyDuration(NanoTime() - start);
+ total_number_of_profile_entries_cached +=
+ cached_info->GetNumberOfMethods() +
+ cached_info->GetNumberOfResolvedClasses();
}
- first_profile_ = false;
- return true;
+ max_number_of_profile_entries_cached_ = std::max(
+ max_number_of_profile_entries_cached_,
+ total_number_of_profile_entries_cached);
+ return profile_file_saved;
}
void* ProfileSaver::RunProfileSaverThread(void* arg) {
@@ -183,6 +260,26 @@ void* ProfileSaver::RunProfileSaverThread(void* arg) {
return nullptr;
}
+static bool ShouldProfileLocation(const std::string& location) {
+ OatFileManager& oat_manager = Runtime::Current()->GetOatFileManager();
+ const OatFile* oat_file = oat_manager.FindOpenedOatFileFromDexLocation(location);
+ if (oat_file == nullptr) {
+ // This can happen if we fallback to run code directly from the APK.
+ // Profile it with the hope that the background dexopt will get us back into
+ // a good state.
+ VLOG(profiler) << "Asked to profile a location without an oat file:" << location;
+ return true;
+ }
+ CompilerFilter::Filter filter = oat_file->GetCompilerFilter();
+ if ((filter == CompilerFilter::kSpeed) || (filter == CompilerFilter::kEverything)) {
+ VLOG(profiler)
+ << "Skip profiling oat file because it's already speed|everything compiled: "
+ << location << " oat location: " << oat_file->GetLocation();
+ return false;
+ }
+ return true;
+}
+
void ProfileSaver::Start(const std::string& output_filename,
jit::JitCodeCache* jit_code_cache,
const std::vector<std::string>& code_paths,
@@ -192,6 +289,18 @@ void ProfileSaver::Start(const std::string& output_filename,
DCHECK(!output_filename.empty());
DCHECK(jit_code_cache != nullptr);
+ std::vector<std::string> code_paths_to_profile;
+
+ for (const std::string& location : code_paths) {
+ if (ShouldProfileLocation(location)) {
+ code_paths_to_profile.push_back(location);
+ }
+ }
+ if (code_paths_to_profile.empty()) {
+ VLOG(profiler) << "No code paths should be profiled.";
+ return;
+ }
+
MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
if (instance_ != nullptr) {
// If we already have an instance, make sure it uses the same jit_code_cache.
@@ -199,16 +308,16 @@ void ProfileSaver::Start(const std::string& output_filename,
// apps which share the same runtime).
DCHECK_EQ(instance_->jit_code_cache_, jit_code_cache);
// Add the code_paths to the tracked locations.
- instance_->AddTrackedLocations(output_filename, code_paths);
+ instance_->AddTrackedLocations(output_filename, app_data_dir, code_paths_to_profile);
return;
}
VLOG(profiler) << "Starting profile saver using output file: " << output_filename
- << ". Tracking: " << Join(code_paths, ':');
+ << ". Tracking: " << Join(code_paths_to_profile, ':');
instance_ = new ProfileSaver(output_filename,
jit_code_cache,
- code_paths,
+ code_paths_to_profile,
foreign_dex_profile_path,
app_data_dir);
@@ -219,7 +328,7 @@ void ProfileSaver::Start(const std::string& output_filename,
"Profile saver thread");
}
-void ProfileSaver::Stop() {
+void ProfileSaver::Stop(bool dump_info) {
ProfileSaver* profile_saver = nullptr;
pthread_t profiler_pthread = 0U;
@@ -237,6 +346,9 @@ void ProfileSaver::Stop() {
return;
}
instance_->shutting_down_ = true;
+ if (dump_info) {
+ instance_->DumpInfo(LOG(INFO));
+ }
}
{
@@ -267,49 +379,62 @@ bool ProfileSaver::IsStarted() {
}
void ProfileSaver::AddTrackedLocations(const std::string& output_filename,
+ const std::string& app_data_dir,
const std::vector<std::string>& code_paths) {
auto it = tracked_dex_base_locations_.find(output_filename);
if (it == tracked_dex_base_locations_.end()) {
tracked_dex_base_locations_.Put(output_filename,
std::set<std::string>(code_paths.begin(), code_paths.end()));
+ app_data_dirs_.insert(app_data_dir);
} else {
it->second.insert(code_paths.begin(), code_paths.end());
}
}
void ProfileSaver::NotifyDexUse(const std::string& dex_location) {
+ if (!ShouldProfileLocation(dex_location)) {
+ return;
+ }
std::set<std::string> app_code_paths;
std::string foreign_dex_profile_path;
- std::string app_data_dir;
+ std::set<std::string> app_data_dirs;
{
MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
- DCHECK(instance_ != nullptr);
+ if (instance_ == nullptr) {
+ return;
+ }
// Make a copy so that we don't hold the lock while doing I/O.
for (const auto& it : instance_->tracked_dex_base_locations_) {
app_code_paths.insert(it.second.begin(), it.second.end());
}
foreign_dex_profile_path = instance_->foreign_dex_profile_path_;
- app_data_dir = instance_->app_data_dir_;
+ app_data_dirs.insert(instance_->app_data_dirs_.begin(), instance_->app_data_dirs_.end());
}
- MaybeRecordDexUseInternal(dex_location,
- app_code_paths,
- foreign_dex_profile_path,
- app_data_dir);
+ bool mark_created = MaybeRecordDexUseInternal(dex_location,
+ app_code_paths,
+ foreign_dex_profile_path,
+ app_data_dirs);
+ if (mark_created) {
+ MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
+ if (instance_ != nullptr) {
+ instance_->total_number_of_foreign_dex_marks_++;
+ }
+ }
}
-void ProfileSaver::MaybeRecordDexUseInternal(
+bool ProfileSaver::MaybeRecordDexUseInternal(
const std::string& dex_location,
const std::set<std::string>& app_code_paths,
const std::string& foreign_dex_profile_path,
- const std::string& app_data_dir) {
+ const std::set<std::string>& app_data_dirs) {
if (dex_location.empty()) {
LOG(WARNING) << "Asked to record foreign dex use with an empty dex location.";
- return;
+ return false;
}
if (foreign_dex_profile_path.empty()) {
LOG(WARNING) << "Asked to record foreign dex use without a valid profile path ";
- return;
+ return false;
}
UniqueCPtr<const char[]> dex_location_real_path(realpath(dex_location.c_str(), nullptr));
@@ -320,14 +445,14 @@ void ProfileSaver::MaybeRecordDexUseInternal(
? dex_location.c_str()
: dex_location_real_path.get());
- if (dex_location_real_path_str.compare(0, app_data_dir.length(), app_data_dir) == 0) {
+ if (app_data_dirs.find(dex_location_real_path_str) != app_data_dirs.end()) {
// The dex location is under the application folder. Nothing to record.
- return;
+ return false;
}
if (app_code_paths.find(dex_location) != app_code_paths.end()) {
// The dex location belongs to the application code paths. Nothing to record.
- return;
+ return false;
}
// Do another round of checks with the real paths.
// Note that we could cache all the real locations in the saver (since it's an expensive
@@ -344,7 +469,7 @@ void ProfileSaver::MaybeRecordDexUseInternal(
: real_app_code_location.get());
if (real_app_code_location_str == dex_location_real_path_str) {
// The dex location belongs to the application code paths. Nothing to record.
- return;
+ return false;
}
}
@@ -362,12 +487,37 @@ void ProfileSaver::MaybeRecordDexUseInternal(
if (close(fd) != 0) {
PLOG(WARNING) << "Could not close file after flagging foreign dex use " << flag_path;
}
+ return true;
} else {
if (errno != EEXIST) {
// Another app could have already created the file.
PLOG(WARNING) << "Could not create foreign dex use mark " << flag_path;
+ return false;
}
+ return true;
+ }
+}
+
+void ProfileSaver::DumpInstanceInfo(std::ostream& os) {
+ MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
+ if (instance_ != nullptr) {
+ instance_->DumpInfo(os);
}
}
+void ProfileSaver::DumpInfo(std::ostream& os) {
+ os << "ProfileSaver total_bytes_written=" << total_bytes_written_ << '\n'
+ << "ProfileSaver total_number_of_writes=" << total_number_of_writes_ << '\n'
+ << "ProfileSaver total_number_of_code_cache_queries="
+ << total_number_of_code_cache_queries_ << '\n'
+ << "ProfileSaver total_number_of_skipped_writes=" << total_number_of_skipped_writes_ << '\n'
+ << "ProfileSaver total_number_of_failed_writes=" << total_number_of_failed_writes_ << '\n'
+ << "ProfileSaver total_ms_of_sleep=" << total_ms_of_sleep_ << '\n'
+ << "ProfileSaver total_ms_of_work=" << NsToMs(total_ns_of_work_) << '\n'
+ << "ProfileSaver total_number_of_foreign_dex_marks="
+ << total_number_of_foreign_dex_marks_ << '\n'
+ << "ProfileSaver max_number_profile_entries_cached="
+ << max_number_of_profile_entries_cached_ << '\n';
+}
+
} // namespace art
diff --git a/runtime/jit/profile_saver.h b/runtime/jit/profile_saver.h
index e7eab95f3d..0a222bfdcd 100644
--- a/runtime/jit/profile_saver.h
+++ b/runtime/jit/profile_saver.h
@@ -37,7 +37,7 @@ class ProfileSaver {
// Stops the profile saver thread.
// NO_THREAD_SAFETY_ANALYSIS for static function calling into member function with excludes lock.
- static void Stop()
+ static void Stop(bool dump_info_)
REQUIRES(!Locks::profiler_lock_, !wait_lock_)
NO_THREAD_SAFETY_ANALYSIS;
@@ -46,6 +46,9 @@ class ProfileSaver {
static void NotifyDexUse(const std::string& dex_location);
+ // If the profile saver is running, dumps statistics to the `os`. Otherwise it does nothing.
+ static void DumpInstanceInfo(std::ostream& os);
+
private:
ProfileSaver(const std::string& output_filename,
jit::JitCodeCache* jit_code_cache,
@@ -67,14 +70,25 @@ class ProfileSaver {
bool ShuttingDown(Thread* self) REQUIRES(!Locks::profiler_lock_);
void AddTrackedLocations(const std::string& output_filename,
+ const std::string& app_data_dir,
const std::vector<std::string>& code_paths)
REQUIRES(Locks::profiler_lock_);
- static void MaybeRecordDexUseInternal(
+ // Retrieves the cached profile compilation info for the given profile file.
+ // If no entry exists, a new empty one will be created, added to the cache and
+ // then returned.
+ ProfileCompilationInfo* GetCachedProfiledInfo(const std::string& filename);
+ // Fetches the current resolved classes from the ClassLinker and stores them
+ // in the profile_cache_ for later save.
+ void FetchAndCacheResolvedClasses();
+
+ static bool MaybeRecordDexUseInternal(
const std::string& dex_location,
const std::set<std::string>& tracked_locations,
const std::string& foreign_dex_profile_path,
- const std::string& app_data_dir);
+ const std::set<std::string>& app_data_dirs);
+
+ void DumpInfo(std::ostream& os);
// The only instance of the saver.
static ProfileSaver* instance_ GUARDED_BY(Locks::profiler_lock_);
@@ -82,18 +96,44 @@ class ProfileSaver {
static pthread_t profiler_pthread_ GUARDED_BY(Locks::profiler_lock_);
jit::JitCodeCache* jit_code_cache_;
+
+ // Collection of code paths that the profiles tracks.
+ // It maps profile locations to code paths (dex base locations).
SafeMap<std::string, std::set<std::string>> tracked_dex_base_locations_
GUARDED_BY(Locks::profiler_lock_);
+ // The directory were the we should store the code paths.
std::string foreign_dex_profile_path_;
- std::string app_data_dir_;
- uint64_t code_cache_last_update_time_ns_;
+
+ // A list of application directories, used to infer if a loaded dex belongs
+ // to the application or not. Multiple application data directories are possible when
+ // different apps share the same runtime.
+ std::set<std::string> app_data_dirs_ GUARDED_BY(Locks::profiler_lock_);
+
bool shutting_down_ GUARDED_BY(Locks::profiler_lock_);
- bool first_profile_ = true;
+ uint32_t last_save_number_of_methods_;
+ uint32_t last_save_number_of_classes_;
+
+ // A local cache for the profile information. Maps each tracked file to its
+ // profile information. The size of this cache is usually very small and tops
+ // to just a few hundreds entries in the ProfileCompilationInfo objects.
+ // It helps avoiding unnecessary writes to disk.
+ SafeMap<std::string, ProfileCompilationInfo> profile_cache_;
// Save period condition support.
Mutex wait_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
ConditionVariable period_condition_ GUARDED_BY(wait_lock_);
+ uint64_t total_bytes_written_;
+ uint64_t total_number_of_writes_;
+ uint64_t total_number_of_code_cache_queries_;
+ uint64_t total_number_of_skipped_writes_;
+ uint64_t total_number_of_failed_writes_;
+ uint64_t total_ms_of_sleep_;
+ uint64_t total_ns_of_work_;
+ uint64_t total_number_of_foreign_dex_marks_;
+ // TODO(calin): replace with an actual size.
+ uint64_t max_number_of_profile_entries_cached_;
+
DISALLOW_COPY_AND_ASSIGN(ProfileSaver);
};
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index 98943537b1..9ab0072ea9 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -74,6 +74,20 @@ void OatFileManager::UnRegisterAndDeleteOatFile(const OatFile* oat_file) {
compare.release();
}
+const OatFile* OatFileManager::FindOpenedOatFileFromDexLocation(
+ const std::string& dex_base_location) const {
+ ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
+ for (const std::unique_ptr<const OatFile>& oat_file : oat_files_) {
+ const std::vector<const OatDexFile*>& oat_dex_files = oat_file->GetOatDexFiles();
+ for (const OatDexFile* oat_dex_file : oat_dex_files) {
+ if (DexFile::GetBaseLocation(oat_dex_file->GetDexFileLocation()) == dex_base_location) {
+ return oat_file.get();
+ }
+ }
+ }
+ return nullptr;
+}
+
const OatFile* OatFileManager::FindOpenedOatFileFromOatLocation(const std::string& oat_location)
const {
ReaderMutexLock mu(Thread::Current(), *Locks::oat_file_manager_lock_);
diff --git a/runtime/oat_file_manager.h b/runtime/oat_file_manager.h
index 574d0e2582..f98102e844 100644
--- a/runtime/oat_file_manager.h
+++ b/runtime/oat_file_manager.h
@@ -60,6 +60,11 @@ class OatFileManager {
const OatFile* FindOpenedOatFileFromOatLocation(const std::string& oat_location) const
REQUIRES(!Locks::oat_file_manager_lock_);
+ // Find the oat file which contains a dex files with the given dex base location,
+ // returns null if there are none.
+ const OatFile* FindOpenedOatFileFromDexLocation(const std::string& dex_base_location) const
+ REQUIRES(!Locks::oat_file_manager_lock_);
+
// Attempt to reserve a location, returns false if it is already reserved or already in used by
// an oat file.
bool RegisterOatFileLocation(const std::string& oat_location)
diff --git a/runtime/utils.cc b/runtime/utils.cc
index 472a85c042..6a50b8eee2 100644
--- a/runtime/utils.cc
+++ b/runtime/utils.cc
@@ -1459,6 +1459,14 @@ bool FileExists(const std::string& filename) {
return stat(filename.c_str(), &buffer) == 0;
}
+bool FileExistsAndNotEmpty(const std::string& filename) {
+ struct stat buffer;
+ if (stat(filename.c_str(), &buffer) != 0) {
+ return false;
+ }
+ return buffer.st_size > 0;
+}
+
std::string PrettyDescriptor(Primitive::Type type) {
return PrettyDescriptor(Primitive::Descriptor(type));
}
diff --git a/runtime/utils.h b/runtime/utils.h
index 83ac0b870e..c1e88a4feb 100644
--- a/runtime/utils.h
+++ b/runtime/utils.h
@@ -296,6 +296,7 @@ int ExecAndReturnCode(std::vector<std::string>& arg_vector, std::string* error_m
// Returns true if the file exists.
bool FileExists(const std::string& filename);
+bool FileExistsAndNotEmpty(const std::string& filename);
class VoidFunctor {
public: