OLD | NEW |
| (Empty) |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 #include "components/browser_watcher/postmortem_report_extractor.h" | |
6 | |
7 #include <memory> | |
8 #include <string> | |
9 #include <utility> | |
10 #include <vector> | |
11 | |
12 #include "base/debug/activity_analyzer.h" | |
13 #include "base/logging.h" | |
14 #include "base/metrics/histogram_macros.h" | |
15 #include "base/strings/string_piece.h" | |
16 #include "base/strings/string_util.h" | |
17 #include "base/strings/stringprintf.h" | |
18 #include "base/strings/utf_string_conversions.h" | |
19 #include "components/browser_watcher/stability_data_names.h" | |
20 #include "components/variations/active_field_trials.h" | |
21 #include "third_party/crashpad/crashpad/util/misc/uuid.h" | |
22 | |
23 namespace browser_watcher { | |
24 | |
25 using ActivitySnapshot = base::debug::ThreadActivityAnalyzer::Snapshot; | |
26 using base::debug::ActivityUserData; | |
27 using base::debug::GlobalActivityAnalyzer; | |
28 using base::debug::GlobalActivityTracker; | |
29 using base::debug::ThreadActivityAnalyzer; | |
30 | |
31 namespace { | |
32 | |
33 const char kFieldTrialKeyPrefix[] = "FieldTrial."; | |
34 | |
35 // Collects stability user data from the recorded format to the collected | |
36 // format. | |
37 void CollectUserData( | |
38 const ActivityUserData::Snapshot& recorded_map, | |
39 google::protobuf::Map<std::string, TypedValue>* collected_map, | |
40 StabilityReport* report) { | |
41 DCHECK(collected_map); | |
42 | |
43 for (const auto& name_and_value : recorded_map) { | |
44 const std::string& key = name_and_value.first; | |
45 const ActivityUserData::TypedValue& recorded_value = name_and_value.second; | |
46 TypedValue collected_value; | |
47 | |
48 switch (recorded_value.type()) { | |
49 case ActivityUserData::END_OF_VALUES: | |
50 NOTREACHED(); | |
51 break; | |
52 case ActivityUserData::RAW_VALUE: { | |
53 base::StringPiece raw = recorded_value.Get(); | |
54 collected_value.set_bytes_value(raw.data(), raw.size()); | |
55 break; | |
56 } | |
57 case ActivityUserData::RAW_VALUE_REFERENCE: { | |
58 base::StringPiece recorded_ref = recorded_value.GetReference(); | |
59 TypedValue::Reference* collected_ref = | |
60 collected_value.mutable_bytes_reference(); | |
61 collected_ref->set_address( | |
62 reinterpret_cast<uintptr_t>(recorded_ref.data())); | |
63 collected_ref->set_size(recorded_ref.size()); | |
64 break; | |
65 } | |
66 case ActivityUserData::STRING_VALUE: { | |
67 base::StringPiece value = recorded_value.GetString(); | |
68 | |
69 if (report && base::StartsWith(key, kFieldTrialKeyPrefix, | |
70 base::CompareCase::SENSITIVE)) { | |
71 // This entry represents an active Field Trial. | |
72 std::string trial_name = | |
73 key.substr(std::strlen(kFieldTrialKeyPrefix)); | |
74 variations::ActiveGroupId group_id = | |
75 variations::MakeActiveGroupId(trial_name, value.as_string()); | |
76 FieldTrial* field_trial = report->add_field_trials(); | |
77 field_trial->set_name_id(group_id.name); | |
78 field_trial->set_group_id(group_id.group); | |
79 continue; | |
80 } | |
81 | |
82 collected_value.set_string_value(value.data(), value.size()); | |
83 break; | |
84 } | |
85 case ActivityUserData::STRING_VALUE_REFERENCE: { | |
86 base::StringPiece recorded_ref = recorded_value.GetStringReference(); | |
87 TypedValue::Reference* collected_ref = | |
88 collected_value.mutable_string_reference(); | |
89 collected_ref->set_address( | |
90 reinterpret_cast<uintptr_t>(recorded_ref.data())); | |
91 collected_ref->set_size(recorded_ref.size()); | |
92 break; | |
93 } | |
94 case ActivityUserData::CHAR_VALUE: { | |
95 char char_value = recorded_value.GetChar(); | |
96 collected_value.set_char_value(&char_value, 1); | |
97 break; | |
98 } | |
99 case ActivityUserData::BOOL_VALUE: | |
100 collected_value.set_bool_value(recorded_value.GetBool()); | |
101 break; | |
102 case ActivityUserData::SIGNED_VALUE: | |
103 collected_value.set_signed_value(recorded_value.GetInt()); | |
104 break; | |
105 case ActivityUserData::UNSIGNED_VALUE: | |
106 collected_value.set_unsigned_value(recorded_value.GetUint()); | |
107 break; | |
108 } | |
109 | |
110 (*collected_map)[key].Swap(&collected_value); | |
111 } | |
112 } | |
113 | |
114 void CollectModuleInformation( | |
115 const std::vector<GlobalActivityTracker::ModuleInfo>& modules, | |
116 ProcessState* process_state) { | |
117 DCHECK(process_state); | |
118 | |
119 for (const GlobalActivityTracker::ModuleInfo& recorded : modules) { | |
120 CodeModule* collected = process_state->add_modules(); | |
121 collected->set_base_address(recorded.address); | |
122 collected->set_size(recorded.size); | |
123 collected->set_code_file(recorded.file); | |
124 | |
125 // Compute the code identifier using the required format. | |
126 std::string code_identifier = | |
127 base::StringPrintf("%08X%zx", recorded.timestamp, recorded.size); | |
128 collected->set_code_identifier(code_identifier); | |
129 collected->set_debug_file(recorded.debug_file); | |
130 | |
131 // Compute the debug identifier using the required format. | |
132 const crashpad::UUID* uuid = | |
133 reinterpret_cast<const crashpad::UUID*>(recorded.identifier); | |
134 std::string debug_identifier = base::StringPrintf( | |
135 "%08X%04X%04X%02X%02X%02X%02X%02X%02X%02X%02X%x", uuid->data_1, | |
136 uuid->data_2, uuid->data_3, uuid->data_4[0], uuid->data_4[1], | |
137 uuid->data_5[0], uuid->data_5[1], uuid->data_5[2], uuid->data_5[3], | |
138 uuid->data_5[4], uuid->data_5[5], recorded.age); | |
139 collected->set_debug_identifier(debug_identifier); | |
140 collected->set_is_unloaded(!recorded.is_loaded); | |
141 } | |
142 } | |
143 | |
144 void CollectActivity(const base::debug::Activity& recorded, | |
145 Activity* collected) { | |
146 DCHECK(collected); | |
147 | |
148 collected->set_time(recorded.time_internal); | |
149 collected->set_address(recorded.calling_address); | |
150 collected->set_origin_address(recorded.origin_address); | |
151 | |
152 // TODO(manzagop): the current collection deals with specific activity types; | |
153 // consider modifying it to handle the notion of activity categories. | |
154 switch (recorded.activity_type) { | |
155 case base::debug::Activity::ACT_TASK_RUN: | |
156 collected->set_type(Activity::ACT_TASK_RUN); | |
157 collected->set_task_sequence_id(recorded.data.task.sequence_id); | |
158 break; | |
159 case base::debug::Activity::ACT_LOCK_ACQUIRE: | |
160 collected->set_type(Activity::ACT_LOCK_ACQUIRE); | |
161 collected->set_lock_address(recorded.data.lock.lock_address); | |
162 break; | |
163 case base::debug::Activity::ACT_EVENT_WAIT: | |
164 collected->set_type(Activity::ACT_EVENT_WAIT); | |
165 collected->set_event_address(recorded.data.event.event_address); | |
166 break; | |
167 case base::debug::Activity::ACT_THREAD_JOIN: | |
168 collected->set_type(Activity::ACT_THREAD_JOIN); | |
169 collected->set_thread_id(recorded.data.thread.thread_id); | |
170 break; | |
171 case base::debug::Activity::ACT_PROCESS_WAIT: | |
172 collected->set_type(Activity::ACT_PROCESS_WAIT); | |
173 collected->set_process_id(recorded.data.process.process_id); | |
174 break; | |
175 case base::debug::Activity::ACT_GENERIC: | |
176 collected->set_type(Activity::ACT_GENERIC); | |
177 collected->set_generic_id(recorded.data.generic.id); | |
178 collected->set_generic_data(recorded.data.generic.info); | |
179 break; | |
180 default: | |
181 break; | |
182 } | |
183 } | |
184 | |
185 void CollectThread( | |
186 const base::debug::ThreadActivityAnalyzer::Snapshot& snapshot, | |
187 ThreadState* thread_state) { | |
188 DCHECK(thread_state); | |
189 | |
190 thread_state->set_thread_name(snapshot.thread_name); | |
191 thread_state->set_thread_id(snapshot.thread_id); | |
192 thread_state->set_activity_count(snapshot.activity_stack_depth); | |
193 | |
194 for (size_t i = 0; i < snapshot.activity_stack.size(); ++i) { | |
195 Activity* collected = thread_state->add_activities(); | |
196 | |
197 CollectActivity(snapshot.activity_stack[i], collected); | |
198 if (i < snapshot.user_data_stack.size()) { | |
199 CollectUserData(snapshot.user_data_stack[i], | |
200 collected->mutable_user_data(), nullptr); | |
201 } | |
202 } | |
203 } | |
204 | |
205 } // namespace | |
206 | |
207 CollectionStatus Extract(const base::FilePath& stability_file, | |
208 StabilityReport* report) { | |
209 DCHECK(report); | |
210 | |
211 // Create a global analyzer. | |
212 std::unique_ptr<GlobalActivityAnalyzer> global_analyzer = | |
213 GlobalActivityAnalyzer::CreateWithFile(stability_file); | |
214 if (!global_analyzer) | |
215 return ANALYZER_CREATION_FAILED; | |
216 | |
217 // Extract data for only the first process. | |
218 // TODO(manzagop): Extend this to all processes. | |
219 int64_t pid = global_analyzer->GetFirstProcess(); | |
220 | |
221 // Early exit if there is no data. | |
222 std::vector<std::string> log_messages = global_analyzer->GetLogMessages(); | |
223 ActivityUserData::Snapshot process_data_snapshot = | |
224 global_analyzer->GetProcessDataSnapshot(pid); | |
225 | |
226 ThreadActivityAnalyzer* thread_analyzer = | |
227 global_analyzer->GetFirstAnalyzer(pid); | |
228 if (log_messages.empty() && process_data_snapshot.empty() && | |
229 !thread_analyzer) { | |
230 return DEBUG_FILE_NO_DATA; | |
231 } | |
232 | |
233 report->set_is_complete(global_analyzer->IsDataComplete()); | |
234 | |
235 // Collect log messages. | |
236 for (const std::string& message : log_messages) { | |
237 report->add_log_messages(message); | |
238 } | |
239 | |
240 // Collect global user data. | |
241 google::protobuf::Map<std::string, TypedValue>& global_data = | |
242 *(report->mutable_global_data()); | |
243 CollectUserData(process_data_snapshot, &global_data, report); | |
244 | |
245 // Collect thread activity data. | |
246 // Note: a single process is instrumented. | |
247 ProcessState* process_state = report->add_process_states(); | |
248 for (; thread_analyzer != nullptr; | |
249 thread_analyzer = global_analyzer->GetNextAnalyzer()) { | |
250 // Only valid analyzers are expected per contract of GetFirstAnalyzer / | |
251 // GetNextAnalyzer. | |
252 DCHECK(thread_analyzer->IsValid()); | |
253 | |
254 if (!process_state->has_process_id()) { | |
255 process_state->set_process_id( | |
256 thread_analyzer->activity_snapshot().process_id); | |
257 } | |
258 DCHECK_EQ(thread_analyzer->activity_snapshot().process_id, | |
259 process_state->process_id()); | |
260 | |
261 ThreadState* thread_state = process_state->add_threads(); | |
262 CollectThread(thread_analyzer->activity_snapshot(), thread_state); | |
263 } | |
264 | |
265 // Collect module information. | |
266 CollectModuleInformation(global_analyzer->GetModules(), process_state); | |
267 | |
268 return SUCCESS; | |
269 } | |
270 | |
271 } // namespace browser_watcher | |
OLD | NEW |