• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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