Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "base/debug/activity_analyzer.h" | 5 #include "base/debug/activity_analyzer.h" |
| 6 | 6 |
| 7 #include <algorithm> | |
| 8 | |
| 7 #include "base/files/file.h" | 9 #include "base/files/file.h" |
| 8 #include "base/files/file_path.h" | 10 #include "base/files/file_path.h" |
| 9 #include "base/files/memory_mapped_file.h" | 11 #include "base/files/memory_mapped_file.h" |
| 12 #include "base/lazy_instance.h" | |
| 10 #include "base/logging.h" | 13 #include "base/logging.h" |
| 11 #include "base/memory/ptr_util.h" | 14 #include "base/memory/ptr_util.h" |
| 12 #include "base/stl_util.h" | 15 #include "base/stl_util.h" |
| 13 #include "base/strings/string_util.h" | 16 #include "base/strings/string_util.h" |
| 14 | 17 |
| 15 namespace base { | 18 namespace base { |
| 16 namespace debug { | 19 namespace debug { |
| 17 | 20 |
| 21 namespace { | |
| 22 // An empty snapshot that can be returned when there otherwise is none. | |
| 23 LazyInstance<ActivityUserData::Snapshot>::Leaky g_empty_user_data_snapshot; | |
| 24 } // namespace | |
| 25 | |
| 18 ThreadActivityAnalyzer::Snapshot::Snapshot() {} | 26 ThreadActivityAnalyzer::Snapshot::Snapshot() {} |
| 19 ThreadActivityAnalyzer::Snapshot::~Snapshot() {} | 27 ThreadActivityAnalyzer::Snapshot::~Snapshot() {} |
| 20 | 28 |
| 21 ThreadActivityAnalyzer::ThreadActivityAnalyzer( | 29 ThreadActivityAnalyzer::ThreadActivityAnalyzer( |
| 22 const ThreadActivityTracker& tracker) | 30 const ThreadActivityTracker& tracker) |
| 23 : activity_snapshot_valid_(tracker.CreateSnapshot(&activity_snapshot_)) {} | 31 : activity_snapshot_valid_(tracker.CreateSnapshot(&activity_snapshot_)) {} |
| 24 | 32 |
| 25 ThreadActivityAnalyzer::ThreadActivityAnalyzer(void* base, size_t size) | 33 ThreadActivityAnalyzer::ThreadActivityAnalyzer(void* base, size_t size) |
| 26 : ThreadActivityAnalyzer(ThreadActivityTracker(base, size)) {} | 34 : ThreadActivityAnalyzer(ThreadActivityTracker(base, size)) {} |
| 27 | 35 |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 41 if (!IsValid()) | 49 if (!IsValid()) |
| 42 return; | 50 return; |
| 43 | 51 |
| 44 // User-data is held at the global scope even though it's referenced at the | 52 // User-data is held at the global scope even though it's referenced at the |
| 45 // thread scope. | 53 // thread scope. |
| 46 activity_snapshot_.user_data_stack.clear(); | 54 activity_snapshot_.user_data_stack.clear(); |
| 47 for (auto& activity : activity_snapshot_.activity_stack) { | 55 for (auto& activity : activity_snapshot_.activity_stack) { |
| 48 // The global GetUserDataSnapshot will return an empty snapshot if the ref | 56 // The global GetUserDataSnapshot will return an empty snapshot if the ref |
| 49 // or id is not valid. | 57 // or id is not valid. |
| 50 activity_snapshot_.user_data_stack.push_back(global->GetUserDataSnapshot( | 58 activity_snapshot_.user_data_stack.push_back(global->GetUserDataSnapshot( |
| 51 activity.user_data_ref, activity.user_data_id)); | 59 activity_snapshot_.process_id, activity.user_data_ref, |
| 60 activity.user_data_id)); | |
| 52 } | 61 } |
| 53 } | 62 } |
| 54 | 63 |
| 55 GlobalActivityAnalyzer::GlobalActivityAnalyzer( | 64 GlobalActivityAnalyzer::GlobalActivityAnalyzer( |
| 56 std::unique_ptr<PersistentMemoryAllocator> allocator) | 65 std::unique_ptr<PersistentMemoryAllocator> allocator) |
| 57 : allocator_(std::move(allocator)), allocator_iterator_(allocator_.get()) {} | 66 : allocator_(std::move(allocator)), allocator_iterator_(allocator_.get()) {} |
| 58 | 67 |
| 59 GlobalActivityAnalyzer::~GlobalActivityAnalyzer() {} | 68 GlobalActivityAnalyzer::~GlobalActivityAnalyzer() {} |
| 60 | 69 |
| 61 #if !defined(OS_NACL) | 70 #if !defined(OS_NACL) |
| 62 // static | 71 // static |
| 63 std::unique_ptr<GlobalActivityAnalyzer> GlobalActivityAnalyzer::CreateWithFile( | 72 std::unique_ptr<GlobalActivityAnalyzer> GlobalActivityAnalyzer::CreateWithFile( |
| 64 const FilePath& file_path) { | 73 const FilePath& file_path) { |
| 65 // Map the file read-write so it can guarantee consistency between | 74 // Map the file read-write so it can guarantee consistency between |
| 66 // the analyzer and any trackers that my still be active. | 75 // the analyzer and any trackers that my still be active. |
| 67 std::unique_ptr<MemoryMappedFile> mmfile(new MemoryMappedFile()); | 76 std::unique_ptr<MemoryMappedFile> mmfile(new MemoryMappedFile()); |
| 68 mmfile->Initialize(file_path, MemoryMappedFile::READ_WRITE); | 77 mmfile->Initialize(file_path, MemoryMappedFile::READ_WRITE); |
| 69 if (!mmfile->IsValid()) | 78 if (!mmfile->IsValid()) |
| 70 return nullptr; | 79 return nullptr; |
| 71 | 80 |
| 72 if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mmfile, true)) | 81 if (!FilePersistentMemoryAllocator::IsFileAcceptable(*mmfile, true)) |
| 73 return nullptr; | 82 return nullptr; |
| 74 | 83 |
| 75 return WrapUnique( | 84 return WrapUnique( |
| 76 new GlobalActivityAnalyzer(MakeUnique<FilePersistentMemoryAllocator>( | 85 new GlobalActivityAnalyzer(MakeUnique<FilePersistentMemoryAllocator>( |
| 77 std::move(mmfile), 0, 0, base::StringPiece(), true))); | 86 std::move(mmfile), 0, 0, base::StringPiece(), true))); |
| 78 } | 87 } |
| 79 #endif // !defined(OS_NACL) | 88 #endif // !defined(OS_NACL) |
| 80 | 89 |
| 81 ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetFirstAnalyzer() { | 90 int64_t GlobalActivityAnalyzer::GetFirstProcess() { |
| 82 PrepareAllAnalyzers(); | 91 PrepareAllAnalyzers(); |
| 92 return GetNextProcess(); | |
| 93 } | |
| 94 | |
| 95 int64_t GlobalActivityAnalyzer::GetNextProcess() { | |
| 96 if (process_ids_.empty()) | |
| 97 return 0; | |
| 98 int64_t pid = process_ids_.back(); | |
| 99 process_ids_.pop_back(); | |
| 100 return pid; | |
| 101 } | |
| 102 | |
| 103 ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetFirstAnalyzer(int64_t pid) { | |
| 83 analyzers_iterator_ = analyzers_.begin(); | 104 analyzers_iterator_ = analyzers_.begin(); |
| 84 if (analyzers_iterator_ == analyzers_.end()) | 105 if (analyzers_iterator_ == analyzers_.end()) |
| 85 return nullptr; | 106 return nullptr; |
| 107 int64_t create_stamp; | |
| 108 if (analyzers_iterator_->second->GetProcessId(&create_stamp) == pid && | |
| 109 create_stamp <= analysis_stamp_) { | |
| 110 return analyzers_iterator_->second.get(); | |
| 111 } | |
| 112 return GetNextAnalyzer(pid); | |
| 113 } | |
| 114 | |
| 115 ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetNextAnalyzer(int64_t pid) { | |
| 116 DCHECK(analyzers_iterator_ != analyzers_.end()); | |
| 117 int64_t create_stamp; | |
| 118 do { | |
| 119 ++analyzers_iterator_; | |
| 120 if (analyzers_iterator_ == analyzers_.end()) | |
| 121 return nullptr; | |
| 122 } while (analyzers_iterator_->second->GetProcessId(&create_stamp) != pid && | |
| 123 create_stamp <= analysis_stamp_); | |
|
manzagop (departed)
2017/03/21 21:10:05
Not sure I follow this condition. Should it be "wh
bcwhite
2017/03/29 22:00:51
Done.
| |
| 86 return analyzers_iterator_->second.get(); | 124 return analyzers_iterator_->second.get(); |
| 87 } | 125 } |
| 88 | 126 |
| 89 ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetNextAnalyzer() { | |
| 90 DCHECK(analyzers_iterator_ != analyzers_.end()); | |
| 91 ++analyzers_iterator_; | |
| 92 if (analyzers_iterator_ == analyzers_.end()) | |
| 93 return nullptr; | |
| 94 return analyzers_iterator_->second.get(); | |
| 95 } | |
| 96 | |
| 97 ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetAnalyzerForThread( | 127 ThreadActivityAnalyzer* GlobalActivityAnalyzer::GetAnalyzerForThread( |
| 98 const ThreadKey& key) { | 128 const ThreadKey& key) { |
| 99 auto found = analyzers_.find(key); | 129 auto found = analyzers_.find(key); |
| 100 if (found == analyzers_.end()) | 130 if (found == analyzers_.end()) |
| 101 return nullptr; | 131 return nullptr; |
| 102 return found->second.get(); | 132 return found->second.get(); |
| 103 } | 133 } |
| 104 | 134 |
| 105 ActivityUserData::Snapshot GlobalActivityAnalyzer::GetUserDataSnapshot( | 135 ActivityUserData::Snapshot GlobalActivityAnalyzer::GetUserDataSnapshot( |
| 136 int64_t pid, | |
| 106 uint32_t ref, | 137 uint32_t ref, |
| 107 uint32_t id) { | 138 uint32_t id) { |
| 108 ActivityUserData::Snapshot snapshot; | 139 ActivityUserData::Snapshot snapshot; |
| 109 | 140 |
| 110 void* memory = allocator_->GetAsArray<char>( | 141 void* memory = allocator_->GetAsArray<char>( |
| 111 ref, GlobalActivityTracker::kTypeIdUserDataRecord, | 142 ref, GlobalActivityTracker::kTypeIdUserDataRecord, |
| 112 PersistentMemoryAllocator::kSizeAny); | 143 PersistentMemoryAllocator::kSizeAny); |
| 113 if (memory) { | 144 if (memory) { |
| 114 size_t size = allocator_->GetAllocSize(ref); | 145 size_t size = allocator_->GetAllocSize(ref); |
| 115 const ActivityUserData user_data(memory, size); | 146 const ActivityUserData user_data(memory, size); |
| 116 user_data.CreateSnapshot(&snapshot); | 147 user_data.CreateSnapshot(&snapshot); |
| 117 if (user_data.id() != id) { | 148 ProcessId process_id; |
| 149 int64_t create_stamp; | |
| 150 if (!ActivityUserData::GetOwningProcessId(memory, &process_id, | |
| 151 &create_stamp) || | |
| 152 process_id != pid || user_data.id() != id) { | |
| 118 // This allocation has been overwritten since it was created. Return an | 153 // This allocation has been overwritten since it was created. Return an |
| 119 // empty snapshot because whatever was captured is incorrect. | 154 // empty snapshot because whatever was captured is incorrect. |
| 120 snapshot.clear(); | 155 snapshot.clear(); |
| 121 } | 156 } |
| 122 } | 157 } |
| 123 | 158 |
| 124 return snapshot; | 159 return snapshot; |
| 125 } | 160 } |
| 126 | 161 |
| 127 ActivityUserData::Snapshot GlobalActivityAnalyzer::GetGlobalUserDataSnapshot() { | 162 const ActivityUserData::Snapshot& |
| 128 ActivityUserData::Snapshot snapshot; | 163 GlobalActivityAnalyzer::GetProcessDataSnapshot(int64_t pid) { |
| 164 auto iter = process_data_.find(pid); | |
| 165 if (iter == process_data_.end()) | |
| 166 return g_empty_user_data_snapshot.Get(); | |
| 167 if (iter->second.create_stamp > analysis_stamp_) | |
| 168 return g_empty_user_data_snapshot.Get(); | |
| 169 DCHECK_EQ(pid, iter->second.process_id); | |
| 170 return iter->second.data; | |
| 171 } | |
| 172 | |
| 173 const ActivityUserData::Snapshot& | |
| 174 GlobalActivityAnalyzer::GetGlobalDataSnapshot() { | |
| 175 global_data_snapshot_.clear(); | |
| 129 | 176 |
| 130 PersistentMemoryAllocator::Reference ref = | 177 PersistentMemoryAllocator::Reference ref = |
| 131 PersistentMemoryAllocator::Iterator(allocator_.get()) | 178 PersistentMemoryAllocator::Iterator(allocator_.get()) |
| 132 .GetNextOfType(GlobalActivityTracker::kTypeIdGlobalDataRecord); | 179 .GetNextOfType(GlobalActivityTracker::kTypeIdGlobalDataRecord); |
| 133 void* memory = allocator_->GetAsArray<char>( | 180 void* memory = allocator_->GetAsArray<char>( |
| 134 ref, GlobalActivityTracker::kTypeIdGlobalDataRecord, | 181 ref, GlobalActivityTracker::kTypeIdGlobalDataRecord, |
| 135 PersistentMemoryAllocator::kSizeAny); | 182 PersistentMemoryAllocator::kSizeAny); |
| 136 if (memory) { | 183 if (memory) { |
| 137 size_t size = allocator_->GetAllocSize(ref); | 184 size_t size = allocator_->GetAllocSize(ref); |
| 138 const ActivityUserData global_data(memory, size); | 185 const ActivityUserData global_data(memory, size); |
| 139 global_data.CreateSnapshot(&snapshot); | 186 global_data.CreateSnapshot(&global_data_snapshot_); |
| 140 } | 187 } |
| 141 | 188 |
| 142 return snapshot; | 189 return global_data_snapshot_; |
| 143 } | 190 } |
| 144 | 191 |
| 145 std::vector<std::string> GlobalActivityAnalyzer::GetLogMessages() { | 192 std::vector<std::string> GlobalActivityAnalyzer::GetLogMessages() { |
| 146 std::vector<std::string> messages; | 193 std::vector<std::string> messages; |
| 147 PersistentMemoryAllocator::Reference ref; | 194 PersistentMemoryAllocator::Reference ref; |
| 148 | 195 |
| 149 PersistentMemoryAllocator::Iterator iter(allocator_.get()); | 196 PersistentMemoryAllocator::Iterator iter(allocator_.get()); |
| 150 while ((ref = iter.GetNextOfType( | 197 while ((ref = iter.GetNextOfType( |
| 151 GlobalActivityTracker::kTypeIdGlobalLogMessage)) != 0) { | 198 GlobalActivityTracker::kTypeIdGlobalLogMessage)) != 0) { |
| 152 const char* message = allocator_->GetAsArray<char>( | 199 const char* message = allocator_->GetAsArray<char>( |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 178 | 225 |
| 179 return modules; | 226 return modules; |
| 180 } | 227 } |
| 181 | 228 |
| 182 GlobalActivityAnalyzer::ProgramLocation | 229 GlobalActivityAnalyzer::ProgramLocation |
| 183 GlobalActivityAnalyzer::GetProgramLocationFromAddress(uint64_t address) { | 230 GlobalActivityAnalyzer::GetProgramLocationFromAddress(uint64_t address) { |
| 184 // TODO(bcwhite): Implement this. | 231 // TODO(bcwhite): Implement this. |
| 185 return { 0, 0 }; | 232 return { 0, 0 }; |
| 186 } | 233 } |
| 187 | 234 |
| 235 GlobalActivityAnalyzer::UserDataSnapshot::UserDataSnapshot() {} | |
| 236 GlobalActivityAnalyzer::UserDataSnapshot::UserDataSnapshot( | |
| 237 const UserDataSnapshot& rhs) = default; | |
| 238 GlobalActivityAnalyzer::UserDataSnapshot::UserDataSnapshot( | |
| 239 UserDataSnapshot&& rhs) = default; | |
| 240 GlobalActivityAnalyzer::UserDataSnapshot::~UserDataSnapshot() {} | |
| 241 | |
| 188 void GlobalActivityAnalyzer::PrepareAllAnalyzers() { | 242 void GlobalActivityAnalyzer::PrepareAllAnalyzers() { |
| 243 // Record the time when analysis started. | |
| 244 analysis_stamp_ = base::Time::Now().ToInternalValue(); | |
| 245 | |
| 189 // Fetch all the records. This will retrieve only ones created since the | 246 // Fetch all the records. This will retrieve only ones created since the |
| 190 // last run since the PMA iterator will continue from where it left off. | 247 // last run since the PMA iterator will continue from where it left off. |
| 191 uint32_t type; | 248 uint32_t type; |
| 192 PersistentMemoryAllocator::Reference ref; | 249 PersistentMemoryAllocator::Reference ref; |
| 193 while ((ref = allocator_iterator_.GetNext(&type)) != 0) { | 250 while ((ref = allocator_iterator_.GetNext(&type)) != 0) { |
| 194 switch (type) { | 251 switch (type) { |
| 195 case GlobalActivityTracker::kTypeIdActivityTracker: | 252 case GlobalActivityTracker::kTypeIdActivityTracker: |
| 196 case GlobalActivityTracker::kTypeIdActivityTrackerFree: | 253 case GlobalActivityTracker::kTypeIdActivityTrackerFree: |
| 197 // Free or not, add it to the list of references for later analysis. | 254 case GlobalActivityTracker::kTypeIdProcessDataRecord: |
| 198 tracker_references_.insert(ref); | 255 case GlobalActivityTracker::kTypeIdProcessDataRecordFree: |
| 256 case PersistentMemoryAllocator::kTypeIdTransitioning: | |
| 257 // Active, free, or transitioning: add it to the list of references | |
| 258 // for later analysis. | |
| 259 memory_references_.insert(ref); | |
| 199 break; | 260 break; |
| 200 } | 261 } |
| 201 } | 262 } |
| 202 | 263 |
| 203 // Go through all the known references and create analyzers for them with | 264 // Clear out any old information. |
| 265 analyzers_.clear(); | |
| 266 process_data_.clear(); | |
| 267 process_ids_.clear(); | |
| 268 std::set<int64_t> seen_pids; | |
| 269 | |
| 270 // Go through all the known references and create objects for them with | |
| 204 // snapshots of the current state. | 271 // snapshots of the current state. |
| 205 analyzers_.clear(); | 272 for (PersistentMemoryAllocator::Reference memory_ref : memory_references_) { |
| 206 for (PersistentMemoryAllocator::Reference tracker_ref : tracker_references_) { | |
| 207 // Get the actual data segment for the tracker. This can fail if the | 273 // Get the actual data segment for the tracker. This can fail if the |
| 208 // record has been marked "free" since the type will not match. | 274 // record has been marked "free" since the type will not match. |
|
manzagop (departed)
2017/03/21 21:10:05
Update comment: retrieval of kTypeIdAny can't fail
bcwhite
2017/03/29 22:00:51
Done.
| |
| 209 void* base = allocator_->GetAsArray<char>( | 275 void* const base = allocator_->GetAsArray<char>( |
| 210 tracker_ref, GlobalActivityTracker::kTypeIdActivityTracker, | 276 memory_ref, 0, PersistentMemoryAllocator::kSizeAny); |
|
manzagop (departed)
2017/03/21 21:10:05
Is 0 the same as kTypeIdAny?
bcwhite
2017/03/29 22:00:51
Done.
| |
| 211 PersistentMemoryAllocator::kSizeAny); | 277 const size_t size = allocator_->GetAllocSize(memory_ref); |
| 212 if (!base) | 278 if (!base) |
| 213 continue; | 279 continue; |
| 214 | 280 |
| 215 // Create the analyzer on the data. This will capture a snapshot of the | 281 switch (allocator_->GetType(memory_ref)) { |
| 216 // tracker state. This can fail if the tracker is somehow corrupted or is | 282 case GlobalActivityTracker::kTypeIdActivityTracker: { |
| 217 // in the process of shutting down. | 283 // Create the analyzer on the data. This will capture a snapshot of the |
| 218 std::unique_ptr<ThreadActivityAnalyzer> analyzer(new ThreadActivityAnalyzer( | 284 // tracker state. This can fail if the tracker is somehow corrupted or |
| 219 base, allocator_->GetAllocSize(tracker_ref))); | 285 // is in the process of shutting down. |
| 220 if (!analyzer->IsValid()) | 286 std::unique_ptr<ThreadActivityAnalyzer> analyzer( |
| 221 continue; | 287 new ThreadActivityAnalyzer(base, size)); |
| 222 analyzer->AddGlobalInformation(this); | 288 if (!analyzer->IsValid()) |
| 289 continue; | |
| 290 analyzer->AddGlobalInformation(this); | |
| 223 | 291 |
| 224 // Add this analyzer to the map of known ones, indexed by a unique thread | 292 // Track PIDs. |
| 225 // identifier. | 293 int64_t pid = analyzer->GetProcessId(); |
| 226 DCHECK(!base::ContainsKey(analyzers_, analyzer->GetThreadKey())); | 294 if (seen_pids.find(pid) == seen_pids.end()) { |
| 227 analyzer->allocator_reference_ = ref; | 295 process_ids_.push_back(pid); |
| 228 analyzers_[analyzer->GetThreadKey()] = std::move(analyzer); | 296 seen_pids.insert(pid); |
| 297 } | |
| 298 | |
| 299 // Add this analyzer to the map of known ones, indexed by a unique | |
| 300 // thread | |
| 301 // identifier. | |
| 302 DCHECK(!base::ContainsKey(analyzers_, analyzer->GetThreadKey())); | |
| 303 analyzer->allocator_reference_ = ref; | |
| 304 analyzers_[analyzer->GetThreadKey()] = std::move(analyzer); | |
| 305 } break; | |
| 306 | |
| 307 case GlobalActivityTracker::kTypeIdProcessDataRecord: { | |
|
manzagop (departed)
2017/03/21 21:10:05
Looks like a process that had no threads won't be
bcwhite
2017/03/29 22:00:51
Done.
| |
| 308 // Get the PID associated with this data record. | |
| 309 ProcessId process_id; | |
| 310 int64_t create_stamp; | |
| 311 ActivityUserData::GetOwningProcessId(base, &process_id, &create_stamp); | |
| 312 | |
| 313 // Create a snapshot of the data. This can fail if the data is somehow | |
| 314 // corrupted or the process shutdown and the memory being released. | |
| 315 UserDataSnapshot& snapshot = process_data_[process_id]; | |
| 316 snapshot.process_id = process_id; | |
| 317 snapshot.create_stamp = create_stamp; | |
| 318 const ActivityUserData process_data(base, size); | |
| 319 if (!process_data.CreateSnapshot(&snapshot.data)) | |
| 320 break; | |
| 321 | |
| 322 // Check that nothing changed. If it did, forget what was recorded. | |
| 323 ActivityUserData::GetOwningProcessId(base, &process_id, &create_stamp); | |
| 324 if (process_id != snapshot.process_id || | |
| 325 create_stamp != snapshot.create_stamp) { | |
| 326 process_data_.erase(process_id); | |
| 327 } | |
| 328 } break; | |
| 329 } | |
| 229 } | 330 } |
| 331 | |
| 332 // Reverse the list of PIDs so that they get popped in the order found. | |
|
manzagop (departed)
2017/03/21 21:10:05
Is the order important? We don't seem to care abou
bcwhite
2017/03/29 22:00:51
There has to be some list of PIDs though it could
manzagop (departed)
2017/03/30 18:13:24
The retrieval by creation order sounds worth addin
bcwhite
2017/03/30 22:20:51
Done.
| |
| 333 std::reverse(process_ids_.begin(), process_ids_.end()); | |
| 230 } | 334 } |
| 231 | 335 |
| 232 } // namespace debug | 336 } // namespace debug |
| 233 } // namespace base | 337 } // namespace base |
| OLD | NEW |