Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1764)

Unified Diff: tracing/tracing/extras/importer/android/event_log_importer.html

Issue 1319013002: Android event log importer. (Closed) Base URL: https://github.com/catapult-project/catapult.git@master
Patch Set: Removed console.log Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tracing/trace_viewer.gypi ('k') | tracing/tracing/extras/importer/linux_perf/ftrace_importer.html » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tracing/tracing/extras/importer/android/event_log_importer.html
diff --git a/tracing/tracing/extras/importer/android/event_log_importer.html b/tracing/tracing/extras/importer/android/event_log_importer.html
new file mode 100644
index 0000000000000000000000000000000000000000..2c06113243280f69b940ac53bf42bce6afc9a43f
--- /dev/null
+++ b/tracing/tracing/extras/importer/android/event_log_importer.html
@@ -0,0 +1,313 @@
+<!DOCTYPE html>
+<!--
+Copyright (c) 2015 The Chromium Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<link rel="import" href="/tracing/importer/importer.html">
+<link rel="import" href="/tracing/importer/simple_line_reader.html">
+<link rel="import" href="/tracing/model/activity.html">
+<link rel="import" href="/tracing/model/model.html">
+
+
+<script>
+/**
+ * @fileoverview Imports android event log data into the trace model.
+ * Android event log data contains information about activities that
+ * are launched/paused, processes that are started, memory usage, etc.
+ *
+ * The current implementation only parses activity events, with the goal of
+ * determining which Activity is running in the foreground for a process.
+ *
+ * This importer assumes the events arrive as a string. The unit tests provide
+ * examples of the trace format.
+ */
+'use strict';
+
+tr.exportTo('tr.e.importer.android', function() {
+ var Importer = tr.importer.Importer;
+
+ var ACTIVITY_STATE = {
+ NONE: 'none',
+ CREATED: 'created',
+ STARTED: 'started',
+ RESUMED: 'resumed',
+ PAUSED: 'paused',
+ STOPPED: 'stopped',
+ DESTROYED: 'destroyed'
+ };
+
+ var activityMap = {};
+
+ /**
+ * Imports android event log data (adb logcat -b events)
+ * @constructor
+ */
+ function EventLogImporter(model, events) {
+ this.model_ = model;
+ this.events_ = events;
+ this.importPriority = 3;
+ }
+
+ // Generic format of event log entries.
+ // Sample event log entry that this matches (split over 2 lines):
+ // 08-11 13:12:31.405 880 2645 I am_focused_activity: [0,com.google.android.googlequicksearchbox/com.google.android.launcher.GEL] // @suppress longLineCheck
+ var eventLogActivityRE = new RegExp(
+ '(\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d+)' +
+ '\\s+(\\d+)\\s+(\\d+)\\s+([A-Z])\\s*' +
+ '(am_\\w+)\\s*:(.*)');
+
+ // 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
+ // Store the name of the created activity only
+ var amCreateRE = new RegExp('\s*\\[.*,.*,.*,(.*),.*,.*,.*,.*\\]');
+
+ // 07-22 12:22:19.504 920 2504 I am_focused_activity: [0,com.android.systemui/.recents.RecentsActivity] // @suppress longLineCheck
+ //Store the name of the focused activity only
+ var amFocusedRE = new RegExp('\s*\\[\\d+,(.*)\\]');
+
+ // 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
+ // We care about proc starts on behalf of activities, and store the activity
+ var amProcStartRE = new RegExp('\s*\\[\\d+,\\d+,\\d+,.*,activity,(.*)\\]');
+
+ // 07-22 12:21:43.490 2893 2893 I am_on_resume_called: [0,com.google.android.launcher.GEL] // @suppress longLineCheck
+ // Store the activity name only
+ var amOnResumeRE = new RegExp('\s*\\[\\d+,(.*)\\]');
+
+ // 07-22 12:22:19.545 2893 2893 I am_on_paused_called: [0,com.google.android.launcher.GEL] // @suppress longLineCheck
+ // Store the activity name only
+ var amOnPauseRE = new RegExp('\s*\\[\\d+,(.*)\\]');
+
+ // 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
+ // Store the activity name and launch times
+ var amLaunchTimeRE = new RegExp('\s*\\[\\d+,\\d+,(.*),(\\d+),(\\d+)');
+
+ // 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
+ // Store the activity name only
+ var amDestroyRE = new RegExp('\s*\\[\\d+,\\d+,\\d+,(.*)\\]');
+
+ /**
+ * @return {boolean} True when events is an android event log array.
+ */
+ EventLogImporter.canImport = function(events) {
+ if (!(typeof(events) === 'string' || events instanceof String))
+ return false;
+
+ return eventLogActivityRE.test(events);
+ };
+
+ EventLogImporter.prototype = {
+ __proto__: Importer.prototype,
+
+ get model() {
+ return this.model_;
+ },
+
+ /**
+ * @return {string} the full activity name (including package) from
+ * a component
+ */
+ getFullActivityName: function(component) {
+ var componentSplit = component.split('/');
+ if (componentSplit[1].startsWith('.'))
+ return componentSplit[0] + componentSplit[1];
+
+ return componentSplit[1];
+ },
+
+ /**
+ * @return {string} the process name of a component
+ */
+ getProcName: function(component) {
+ var componentSplit = component.split('/');
+ return componentSplit[0];
+ },
+
+ findOrCreateActivity: function(activityName) {
+ if (activityName in activityMap)
+ return activityMap[activityName];
+ var activity = {
+ state: ACTIVITY_STATE.NONE,
+ name: activityName
+ };
+ activityMap[activityName] = activity;
+ return activity;
+ },
+
+ deleteActivity: function(activityName) {
+ delete activityMap[activityName];
+ },
+
+ handleCreateActivity: function(ts, activityName) {
+ var activity = this.findOrCreateActivity(activityName);
+ activity.state = ACTIVITY_STATE.CREATED;
+ activity.createdTs = ts;
+ },
+
+ handleFocusActivity: function(ts, procName, activityName) {
+ var activity = this.findOrCreateActivity(activityName);
+ activity.lastFocusedTs = ts;
+ },
+
+ handleProcStartForActivity: function(ts, activityName) {
+ var activity = this.findOrCreateActivity(activityName);
+ activity.procStartTs = ts;
+ },
+
+ handleOnResumeCalled: function(ts, pid, activityName) {
+ var activity = this.findOrCreateActivity(activityName);
+ activity.state = ACTIVITY_STATE.RESUMED;
+ activity.lastResumeTs = ts;
+ // on_resume_called shows the actual PID; use this
+ // to link the activity up with a process later
+ activity.pid = pid;
+ },
+
+ handleOnPauseCalled: function(ts, activityName) {
+ var activity = this.findOrCreateActivity(activityName);
+ activity.state = ACTIVITY_STATE.PAUSED;
+ activity.lastPauseTs = ts;
+ // Create a new AndroidActivity representing the foreground state,
+ // but only if the pause happened within the model bounds
+ if (ts > this.model_.bounds.min && ts < this.model_.bounds.max)
+ this.addActivityToProcess(activity);
+ },
+
+ handleLaunchTime: function(ts, activityName, launchTime) {
+ var activity = this.findOrCreateActivity(activityName);
+ activity.launchTime = launchTime;
+ },
+
+ handleDestroyActivity: function(ts, activityName) {
+ this.deleteActivity(activityName);
+ },
+
+ addActivityToProcess: function(activity) {
+ if (activity.pid === undefined)
+ return;
+ var process = this.model_.getOrCreateProcess(activity.pid);
+ // The range of the activity is the time from resume to time
+ // of pause; limit the start time to the beginning of the model
+ var range = tr.b.Range.fromExplicitRange(
+ Math.max(this.model_.bounds.min, activity.lastResumeTs),
+ activity.lastPauseTs);
+ var newActivity = new tr.model.Activity(activity.name,
+ 'Android Activity', range,
+ {created: activity.createdTs,
+ procstart: activity.procStartTs,
+ lastfocus: activity.lastFocusedTs});
+ process.activities.push(newActivity);
+ },
+
+ parseAmLine_: function(line) {
+ var match = eventLogActivityRE.exec(line);
+ if (!match)
+ return;
+
+ // Possible activity life-cycles:
+ // 1) Launch from scratch:
+ // - am_create_activity
+ // - am_focused_activity
+ // - am_proc_start
+ // - am_proc_bound
+ // - am_restart_activity
+ // - am_on_resume_called
+ // 2) Re-open existing activity
+ // - am_focused_activity
+ // - am_on_resume_called
+
+ // HACK: event log date format is "MM-DD" and doesn't contain the year;
+ // to figure out the year, take the min bound of the model, convert
+ // to real-time and use that as the year.
+ // The Android event log will eventually contain the year once this
+ // CL is in a release:
+ // https://android-review.googlesource.com/#/c/168900
+ var first_realtime_ts = this.model_.bounds.min -
+ this.model_.realtime_to_monotonic_offset_ms;
+ var year = new Date(first_realtime_ts).getFullYear();
+ var ts = match[1].substring(0, 5) + '-' + year + ' ' +
+ match[1].substring(5, match[1].length);
+
+ var monotonic_ts = Date.parse(ts) +
+ this.model_.realtime_to_monotonic_offset_ms;
+
+ var pid = match[2];
+ var action = match[5];
+ var data = match[6];
+
+ if (action === 'am_create_activity') {
+ match = amCreateRE.exec(data);
+ if (match && match.length >= 2) {
+ this.handleCreateActivity(monotonic_ts,
+ this.getFullActivityName(match[1]));
+ }
+ } else if (action === 'am_focused_activity') {
+ match = amFocusedRE.exec(data);
+ if (match && match.length >= 2) {
+ this.handleFocusActivity(monotonic_ts,
+ this.getProcName(match[1]), this.getFullActivityName(match[1]));
+ }
+ } else if (action === 'am_proc_start') {
+ match = amProcStartRE.exec(data);
+ if (match && match.length >= 2) {
+ this.handleProcStartForActivity(monotonic_ts,
+ this.getFullActivityName(match[1]));
+ }
+ } else if (action === 'am_on_resume_called') {
+ match = amOnResumeRE.exec(data);
+ if (match && match.length >= 2)
+ this.handleOnResumeCalled(monotonic_ts, pid, match[1]);
+ } else if (action === 'am_on_paused_called') {
+ match = amOnPauseRE.exec(data);
+ if (match && match.length >= 2)
+ this.handleOnPauseCalled(monotonic_ts, match[1]);
+ } else if (action === 'am_activity_launch_time') {
+ match = amLaunchTimeRE.exec(data);
+ this.handleLaunchTime(monotonic_ts,
+ this.getFullActivityName(match[1]), match[2]);
+ } else if (action === 'am_destroy_activity') {
+ match = amDestroyRE.exec(data);
+ if (match && match.length == 2) {
+ this.handleDestroyActivity(monotonic_ts,
+ this.getFullActivityName(match[1]));
+ }
+ }
+ },
+
+ importEvents: function(isSecondaryImport) {
+ // Check if we have a mapping from real-time to CLOCK_MONOTONIC
+ if (isNaN(this.model_.realtime_to_monotonic_offset_ms)) {
+ this.model_.importWarning({
+ type: 'eveng_log_clock_sync',
+ message: 'Need a trace_event_clock_sync to map realtime to import.'
+ });
+ return;
+ }
+ // Since the event log typically spans a much larger timeframe
+ // than the ftrace data, we want to calculate the bounds of the existing
+ // model, and dump all event log data outside of those bounds
+ this.model_.updateBounds();
+
+ var lines = this.events_.split('\n');
+ lines.forEach(this.parseAmLine_, this);
+
+ // Iterate over all created activities that are not destroyed yet
+ for (var activityName in activityMap) {
+ var activity = activityMap[activityName];
+ // If we're still in the foreground, store the activity anyway
+ if (activity.state == ACTIVITY_STATE.RESUMED) {
+ // Set the pause timestamp to the end of the model bounds
+ activity.lastPauseTs = this.model_.bounds.max;
+ this.addActivityToProcess(activity);
+ }
+ }
+ }
+ };
+
+ Importer.register(EventLogImporter);
+
+ return {
+ EventLogImporter: EventLogImporter
+ };
+});
+</script>
« no previous file with comments | « tracing/trace_viewer.gypi ('k') | tracing/tracing/extras/importer/linux_perf/ftrace_importer.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698