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 importerName() { 107 return 'EventLogImporter'; 108 }, 109 110 get model() { 111 return this.model_; 112 }, 113 114 /** 115 * @return {string} the full activity name (including package) from 116 * a component 117 */ 118 getFullActivityName: function(component) { 119 var componentSplit = component.split('/'); 120 if (componentSplit[1].startsWith('.')) 121 return componentSplit[0] + componentSplit[1]; 122 123 return componentSplit[1]; 124 }, 125 126 /** 127 * @return {string} the process name of a component 128 */ 129 getProcName: function(component) { 130 var componentSplit = component.split('/'); 131 return componentSplit[0]; 132 }, 133 134 findOrCreateActivity: function(activityName) { 135 if (activityName in activityMap) 136 return activityMap[activityName]; 137 var activity = { 138 state: ACTIVITY_STATE.NONE, 139 name: activityName 140 }; 141 activityMap[activityName] = activity; 142 return activity; 143 }, 144 145 deleteActivity: function(activityName) { 146 delete activityMap[activityName]; 147 }, 148 149 handleCreateActivity: function(ts, activityName) { 150 var activity = this.findOrCreateActivity(activityName); 151 activity.state = ACTIVITY_STATE.CREATED; 152 activity.createdTs = ts; 153 }, 154 155 handleFocusActivity: function(ts, procName, activityName) { 156 var activity = this.findOrCreateActivity(activityName); 157 activity.lastFocusedTs = ts; 158 }, 159 160 handleProcStartForActivity: function(ts, activityName) { 161 var activity = this.findOrCreateActivity(activityName); 162 activity.procStartTs = ts; 163 }, 164 165 handleOnResumeCalled: function(ts, pid, activityName) { 166 var activity = this.findOrCreateActivity(activityName); 167 activity.state = ACTIVITY_STATE.RESUMED; 168 activity.lastResumeTs = ts; 169 // on_resume_called shows the actual PID; use this 170 // to link the activity up with a process later 171 activity.pid = pid; 172 }, 173 174 handleOnPauseCalled: function(ts, activityName) { 175 var activity = this.findOrCreateActivity(activityName); 176 activity.state = ACTIVITY_STATE.PAUSED; 177 activity.lastPauseTs = ts; 178 // Create a new AndroidActivity representing the foreground state, 179 // but only if the pause happened within the model bounds 180 if (ts > this.model_.bounds.min && ts < this.model_.bounds.max) 181 this.addActivityToProcess(activity); 182 }, 183 184 handleLaunchTime: function(ts, activityName, launchTime) { 185 var activity = this.findOrCreateActivity(activityName); 186 activity.launchTime = launchTime; 187 }, 188 189 handleDestroyActivity: function(ts, activityName) { 190 this.deleteActivity(activityName); 191 }, 192 193 addActivityToProcess: function(activity) { 194 if (activity.pid === undefined) 195 return; 196 var process = this.model_.getOrCreateProcess(activity.pid); 197 // The range of the activity is the time from resume to time 198 // of pause; limit the start time to the beginning of the model 199 var range = tr.b.Range.fromExplicitRange( 200 Math.max(this.model_.bounds.min, activity.lastResumeTs), 201 activity.lastPauseTs); 202 var newActivity = new tr.model.Activity(activity.name, 203 'Android Activity', range, 204 {created: activity.createdTs, 205 procstart: activity.procStartTs, 206 lastfocus: activity.lastFocusedTs}); 207 process.activities.push(newActivity); 208 }, 209 210 parseAmLine_: function(line) { 211 var match = eventLogActivityRE.exec(line); 212 if (!match) 213 return; 214 215 // Possible activity life-cycles: 216 // 1) Launch from scratch: 217 // - am_create_activity 218 // - am_focused_activity 219 // - am_proc_start 220 // - am_proc_bound 221 // - am_restart_activity 222 // - am_on_resume_called 223 // 2) Re-open existing activity 224 // - am_focused_activity 225 // - am_on_resume_called 226 227 // HACK: event log date format is "MM-DD" and doesn't contain the year; 228 // to figure out the year, take the min bound of the model, convert 229 // to real-time and use that as the year. 230 // The Android event log will eventually contain the year once this 231 // CL is in a release: 232 // https://android-review.googlesource.com/#/c/168900 233 var first_realtime_ts = this.model_.bounds.min - 234 this.model_.realtime_to_monotonic_offset_ms; 235 var year = new Date(first_realtime_ts).getFullYear(); 236 var ts = match[1].substring(0, 5) + '-' + year + ' ' + 237 match[1].substring(5, match[1].length); 238 239 var monotonic_ts = Date.parse(ts) + 240 this.model_.realtime_to_monotonic_offset_ms; 241 242 var pid = match[2]; 243 var action = match[5]; 244 var data = match[6]; 245 246 if (action === 'am_create_activity') { 247 match = amCreateRE.exec(data); 248 if (match && match.length >= 2) { 249 this.handleCreateActivity(monotonic_ts, 250 this.getFullActivityName(match[1])); 251 } 252 } else if (action === 'am_focused_activity') { 253 match = amFocusedRE.exec(data); 254 if (match && match.length >= 2) { 255 this.handleFocusActivity(monotonic_ts, 256 this.getProcName(match[1]), this.getFullActivityName(match[1])); 257 } 258 } else if (action === 'am_proc_start') { 259 match = amProcStartRE.exec(data); 260 if (match && match.length >= 2) { 261 this.handleProcStartForActivity(monotonic_ts, 262 this.getFullActivityName(match[1])); 263 } 264 } else if (action === 'am_on_resume_called') { 265 match = amOnResumeRE.exec(data); 266 if (match && match.length >= 2) 267 this.handleOnResumeCalled(monotonic_ts, pid, match[1]); 268 } else if (action === 'am_on_paused_called') { 269 match = amOnPauseRE.exec(data); 270 if (match && match.length >= 2) 271 this.handleOnPauseCalled(monotonic_ts, match[1]); 272 } else if (action === 'am_activity_launch_time') { 273 match = amLaunchTimeRE.exec(data); 274 this.handleLaunchTime(monotonic_ts, 275 this.getFullActivityName(match[1]), match[2]); 276 } else if (action === 'am_destroy_activity') { 277 match = amDestroyRE.exec(data); 278 if (match && match.length == 2) { 279 this.handleDestroyActivity(monotonic_ts, 280 this.getFullActivityName(match[1])); 281 } 282 } 283 }, 284 285 importEvents: function() { 286 // Check if we have a mapping from real-time to CLOCK_MONOTONIC 287 if (isNaN(this.model_.realtime_to_monotonic_offset_ms)) { 288 this.model_.importWarning({ 289 type: 'eveng_log_clock_sync', 290 message: 'Need a trace_event_clock_sync to map realtime to import.' 291 }); 292 return; 293 } 294 // Since the event log typically spans a much larger timeframe 295 // than the ftrace data, we want to calculate the bounds of the existing 296 // model, and dump all event log data outside of those bounds 297 this.model_.updateBounds(); 298 299 var lines = this.events_.split('\n'); 300 lines.forEach(this.parseAmLine_, this); 301 302 // Iterate over all created activities that are not destroyed yet 303 for (var activityName in activityMap) { 304 var activity = activityMap[activityName]; 305 // If we're still in the foreground, store the activity anyway 306 if (activity.state == ACTIVITY_STATE.RESUMED) { 307 // Set the pause timestamp to the end of the model bounds 308 activity.lastPauseTs = this.model_.bounds.max; 309 this.addActivityToProcess(activity); 310 } 311 } 312 } 313 }; 314 315 Importer.register(EventLogImporter); 316 317 return { 318 EventLogImporter: EventLogImporter 319 }; 320}); 321</script> 322