| 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 "components/browser_watcher/postmortem_report_collector.h" | 5 #include "components/browser_watcher/postmortem_report_collector_impl.h" |
| 6 | 6 |
| 7 #include <memory> |
| 8 #include <string> |
| 7 #include <utility> | 9 #include <utility> |
| 10 #include <vector> |
| 8 | 11 |
| 9 #include "base/debug/activity_analyzer.h" | 12 #include "base/debug/activity_analyzer.h" |
| 10 #include "base/files/file_enumerator.h" | |
| 11 #include "base/files/file_util.h" | |
| 12 #include "base/logging.h" | 13 #include "base/logging.h" |
| 13 #include "base/metrics/histogram_macros.h" | |
| 14 #include "base/path_service.h" | |
| 15 #include "base/strings/string_piece.h" | 14 #include "base/strings/string_piece.h" |
| 16 #include "base/strings/string_util.h" | 15 #include "base/strings/string_util.h" |
| 17 #include "base/strings/utf_string_conversions.h" | 16 #include "base/strings/utf_string_conversions.h" |
| 18 #include "components/browser_watcher/postmortem_minidump_writer.h" | |
| 19 #include "components/browser_watcher/stability_data_names.h" | 17 #include "components/browser_watcher/stability_data_names.h" |
| 20 #include "components/variations/active_field_trials.h" | 18 #include "components/variations/active_field_trials.h" |
| 21 #include "third_party/crashpad/crashpad/client/settings.h" | |
| 22 #include "third_party/crashpad/crashpad/util/misc/uuid.h" | 19 #include "third_party/crashpad/crashpad/util/misc/uuid.h" |
| 23 | 20 |
| 24 using base::FilePath; | |
| 25 | |
| 26 namespace browser_watcher { | 21 namespace browser_watcher { |
| 22 namespace impl { |
| 27 | 23 |
| 28 using ActivitySnapshot = base::debug::ThreadActivityAnalyzer::Snapshot; | 24 using ActivitySnapshot = base::debug::ThreadActivityAnalyzer::Snapshot; |
| 29 using base::debug::ActivityUserData; | 25 using base::debug::ActivityUserData; |
| 30 using base::debug::GlobalActivityAnalyzer; | 26 using base::debug::GlobalActivityAnalyzer; |
| 31 using base::debug::GlobalActivityTracker; | 27 using base::debug::GlobalActivityTracker; |
| 32 using base::debug::ThreadActivityAnalyzer; | 28 using base::debug::ThreadActivityAnalyzer; |
| 33 using crashpad::CrashReportDatabase; | |
| 34 | 29 |
| 35 namespace { | 30 namespace { |
| 36 | 31 |
| 37 const char kFieldTrialKeyPrefix[] = "FieldTrial."; | 32 const char kFieldTrialKeyPrefix[] = "FieldTrial."; |
| 38 | 33 |
| 39 // Collects stability user data from the recorded format to the collected | 34 // Collects stability user data from the recorded format to the collected |
| 40 // format. | 35 // format. |
| 41 void CollectUserData( | 36 void CollectUserData( |
| 42 const ActivityUserData::Snapshot& recorded_map, | 37 const ActivityUserData::Snapshot& recorded_map, |
| 43 google::protobuf::Map<std::string, TypedValue>* collected_map, | 38 google::protobuf::Map<std::string, TypedValue>* collected_map, |
| (...skipping 99 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 143 uuid->data_2, uuid->data_3, uuid->data_4[0], uuid->data_4[1], | 138 uuid->data_2, uuid->data_3, uuid->data_4[0], uuid->data_4[1], |
| 144 uuid->data_5[0], uuid->data_5[1], uuid->data_5[2], uuid->data_5[3], | 139 uuid->data_5[0], uuid->data_5[1], uuid->data_5[2], uuid->data_5[3], |
| 145 uuid->data_5[4], uuid->data_5[5], recorded.age); | 140 uuid->data_5[4], uuid->data_5[5], recorded.age); |
| 146 collected->set_debug_identifier(debug_identifier); | 141 collected->set_debug_identifier(debug_identifier); |
| 147 collected->set_is_unloaded(!recorded.is_loaded); | 142 collected->set_is_unloaded(!recorded.is_loaded); |
| 148 } | 143 } |
| 149 } | 144 } |
| 150 | 145 |
| 151 } // namespace | 146 } // namespace |
| 152 | 147 |
| 153 PostmortemReportCollector::PostmortemReportCollector( | 148 // DO NOT SUBMIT: review diff stepping stone. Move the function after 1st round. |
| 154 const std::string& product_name, | 149 void CollectThread( |
| 155 const std::string& version_number, | 150 const base::debug::ThreadActivityAnalyzer::Snapshot& snapshot, |
| 156 const std::string& channel_name) | 151 ThreadState* thread_state); |
| 157 : product_name_(product_name), | |
| 158 version_number_(version_number), | |
| 159 channel_name_(channel_name) {} | |
| 160 | 152 |
| 161 int PostmortemReportCollector::CollectAndSubmitForUpload( | 153 CollectionStatus Collect(const base::FilePath& stability_file, |
| 162 const base::FilePath& debug_info_dir, | 154 StabilityReport* report) { |
| 163 const base::FilePath::StringType& debug_file_pattern, | 155 DCHECK(report); |
| 164 const std::set<base::FilePath>& excluded_debug_files, | |
| 165 crashpad::CrashReportDatabase* report_database) { | |
| 166 DCHECK_NE(true, debug_info_dir.empty()); | |
| 167 DCHECK_NE(true, debug_file_pattern.empty()); | |
| 168 DCHECK_NE(nullptr, report_database); | |
| 169 | |
| 170 // Collect the list of files to harvest. | |
| 171 std::vector<FilePath> debug_files = GetDebugStateFilePaths( | |
| 172 debug_info_dir, debug_file_pattern, excluded_debug_files); | |
| 173 | |
| 174 // Determine the crashpad client id. | |
| 175 crashpad::UUID client_id; | |
| 176 crashpad::Settings* settings = report_database->GetSettings(); | |
| 177 if (settings) { | |
| 178 // If GetSettings() or GetClientID() fails client_id will be left at its | |
| 179 // default value, all zeroes, which is appropriate. | |
| 180 settings->GetClientID(&client_id); | |
| 181 } | |
| 182 | |
| 183 // Process each stability file. | |
| 184 int success_cnt = 0; | |
| 185 for (const FilePath& file : debug_files) { | |
| 186 CollectionStatus status = | |
| 187 CollectAndSubmit(client_id, file, report_database); | |
| 188 // TODO(manzagop): consider making this a stability metric. | |
| 189 UMA_HISTOGRAM_ENUMERATION("ActivityTracker.Collect.Status", status, | |
| 190 COLLECTION_STATUS_MAX); | |
| 191 if (status == SUCCESS) | |
| 192 ++success_cnt; | |
| 193 } | |
| 194 | |
| 195 return success_cnt; | |
| 196 } | |
| 197 | |
| 198 std::vector<FilePath> PostmortemReportCollector::GetDebugStateFilePaths( | |
| 199 const base::FilePath& debug_info_dir, | |
| 200 const base::FilePath::StringType& debug_file_pattern, | |
| 201 const std::set<FilePath>& excluded_debug_files) { | |
| 202 DCHECK_NE(true, debug_info_dir.empty()); | |
| 203 DCHECK_NE(true, debug_file_pattern.empty()); | |
| 204 | |
| 205 std::vector<FilePath> paths; | |
| 206 base::FileEnumerator enumerator(debug_info_dir, false /* recursive */, | |
| 207 base::FileEnumerator::FILES, | |
| 208 debug_file_pattern); | |
| 209 FilePath path; | |
| 210 for (path = enumerator.Next(); !path.empty(); path = enumerator.Next()) { | |
| 211 if (excluded_debug_files.find(path) == excluded_debug_files.end()) | |
| 212 paths.push_back(path); | |
| 213 } | |
| 214 return paths; | |
| 215 } | |
| 216 | |
| 217 PostmortemReportCollector::CollectionStatus | |
| 218 PostmortemReportCollector::CollectAndSubmit( | |
| 219 const crashpad::UUID& client_id, | |
| 220 const FilePath& file, | |
| 221 crashpad::CrashReportDatabase* report_database) { | |
| 222 DCHECK_NE(nullptr, report_database); | |
| 223 | |
| 224 // Note: the code below involves two notions of report: chrome internal state | |
| 225 // reports and the crashpad reports they get wrapped into. | |
| 226 | |
| 227 // Collect the data from the debug file to a proto. Note: a non-empty report | |
| 228 // is interpreted here as an unclean exit. | |
| 229 std::unique_ptr<StabilityReport> report_proto; | |
| 230 CollectionStatus status = Collect(file, &report_proto); | |
| 231 if (status != SUCCESS) { | |
| 232 // The file was empty, or there was an error collecting the data. Detailed | |
| 233 // logging happens within the Collect function. | |
| 234 if (!base::DeleteFile(file, false)) | |
| 235 LOG(ERROR) << "Failed to delete " << file.value(); | |
| 236 return status; | |
| 237 } | |
| 238 DCHECK_NE(nullptr, report_proto.get()); | |
| 239 | |
| 240 // Prepare a crashpad report. | |
| 241 CrashReportDatabase::NewReport* new_report = nullptr; | |
| 242 CrashReportDatabase::OperationStatus database_status = | |
| 243 report_database->PrepareNewCrashReport(&new_report); | |
| 244 if (database_status != CrashReportDatabase::kNoError) { | |
| 245 LOG(ERROR) << "PrepareNewCrashReport failed"; | |
| 246 return PREPARE_NEW_CRASH_REPORT_FAILED; | |
| 247 } | |
| 248 CrashReportDatabase::CallErrorWritingCrashReport | |
| 249 call_error_writing_crash_report(report_database, new_report); | |
| 250 | |
| 251 // Write the report to a minidump. | |
| 252 if (!WriteReportToMinidump(report_proto.get(), client_id, new_report->uuid, | |
| 253 reinterpret_cast<FILE*>(new_report->handle))) { | |
| 254 return WRITE_TO_MINIDUMP_FAILED; | |
| 255 } | |
| 256 | |
| 257 // If the file cannot be deleted, do not report its contents. Note this can | |
| 258 // lead to under reporting and retries. However, under reporting is | |
| 259 // preferable to the over reporting that would happen with a file that | |
| 260 // cannot be deleted. | |
| 261 // TODO(manzagop): metrics for the number of non-deletable files. | |
| 262 if (!base::DeleteFile(file, false)) { | |
| 263 LOG(ERROR) << "Failed to delete " << file.value(); | |
| 264 return DEBUG_FILE_DELETION_FAILED; | |
| 265 } | |
| 266 | |
| 267 // Finalize the report wrt the report database. Note that this doesn't trigger | |
| 268 // an immediate upload, but Crashpad will eventually upload the report (as of | |
| 269 // writing, the delay is on the order of up to 15 minutes). | |
| 270 call_error_writing_crash_report.Disarm(); | |
| 271 crashpad::UUID unused_report_id; | |
| 272 database_status = report_database->FinishedWritingCrashReport( | |
| 273 new_report, &unused_report_id); | |
| 274 if (database_status != CrashReportDatabase::kNoError) { | |
| 275 LOG(ERROR) << "FinishedWritingCrashReport failed"; | |
| 276 return FINISHED_WRITING_CRASH_REPORT_FAILED; | |
| 277 } | |
| 278 | |
| 279 return SUCCESS; | |
| 280 } | |
| 281 | |
| 282 PostmortemReportCollector::CollectionStatus PostmortemReportCollector::Collect( | |
| 283 const base::FilePath& debug_state_file, | |
| 284 std::unique_ptr<StabilityReport>* report) { | |
| 285 DCHECK_NE(nullptr, report); | |
| 286 report->reset(); | |
| 287 | 156 |
| 288 // Create a global analyzer. | 157 // Create a global analyzer. |
| 289 std::unique_ptr<GlobalActivityAnalyzer> global_analyzer = | 158 std::unique_ptr<GlobalActivityAnalyzer> global_analyzer = |
| 290 GlobalActivityAnalyzer::CreateWithFile(debug_state_file); | 159 GlobalActivityAnalyzer::CreateWithFile(stability_file); |
| 291 if (!global_analyzer) | 160 if (!global_analyzer) |
| 292 return ANALYZER_CREATION_FAILED; | 161 return ANALYZER_CREATION_FAILED; |
| 293 | 162 |
| 294 // Early exit if there is no data. | 163 // Early exit if there is no data. |
| 295 std::vector<std::string> log_messages = global_analyzer->GetLogMessages(); | 164 std::vector<std::string> log_messages = global_analyzer->GetLogMessages(); |
| 296 ActivityUserData::Snapshot global_data_snapshot = | 165 ActivityUserData::Snapshot global_data_snapshot = |
| 297 global_analyzer->GetGlobalUserDataSnapshot(); | 166 global_analyzer->GetGlobalUserDataSnapshot(); |
| 298 ThreadActivityAnalyzer* thread_analyzer = global_analyzer->GetFirstAnalyzer(); | 167 ThreadActivityAnalyzer* thread_analyzer = global_analyzer->GetFirstAnalyzer(); |
| 299 if (log_messages.empty() && global_data_snapshot.empty() && | 168 if (log_messages.empty() && global_data_snapshot.empty() && |
| 300 !thread_analyzer) { | 169 !thread_analyzer) { |
| 301 return DEBUG_FILE_NO_DATA; | 170 return DEBUG_FILE_NO_DATA; |
| 302 } | 171 } |
| 303 | 172 |
| 304 // Create the report, then flesh it out. | |
| 305 report->reset(new StabilityReport()); | |
| 306 | |
| 307 // Collect log messages. | 173 // Collect log messages. |
| 308 for (const std::string& message : log_messages) { | 174 for (const std::string& message : log_messages) { |
| 309 (*report)->add_log_messages(message); | 175 report->add_log_messages(message); |
| 310 } | 176 } |
| 311 | 177 |
| 312 // Collect global user data. | 178 // Collect global user data. |
| 313 google::protobuf::Map<std::string, TypedValue>& global_data = | 179 google::protobuf::Map<std::string, TypedValue>& global_data = |
| 314 *(*report)->mutable_global_data(); | 180 *(report->mutable_global_data()); |
| 315 CollectUserData(global_data_snapshot, &global_data, report->get()); | 181 CollectUserData(global_data_snapshot, &global_data, report); |
| 316 | |
| 317 // Add the reporting Chrome's details to the report. | |
| 318 global_data[kStabilityReporterChannel].set_string_value(channel_name()); | |
| 319 #if defined(ARCH_CPU_X86) | |
| 320 global_data[kStabilityReporterPlatform].set_string_value( | |
| 321 std::string("Win32")); | |
| 322 #elif defined(ARCH_CPU_X86_64) | |
| 323 global_data[kStabilityReporterPlatform].set_string_value( | |
| 324 std::string("Win64")); | |
| 325 #endif | |
| 326 global_data[kStabilityReporterProduct].set_string_value(product_name()); | |
| 327 global_data[kStabilityReporterVersion].set_string_value(version_number()); | |
| 328 | 182 |
| 329 // Collect thread activity data. | 183 // Collect thread activity data. |
| 330 // Note: a single process is instrumented. | 184 // Note: a single process is instrumented. |
| 331 ProcessState* process_state = (*report)->add_process_states(); | 185 ProcessState* process_state = report->add_process_states(); |
| 332 for (; thread_analyzer != nullptr; | 186 for (; thread_analyzer != nullptr; |
| 333 thread_analyzer = global_analyzer->GetNextAnalyzer()) { | 187 thread_analyzer = global_analyzer->GetNextAnalyzer()) { |
| 334 // Only valid analyzers are expected per contract of GetFirstAnalyzer / | 188 // Only valid analyzers are expected per contract of GetFirstAnalyzer / |
| 335 // GetNextAnalyzer. | 189 // GetNextAnalyzer. |
| 336 DCHECK(thread_analyzer->IsValid()); | 190 DCHECK(thread_analyzer->IsValid()); |
| 337 | 191 |
| 338 if (!process_state->has_process_id()) { | 192 if (!process_state->has_process_id()) { |
| 339 process_state->set_process_id( | 193 process_state->set_process_id( |
| 340 thread_analyzer->activity_snapshot().process_id); | 194 thread_analyzer->activity_snapshot().process_id); |
| 341 } | 195 } |
| 342 DCHECK_EQ(thread_analyzer->activity_snapshot().process_id, | 196 DCHECK_EQ(thread_analyzer->activity_snapshot().process_id, |
| 343 process_state->process_id()); | 197 process_state->process_id()); |
| 344 | 198 |
| 345 ThreadState* thread_state = process_state->add_threads(); | 199 ThreadState* thread_state = process_state->add_threads(); |
| 346 CollectThread(thread_analyzer->activity_snapshot(), thread_state); | 200 CollectThread(thread_analyzer->activity_snapshot(), thread_state); |
| 347 } | 201 } |
| 348 | 202 |
| 349 // Collect module information. | 203 // Collect module information. |
| 350 CollectModuleInformation(global_analyzer->GetModules(), process_state); | 204 CollectModuleInformation(global_analyzer->GetModules(), process_state); |
| 351 | 205 |
| 352 return SUCCESS; | 206 return SUCCESS; |
| 353 } | 207 } |
| 354 | 208 |
| 355 void PostmortemReportCollector::CollectThread( | 209 // DO NOT SUBMIT: review diff stepping stone. Move the function after 1st round. |
| 210 void CollectThread( |
| 356 const base::debug::ThreadActivityAnalyzer::Snapshot& snapshot, | 211 const base::debug::ThreadActivityAnalyzer::Snapshot& snapshot, |
| 357 ThreadState* thread_state) { | 212 ThreadState* thread_state) { |
| 358 DCHECK(thread_state); | 213 DCHECK(thread_state); |
| 359 | 214 |
| 360 thread_state->set_thread_name(snapshot.thread_name); | 215 thread_state->set_thread_name(snapshot.thread_name); |
| 361 thread_state->set_thread_id(snapshot.thread_id); | 216 thread_state->set_thread_id(snapshot.thread_id); |
| 362 thread_state->set_activity_count(snapshot.activity_stack_depth); | 217 thread_state->set_activity_count(snapshot.activity_stack_depth); |
| 363 | 218 |
| 364 for (size_t i = 0; i < snapshot.activity_stack.size(); ++i) { | 219 for (size_t i = 0; i < snapshot.activity_stack.size(); ++i) { |
| 365 const base::debug::Activity& recorded = snapshot.activity_stack[i]; | 220 const base::debug::Activity& recorded = snapshot.activity_stack[i]; |
| (...skipping 27 matching lines...) Expand all Loading... |
| 393 } | 248 } |
| 394 | 249 |
| 395 // Collect user data | 250 // Collect user data |
| 396 if (i < snapshot.user_data_stack.size()) { | 251 if (i < snapshot.user_data_stack.size()) { |
| 397 CollectUserData(snapshot.user_data_stack[i], | 252 CollectUserData(snapshot.user_data_stack[i], |
| 398 collected->mutable_user_data(), nullptr); | 253 collected->mutable_user_data(), nullptr); |
| 399 } | 254 } |
| 400 } | 255 } |
| 401 } | 256 } |
| 402 | 257 |
| 403 bool PostmortemReportCollector::WriteReportToMinidump( | 258 } // namespace impl |
| 404 StabilityReport* report, | |
| 405 const crashpad::UUID& client_id, | |
| 406 const crashpad::UUID& report_id, | |
| 407 base::PlatformFile minidump_file) { | |
| 408 DCHECK(report); | |
| 409 | |
| 410 return WritePostmortemDump(minidump_file, client_id, report_id, report); | |
| 411 } | |
| 412 | |
| 413 } // namespace browser_watcher | 259 } // namespace browser_watcher |
| OLD | NEW |