diff options
-rw-r--r-- | compiler/dex/frontend.cc | 20 | ||||
-rw-r--r-- | compiler/dex/mir_analysis.cc | 8 | ||||
-rw-r--r-- | compiler/dex/mir_graph.h | 5 | ||||
-rw-r--r-- | compiler/dex/verification_results.cc | 2 | ||||
-rw-r--r-- | compiler/driver/compiler_driver.cc | 94 | ||||
-rw-r--r-- | compiler/driver/compiler_driver.h | 38 | ||||
-rw-r--r-- | compiler/driver/compiler_options.h | 5 | ||||
-rw-r--r-- | dex2oat/dex2oat.cc | 19 | ||||
-rw-r--r-- | runtime/native/dalvik_system_DexFile.cc | 138 | ||||
-rw-r--r-- | runtime/native/dalvik_system_VMRuntime.cc | 12 | ||||
-rw-r--r-- | runtime/profiler.cc | 195 | ||||
-rw-r--r-- | runtime/profiler.h | 27 | ||||
-rw-r--r-- | runtime/runtime.cc | 21 | ||||
-rw-r--r-- | runtime/runtime.h | 9 | ||||
-rw-r--r-- | runtime/thread_list.cc | 30 | ||||
-rw-r--r-- | runtime/thread_list.h | 4 |
16 files changed, 563 insertions, 64 deletions
diff --git a/compiler/dex/frontend.cc b/compiler/dex/frontend.cc index 1c2d16f..243395a 100644 --- a/compiler/dex/frontend.cc +++ b/compiler/dex/frontend.cc @@ -17,6 +17,7 @@ #include "compiler_backend.h" #include "compiler_internals.h" #include "driver/compiler_driver.h" +#include "driver/compiler_options.h" #include "dataflow_iterator-inl.h" #include "leb128.h" #include "mirror/object.h" @@ -25,7 +26,7 @@ #include "backend.h" #include "base/logging.h" #include "base/timing_logger.h" - +#include "driver/compiler_options.h" #include "dex/quick/dex_file_to_method_inliner_map.h" namespace art { @@ -209,13 +210,26 @@ static CompiledMethod* CompileMethod(CompilerDriver& driver, cu.mir_graph->EnableOpcodeCounting(); } + const CompilerOptions& compiler_options = cu.compiler_driver->GetCompilerOptions(); + CompilerOptions::CompilerFilter compiler_filter = compiler_options.GetCompilerFilter(); + + // Check early if we should skip this compilation if using the profiled filter. + if (cu.compiler_driver->ProfilePresent()) { + std::string methodname = PrettyMethod(method_idx, dex_file); + if (cu.mir_graph->SkipCompilation(methodname)) { + return NULL; + } + } + /* Build the raw MIR graph */ cu.mir_graph->InlineMethod(code_item, access_flags, invoke_type, class_def_idx, method_idx, class_loader, dex_file); cu.NewTimingSplit("MIROpt:CheckFilters"); - if (cu.mir_graph->SkipCompilation()) { - return NULL; + if (compiler_filter != CompilerOptions::kInterpretOnly) { + if (cu.mir_graph->SkipCompilation()) { + return NULL; + } } /* Create the pass driver and launch it */ diff --git a/compiler/dex/mir_analysis.cc b/compiler/dex/mir_analysis.cc index 667ee26..5314bb7 100644 --- a/compiler/dex/mir_analysis.cc +++ b/compiler/dex/mir_analysis.cc @@ -999,7 +999,6 @@ bool MIRGraph::ComputeSkipCompilation(MethodStats* stats, bool skip_default) { /* * Will eventually want this to be a bit more sophisticated and happen at verification time. - * Ultimate goal is to drive with profile data. */ bool MIRGraph::SkipCompilation() { const CompilerOptions& compiler_options = cu_->compiler_driver->GetCompilerOptions(); @@ -1013,8 +1012,7 @@ bool MIRGraph::SkipCompilation() { return true; } - if (compiler_filter == CompilerOptions::kInterpretOnly) { - LOG(WARNING) << "InterpretOnly should ideally be filtered out prior to parsing."; + if (compiler_filter == CompilerOptions::kInterpretOnly || compiler_filter == CompilerOptions::kProfiled) { return true; } @@ -1170,4 +1168,8 @@ void MIRGraph::DoCacheFieldLoweringInfo() { } } +bool MIRGraph::SkipCompilation(const std::string& methodname) { + return cu_->compiler_driver->SkipCompilation(methodname); +} + } // namespace art diff --git a/compiler/dex/mir_graph.h b/compiler/dex/mir_graph.h index 85d6d89..94b3816 100644 --- a/compiler/dex/mir_graph.h +++ b/compiler/dex/mir_graph.h @@ -383,6 +383,11 @@ class MIRGraph { bool SkipCompilation(); /* + * Should we skip the compilation of this method based on its name? + */ + bool SkipCompilation(const std::string& methodname); + + /* * Parse dex method and add MIR at current insert point. Returns id (which is * actually the index of the method in the m_units_ array). */ diff --git a/compiler/dex/verification_results.cc b/compiler/dex/verification_results.cc index 947c22d..6b0875c 100644 --- a/compiler/dex/verification_results.cc +++ b/compiler/dex/verification_results.cc @@ -110,7 +110,7 @@ bool VerificationResults::IsCandidateForCompilation(MethodReference& method_ref, if (((access_flags & kAccConstructor) != 0) && ((access_flags & kAccStatic) != 0)) { return false; } - return (compiler_options_->GetCompilerFilter() != CompilerOptions::kInterpretOnly); + return true; } } // namespace art diff --git a/compiler/driver/compiler_driver.cc b/compiler/driver/compiler_driver.cc index d3d58c9..a46015d 100644 --- a/compiler/driver/compiler_driver.cc +++ b/compiler/driver/compiler_driver.cc @@ -21,6 +21,7 @@ #include <vector> #include <unistd.h> +#include <fstream> #include "base/stl_util.h" #include "base/timing_logger.h" @@ -303,8 +304,9 @@ CompilerDriver::CompilerDriver(const CompilerOptions* compiler_options, InstructionSet instruction_set, InstructionSetFeatures instruction_set_features, bool image, DescriptorSet* image_classes, size_t thread_count, - bool dump_stats, bool dump_passes, CumulativeLogger* timer) - : compiler_options_(compiler_options), + bool dump_stats, bool dump_passes, CumulativeLogger* timer, + std::string profile_file) + : profile_ok_(false), compiler_options_(compiler_options), verification_results_(verification_results), method_inliner_map_(method_inliner_map), compiler_backend_(CompilerBackend::Create(compiler_backend_kind)), @@ -338,6 +340,11 @@ CompilerDriver::CompilerDriver(const CompilerOptions* compiler_options, CHECK_PTHREAD_CALL(pthread_key_create, (&tls_key_, NULL), "compiler tls key"); + // Read the profile file if one is provided. + if (profile_file != "") { + profile_ok_ = ReadProfile(profile_file); + } + dex_to_dex_compiler_ = reinterpret_cast<DexToDexCompilerFn>(ArtCompileDEX); compiler_backend_->Init(*this); @@ -1936,7 +1943,6 @@ void CompilerDriver::CompileMethod(const DexFile::CodeItem* code_item, uint32_t } else { MethodReference method_ref(&dex_file, method_idx); bool compile = verification_results_->IsCandidateForCompilation(method_ref, access_flags); - if (compile) { // NOTE: if compiler declines to compile this method, it will return NULL. compiled_method = compiler_backend_->Compile( @@ -2073,4 +2079,86 @@ void CompilerDriver::InstructionSetToLLVMTarget(InstructionSet instruction_set, LOG(FATAL) << "Unknown instruction set: " << instruction_set; } } + +bool CompilerDriver::ReadProfile(const std::string& filename) { + VLOG(compiler) << "reading profile file " << filename; + struct stat st; + int err = stat(filename.c_str(), &st); + if (err == -1) { + VLOG(compiler) << "not found"; + return false; + } + std::ifstream in(filename.c_str()); + if (!in) { + VLOG(compiler) << "profile file " << filename << " exists but can't be opened"; + VLOG(compiler) << "file owner: " << st.st_uid << ":" << st.st_gid; + VLOG(compiler) << "me: " << getuid() << ":" << getgid(); + VLOG(compiler) << "file permissions: " << std::oct << st.st_mode; + VLOG(compiler) << "errno: " << errno; + return false; + } + // The first line contains summary information. + std::string line; + std::getline(in, line); + if (in.eof()) { + return false; + } + std::vector<std::string> summary_info; + Split(line, '/', summary_info); + if (summary_info.size() != 3) { + // Bad summary info. It should be count/total/bootpath + return false; + } + // This is the number of hits in all methods. + uint32_t total_count = 0; + for (int i = 0 ; i < 3; ++i) { + total_count += atoi(summary_info[0].c_str()); + } + + // Now read each line until the end of file. Each line consists of 3 fields separated by / + while (!in.eof()) { + std::getline(in, line); + if (in.eof()) { + break; + } + std::vector<std::string> info; + Split(line, '/', info); + if (info.size() != 3) { + // Malformed. + break; + } + const std::string& methodname = info[0]; + uint32_t count = atoi(info[1].c_str()); + uint32_t size = atoi(info[2].c_str()); + double percent = (count * 100.0) / total_count; + // Add it to the profile map + profile_map_[methodname] = ProfileData(methodname, count, size, percent); + } + return true; +} + +bool CompilerDriver::SkipCompilation(const std::string& method_name) { + if (!profile_ok_) { + return true; + } + constexpr double kThresholdPercent = 2.0; // Anything above this threshold will be compiled. + + // First find the method in the profile map. + ProfileMap::iterator i = profile_map_.find(method_name); + if (i == profile_map_.end()) { + // Not in profile, no information can be determined. + VLOG(compiler) << "not compiling " << method_name << " because it's not in the profile"; + return true; + } + const ProfileData& data = i->second; + bool compile = data.IsAbove(kThresholdPercent); + if (compile) { + LOG(INFO) << "compiling method " << method_name << " because its usage is " << + data.GetPercent() << "%"; + } else { + VLOG(compiler) << "not compiling method " << method_name << " because usage is too low (" + << data.GetPercent() << "%)"; + } + return !compile; +} } // namespace art diff --git a/compiler/driver/compiler_driver.h b/compiler/driver/compiler_driver.h index ac70e5a..12463a9 100644 --- a/compiler/driver/compiler_driver.h +++ b/compiler/driver/compiler_driver.h @@ -105,7 +105,8 @@ class CompilerDriver { InstructionSetFeatures instruction_set_features, bool image, DescriptorSet* image_classes, size_t thread_count, bool dump_stats, bool dump_passes, - CumulativeLogger* timer); + CumulativeLogger* timer, + std::string profile_file = ""); ~CompilerDriver(); @@ -141,6 +142,10 @@ class CompilerDriver { return compiler_backend_.get(); } + bool ProfilePresent() const { + return profile_ok_; + } + // Are we compiling and creating an image file? bool IsImage() const { return image_; @@ -554,6 +559,37 @@ class CompilerDriver { return cfi_info_.get(); } + // Profile data. This is generated from previous runs of the program and stored + // in a file. It is used to determine whether to compile a particular method or not. + class ProfileData { + public: + ProfileData() : count_(0), method_size_(0), percent_(0) {} + ProfileData(std::string method_name, uint32_t count, uint32_t method_size, double percent) : + method_name_(method_name), count_(count), method_size_(method_size), percent_(percent) { + } + + bool IsAbove(double v) const { return percent_ >= v; } + double GetPercent() const { return percent_; } + + private: + std::string method_name_; // Method name. + uint32_t count_; // Number number of times it has been called. + uint32_t method_size_; // Size of the method on dex instructions. + double percent_; // Percentage of time spent in this method. + }; + + // Profile data is stored in a map, indexed by the full method name. + typedef std::map<const std::string, ProfileData> ProfileMap; + ProfileMap profile_map_; + bool profile_ok_; + + // Read the profile data from the given file. Calculates the percentage for each method. + // Returns false if there was no profile file or it was malformed. + bool ReadProfile(const std::string& filename); + + // Should the compiler run on this method given profile information? + bool SkipCompilation(const std::string& method_name); + private: // Compute constant code and method pointers when possible void GetCodeAndMethodForDirectCall(InvokeType* type, InvokeType sharp_type, diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h index 39738ab..0cca1e9 100644 --- a/compiler/driver/compiler_options.h +++ b/compiler/driver/compiler_options.h @@ -23,6 +23,7 @@ class CompilerOptions { public: enum CompilerFilter { kInterpretOnly, // Compile nothing. + kProfiled, // Compile based on profile. kSpace, // Maximize space savings. kBalanced, // Try to get the best performance return on compilation investment. kSpeed, // Maximize runtime performance. @@ -30,7 +31,11 @@ class CompilerOptions { }; // Guide heuristics to determine whether to compile method if profile data not available. +#if ART_SMALL_MODE + static const CompilerFilter kDefaultCompilerFilter = kProfiled; +#else static const CompilerFilter kDefaultCompilerFilter = kSpeed; +#endif static const size_t kDefaultHugeMethodThreshold = 10000; static const size_t kDefaultLargeMethodThreshold = 600; static const size_t kDefaultSmallMethodThreshold = 60; diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc index 7c81ffb..cc78816 100644 --- a/dex2oat/dex2oat.cc +++ b/dex2oat/dex2oat.cc @@ -200,6 +200,8 @@ static void Usage(const char* fmt, ...) { UsageError(" such as initial heap size, maximum heap size, and verbose output."); UsageError(" Use a separate --runtime-arg switch for each argument."); UsageError(" Example: --runtime-arg -Xms256m"); + UsageError(""); + UsageError(" --profile-file=<filename>: specify profiler output file to use for compilation."); UsageError(""); std::cerr << "See log for usage error information\n"; exit(EXIT_FAILURE); @@ -310,7 +312,8 @@ class Dex2Oat { bool dump_stats, bool dump_passes, TimingLogger& timings, - CumulativeLogger& compiler_phases_timings) { + CumulativeLogger& compiler_phases_timings, + std::string profile_file) { // SirtRef and ClassLoader creation needs to come after Runtime::Create jobject class_loader = NULL; Thread* self = Thread::Current(); @@ -340,7 +343,8 @@ class Dex2Oat { thread_count_, dump_stats, dump_passes, - &compiler_phases_timings)); + &compiler_phases_timings, + profile_file)); driver->GetCompilerBackend()->SetBitcodeFileName(*driver.get(), bitcode_filename); @@ -742,6 +746,8 @@ static int dex2oat(int argc, char** argv) { InstructionSet instruction_set = kNone; #endif + // Profile file to use + std::string profile_file; bool is_host = false; bool dump_stats = false; @@ -896,6 +902,12 @@ static int dex2oat(int argc, char** argv) { dump_passes = true; } else if (option == "--dump-stats") { dump_stats = true; + } else if (option.starts_with("--profile-file=")) { + profile_file = option.substr(strlen("--profile-file=")).data(); + VLOG(compiler) << "dex2oat: profile file is " << profile_file; + } else if (option == "--no-profile-file") { + LOG(INFO) << "dex2oat: no profile file supplied (explictly)"; + // No profile } else { Usage("Unknown argument %s", option.data()); } @@ -1204,7 +1216,8 @@ static int dex2oat(int argc, char** argv) { dump_stats, dump_passes, timings, - compiler_phases_timings)); + compiler_phases_timings, + profile_file)); if (compiler.get() == NULL) { LOG(ERROR) << "Failed to create oat file: " << oat_location; diff --git a/runtime/native/dalvik_system_DexFile.cc b/runtime/native/dalvik_system_DexFile.cc index 8a96d79..bab0604 100644 --- a/runtime/native/dalvik_system_DexFile.cc +++ b/runtime/native/dalvik_system_DexFile.cc @@ -15,6 +15,7 @@ */ #include <unistd.h> +#include <fcntl.h> #include "base/logging.h" #include "class_linker.h" @@ -36,6 +37,10 @@ #include "toStringArray.h" #include "zip_archive.h" +#ifdef HAVE_ANDROID_OS +#include "cutils/properties.h" +#endif + namespace art { // A smart pointer that provides read-only access to a Java string's UTF chars. @@ -193,7 +198,40 @@ static jobjectArray DexFile_getClassNameList(JNIEnv* env, jclass, jlong cookie) return toStringArray(env, class_names); } -static jboolean DexFile_isDexOptNeeded(JNIEnv* env, jclass, jstring javaFilename) { +// Copy a profile file +static void CopyProfileFile(const char* oldfile, const char* newfile) { + int fd = open(oldfile, O_RDONLY); + if (fd < 0) { + // If we can't open the file show the uid:gid of the this process to allow + // diagnosis of the problem. + LOG(ERROR) << "Failed to open profile file " << oldfile<< ". My uid:gid is " + << getuid() << ":" << getgid(); + return; + } + + // Create the copy with rw------- (only accessible by system) + int fd2 = open(newfile, O_WRONLY|O_CREAT|O_TRUNC, 0600); + if (fd2 < 0) { + // If we can't open the file show the uid:gid of the this process to allow + // diagnosis of the problem. + LOG(ERROR) << "Failed to create/write prev profile file " << newfile << ". My uid:gid is " + << getuid() << ":" << getgid(); + return; + } + char buf[4096]; + while (true) { + int n = read(fd, buf, sizeof(buf)); + if (n <= 0) { + break; + } + write(fd2, buf, n); + } + close(fd); + close(fd2); +} + +static jboolean DexFile_isDexOptNeededInternal(JNIEnv* env, jclass, jstring javaFilename, + jstring javaPkgname, jboolean defer) { const bool kVerboseLogging = false; // Spammy logging. const bool kDebugLogging = true; // Logging useful for debugging. @@ -221,6 +259,97 @@ static jboolean DexFile_isDexOptNeeded(JNIEnv* env, jclass, jstring javaFilename } } + // Check the profile file. We need to rerun dex2oat if the profile has changed significantly + // since the last time, or it's new. + // If the 'defer' argument is true then this will be retried later. In this case we + // need to make sure that the profile file copy is not made so that we will get the + // same result second time. + if (javaPkgname != NULL) { + ScopedUtfChars pkgname(env, javaPkgname); + std::string profile_file = GetDalvikCacheOrDie(GetAndroidData()) + std::string("/profiles/") + + pkgname.c_str(); + + std::string profile_cache_dir = GetDalvikCacheOrDie(GetAndroidData()) + "/profile-cache"; + + // Make the profile cache if it doesn't exist. + mkdir(profile_cache_dir.c_str(), 0700); + + // The previous profile file (a copy of the profile the last time this was run) is + // in the dalvik-cache directory because this is owned by system. The profiles + // directory is owned by install so system cannot write files in there. + std::string prev_profile_file = profile_cache_dir + std::string("/") + pkgname.c_str(); + + struct stat profstat, prevstat; + int e1 = stat(profile_file.c_str(), &profstat); + int e2 = stat(prev_profile_file.c_str(), &prevstat); + + if (e1 < 0) { + // No profile file, need to run dex2oat + if (kDebugLogging) { + LOG(INFO) << "DexFile_isDexOptNeeded profile file " << profile_file << " doesn't exist"; + } + return JNI_TRUE; + } + if (e2 == 0) { + // There is a previous profile file. Check if the profile has changed significantly. + // Let's use the file size as a proxy for significance. If the new profile is 10% + // different in size than the the old profile then we run dex2oat. + double newsize = profstat.st_size; + double oldsize = prevstat.st_size; + bool need_profile = false; + + double ratio = 0; // If the old file was empty and the new one not + if (oldsize > 0 && newsize > 0) { + ratio = newsize / oldsize; + } else if (oldsize == 0 && newsize > 0) { + need_profile = true; + } else if (oldsize > 0 && newsize == 0) { + // Unlikely to happen, but cover all the bases. + need_profile = true; + } + + double significant_difference = 10.0; +#ifdef HAVE_ANDROID_OS + // Switch off profiler if the dalvik.vm.profiler property has value 0. + char buf[PROP_VALUE_MAX]; + property_get("dalvik.vm.profiler.dex2oat.threshold", buf, "10.0"); + significant_difference = strtod(buf, nullptr); + + // Something reasonable? + if (significant_difference < 1.0 || significant_difference > 90.0) { + significant_difference = 10.0; + } +#endif // The percentage difference that we consider as being significant. + double diff_hwm = 1.0 + significant_difference/10.0; + double diff_lwm = 1.0 - significant_difference/10.0; + + if (ratio > diff_hwm || ratio < diff_lwm) { + need_profile = true; + } + + if (need_profile) { + if (kDebugLogging) { + LOG(INFO) << "DexFile_isDexOptNeeded size of new profile file " << profile_file << + " is significantly different from old profile file " << prev_profile_file << " (new: " << + newsize << ", old: " << oldsize << ", ratio: " << ratio << ")"; + } + if (!defer) { + CopyProfileFile(profile_file.c_str(), prev_profile_file.c_str()); + } + return JNI_TRUE; + } + } else { + // Previous profile does not exist. Make a copy of the current one. + if (kDebugLogging) { + LOG(INFO) << "DexFile_isDexOptNeeded previous profile doesn't exist: " << prev_profile_file; + } + if (!defer) { + CopyProfileFile(profile_file.c_str(), prev_profile_file.c_str()); + } + return JNI_TRUE; + } + } + // Check if we have an odex file next to the dex file. std::string odex_filename(OatFile::DexFilenameToOdexFilename(filename.c_str())); std::string error_msg; @@ -329,11 +458,18 @@ static jboolean DexFile_isDexOptNeeded(JNIEnv* env, jclass, jstring javaFilename return JNI_FALSE; } +// public API, NULL pkgname +static jboolean DexFile_isDexOptNeeded(JNIEnv* env, jclass c, jstring javaFilename) { + return DexFile_isDexOptNeededInternal(env, c, javaFilename, NULL, false); +} + + static JNINativeMethod gMethods[] = { NATIVE_METHOD(DexFile, closeDexFile, "(J)V"), NATIVE_METHOD(DexFile, defineClassNative, "(Ljava/lang/String;Ljava/lang/ClassLoader;J)Ljava/lang/Class;"), NATIVE_METHOD(DexFile, getClassNameList, "(J)[Ljava/lang/String;"), NATIVE_METHOD(DexFile, isDexOptNeeded, "(Ljava/lang/String;)Z"), + NATIVE_METHOD(DexFile, isDexOptNeededInternal, "(Ljava/lang/String;Ljava/lang/String;Z)Z"), NATIVE_METHOD(DexFile, openDexFileNative, "(Ljava/lang/String;Ljava/lang/String;I)J"), }; diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc index 4aa1d10..0e2d921 100644 --- a/runtime/native/dalvik_system_VMRuntime.cc +++ b/runtime/native/dalvik_system_VMRuntime.cc @@ -203,6 +203,7 @@ static void VMRuntime_registerNativeFree(JNIEnv* env, jobject, jint bytes) { static void VMRuntime_updateProcessState(JNIEnv* env, jobject, jint process_state) { Runtime::Current()->GetHeap()->UpdateProcessState(static_cast<gc::ProcessState>(process_state)); + Runtime::Current()->UpdateProfilerState(process_state); } static void VMRuntime_trimHeap(JNIEnv*, jobject) { @@ -511,13 +512,16 @@ static void VMRuntime_preloadDexCaches(JNIEnv* env, jobject) { * process name. We use this information to start up the sampling profiler for * for ART. */ -static void VMRuntime_registerAppInfo(JNIEnv* env, jclass, jstring appDir, jstring procName) { +static void VMRuntime_registerAppInfo(JNIEnv* env, jclass, jstring pkgName, jstring appDir, jstring procName) { + const char *pkgNameChars = env->GetStringUTFChars(pkgName, NULL); const char *appDirChars = env->GetStringUTFChars(appDir, NULL); const char *procNameChars = env->GetStringUTFChars(procName, NULL); - std::string profileFile = std::string(appDirChars) + "/art-profile-" + std::string(procNameChars); - Runtime::Current()->StartProfiler(profileFile.c_str()); + + std::string profileFile = StringPrintf("/data/dalvik-cache/profiles/%s", pkgNameChars); + Runtime::Current()->StartProfiler(profileFile.c_str(), procNameChars); env->ReleaseStringUTFChars(appDir, appDirChars); env->ReleaseStringUTFChars(procName, procNameChars); + env->ReleaseStringUTFChars(pkgName, pkgNameChars); } static JNINativeMethod gMethods[] = { @@ -542,7 +546,7 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(VMRuntime, vmVersion, "()Ljava/lang/String;"), NATIVE_METHOD(VMRuntime, vmLibrary, "()Ljava/lang/String;"), NATIVE_METHOD(VMRuntime, preloadDexCaches, "()V"), - NATIVE_METHOD(VMRuntime, registerAppInfo, "(Ljava/lang/String;Ljava/lang/String;)V"), + NATIVE_METHOD(VMRuntime, registerAppInfo, "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"), }; void register_dalvik_system_VMRuntime(JNIEnv* env) { diff --git a/runtime/profiler.cc b/runtime/profiler.cc index 20e08b8..da98938 100644 --- a/runtime/profiler.cc +++ b/runtime/profiler.cc @@ -17,6 +17,7 @@ #include "profiler.h" #include <sys/uio.h> +#include <sys/file.h> #include "base/stl_util.h" #include "base/unix_file/fd_file.h" @@ -170,6 +171,7 @@ void* BackgroundMethodSamplingProfiler::RunProfilerThread(void* arg) { SampleCheckpoint check_point(profiler); + size_t valid_samples = 0; while (now_us < end_us) { if (ShuttingDown(self)) { break; @@ -180,7 +182,15 @@ void* BackgroundMethodSamplingProfiler::RunProfilerThread(void* arg) { ThreadList* thread_list = runtime->GetThreadList(); profiler->profiler_barrier_->Init(self, 0); - size_t barrier_count = thread_list->RunCheckpoint(&check_point); + size_t barrier_count = thread_list->RunCheckpointOnRunnableThreads(&check_point); + + // All threads are suspended, nothing to do. + if (barrier_count == 0) { + now_us = MicroTime(); + continue; + } + + valid_samples += barrier_count; ThreadState old_state = self->SetState(kWaitingForCheckPointsToRun); @@ -206,7 +216,7 @@ void* BackgroundMethodSamplingProfiler::RunProfilerThread(void* arg) { now_us = MicroTime(); } - if (!ShuttingDown(self)) { + if (valid_samples > 0 && !ShuttingDown(self)) { // After the profile has been taken, write it out. ScopedObjectAccess soa(self); // Acquire the mutator lock. uint32_t size = profiler->WriteProfile(); @@ -221,39 +231,65 @@ void* BackgroundMethodSamplingProfiler::RunProfilerThread(void* arg) { // Write out the profile file if we are generating a profile. uint32_t BackgroundMethodSamplingProfiler::WriteProfile() { - UniquePtr<File> profile_file; - Runtime* runtime = Runtime::Current(); - std::string classpath = runtime->GetClassPathString(); - size_t colon = classpath.find(':'); - if (colon != std::string::npos) { - // More than one file in the classpath. Possible? - classpath = classpath.substr(0, colon); - } - - std::replace(classpath.begin(), classpath.end(), '/', '@'); std::string full_name = profile_file_name_; - if (classpath != "") { - full_name = StringPrintf("%s-%s", profile_file_name_.c_str(), classpath.c_str()); - } LOG(DEBUG) << "Saving profile to " << full_name; - profile_file.reset(OS::CreateEmptyFile(full_name.c_str())); - if (profile_file.get() == nullptr) { - // Failed to open the profile file, ignore. - LOG(INFO) << "Failed to op file"; + int fd = open(full_name.c_str(), O_RDWR); + if (fd < 0) { + // Open failed. + LOG(ERROR) << "Failed to open profile file " << full_name; + return 0; + } + + // Lock the file for exclusive access. This will block if another process is using + // the file. + int err = flock(fd, LOCK_EX); + if (err < 0) { + LOG(ERROR) << "Failed to lock profile file " << full_name; return 0; } + + // Read the previous profile. + profile_table_.ReadPrevious(fd); + + // Move back to the start of the file. + lseek(fd, 0, SEEK_SET); + + // Format the profile output and write to the file. std::ostringstream os; uint32_t num_methods = DumpProfile(os); std::string data(os.str()); - profile_file->WriteFully(data.c_str(), data.length()); - profile_file->Close(); + const char *p = data.c_str(); + size_t length = data.length(); + size_t full_length = length; + do { + int n = ::write(fd, p, length); + p += n; + length -= n; + } while (length > 0); + + // Truncate the file to the new length. + ftruncate(fd, full_length); + + // Now unlock the file, allowing another process in. + err = flock(fd, LOCK_UN); + if (err < 0) { + LOG(ERROR) << "Failed to unlock profile file " << full_name; + } + + // Done, close the file. + ::close(fd); + + // Clean the profile for the next time. + CleanProfile(); + return num_methods; } // Start a profile thread with the user-supplied arguments. void BackgroundMethodSamplingProfiler::Start(int period, int duration, - std::string profile_file_name, int interval_us, + const std::string& profile_file_name, const std::string& procName, + int interval_us, double backoff_coefficient, bool startImmediately) { Thread* self = Thread::Current(); { @@ -266,12 +302,14 @@ void BackgroundMethodSamplingProfiler::Start(int period, int duration, // Only on target... #ifdef HAVE_ANDROID_OS - // Switch off profiler if the dalvik.vm.profiler property has value 0. - char buf[PROP_VALUE_MAX]; - property_get("dalvik.vm.profiler", buf, "0"); - if (strcmp(buf, "0") == 0) { - LOG(INFO) << "Profiler disabled. To enable setprop dalvik.vm.profiler 1"; - return; + if (!startImmediately) { + // Switch off profiler if the dalvik.vm.profiler property has value 0. + char buf[PROP_VALUE_MAX]; + property_get("dalvik.vm.profiler", buf, "0"); + if (strcmp(buf, "0") == 0) { + LOG(INFO) << "Profiler disabled. To enable setprop dalvik.vm.profiler 1"; + return; + } } #endif @@ -281,6 +319,7 @@ void BackgroundMethodSamplingProfiler::Start(int period, int duration, { MutexLock mu(self, *Locks::profiler_lock_); profiler_ = new BackgroundMethodSamplingProfiler(period, duration, profile_file_name, + procName, backoff_coefficient, interval_us, startImmediately); @@ -323,9 +362,10 @@ void BackgroundMethodSamplingProfiler::Shutdown() { } BackgroundMethodSamplingProfiler::BackgroundMethodSamplingProfiler(int period, int duration, - std::string profile_file_name, + const std::string& profile_file_name, + const std::string& process_name, double backoff_coefficient, int interval_us, bool startImmediately) - : profile_file_name_(profile_file_name), + : profile_file_name_(profile_file_name), process_name_(process_name), period_s_(period), start_immediately_(startImmediately), interval_us_(interval_us), backoff_factor_(1.0), backoff_coefficient_(backoff_coefficient), duration_s_(duration), @@ -423,9 +463,13 @@ void ProfileSampleResults::Put(mirror::ArtMethod* method) { lock_.Unlock(Thread::Current()); } -// Write the profile table to the output stream. +// Write the profile table to the output stream. Also merge with the previous profile. uint32_t ProfileSampleResults::Write(std::ostream &os) { ScopedObjectAccess soa(Thread::Current()); + num_samples_ += previous_num_samples_; + num_null_methods_ += previous_num_null_methods_; + num_boot_methods_ += previous_num_boot_methods_; + LOG(DEBUG) << "Profile: " << num_samples_ << "/" << num_null_methods_ << "/" << num_boot_methods_; os << num_samples_ << "/" << num_null_methods_ << "/" << num_boot_methods_ << "\n"; uint32_t num_methods = 0; @@ -433,14 +477,35 @@ uint32_t ProfileSampleResults::Write(std::ostream &os) { Map *map = table[i]; if (map != nullptr) { for (const auto &meth_iter : *map) { - mirror::ArtMethod *method = meth_iter.first; - std::string method_name = PrettyMethod(method); - uint32_t method_size = method->GetCodeSize(); - os << StringPrintf("%s/%u/%u\n", method_name.c_str(), meth_iter.second, method_size); - ++num_methods; - } + mirror::ArtMethod *method = meth_iter.first; + std::string method_name = PrettyMethod(method); + + MethodHelper mh(method); + const DexFile::CodeItem* codeitem = mh.GetCodeItem(); + uint32_t method_size = 0; + if (codeitem != nullptr) { + method_size = codeitem->insns_size_in_code_units_; + } + uint32_t count = meth_iter.second; + + // Merge this profile entry with one from a previous run (if present). Also + // remove the previous entry. + PreviousProfile::iterator pi = previous_.find(method_name); + if (pi != previous_.end()) { + count += pi->second.count_; + previous_.erase(pi); + } + os << StringPrintf("%s/%u/%u\n", method_name.c_str(), count, method_size); + ++num_methods; + } } } + + // Now we write out the remaining previous methods. + for (PreviousProfile::iterator pi = previous_.begin(); pi != previous_.end(); ++pi) { + os << StringPrintf("%s/%u/%u\n", pi->first.c_str(), pi->second.count_, pi->second.method_size_); + ++num_methods; + } return num_methods; } @@ -452,11 +517,67 @@ void ProfileSampleResults::Clear() { delete table[i]; table[i] = nullptr; } + previous_.clear(); } uint32_t ProfileSampleResults::Hash(mirror::ArtMethod* method) { return (PointerToLowMemUInt32(method) >> 3) % kHashSize; } +// Read a single line into the given string. Returns true if everything OK, false +// on EOF or error. +static bool ReadProfileLine(int fd, std::string& line) { + char buf[4]; + line.clear(); + while (true) { + int n = read(fd, buf, 1); // TODO: could speed this up but is it worth it? + if (n != 1) { + return false; + } + if (buf[0] == '\n') { + break; + } + line += buf[0]; + } + return true; +} + +void ProfileSampleResults::ReadPrevious(int fd) { + // Reset counters. + previous_num_samples_ = previous_num_null_methods_ = previous_num_boot_methods_ = 0; + + std::string line; + + // The first line contains summary information. + if (!ReadProfileLine(fd, line)) { + return; + } + std::vector<std::string> summary_info; + Split(line, '/', summary_info); + if (summary_info.size() != 3) { + // Bad summary info. It should be count/nullcount/bootcount + return; + } + previous_num_samples_ = atoi(summary_info[0].c_str()); + previous_num_null_methods_ = atoi(summary_info[1].c_str()); + previous_num_boot_methods_ = atoi(summary_info[2].c_str()); + + // Now read each line until the end of file. Each line consists of 3 fields separated by / + while (true) { + if (!ReadProfileLine(fd, line)) { + break; + } + std::vector<std::string> info; + Split(line, '/', info); + if (info.size() != 3) { + // Malformed. + break; + } + std::string methodname = info[0]; + uint32_t count = atoi(info[1].c_str()); + uint32_t size = atoi(info[2].c_str()); + previous_[methodname] = PreviousValue(count, size); + } +} } // namespace art diff --git a/runtime/profiler.h b/runtime/profiler.h index 6ea6c84..b03b170 100644 --- a/runtime/profiler.h +++ b/runtime/profiler.h @@ -54,10 +54,12 @@ class ProfileSampleResults { void Put(mirror::ArtMethod* method); uint32_t Write(std::ostream &os); + void ReadPrevious(int fd); void Clear(); uint32_t GetNumSamples() { return num_samples_; } void NullMethod() { ++num_null_methods_; } void BootMethod() { ++num_boot_methods_; } + private: uint32_t Hash(mirror::ArtMethod* method); static constexpr int kHashSize = 17; @@ -68,6 +70,19 @@ class ProfileSampleResults { typedef std::map<mirror::ArtMethod*, uint32_t> Map; // Map of method vs its count. Map *table[kHashSize]; + + struct PreviousValue { + PreviousValue() : count_(0), method_size_(0) {} + PreviousValue(uint32_t count, uint32_t method_size) : count_(count), method_size_(method_size) {} + uint32_t count_; + uint32_t method_size_; + }; + + typedef std::map<std::string, PreviousValue> PreviousProfile; + PreviousProfile previous_; + uint32_t previous_num_samples_; + uint32_t previous_num_null_methods_; // Number of samples where can don't know the method. + uint32_t previous_num_boot_methods_; // Number of samples in the boot path. }; // @@ -87,7 +102,8 @@ class ProfileSampleResults { class BackgroundMethodSamplingProfiler { public: - static void Start(int period, int duration, std::string profile_filename, int interval_us, + static void Start(int period, int duration, const std::string& profile_filename, + const std::string& procName, int interval_us, double backoff_coefficient, bool startImmediately) LOCKS_EXCLUDED(Locks::mutator_lock_, Locks::thread_list_lock_, @@ -104,8 +120,10 @@ class BackgroundMethodSamplingProfiler { } private: - explicit BackgroundMethodSamplingProfiler(int period, int duration, std::string profile_filename, - double backoff_coefficient, int interval_us, bool startImmediately); + explicit BackgroundMethodSamplingProfiler(int period, int duration, + const std::string& profile_filename, + const std::string& process_name, + double backoff_coefficient, int interval_us, bool startImmediately); // The sampling interval in microseconds is passed as an argument. static void* RunProfilerThread(void* arg) LOCKS_EXCLUDED(Locks::profiler_lock_); @@ -130,6 +148,9 @@ class BackgroundMethodSamplingProfiler { // File to write profile data out to. Cannot be empty if we are profiling. std::string profile_file_name_; + // Process name. + std::string process_name_; + // Number of seconds between profile runs. uint32_t period_s_; diff --git a/runtime/runtime.cc b/runtime/runtime.cc index fdbf245..d1c8370 100644 --- a/runtime/runtime.cc +++ b/runtime/runtime.cc @@ -27,6 +27,7 @@ #include <cstdlib> #include <limits> #include <vector> +#include <fcntl.h> #include "arch/arm/registers_arm.h" #include "arch/mips/registers_mips.h" @@ -69,6 +70,10 @@ #include "JniConstants.h" // Last to avoid LOG redefinition in ics-mr1-plus-art. +#ifdef HAVE_ANDROID_OS +#include "cutils/properties.h" +#endif + namespace art { Runtime* Runtime::instance_ = NULL; @@ -370,7 +375,12 @@ bool Runtime::Start() { if (profile_) { // User has asked for a profile using -Xprofile - StartProfiler(profile_output_filename_.c_str(), true); + // Create the profile file if it doesn't exist. + int fd = open(profile_output_filename_.c_str(), O_RDWR|O_CREAT|O_EXCL, 0660); + if (fd >= 0) { + close(fd); + } + StartProfiler(profile_output_filename_.c_str(), "", true); } return true; @@ -1055,10 +1065,10 @@ void Runtime::RemoveMethodVerifier(verifier::MethodVerifier* verifier) { method_verifiers_.erase(it); } -void Runtime::StartProfiler(const char *appDir, bool startImmediately) { +void Runtime::StartProfiler(const char* appDir, const char* procName, bool startImmediately) { BackgroundMethodSamplingProfiler::Start(profile_period_s_, profile_duration_s_, appDir, - profile_interval_us_, profile_backoff_coefficient_, - startImmediately); + procName, profile_interval_us_, + profile_backoff_coefficient_, startImmediately); } // Transaction support. @@ -1136,4 +1146,7 @@ void Runtime::SetFaultMessage(const std::string& message) { fault_message_ = message; } +void Runtime::UpdateProfilerState(int state) { + LOG(DEBUG) << "Profiler state updated to " << state; +} } // namespace art diff --git a/runtime/runtime.h b/runtime/runtime.h index 65d296a..109f031 100644 --- a/runtime/runtime.h +++ b/runtime/runtime.h @@ -373,7 +373,8 @@ class Runtime { const std::vector<const DexFile*>& GetCompileTimeClassPath(jobject class_loader); void SetCompileTimeClassPath(jobject class_loader, std::vector<const DexFile*>& class_path); - void StartProfiler(const char *appDir, bool startImmediately = false); + void StartProfiler(const char* appDir, const char* procName, bool startImmediately = false); + void UpdateProfilerState(int state); // Transaction support. bool IsActiveTransaction() const; @@ -419,6 +420,12 @@ class Runtime { void StartDaemonThreads(); void StartSignalCatcher(); + // NOTE: these must match the gc::ProcessState values as they come directly + // from the framework. + static constexpr int kProfileForground = 0; + static constexpr int kProfileBackgrouud = 1; + + // A pointer to the active runtime or NULL. static Runtime* instance_; diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc index bddebbd..ac5750b 100644 --- a/runtime/thread_list.cc +++ b/runtime/thread_list.cc @@ -269,6 +269,36 @@ size_t ThreadList::RunCheckpoint(Closure* checkpoint_function) { return count + suspended_count_modified_threads.size() + 1; } +// Request that a checkpoint function be run on all active (non-suspended) +// threads. Returns the number of successful requests. +size_t ThreadList::RunCheckpointOnRunnableThreads(Closure* checkpoint_function) { + Thread* self = Thread::Current(); + if (kIsDebugBuild) { + Locks::mutator_lock_->AssertNotExclusiveHeld(self); + Locks::thread_list_lock_->AssertNotHeld(self); + Locks::thread_suspend_count_lock_->AssertNotHeld(self); + CHECK_NE(self->GetState(), kRunnable); + } + + size_t count = 0; + { + // Call a checkpoint function for each non-suspended thread. + MutexLock mu(self, *Locks::thread_list_lock_); + MutexLock mu2(self, *Locks::thread_suspend_count_lock_); + for (const auto& thread : list_) { + if (thread != self) { + if (thread->RequestCheckpoint(checkpoint_function)) { + // This thread will run its checkpoint some time in the near future. + count++; + } + } + } + } + + // Return the number of threads that will run the checkpoint function. + return count; +} + void ThreadList::SuspendAll() { Thread* self = Thread::Current(); DCHECK(self != nullptr); diff --git a/runtime/thread_list.h b/runtime/thread_list.h index 1a76705..58bd92a 100644 --- a/runtime/thread_list.h +++ b/runtime/thread_list.h @@ -90,6 +90,10 @@ class ThreadList { LOCKS_EXCLUDED(Locks::thread_list_lock_, Locks::thread_suspend_count_lock_); + size_t RunCheckpointOnRunnableThreads(Closure* checkpoint_function); + LOCKS_EXCLUDED(Locks::thread_list_lock_, + Locks::thread_suspend_count_lock_); + // Suspends all threads void SuspendAllForDebugger() LOCKS_EXCLUDED(Locks::mutator_lock_, |