From 6d1228a2f3c2c48cae9926498043a87b21dfc3d7 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Fri, 20 Mar 2015 14:16:43 -0700 Subject: [PATCH] Enable tracing in SkyShell This CL teaches shelldb how to trace SkyShell. R=eseidel@chromium.org Review URL: https://codereview.chromium.org/1027903002 --- shell/BUILD.gn | 6 +- shell/library_loader.cc | 4 +- shell/org/domokit/sky/shell/SkyActivity.java | 2 + .../domokit/sky/shell/TracingController.java | 75 +++++++++++++++++++ shell/tracing_controller.cc | 70 +++++++++++++++++ shell/tracing_controller.h | 19 +++++ tools/shelldb | 55 +++++++++++++- 7 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 shell/org/domokit/sky/shell/TracingController.java create mode 100644 shell/tracing_controller.cc create mode 100644 shell/tracing_controller.h diff --git a/shell/BUILD.gn b/shell/BUILD.gn index d6e112f8d..2cc678f89 100644 --- a/shell/BUILD.gn +++ b/shell/BUILD.gn @@ -10,8 +10,9 @@ import("//build/config/android/rules.gni") generate_jni("jni_headers") { sources = [ "org/domokit/sky/shell/JavaServiceProvider.java", - "org/domokit/sky/shell/SkyMain.java", "org/domokit/sky/shell/PlatformView.java", + "org/domokit/sky/shell/SkyMain.java", + "org/domokit/sky/shell/TracingController.java", ] jni_package = "sky/shell" } @@ -35,6 +36,8 @@ shared_library("sky_shell") { "shell.h", "sky_main.cc", "sky_main.h", + "tracing_controller.cc", + "tracing_controller.h", "ui/animator.cc", "ui/animator.h", "ui/engine.cc", @@ -80,6 +83,7 @@ android_library("java") { "org/domokit/sky/shell/SkyActivity.java", "org/domokit/sky/shell/SkyApplication.java", "org/domokit/sky/shell/SkyMain.java", + "org/domokit/sky/shell/TracingController.java", ] deps = [ diff --git a/shell/library_loader.cc b/shell/library_loader.cc index dbe3b2a27..cd2ce139d 100644 --- a/shell/library_loader.cc +++ b/shell/library_loader.cc @@ -13,14 +13,16 @@ #include "sky/shell/java_service_provider.h" #include "sky/shell/platform_view.h" #include "sky/shell/sky_main.h" +#include "sky/shell/tracing_controller.h" namespace { base::android::RegistrationMethod kSkyRegisteredMethods[] = { {"CoreImpl", mojo::android::RegisterCoreImpl}, {"JavaServiceProvider", sky::shell::RegisterJavaServiceProvider}, - {"SkyMain", sky::shell::RegisterSkyMain}, {"PlatformView", sky::shell::PlatformView::Register}, + {"SkyMain", sky::shell::RegisterSkyMain}, + {"TracingController", sky::shell::RegisterTracingController}, }; bool RegisterJNI(JNIEnv* env) { diff --git a/shell/org/domokit/sky/shell/SkyActivity.java b/shell/org/domokit/sky/shell/SkyActivity.java index 28226391e..f73280f7b 100644 --- a/shell/org/domokit/sky/shell/SkyActivity.java +++ b/shell/org/domokit/sky/shell/SkyActivity.java @@ -14,6 +14,7 @@ import android.view.WindowManager; * Base class for activities that use Sky. */ public class SkyActivity extends Activity { + private TracingController mTracingController; private PlatformView mView; /** @@ -35,6 +36,7 @@ public class SkyActivity extends Activity { SkyMain.ensureInitialized(getApplicationContext()); mView = new PlatformView(this); setContentView(mView); + mTracingController = new TracingController(this); } public void loadUrl(String url) { diff --git a/shell/org/domokit/sky/shell/TracingController.java b/shell/org/domokit/sky/shell/TracingController.java new file mode 100644 index 000000000..716e0c007 --- /dev/null +++ b/shell/org/domokit/sky/shell/TracingController.java @@ -0,0 +1,75 @@ +// Copyright 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. + +package org.domokit.sky.shell; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Environment; +import android.util.Log; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import org.chromium.base.JNINamespace; + +/** + * A controller for the tracing system. + */ +@JNINamespace("sky::shell") +class TracingController { + private static final String TAG = "TracingController"; + private static final String TRACING_START = ".TRACING_START"; + private static final String TRACING_STOP = ".TRACING_STOP"; + + private final Context mContext; + private final TracingBroadcastReceiver mBroadcastReceiver; + private final TracingIntentFilter mIntentFilter; + + public TracingController(Context context) { + mContext = context; + mBroadcastReceiver = new TracingBroadcastReceiver(); + mIntentFilter = new TracingIntentFilter(context); + + mContext.registerReceiver(mBroadcastReceiver, mIntentFilter); + } + + private String generateTracingFilePath() { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HHmmss", Locale.US); + formatter.setTimeZone(TimeZone.getTimeZone("UTC")); + File dir = mContext.getCacheDir(); + String date = formatter.format(new Date()); + File file = new File(dir, "sky-trace-" + date + ".json"); + return file.getPath(); + } + + class TracingIntentFilter extends IntentFilter { + TracingIntentFilter(Context context) { + Log.e(TAG, context.getPackageName() + TRACING_START); + addAction(context.getPackageName() + TRACING_START); + addAction(context.getPackageName() + TRACING_STOP); + } + } + + class TracingBroadcastReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().endsWith(TRACING_START)) { + nativeStartTracing(); + } else if (intent.getAction().endsWith(TRACING_STOP)) { + nativeStopTracing(generateTracingFilePath()); + } else { + Log.e(TAG, "Unexpected intent: " + intent); + } + } + } + + private static native void nativeStartTracing(); + private static native void nativeStopTracing(String path); +} diff --git a/shell/tracing_controller.cc b/shell/tracing_controller.cc new file mode 100644 index 000000000..e22e1a386 --- /dev/null +++ b/shell/tracing_controller.cc @@ -0,0 +1,70 @@ +// Copyright 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. + +#include "sky/shell/tracing_controller.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/files/file_util.h" +#include "base/macros.h" +#include "base/trace_event/trace_event.h" +#include "jni/TracingController_jni.h" + +namespace sky { +namespace shell { +namespace { + +const char kStart[] = "{\"traceEvents\":["; +const char kEnd[] = "]}"; + +static FILE* g_file = NULL; + +void Write(const std::string& data) { + ignore_result(fwrite(data.data(), data.length(), 1, g_file)); +} + +void HandleChunk(const scoped_refptr& chunk, + bool has_more_events) { + Write(chunk->data()); + if (has_more_events) + Write(","); + + if (!has_more_events) { + Write(kEnd); + base::CloseFile(g_file); + g_file = NULL; + + LOG(INFO) << "Trace complete"; + } +} + +} // namespace + +static void StartTracing(JNIEnv* env, jclass clazz) { + LOG(INFO) << "Starting trace"; + + base::trace_event::TraceLog::GetInstance()->SetEnabled( + base::trace_event::CategoryFilter("*"), + base::trace_event::TraceLog::RECORDING_MODE, + base::trace_event::TraceOptions(base::trace_event::RECORD_UNTIL_FULL)); +} + +static void StopTracing(JNIEnv* env, jclass clazz, jstring path) { + base::trace_event::TraceLog::GetInstance()->SetDisabled(); + + base::FilePath file_path(base::android::ConvertJavaStringToUTF8(env, path)); + g_file = base::OpenFile(file_path, "w"); + CHECK(g_file) << "Failed to open file " << file_path.LossyDisplayName(); + + LOG(INFO) << "Saving trace to " << file_path.LossyDisplayName(); + Write(kStart); + base::trace_event::TraceLog::GetInstance()->Flush(base::Bind(&HandleChunk)); +} + +bool RegisterTracingController(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace shell +} // namespace sky diff --git a/shell/tracing_controller.h b/shell/tracing_controller.h new file mode 100644 index 000000000..4a3d3d561 --- /dev/null +++ b/shell/tracing_controller.h @@ -0,0 +1,19 @@ +// Copyright 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. + +#ifndef SKY_SHELL_TRACING_CONTROLLER_H_ +#define SKY_SHELL_TRACING_CONTROLLER_H_ + +#include "base/android/jni_weak_ref.h" +#include "base/android/scoped_java_ref.h" + +namespace sky { +namespace shell { + +bool RegisterTracingController(JNIEnv* env); + +} // namespace shell +} // namespace sky + +#endif // SKY_SHELL_TRACING_CONTROLLER_H_ diff --git a/tools/shelldb b/tools/shelldb index 087bb3988..b52bd27b5 100755 --- a/tools/shelldb +++ b/tools/shelldb @@ -8,8 +8,10 @@ import argparse import json import logging import os +import re import subprocess import sys +import time import urlparse SKY_TOOLS_DIR = os.path.dirname(os.path.abspath(__file__)) @@ -228,6 +230,49 @@ class Analyze(object): ] subprocess.call(analyzer_args) +class StartTracing(object): + def add_subparser(self, subparsers): + start_tracing_parser = subparsers.add_parser('start_tracing', + help=('start tracing a running sky instance')) + start_tracing_parser.set_defaults(func=self.run) + + def run(self, args, pids): + subprocess.check_output([ADB_PATH, 'shell', + 'am', 'broadcast', + '-a', 'org.domokit.sky.demo.TRACING_START']) + + +TRACE_COMPLETE_REGEXP = re.compile('Trace complete') +TRACE_FILE_REGEXP = re.compile(r'Saving trace to (?P\S+)') + +class StopTracing(object): + def add_subparser(self, subparsers): + stop_tracing_parser = subparsers.add_parser('stop_tracing', + help=('stop tracing a running sky instance')) + stop_tracing_parser.set_defaults(func=self.run) + + def run(self, args, pids): + subprocess.check_output([ADB_PATH, 'logcat', '-c']) + subprocess.check_output([ADB_PATH, 'shell', + 'am', 'broadcast', + '-a', 'org.domokit.sky.demo.TRACING_STOP']) + device_path = None + is_complete = False + while not is_complete: + time.sleep(0.2) + log = subprocess.check_output([ADB_PATH, 'logcat', '-d']) + if device_path is None: + result = TRACE_FILE_REGEXP.search(log) + if result: + device_path = result.group('path') + is_complete = TRACE_COMPLETE_REGEXP.search(log) is not None + + print 'Downloading trace %s ...' % os.path.basename(device_path) + + if device_path: + subprocess.check_output([ADB_PATH, 'pull', device_path]) + subprocess.check_output([ADB_PATH, 'shell', 'rm', device_path]) + class SkyShellRunner(object): def main(self): @@ -236,7 +281,15 @@ class SkyShellRunner(object): parser = argparse.ArgumentParser(description='Sky Shell Runner') subparsers = parser.add_subparsers(help='sub-command help') - for command in [StartSky(), StopSky(), Analyze()]: + commands = [ + StartSky(), + StopSky(), + Analyze(), + StartTracing(), + StopTracing(), + ] + + for command in commands: command.add_subparser(subparsers) args = parser.parse_args() -- GitLab