blob: 10dddaefc8ca004013ae4db3b92ab554ce09125b [file] [log] [blame]
Calin Juravle4d77b6a2015-12-01 18:38:09 +00001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "profile_saver.h"
18
Nicolas Geoffray23caed82017-04-21 14:30:18 +010019#include <sys/resource.h>
Calin Juravle86a9ebe2016-02-24 10:13:09 +000020#include <sys/types.h>
21#include <sys/stat.h>
22#include <fcntl.h>
23
Andreas Gampe9186ced2016-12-12 14:28:21 -080024#include "android-base/strings.h"
25
Calin Juravle4d77b6a2015-12-01 18:38:09 +000026#include "art_method-inl.h"
Andreas Gampe542451c2016-07-26 09:02:02 -070027#include "base/enums.h"
Mathieu Chartierfaf83202017-06-08 10:35:20 -070028#include "base/scoped_arena_containers.h"
Andreas Gampe5678db52017-06-08 14:11:18 -070029#include "base/stl_util.h"
Mathieu Chartierdabdc0f2016-03-04 14:58:03 -080030#include "base/systrace.h"
Calin Juravle6044fa72016-03-25 17:17:09 +000031#include "base/time_utils.h"
32#include "compiler_filter.h"
Mathieu Chartierfaf83202017-06-08 10:35:20 -070033#include "dex_reference_collection.h"
Mathieu Chartier39100372017-05-17 13:14:10 -070034#include "gc/collector_type.h"
35#include "gc/gc_cause.h"
36#include "gc/scoped_gc_critical_section.h"
Mathieu Chartierbbe3a5e2017-06-13 16:36:17 -070037#include "jit/profile_compilation_info.h"
Calin Juravle4d77b6a2015-12-01 18:38:09 +000038#include "oat_file_manager.h"
Mathieu Chartier0795f232016-09-27 18:43:30 -070039#include "scoped_thread_state_change-inl.h"
Calin Juravle6044fa72016-03-25 17:17:09 +000040
Calin Juravle4d77b6a2015-12-01 18:38:09 +000041namespace art {
42
Calin Juravle4d77b6a2015-12-01 18:38:09 +000043ProfileSaver* ProfileSaver::instance_ = nullptr;
44pthread_t ProfileSaver::profiler_pthread_ = 0U;
45
Mathieu Chartier5494e5b2017-06-26 14:13:27 -070046// At what priority to schedule the saver threads. 9 is the lowest foreground priority on device.
47static constexpr int kProfileSaverPthreadPriority = 9;
48
49static void SetProfileSaverThreadPriority(pthread_t thread, int priority) {
50#if defined(ART_TARGET_ANDROID)
51 int result = setpriority(PRIO_PROCESS, pthread_gettid_np(thread), priority);
52 if (result != 0) {
53 LOG(ERROR) << "Failed to setpriority to :" << priority;
54 }
55#else
56 UNUSED(thread);
57 UNUSED(priority);
58#endif
59}
60
61static int GetDefaultThreadPriority() {
62#if defined(ART_TARGET_ANDROID)
63 pthread_attr_t attr;
64 sched_param param;
65 pthread_attr_init(&attr);
66 pthread_attr_getschedparam(&attr, &param);
67 return param.sched_priority;
68#else
69 return 0;
70#endif
71}
72
Calin Juravle138dbff2016-06-28 19:36:58 +010073ProfileSaver::ProfileSaver(const ProfileSaverOptions& options,
74 const std::string& output_filename,
Calin Juravle4d77b6a2015-12-01 18:38:09 +000075 jit::JitCodeCache* jit_code_cache,
Calin Juravle77651c42017-03-03 18:04:02 -080076 const std::vector<std::string>& code_paths)
Calin Juravleb4eddd22016-01-13 15:52:33 -080077 : jit_code_cache_(jit_code_cache),
Calin Juravle4d77b6a2015-12-01 18:38:09 +000078 shutting_down_(false),
Calin Juravle5fbb0fe2016-04-29 16:44:11 +010079 last_time_ns_saver_woke_up_(0),
80 jit_activity_notifications_(0),
Calin Juravle4d77b6a2015-12-01 18:38:09 +000081 wait_lock_("ProfileSaver wait lock"),
Calin Juravlec19c1c22016-03-09 15:37:48 +000082 period_condition_("ProfileSaver period condition", wait_lock_),
83 total_bytes_written_(0),
84 total_number_of_writes_(0),
85 total_number_of_code_cache_queries_(0),
86 total_number_of_skipped_writes_(0),
87 total_number_of_failed_writes_(0),
Calin Juravle85f7bf32016-03-18 16:23:40 +000088 total_ms_of_sleep_(0),
Calin Juravlec19c1c22016-03-09 15:37:48 +000089 total_ns_of_work_(0),
Calin Juravle5fbb0fe2016-04-29 16:44:11 +010090 max_number_of_profile_entries_cached_(0),
91 total_number_of_hot_spikes_(0),
Calin Juravle138dbff2016-06-28 19:36:58 +010092 total_number_of_wake_ups_(0),
93 options_(options) {
94 DCHECK(options_.IsEnabled());
Calin Juravle77651c42017-03-03 18:04:02 -080095 AddTrackedLocations(output_filename, code_paths);
Calin Juravle4d77b6a2015-12-01 18:38:09 +000096}
97
Calin Juravlecc3171a2017-05-19 16:47:53 -070098ProfileSaver::~ProfileSaver() {
99 for (auto& it : profile_cache_) {
100 delete it.second;
101 }
102}
103
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000104void ProfileSaver::Run() {
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000105 Thread* self = Thread::Current();
106
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100107 // Fetch the resolved classes for the app images after sleeping for
Calin Juravle138dbff2016-06-28 19:36:58 +0100108 // options_.GetSaveResolvedClassesDelayMs().
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100109 // TODO(calin) This only considers the case of the primary profile file.
110 // Anything that gets loaded in the same VM will not have their resolved
111 // classes save (unless they started before the initial saving was done).
112 {
113 MutexLock mu(self, wait_lock_);
Calin Juravle138dbff2016-06-28 19:36:58 +0100114 const uint64_t end_time = NanoTime() + MsToNs(options_.GetSaveResolvedClassesDelayMs());
Mathieu Chartier0ec065d2016-05-18 19:51:23 -0700115 while (true) {
116 const uint64_t current_time = NanoTime();
117 if (current_time >= end_time) {
118 break;
119 }
120 period_condition_.TimedWait(self, NsToMs(end_time - current_time), 0);
121 }
Calin Juravle138dbff2016-06-28 19:36:58 +0100122 total_ms_of_sleep_ += options_.GetSaveResolvedClassesDelayMs();
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100123 }
Mathieu Chartierc600eaa2016-05-18 08:51:52 -0700124 FetchAndCacheResolvedClassesAndMethods();
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100125
126 // Loop for the profiled methods.
Mathieu Chartier8913fc12015-12-09 16:38:30 -0800127 while (!ShuttingDown(self)) {
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100128 uint64_t sleep_start = NanoTime();
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000129 {
Calin Juravle0233a412016-05-18 15:49:36 -0700130 uint64_t sleep_time = 0;
131 {
132 MutexLock mu(self, wait_lock_);
133 period_condition_.Wait(self);
Calin Juravledc85bd72016-05-25 18:09:53 +0100134 sleep_time = NanoTime() - sleep_start;
Calin Juravle0233a412016-05-18 15:49:36 -0700135 }
136 // Check if the thread was woken up for shutdown.
137 if (ShuttingDown(self)) {
138 break;
139 }
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100140 total_number_of_wake_ups_++;
141 // We might have been woken up by a huge number of notifications to guarantee saving.
142 // If we didn't meet the minimum saving period go back to sleep (only if missed by
143 // a reasonable margin).
Calin Juravle138dbff2016-06-28 19:36:58 +0100144 uint64_t min_save_period_ns = MsToNs(options_.GetMinSavePeriodMs());
145 while (min_save_period_ns * 0.9 > sleep_time) {
Calin Juravle0233a412016-05-18 15:49:36 -0700146 {
147 MutexLock mu(self, wait_lock_);
Calin Juravle138dbff2016-06-28 19:36:58 +0100148 period_condition_.TimedWait(self, NsToMs(min_save_period_ns - sleep_time), 0);
Calin Juravledc85bd72016-05-25 18:09:53 +0100149 sleep_time = NanoTime() - sleep_start;
Calin Juravle0233a412016-05-18 15:49:36 -0700150 }
151 // Check if the thread was woken up for shutdown.
152 if (ShuttingDown(self)) {
153 break;
154 }
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100155 total_number_of_wake_ups_++;
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100156 }
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000157 }
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100158 total_ms_of_sleep_ += NsToMs(NanoTime() - sleep_start);
159
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000160 if (ShuttingDown(self)) {
161 break;
162 }
163
Calin Juravlea345d312017-03-14 18:45:55 -0700164 uint16_t number_of_new_methods = 0;
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100165 uint64_t start_work = NanoTime();
Calin Juravlea345d312017-03-14 18:45:55 -0700166 bool profile_saved_to_disk = ProcessProfilingInfo(/*force_save*/false, &number_of_new_methods);
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100167 // Update the notification counter based on result. Note that there might be contention on this
168 // but we don't care about to be 100% precise.
169 if (!profile_saved_to_disk) {
170 // If we didn't save to disk it may be because we didn't have enough new methods.
Calin Juravlea345d312017-03-14 18:45:55 -0700171 // Set the jit activity notifications to number_of_new_methods so we can wake up earlier
172 // if needed.
173 jit_activity_notifications_ = number_of_new_methods;
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000174 }
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100175 total_ns_of_work_ += NanoTime() - start_work;
176 }
177}
Calin Juravlec19c1c22016-03-09 15:37:48 +0000178
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100179void ProfileSaver::NotifyJitActivity() {
180 MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
181 if (instance_ == nullptr || instance_->shutting_down_) {
182 return;
183 }
184 instance_->NotifyJitActivityInternal();
185}
186
187void ProfileSaver::WakeUpSaver() {
188 jit_activity_notifications_ = 0;
189 last_time_ns_saver_woke_up_ = NanoTime();
190 period_condition_.Signal(Thread::Current());
191}
192
193void ProfileSaver::NotifyJitActivityInternal() {
194 // Unlikely to overflow but if it happens,
195 // we would have waken up the saver long before that.
196 jit_activity_notifications_++;
197 // Note that we are not as precise as we could be here but we don't want to wake the saver
198 // every time we see a hot method.
Calin Juravle138dbff2016-06-28 19:36:58 +0100199 if (jit_activity_notifications_ > options_.GetMinNotificationBeforeWake()) {
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100200 MutexLock wait_mutex(Thread::Current(), wait_lock_);
Calin Juravle138dbff2016-06-28 19:36:58 +0100201 if ((NanoTime() - last_time_ns_saver_woke_up_) > MsToNs(options_.GetMinSavePeriodMs())) {
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100202 WakeUpSaver();
Serguei Katkov87de9cf2016-08-01 17:47:04 +0700203 } else if (jit_activity_notifications_ > options_.GetMaxNotificationBeforeWake()) {
204 // Make sure to wake up the saver if we see a spike in the number of notifications.
205 // This is a precaution to avoid losing a big number of methods in case
206 // this is a spike with no jit after.
207 total_number_of_hot_spikes_++;
208 WakeUpSaver();
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100209 }
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000210 }
211}
212
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700213using MethodReferenceCollection = DexReferenceCollection<uint16_t, ScopedArenaAllocatorAdapter>;
214using TypeReferenceCollection = DexReferenceCollection<dex::TypeIndex,
215 ScopedArenaAllocatorAdapter>;
216
Mathieu Chartierc600eaa2016-05-18 08:51:52 -0700217// Get resolved methods that have a profile info or more than kStartupMethodSamples samples.
218// Excludes native methods and classes in the boot image.
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700219class GetClassesAndMethodsVisitor : public ClassVisitor {
Mathieu Chartierc600eaa2016-05-18 08:51:52 -0700220 public:
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700221 GetClassesAndMethodsVisitor(MethodReferenceCollection* hot_methods,
222 MethodReferenceCollection* sampled_methods,
223 TypeReferenceCollection* resolved_classes,
Mathieu Chartier885a7132017-06-10 14:35:11 -0700224 uint32_t hot_method_sample_threshold,
225 bool profile_boot_class_path)
Mathieu Chartierea650f32017-05-24 12:04:13 -0700226 : hot_methods_(hot_methods),
Mathieu Chartier7b135c82017-06-05 12:54:01 -0700227 sampled_methods_(sampled_methods),
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700228 resolved_classes_(resolved_classes),
Mathieu Chartier885a7132017-06-10 14:35:11 -0700229 hot_method_sample_threshold_(hot_method_sample_threshold),
230 profile_boot_class_path_(profile_boot_class_path) {}
Mathieu Chartierc600eaa2016-05-18 08:51:52 -0700231
Mathieu Chartier28357fa2016-10-18 16:27:40 -0700232 virtual bool operator()(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_) {
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700233 if (klass->IsProxyClass() ||
234 klass->IsArrayClass() ||
Mathieu Chartier885a7132017-06-10 14:35:11 -0700235 klass->IsPrimitive() ||
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700236 !klass->IsResolved() ||
237 klass->IsErroneousResolved() ||
Mathieu Chartier885a7132017-06-10 14:35:11 -0700238 (!profile_boot_class_path_ && klass->GetClassLoader() == nullptr)) {
Mathieu Chartierc600eaa2016-05-18 08:51:52 -0700239 return true;
240 }
Mathieu Chartier885a7132017-06-10 14:35:11 -0700241 CHECK(klass->GetDexCache() != nullptr) << klass->PrettyClass();
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700242 resolved_classes_->AddReference(&klass->GetDexFile(), klass->GetDexTypeIndex());
Andreas Gampe542451c2016-07-26 09:02:02 -0700243 for (ArtMethod& method : klass->GetMethods(kRuntimePointerSize)) {
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700244 if (!method.IsNative()) {
245 DCHECK(!method.IsProxyMethod());
Mathieu Chartierea650f32017-05-24 12:04:13 -0700246 const uint16_t counter = method.GetCounter();
Mathieu Chartier7b135c82017-06-05 12:54:01 -0700247 // Mark startup methods as hot if they have more than hot_method_sample_threshold_ samples.
248 // This means they will get compiled by the compiler driver.
Mathieu Chartierea650f32017-05-24 12:04:13 -0700249 if (method.GetProfilingInfo(kRuntimePointerSize) != nullptr ||
Mathieu Chartier7b135c82017-06-05 12:54:01 -0700250 (method.GetAccessFlags() & kAccPreviouslyWarm) != 0 ||
251 counter >= hot_method_sample_threshold_) {
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700252 hot_methods_->AddReference(method.GetDexFile(), method.GetDexMethodIndex());
Mathieu Chartier7b135c82017-06-05 12:54:01 -0700253 } else if (counter != 0) {
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700254 sampled_methods_->AddReference(method.GetDexFile(), method.GetDexMethodIndex());
Mathieu Chartierc600eaa2016-05-18 08:51:52 -0700255 }
Mathieu Chartierea650f32017-05-24 12:04:13 -0700256 } else {
257 CHECK_EQ(method.GetCounter(), 0u);
Mathieu Chartierc600eaa2016-05-18 08:51:52 -0700258 }
259 }
260 return true;
261 }
262
263 private:
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700264 MethodReferenceCollection* const hot_methods_;
265 MethodReferenceCollection* const sampled_methods_;
266 TypeReferenceCollection* const resolved_classes_;
Mathieu Chartier7b135c82017-06-05 12:54:01 -0700267 uint32_t hot_method_sample_threshold_;
Mathieu Chartier885a7132017-06-10 14:35:11 -0700268 const bool profile_boot_class_path_;
Mathieu Chartierc600eaa2016-05-18 08:51:52 -0700269};
270
Mathieu Chartier5494e5b2017-06-26 14:13:27 -0700271class ScopedDefaultPriority {
272 public:
273 explicit ScopedDefaultPriority(pthread_t thread) : thread_(thread) {
274 SetProfileSaverThreadPriority(thread_, GetDefaultThreadPriority());
275 }
276
277 ~ScopedDefaultPriority() {
278 SetProfileSaverThreadPriority(thread_, kProfileSaverPthreadPriority);
279 }
280
281 private:
282 const pthread_t thread_;
283};
284
Mathieu Chartierc600eaa2016-05-18 08:51:52 -0700285void ProfileSaver::FetchAndCacheResolvedClassesAndMethods() {
Calin Juravle85f7bf32016-03-18 16:23:40 +0000286 ScopedTrace trace(__PRETTY_FUNCTION__);
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700287 const uint64_t start_time = NanoTime();
Calin Juravle8b5d9b62017-05-05 17:27:23 -0700288
289 // Resolve any new registered locations.
290 ResolveTrackedLocations();
291
Mathieu Chartier39100372017-05-17 13:14:10 -0700292 Thread* const self = Thread::Current();
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700293 Runtime* const runtime = Runtime::Current();
294 ArenaStack stack(runtime->GetArenaPool());
295 ScopedArenaAllocator allocator(&stack);
296 MethodReferenceCollection hot_methods(allocator.Adapter(), allocator.Adapter());
297 MethodReferenceCollection startup_methods(allocator.Adapter(), allocator.Adapter());
298 TypeReferenceCollection resolved_classes(allocator.Adapter(), allocator.Adapter());
Mathieu Chartier273d1102017-06-06 17:07:13 -0700299 const bool is_low_ram = Runtime::Current()->GetHeap()->IsLowMemoryMode();
300 const size_t hot_threshold = options_.GetHotStartupMethodSamples(is_low_ram);
Mathieu Chartier5494e5b2017-06-26 14:13:27 -0700301 pthread_t profiler_pthread;
Mathieu Chartierc600eaa2016-05-18 08:51:52 -0700302 {
Mathieu Chartier5494e5b2017-06-26 14:13:27 -0700303 MutexLock mu(self, *Locks::profiler_lock_);
304 profiler_pthread = profiler_pthread_;
305 }
306 {
307 // Restore profile saver thread priority during the GC critical section. This helps prevent
308 // priority inversions blocking the GC for long periods of time.
309 ScopedDefaultPriority sdp(profiler_pthread);
Mathieu Chartier39100372017-05-17 13:14:10 -0700310 ScopedObjectAccess soa(self);
311 gc::ScopedGCCriticalSection sgcs(self,
312 gc::kGcCauseProfileSaver,
313 gc::kCollectorTypeCriticalSection);
Mathieu Chartier39100372017-05-17 13:14:10 -0700314 {
315 ScopedTrace trace2("Get hot methods");
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700316 GetClassesAndMethodsVisitor visitor(&hot_methods,
317 &startup_methods,
318 &resolved_classes,
Mathieu Chartier885a7132017-06-10 14:35:11 -0700319 hot_threshold,
320 options_.GetProfileBootClassPath());
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700321 runtime->GetClassLinker()->VisitClasses(&visitor);
Mathieu Chartier39100372017-05-17 13:14:10 -0700322 }
Mathieu Chartierc600eaa2016-05-18 08:51:52 -0700323 }
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700324
Mathieu Chartier39100372017-05-17 13:14:10 -0700325 MutexLock mu(self, *Locks::profiler_lock_);
Calin Juravle85f7bf32016-03-18 16:23:40 +0000326 uint64_t total_number_of_profile_entries_cached = 0;
Mathieu Chartierbbe3a5e2017-06-13 16:36:17 -0700327 using Hotness = ProfileCompilationInfo::MethodHotness;
Mathieu Chartier9275af62016-04-29 12:03:56 -0700328
Calin Juravle85f7bf32016-03-18 16:23:40 +0000329 for (const auto& it : tracked_dex_base_locations_) {
Mathieu Chartier9275af62016-04-29 12:03:56 -0700330 std::set<DexCacheResolvedClasses> resolved_classes_for_location;
Calin Juravle85f7bf32016-03-18 16:23:40 +0000331 const std::string& filename = it.first;
Calin Juravlee6f87cc2017-05-24 17:41:05 -0700332 auto info_it = profile_cache_.Put(
333 filename,
334 new ProfileCompilationInfo(Runtime::Current()->GetArenaPool()));
Calin Juravlecc3171a2017-05-19 16:47:53 -0700335 ProfileCompilationInfo* cached_info = info_it->second;
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700336
337 const std::set<std::string>& locations = it.second;
338 for (const auto& pair : hot_methods.GetMap()) {
339 const DexFile* const dex_file = pair.first;
340 if (locations.find(dex_file->GetBaseLocation()) != locations.end()) {
Mathieu Chartierdb40eac2017-06-09 18:34:11 -0700341 const MethodReferenceCollection::IndexVector& indices = pair.second;
Mathieu Chartierbbe3a5e2017-06-13 16:36:17 -0700342 cached_info->AddMethodsForDex(
343 static_cast<Hotness::Flag>(Hotness::kFlagHot | Hotness::kFlagStartup),
344 dex_file,
345 indices.begin(),
346 indices.end());
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700347 }
348 }
349 for (const auto& pair : startup_methods.GetMap()) {
350 const DexFile* const dex_file = pair.first;
351 if (locations.find(dex_file->GetBaseLocation()) != locations.end()) {
Mathieu Chartierdb40eac2017-06-09 18:34:11 -0700352 const MethodReferenceCollection::IndexVector& indices = pair.second;
Mathieu Chartierbbe3a5e2017-06-13 16:36:17 -0700353 cached_info->AddMethodsForDex(Hotness::kFlagStartup,
Mathieu Chartierdb40eac2017-06-09 18:34:11 -0700354 dex_file,
355 indices.begin(),
356 indices.end());
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700357 }
358 }
359 for (const auto& pair : resolved_classes.GetMap()) {
360 const DexFile* const dex_file = pair.first;
361 if (locations.find(dex_file->GetBaseLocation()) != locations.end()) {
362 const TypeReferenceCollection::IndexVector& classes = pair.second;
363 VLOG(profiler) << "Added " << classes.size() << " classes for location "
364 << dex_file->GetBaseLocation()
365 << " (" << dex_file->GetLocation() << ")";
366 cached_info->AddClassesForDex(dex_file, classes.begin(), classes.end());
367 } else {
368 VLOG(profiler) << "Location not found " << dex_file->GetBaseLocation()
369 << " (" << dex_file->GetLocation() << ")";
370 }
371 }
Calin Juravle85f7bf32016-03-18 16:23:40 +0000372 total_number_of_profile_entries_cached += resolved_classes_for_location.size();
373 }
374 max_number_of_profile_entries_cached_ = std::max(
375 max_number_of_profile_entries_cached_,
376 total_number_of_profile_entries_cached);
Mathieu Chartierfaf83202017-06-08 10:35:20 -0700377 VLOG(profiler) << "Profile saver recorded " << hot_methods.NumReferences() << " hot methods and "
378 << startup_methods.NumReferences() << " startup methods with threshold "
379 << hot_threshold << " in " << PrettyDuration(NanoTime() - start_time);
Calin Juravle85f7bf32016-03-18 16:23:40 +0000380}
381
Calin Juravlea345d312017-03-14 18:45:55 -0700382bool ProfileSaver::ProcessProfilingInfo(bool force_save, /*out*/uint16_t* number_of_new_methods) {
Calin Juravle85f7bf32016-03-18 16:23:40 +0000383 ScopedTrace trace(__PRETTY_FUNCTION__);
Calin Juravle8b5d9b62017-05-05 17:27:23 -0700384
385 // Resolve any new registered locations.
386 ResolveTrackedLocations();
387
Calin Juravleb4eddd22016-01-13 15:52:33 -0800388 SafeMap<std::string, std::set<std::string>> tracked_locations;
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000389 {
Calin Juravleb4eddd22016-01-13 15:52:33 -0800390 // Make a copy so that we don't hold the lock while doing I/O.
391 MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
392 tracked_locations = tracked_dex_base_locations_;
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000393 }
Calin Juravlec15e5662016-03-17 17:07:52 +0000394
Calin Juravle85f7bf32016-03-18 16:23:40 +0000395 bool profile_file_saved = false;
Calin Juravlea345d312017-03-14 18:45:55 -0700396 if (number_of_new_methods != nullptr) {
397 *number_of_new_methods = 0;
398 }
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100399
Calin Juravleb4eddd22016-01-13 15:52:33 -0800400 for (const auto& it : tracked_locations) {
Calin Juravlea345d312017-03-14 18:45:55 -0700401 if (!force_save && ShuttingDown(Thread::Current())) {
402 // The ProfileSaver is in shutdown mode, meaning a stop request was made and
403 // we need to exit cleanly (by waiting for the saver thread to finish). Unless
404 // we have a request for a forced save, do not do any processing so that we
405 // speed up the exit.
Calin Juravleb4eddd22016-01-13 15:52:33 -0800406 return true;
407 }
408 const std::string& filename = it.first;
409 const std::set<std::string>& locations = it.second;
Calin Juravle940eb0c2017-01-30 19:30:44 -0800410 std::vector<ProfileMethodInfo> profile_methods;
Calin Juravleb4eddd22016-01-13 15:52:33 -0800411 {
412 ScopedObjectAccess soa(Thread::Current());
Calin Juravle940eb0c2017-01-30 19:30:44 -0800413 jit_code_cache_->GetProfiledMethods(locations, profile_methods);
Calin Juravlec19c1c22016-03-09 15:37:48 +0000414 total_number_of_code_cache_queries_++;
Calin Juravleb4eddd22016-01-13 15:52:33 -0800415 }
Calin Juravlecc3171a2017-05-19 16:47:53 -0700416 {
417 ProfileCompilationInfo info(Runtime::Current()->GetArenaPool());
418 if (!info.Load(filename, /*clear_if_invalid*/ true)) {
419 LOG(WARNING) << "Could not forcefully load profile " << filename;
420 continue;
421 }
422 uint64_t last_save_number_of_methods = info.GetNumberOfMethods();
423 uint64_t last_save_number_of_classes = info.GetNumberOfResolvedClasses();
Calin Juravlec15e5662016-03-17 17:07:52 +0000424
Mathieu Chartierbbe3a5e2017-06-13 16:36:17 -0700425 info.AddMethods(profile_methods);
Calin Juravlecc3171a2017-05-19 16:47:53 -0700426 auto profile_cache_it = profile_cache_.find(filename);
Calin Juravledcab1902017-05-12 19:18:47 -0700427 if (profile_cache_it != profile_cache_.end()) {
Calin Juravlecc3171a2017-05-19 16:47:53 -0700428 info.MergeWith(*(profile_cache_it->second));
Calin Juravledcab1902017-05-12 19:18:47 -0700429 }
Calin Juravlecc3171a2017-05-19 16:47:53 -0700430
431 int64_t delta_number_of_methods =
432 info.GetNumberOfMethods() - last_save_number_of_methods;
433 int64_t delta_number_of_classes =
434 info.GetNumberOfResolvedClasses() - last_save_number_of_classes;
435
436 if (!force_save &&
437 delta_number_of_methods < options_.GetMinMethodsToSave() &&
438 delta_number_of_classes < options_.GetMinClassesToSave()) {
439 VLOG(profiler) << "Not enough information to save to: " << filename
440 << " Number of methods: " << delta_number_of_methods
441 << " Number of classes: " << delta_number_of_classes;
Calin Juravle85f7bf32016-03-18 16:23:40 +0000442 total_number_of_skipped_writes_++;
Calin Juravlecc3171a2017-05-19 16:47:53 -0700443 continue;
Calin Juravlec19c1c22016-03-09 15:37:48 +0000444 }
Calin Juravlecc3171a2017-05-19 16:47:53 -0700445 if (number_of_new_methods != nullptr) {
446 *number_of_new_methods =
447 std::max(static_cast<uint16_t>(delta_number_of_methods),
448 *number_of_new_methods);
449 }
450 uint64_t bytes_written;
451 // Force the save. In case the profile data is corrupted or the the profile
452 // has the wrong version this will "fix" the file to the correct format.
453 if (info.Save(filename, &bytes_written)) {
454 // We managed to save the profile. Clear the cache stored during startup.
455 if (profile_cache_it != profile_cache_.end()) {
456 ProfileCompilationInfo *cached_info = profile_cache_it->second;
457 profile_cache_.erase(profile_cache_it);
458 delete cached_info;
459 }
460 if (bytes_written > 0) {
461 total_number_of_writes_++;
462 total_bytes_written_ += bytes_written;
463 profile_file_saved = true;
464 } else {
465 // At this point we could still have avoided the write.
466 // We load and merge the data from the file lazily at its first ever
467 // save attempt. So, whatever we are trying to save could already be
468 // in the file.
469 total_number_of_skipped_writes_++;
470 }
471 } else {
472 LOG(WARNING) << "Could not save profiling info to " << filename;
473 total_number_of_failed_writes_++;
474 }
Calin Juravleb4eddd22016-01-13 15:52:33 -0800475 }
Calin Juravlee6f87cc2017-05-24 17:41:05 -0700476 // Trim the maps to madvise the pages used for profile info.
477 // It is unlikely we will need them again in the near feature.
Calin Juravlecc3171a2017-05-19 16:47:53 -0700478 Runtime::Current()->GetArenaPool()->TrimMaps();
Calin Juravleb4eddd22016-01-13 15:52:33 -0800479 }
Calin Juravledcab1902017-05-12 19:18:47 -0700480
Calin Juravle85f7bf32016-03-18 16:23:40 +0000481 return profile_file_saved;
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000482}
483
484void* ProfileSaver::RunProfileSaverThread(void* arg) {
485 Runtime* runtime = Runtime::Current();
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000486
Calin Juravlee55fda12016-04-28 12:59:33 +0100487 bool attached = runtime->AttachCurrentThread("Profile Saver",
488 /*as_daemon*/true,
489 runtime->GetSystemThreadGroup(),
490 /*create_peer*/true);
491 if (!attached) {
492 CHECK(runtime->IsShuttingDown(Thread::Current()));
493 return nullptr;
494 }
495
496 ProfileSaver* profile_saver = reinterpret_cast<ProfileSaver*>(arg);
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000497 profile_saver->Run();
498
499 runtime->DetachCurrentThread();
500 VLOG(profiler) << "Profile saver shutdown";
501 return nullptr;
502}
503
Calin Juravle6044fa72016-03-25 17:17:09 +0000504static bool ShouldProfileLocation(const std::string& location) {
Calin Juravle75064232016-04-18 16:38:27 +0100505 OatFileManager& oat_manager = Runtime::Current()->GetOatFileManager();
506 const OatFile* oat_file = oat_manager.FindOpenedOatFileFromDexLocation(location);
Calin Juravle6044fa72016-03-25 17:17:09 +0000507 if (oat_file == nullptr) {
508 // This can happen if we fallback to run code directly from the APK.
509 // Profile it with the hope that the background dexopt will get us back into
510 // a good state.
Calin Juravle75064232016-04-18 16:38:27 +0100511 VLOG(profiler) << "Asked to profile a location without an oat file:" << location;
Calin Juravle6044fa72016-03-25 17:17:09 +0000512 return true;
513 }
514 CompilerFilter::Filter filter = oat_file->GetCompilerFilter();
Calin Juravled19dc462016-04-19 18:17:41 +0100515 if ((filter == CompilerFilter::kSpeed) || (filter == CompilerFilter::kEverything)) {
Calin Juravle75064232016-04-18 16:38:27 +0100516 VLOG(profiler)
Calin Juravled19dc462016-04-19 18:17:41 +0100517 << "Skip profiling oat file because it's already speed|everything compiled: "
518 << location << " oat location: " << oat_file->GetLocation();
Calin Juravle6044fa72016-03-25 17:17:09 +0000519 return false;
520 }
521 return true;
522}
523
Calin Juravle138dbff2016-06-28 19:36:58 +0100524void ProfileSaver::Start(const ProfileSaverOptions& options,
525 const std::string& output_filename,
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000526 jit::JitCodeCache* jit_code_cache,
Calin Juravle77651c42017-03-03 18:04:02 -0800527 const std::vector<std::string>& code_paths) {
Mathieu Chartier885a7132017-06-10 14:35:11 -0700528 Runtime* const runtime = Runtime::Current();
Calin Juravle138dbff2016-06-28 19:36:58 +0100529 DCHECK(options.IsEnabled());
Mathieu Chartier885a7132017-06-10 14:35:11 -0700530 DCHECK(runtime->GetJit() != nullptr);
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000531 DCHECK(!output_filename.empty());
532 DCHECK(jit_code_cache != nullptr);
533
Calin Juravle6044fa72016-03-25 17:17:09 +0000534 std::vector<std::string> code_paths_to_profile;
Calin Juravle6044fa72016-03-25 17:17:09 +0000535 for (const std::string& location : code_paths) {
536 if (ShouldProfileLocation(location)) {
537 code_paths_to_profile.push_back(location);
Calin Juravle6044fa72016-03-25 17:17:09 +0000538 }
539 }
Mathieu Chartier885a7132017-06-10 14:35:11 -0700540
541 MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
542 // Support getting profile samples for the boot class path. This will be used to generate the boot
543 // image profile. The intention is to use this code to generate to boot image but not use it in
544 // production. b/37966211
545 if (options.GetProfileBootClassPath()) {
546 std::set<std::string> code_paths_keys;
547 for (const std::string& location : code_paths) {
548 code_paths_keys.insert(ProfileCompilationInfo::GetProfileDexFileKey(location));
549 }
550 for (const DexFile* dex_file : runtime->GetClassLinker()->GetBootClassPath()) {
551 // Don't check ShouldProfileLocation since the boot class path may be speed compiled.
552 const std::string& location = dex_file->GetLocation();
553 const std::string key = ProfileCompilationInfo::GetProfileDexFileKey(location);
554 VLOG(profiler) << "Registering boot dex file " << location;
555 if (code_paths_keys.find(key) != code_paths_keys.end()) {
556 LOG(WARNING) << "Boot class path location key conflicts with code path " << location;
557 } else if (instance_ == nullptr) {
558 // Only add the boot class path once since Start may be called multiple times for secondary
559 // dexes.
560 // We still do the collision check above. This handles any secondary dexes that conflict
561 // with the boot class path dex files.
562 code_paths_to_profile.push_back(location);
563 }
564 }
565 }
Calin Juravle6044fa72016-03-25 17:17:09 +0000566 if (code_paths_to_profile.empty()) {
Calin Juravle75064232016-04-18 16:38:27 +0100567 VLOG(profiler) << "No code paths should be profiled.";
Calin Juravle6044fa72016-03-25 17:17:09 +0000568 return;
569 }
570
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000571 if (instance_ != nullptr) {
Calin Juravleb4eddd22016-01-13 15:52:33 -0800572 // If we already have an instance, make sure it uses the same jit_code_cache.
573 // This may be called multiple times via Runtime::registerAppInfo (e.g. for
574 // apps which share the same runtime).
575 DCHECK_EQ(instance_->jit_code_cache_, jit_code_cache);
576 // Add the code_paths to the tracked locations.
Calin Juravle77651c42017-03-03 18:04:02 -0800577 instance_->AddTrackedLocations(output_filename, code_paths_to_profile);
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000578 return;
579 }
580
581 VLOG(profiler) << "Starting profile saver using output file: " << output_filename
Andreas Gampe9186ced2016-12-12 14:28:21 -0800582 << ". Tracking: " << android::base::Join(code_paths_to_profile, ':');
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000583
Calin Juravle138dbff2016-06-28 19:36:58 +0100584 instance_ = new ProfileSaver(options,
585 output_filename,
Calin Juravle86a9ebe2016-02-24 10:13:09 +0000586 jit_code_cache,
Calin Juravle77651c42017-03-03 18:04:02 -0800587 code_paths_to_profile);
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000588
589 // Create a new thread which does the saving.
590 CHECK_PTHREAD_CALL(
591 pthread_create,
592 (&profiler_pthread_, nullptr, &RunProfileSaverThread, reinterpret_cast<void*>(instance_)),
593 "Profile saver thread");
Nicolas Geoffray23caed82017-04-21 14:30:18 +0100594
Mathieu Chartier5494e5b2017-06-26 14:13:27 -0700595 SetProfileSaverThreadPriority(profiler_pthread_, kProfileSaverPthreadPriority);
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000596}
597
Calin Juravlec19c1c22016-03-09 15:37:48 +0000598void ProfileSaver::Stop(bool dump_info) {
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000599 ProfileSaver* profile_saver = nullptr;
600 pthread_t profiler_pthread = 0U;
601
602 {
603 MutexLock profiler_mutex(Thread::Current(), *Locks::profiler_lock_);
Calin Juravleb4eddd22016-01-13 15:52:33 -0800604 VLOG(profiler) << "Stopping profile saver thread";
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000605 profile_saver = instance_;
606 profiler_pthread = profiler_pthread_;
607 if (instance_ == nullptr) {
608 DCHECK(false) << "Tried to stop a profile saver which was not started";
609 return;
610 }
611 if (instance_->shutting_down_) {
612 DCHECK(false) << "Tried to stop the profile saver twice";
613 return;
614 }
615 instance_->shutting_down_ = true;
616 }
617
618 {
619 // Wake up the saver thread if it is sleeping to allow for a clean exit.
620 MutexLock wait_mutex(Thread::Current(), profile_saver->wait_lock_);
621 profile_saver->period_condition_.Signal(Thread::Current());
622 }
623
624 // Wait for the saver thread to stop.
625 CHECK_PTHREAD_CALL(pthread_join, (profiler_pthread, nullptr), "profile saver thread shutdown");
626
Calin Juravlea345d312017-03-14 18:45:55 -0700627 // Force save everything before destroying the instance.
628 instance_->ProcessProfilingInfo(/*force_save*/true, /*number_of_new_methods*/nullptr);
629
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000630 {
631 MutexLock profiler_mutex(Thread::Current(), *Locks::profiler_lock_);
Calin Juravle9db22e82017-03-30 16:31:23 -0700632 if (dump_info) {
633 instance_->DumpInfo(LOG_STREAM(INFO));
634 }
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000635 instance_ = nullptr;
636 profiler_pthread_ = 0U;
637 }
638 delete profile_saver;
639}
640
641bool ProfileSaver::ShuttingDown(Thread* self) {
642 MutexLock mu(self, *Locks::profiler_lock_);
643 return shutting_down_;
644}
645
646bool ProfileSaver::IsStarted() {
647 MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
648 return instance_ != nullptr;
649}
650
Calin Juravle8b5d9b62017-05-05 17:27:23 -0700651static void AddTrackedLocationsToMap(const std::string& output_filename,
652 const std::vector<std::string>& code_paths,
653 SafeMap<std::string, std::set<std::string>>* map) {
654 auto it = map->find(output_filename);
655 if (it == map->end()) {
656 map->Put(output_filename, std::set<std::string>(code_paths.begin(), code_paths.end()));
Calin Juravleb4eddd22016-01-13 15:52:33 -0800657 } else {
658 it->second.insert(code_paths.begin(), code_paths.end());
659 }
660}
661
Calin Juravle8b5d9b62017-05-05 17:27:23 -0700662void ProfileSaver::AddTrackedLocations(const std::string& output_filename,
663 const std::vector<std::string>& code_paths) {
664 // Add the code paths to the list of tracked location.
665 AddTrackedLocationsToMap(output_filename, code_paths, &tracked_dex_base_locations_);
666 // The code paths may contain symlinks which could fool the profiler.
667 // If the dex file is compiled with an absolute location but loaded with symlink
668 // the profiler could skip the dex due to location mismatch.
669 // To avoid this, we add the code paths to the temporary cache of 'to_be_resolved'
670 // locations. When the profiler thread executes we will resolve the paths to their
671 // real paths.
672 // Note that we delay taking the realpath to avoid spending more time than needed
673 // when registering location (as it is done during app launch).
674 AddTrackedLocationsToMap(output_filename,
675 code_paths,
676 &tracked_dex_base_locations_to_be_resolved_);
677}
678
Calin Juravlec19c1c22016-03-09 15:37:48 +0000679void ProfileSaver::DumpInstanceInfo(std::ostream& os) {
680 MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
681 if (instance_ != nullptr) {
682 instance_->DumpInfo(os);
683 }
684}
685
686void ProfileSaver::DumpInfo(std::ostream& os) {
687 os << "ProfileSaver total_bytes_written=" << total_bytes_written_ << '\n'
688 << "ProfileSaver total_number_of_writes=" << total_number_of_writes_ << '\n'
Calin Juravle85f7bf32016-03-18 16:23:40 +0000689 << "ProfileSaver total_number_of_code_cache_queries="
690 << total_number_of_code_cache_queries_ << '\n'
Calin Juravlec19c1c22016-03-09 15:37:48 +0000691 << "ProfileSaver total_number_of_skipped_writes=" << total_number_of_skipped_writes_ << '\n'
692 << "ProfileSaver total_number_of_failed_writes=" << total_number_of_failed_writes_ << '\n'
Calin Juravle85f7bf32016-03-18 16:23:40 +0000693 << "ProfileSaver total_ms_of_sleep=" << total_ms_of_sleep_ << '\n'
Calin Juravle6044fa72016-03-25 17:17:09 +0000694 << "ProfileSaver total_ms_of_work=" << NsToMs(total_ns_of_work_) << '\n'
Calin Juravle85f7bf32016-03-18 16:23:40 +0000695 << "ProfileSaver max_number_profile_entries_cached="
Calin Juravle5fbb0fe2016-04-29 16:44:11 +0100696 << max_number_of_profile_entries_cached_ << '\n'
697 << "ProfileSaver total_number_of_hot_spikes=" << total_number_of_hot_spikes_ << '\n'
698 << "ProfileSaver total_number_of_wake_ups=" << total_number_of_wake_ups_ << '\n';
Calin Juravlec19c1c22016-03-09 15:37:48 +0000699}
700
Calin Juravlee5de54c2016-04-20 14:22:09 +0100701
702void ProfileSaver::ForceProcessProfiles() {
703 ProfileSaver* saver = nullptr;
704 {
705 MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
706 saver = instance_;
707 }
708 // TODO(calin): this is not actually thread safe as the instance_ may have been deleted,
709 // but we only use this in testing when we now this won't happen.
710 // Refactor the way we handle the instance so that we don't end up in this situation.
711 if (saver != nullptr) {
Calin Juravlea345d312017-03-14 18:45:55 -0700712 saver->ProcessProfilingInfo(/*force_save*/true, /*number_of_new_methods*/nullptr);
Calin Juravlee5de54c2016-04-20 14:22:09 +0100713 }
714}
715
716bool ProfileSaver::HasSeenMethod(const std::string& profile,
717 const DexFile* dex_file,
718 uint16_t method_idx) {
719 MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
720 if (instance_ != nullptr) {
Calin Juravlecc3171a2017-05-19 16:47:53 -0700721 ProfileCompilationInfo info(Runtime::Current()->GetArenaPool());
Calin Juravledcab1902017-05-12 19:18:47 -0700722 if (!info.Load(profile, /*clear_if_invalid*/false)) {
723 return false;
724 }
Mathieu Chartiere46f3a82017-06-19 19:54:12 -0700725 return info.GetMethodHotness(MethodReference(dex_file, method_idx)).IsInProfile();
Calin Juravlee5de54c2016-04-20 14:22:09 +0100726 }
727 return false;
728}
729
Calin Juravle8b5d9b62017-05-05 17:27:23 -0700730void ProfileSaver::ResolveTrackedLocations() {
731 SafeMap<std::string, std::set<std::string>> locations_to_be_resolved;
732 {
733 // Make a copy so that we don't hold the lock while doing I/O.
734 MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
735 locations_to_be_resolved = tracked_dex_base_locations_to_be_resolved_;
736 tracked_dex_base_locations_to_be_resolved_.clear();
737 }
738
739 // Resolve the locations.
740 SafeMap<std::string, std::vector<std::string>> resolved_locations_map;
741 for (const auto& it : locations_to_be_resolved) {
742 const std::string& filename = it.first;
743 const std::set<std::string>& locations = it.second;
744 auto resolved_locations_it = resolved_locations_map.Put(
745 filename,
746 std::vector<std::string>(locations.size()));
747
748 for (const auto& location : locations) {
749 UniqueCPtr<const char[]> location_real(realpath(location.c_str(), nullptr));
750 // Note that it's ok if we cannot get the real path.
751 if (location_real != nullptr) {
752 resolved_locations_it->second.emplace_back(location_real.get());
753 }
754 }
755 }
756
757 // Add the resolved locations to the tracked collection.
758 MutexLock mu(Thread::Current(), *Locks::profiler_lock_);
759 for (const auto& it : resolved_locations_map) {
760 AddTrackedLocationsToMap(it.first, it.second, &tracked_dex_base_locations_);
761 }
762}
763
Calin Juravle4d77b6a2015-12-01 18:38:09 +0000764} // namespace art