event_log_importer.html revision 8d2b206a675ec20ea07100c35df34e65ee1e45e8
1<!DOCTYPE html> 2<!-- 3Copyright (c) 2015 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="import" href="/tracing/importer/importer.html"> 9<link rel="import" href="/tracing/importer/simple_line_reader.html"> 10<link rel="import" href="/tracing/model/activity.html"> 11<link rel="import" href="/tracing/model/model.html"> 12 13 14<script> 15/** 16 * @fileoverview Imports android event log data into the trace model. 17 * Android event log data contains information about activities that 18 * are launched/paused, processes that are started, memory usage, etc. 19 * 20 * The current implementation only parses activity events, with the goal of 21 * determining which Activity is running in the foreground for a process. 22 * 23 * This importer assumes the events arrive as a string. The unit tests provide 24 * examples of the trace format. 25 */ 26'use strict'; 27 28tr.exportTo('tr.e.importer.android', function() { 29 var Importer = tr.importer.Importer; 30 31 var ACTIVITY_STATE = { 32 NONE: 'none', 33 CREATED: 'created', 34 STARTED: 'started', 35 RESUMED: 'resumed', 36 PAUSED: 'paused', 37 STOPPED: 'stopped', 38 DESTROYED: 'destroyed' 39 }; 40 41 var activityMap = {}; 42 43 /** 44 * Imports android event log data (adb logcat -b events) 45 * @constructor 46 */ 47 function EventLogImporter(model, events) { 48 this.model_ = model; 49 this.events_ = events; 50 this.importPriority = 3; 51 } 52 53 // Generic format of event log entries. 54 // Sample event log entry that this matches (split over 2 lines): 55 // 08-11 13:12:31.405 880 2645 I am_focused_activity: [0,com.google.android.googlequicksearchbox/com.google.android.launcher.GEL] // @suppress longLineCheck 56 var eventLogActivityRE = new RegExp( 57 '(\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d+)' + 58 '\\s+(\\d+)\\s+(\\d+)\\s+([A-Z])\\s*' + 59 '(am_\\w+)\\s*:(.*)'); 60 61 // 08-28 03:58:21.834 888 3177 I am_create_activity: [0,5972200,30,com.nxp.taginfolite/.activities.MainView,android.intent.action.MAIN,NULL,NULL,270532608] // @suppress longLineCheck 62 // Store the name of the created activity only 63 var amCreateRE = new RegExp('\s*\\[.*,.*,.*,(.*),.*,.*,.*,.*\\]'); 64 65 // 07-22 12:22:19.504 920 2504 I am_focused_activity: [0,com.android.systemui/.recents.RecentsActivity] // @suppress longLineCheck 66 //Store the name of the focused activity only 67 var amFocusedRE = new RegExp('\s*\\[\\d+,(.*)\\]'); 68 69 // 07-21 19:56:12.315 920 2261 I am_proc_start: [0,19942,10062,com.google.android.talk,broadcast,com.google.android.talk/com.google.android.apps.hangouts.realtimechat.RealTimeChatService$AlarmReceiver] // @suppress longLineCheck 70 // We care about proc starts on behalf of activities, and store the activity 71 var amProcStartRE = new RegExp('\s*\\[\\d+,\\d+,\\d+,.*,activity,(.*)\\]'); 72 73 // 07-22 12:21:43.490 2893 2893 I am_on_resume_called: [0,com.google.android.launcher.GEL] // @suppress longLineCheck 74 // Store the activity name only 75 var amOnResumeRE = new RegExp('\s*\\[\\d+,(.*)\\]'); 76 77 // 07-22 12:22:19.545 2893 2893 I am_on_paused_called: [0,com.google.android.launcher.GEL] // @suppress longLineCheck 78 // Store the activity name only 79 var amOnPauseRE = new RegExp('\s*\\[\\d+,(.*)\\]'); 80 81 // 08-28 03:51:54.456 888 907 I am_activity_launch_time: [0,185307115,com.google.android.googlequicksearchbox/com.google.android.launcher.GEL,1174,1174] // @suppress longLineCheck 82 // Store the activity name and launch times 83 var amLaunchTimeRE = new RegExp('\s*\\[\\d+,\\d+,(.*),(\\d+),(\\d+)'); 84 85 // 08-28 03:58:15.854 888 902 I am_destroy_activity: [0,203516597,29,com.android.chrome/com.google.android.apps.chrome.Main,finish-idle] // @suppress longLineCheck 86 // Store the activity name only 87 var amDestroyRE = new RegExp('\s*\\[\\d+,\\d+,\\d+,(.*)\\]'); 88 89 /** 90 * @return {boolean} True when events is an android event log array. 91 */ 92 EventLogImporter.canImport = function(events) { 93 if (!(typeof(events) === 'string' || events instanceof String)) 94 return false; 95 96 // Prevent the importer from matching this file in vulcanized traces. 97 if (/^<!DOCTYPE html>/.test(events)) 98 return false; 99 100 return eventLogActivityRE.test(events); 101 }; 102 103 EventLogImporter.prototype = { 104 __proto__: Importer.prototype, 105 106 get model() { 107 return this.model_; 108 }, 109 110 /** 111 * @return {string} the full activity name (including package) from 112 * a component 113 */ 114 getFullActivityName: function(component) { 115 var componentSplit = component.split('/'); 116 if (componentSplit[1].startsWith('.')) 117 return componentSplit[0] + componentSplit[1]; 118 119 return componentSplit[1]; 120 }, 121 122 /** 123 * @return {string} the process name of a component 124 */ 125 getProcName: function(component) { 126 var componentSplit = component.split('/'); 127 return componentSplit[0]; 128 }, 129 130 findOrCreateActivity: function(activityName) { 131 if (activityName in activityMap) 132 return activityMap[activityName]; 133 var activity = { 134 state: ACTIVITY_STATE.NONE, 135 name: activityName 136 }; 137 activityMap[activityName] = activity; 138 return activity; 139 }, 140 141 deleteActivity: function(activityName) { 142 delete activityMap[activityName]; 143 }, 144 145 handleCreateActivity: function(ts, activityName) { 146 var activity = this.findOrCreateActivity(activityName); 147 activity.state = ACTIVITY_STATE.CREATED; 148 activity.createdTs = ts; 149 }, 150 151 handleFocusActivity: function(ts, procName, activityName) { 152 var activity = this.findOrCreateActivity(activityName); 153 activity.lastFocusedTs = ts; 154 }, 155 156 handleProcStartForActivity: function(ts, activityName) { 157 var activity = this.findOrCreateActivity(activityName); 158 activity.procStartTs = ts; 159 }, 160 161 handleOnResumeCalled: function(ts, pid, activityName) { 162 var activity = this.findOrCreateActivity(activityName); 163 activity.state = ACTIVITY_STATE.RESUMED; 164 activity.lastResumeTs = ts; 165 // on_resume_called shows the actual PID; use this 166 // to link the activity up with a process later 167 activity.pid = pid; 168 }, 169 170 handleOnPauseCalled: function(ts, activityName) { 171 var activity = this.findOrCreateActivity(activityName); 172 activity.state = ACTIVITY_STATE.PAUSED; 173 activity.lastPauseTs = ts; 174 // Create a new AndroidActivity representing the foreground state, 175 // but only if the pause happened within the model bounds 176 if (ts > this.model_.bounds.min && ts < this.model_.bounds.max) 177 this.addActivityToProcess(activity); 178 }, 179 180 handleLaunchTime: function(ts, activityName, launchTime) { 181 var activity = this.findOrCreateActivity(activityName); 182 activity.launchTime = launchTime; 183 }, 184 185 handleDestroyActivity: function(ts, activityName) { 186 this.deleteActivity(activityName); 187 }, 188 189 addActivityToProcess: function(activity) { 190 if (activity.pid === undefined) 191 return; 192 var process = this.model_.getOrCreateProcess(activity.pid); 193 // The range of the activity is the time from resume to time 194 // of pause; limit the start time to the beginning of the model 195 var range = tr.b.Range.fromExplicitRange( 196 Math.max(this.model_.bounds.min, activity.lastResumeTs), 197 activity.lastPauseTs); 198 var newActivity = new tr.model.Activity(activity.name, 199 'Android Activity', range, 200 {created: activity.createdTs, 201 procstart: activity.procStartTs, 202 lastfocus: activity.lastFocusedTs}); 203 process.activities.push(newActivity); 204 }, 205 206 parseAmLine_: function(line) { 207 var match = eventLogActivityRE.exec(line); 208 if (!match) 209 return; 210 211 // Possible activity life-cycles: 212 // 1) Launch from scratch: 213 // - am_create_activity 214 // - am_focused_activity 215 // - am_proc_start 216 // - am_proc_bound 217 // - am_restart_activity 218 // - am_on_resume_called 219 // 2) Re-open existing activity 220 // - am_focused_activity 221 // - am_on_resume_called 222 223 // HACK: event log date format is "MM-DD" and doesn't contain the year; 224 // to figure out the year, take the min bound of the model, convert 225 // to real-time and use that as the year. 226 // The Android event log will eventually contain the year once this 227 // CL is in a release: 228 // https://android-review.googlesource.com/#/c/168900 229 var first_realtime_ts = this.model_.bounds.min - 230 this.model_.realtime_to_monotonic_offset_ms; 231 var year = new Date(first_realtime_ts).getFullYear(); 232 var ts = match[1].substring(0, 5) + '-' + year + ' ' + 233 match[1].substring(5, match[1].length); 234 235 var monotonic_ts = Date.parse(ts) + 236 this.model_.realtime_to_monotonic_offset_ms; 237 238 var pid = match[2]; 239 var action = match[5]; 240 var data = match[6]; 241 242 if (action === 'am_create_activity') { 243 match = amCreateRE.exec(data); 244 if (match && match.length >= 2) { 245 this.handleCreateActivity(monotonic_ts, 246 this.getFullActivityName(match[1])); 247 } 248 } else if (action === 'am_focused_activity') { 249 match = amFocusedRE.exec(data); 250 if (match && match.length >= 2) { 251 this.handleFocusActivity(monotonic_ts, 252 this.getProcName(match[1]), this.getFullActivityName(match[1])); 253 } 254 } else if (action === 'am_proc_start') { 255 match = amProcStartRE.exec(data); 256 if (match && match.length >= 2) { 257 this.handleProcStartForActivity(monotonic_ts, 258 this.getFullActivityName(match[1])); 259 } 260 } else if (action === 'am_on_resume_called') { 261 match = amOnResumeRE.exec(data); 262 if (match && match.length >= 2) 263 this.handleOnResumeCalled(monotonic_ts, pid, match[1]); 264 } else if (action === 'am_on_paused_called') { 265 match = amOnPauseRE.exec(data); 266 if (match && match.length >= 2) 267 this.handleOnPauseCalled(monotonic_ts, match[1]); 268 } else if (action === 'am_activity_launch_time') { 269 match = amLaunchTimeRE.exec(data); 270 this.handleLaunchTime(monotonic_ts, 271 this.getFullActivityName(match[1]), match[2]); 272 } else if (action === 'am_destroy_activity') { 273 match = amDestroyRE.exec(data); 274 if (match && match.length == 2) { 275 this.handleDestroyActivity(monotonic_ts, 276 this.getFullActivityName(match[1])); 277 } 278 } 279 }, 280 281 importEvents: function(isSecondaryImport) { 282 // Check if we have a mapping from real-time to CLOCK_MONOTONIC 283 if (isNaN(this.model_.realtime_to_monotonic_offset_ms)) { 284 this.model_.importWarning({ 285 type: 'eveng_log_clock_sync', 286 message: 'Need a trace_event_clock_sync to map realtime to import.' 287 }); 288 return; 289 } 290 // Since the event log typically spans a much larger timeframe 291 // than the ftrace data, we want to calculate the bounds of the existing 292 // model, and dump all event log data outside of those bounds 293 this.model_.updateBounds(); 294 295 var lines = this.events_.split('\n'); 296 lines.forEach(this.parseAmLine_, this); 297 298 // Iterate over all created activities that are not destroyed yet 299 for (var activityName in activityMap) { 300 var activity = activityMap[activityName]; 301 // If we're still in the foreground, store the activity anyway 302 if (activity.state == ACTIVITY_STATE.RESUMED) { 303 // Set the pause timestamp to the end of the model bounds 304 activity.lastPauseTs = this.model_.bounds.max; 305 this.addActivityToProcess(activity); 306 } 307 } 308 } 309 }; 310 311 Importer.register(EventLogImporter); 312 313 return { 314 EventLogImporter: EventLogImporter 315 }; 316}); 317</script> 318