├── .gitignore ├── LICENSE ├── README.md ├── WatcherDemo.iml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── img ├── device-2016-01-27-190400.png └── device-2016-01-27-190507.png ├── settings.gradle └── tracetohtml ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── src ├── all │ └── jni │ │ ├── Profile.h │ │ └── TraceDump.c └── main │ ├── AndroidManifest.xml │ ├── java │ └── dodola │ │ ├── traceutil │ │ ├── MainActivity.java │ │ └── WebViewActivity.java │ │ └── watcher │ │ └── utils │ │ └── TraceUtils.java │ └── res │ ├── layout │ ├── activity_anr_test.xml │ ├── activity_main.xml │ ├── activity_scroll.xml │ └── activity_web_view.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── tracetohtml.iml /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | 28 | # Android Studio Navigation editor temp files 29 | .navigation/ 30 | 31 | # Android Studio captures folder 32 | captures/ 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 dodola 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TraceToHtml 2 | Convert debug trace file to html on Android device 3 | 4 | 这是一个转换debug的trace 文件位html的工具。 5 | 6 | 是个挺鸡肋的工具。。 7 | 8 | 完全使用 dmtracedump 的源码开发 9 | 10 | 使用Android Studio 2.0 Preview 7 NDK 编译 11 | 12 | 可以参照一下Android Studio 的NDK开发,gradle用的都是最新的 13 | 14 | ##screenshot 15 | ![](img/device-2016-01-27-190400.png) 16 | ![](img/device-2016-01-27-190507.png) 17 | 18 | -------------------------------------------------------------------------------- /WatcherDemo.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle-experimental:0.6.0-alpha7' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dodola/TraceToHtml/391cde2a63219481c5770688c74eca7b61746825/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jan 26 13:16:32 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /img/device-2016-01-27-190400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dodola/TraceToHtml/391cde2a63219481c5770688c74eca7b61746825/img/device-2016-01-27-190400.png -------------------------------------------------------------------------------- /img/device-2016-01-27-190507.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dodola/TraceToHtml/391cde2a63219481c5770688c74eca7b61746825/img/device-2016-01-27-190507.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Baidu, Inc. All Rights Reserved. 3 | */ 4 | include ':tracetohtml' 5 | -------------------------------------------------------------------------------- /tracetohtml/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /tracetohtml/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 Baidu, Inc. All Rights Reserved. 3 | */ 4 | apply plugin: 'com.android.model.application' 5 | model { 6 | android { 7 | compileSdkVersion = 23 8 | buildToolsVersion = "23.0.2" 9 | 10 | defaultConfig { 11 | applicationId "dodola.watcher.demo" 12 | minSdkVersion.apiLevel 15 13 | targetSdkVersion.apiLevel 22 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | buildConfigFields { 18 | create() { 19 | type "int" 20 | name "VALUE" 21 | value "1" 22 | } 23 | } 24 | } 25 | } 26 | 27 | 28 | android.ndk { 29 | moduleName = "ndk" 30 | cppFlags.addAll(["-Werror", "-fno-rtti", "-fno-exceptions"]) 31 | CFlags.add("-Werror") 32 | ldLibs.addAll(["log"]) 33 | stl = "stlport_static" 34 | debuggable = true 35 | 36 | } 37 | 38 | android.buildTypes { 39 | release { 40 | minifyEnabled = false 41 | proguardFiles.add(file('proguard-rules.txt')) 42 | } 43 | } 44 | 45 | android.productFlavors { 46 | create("arm7") { 47 | ndk.abiFilters.add("armeabi-v7a") 48 | } 49 | create("arm8") { 50 | ndk.abiFilters.add("arm64-v8a") 51 | } 52 | create("x86-32") { 53 | ndk.abiFilters.add("x86") 54 | } 55 | // for detailed abiFilter descriptions, refer to "Supported ABIs" @ 56 | // https://developer.android.com/ndk/guides/abis.html#sa 57 | 58 | // build one including all cpu architectures 59 | create("all") 60 | } 61 | } 62 | 63 | dependencies { 64 | compile fileTree(include: ['*.jar'], dir: 'libs') 65 | compile 'com.android.support:appcompat-v7:23.1.1' 66 | 67 | } 68 | -------------------------------------------------------------------------------- /tracetohtml/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/baidu/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /tracetohtml/src/all/jni/Profile.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2008 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * Android's method call profiling goodies. 19 | */ 20 | #ifndef DALVIK_PROFILE_H_ 21 | #define DALVIK_PROFILE_H_ 22 | 23 | /* 24 | * Enumeration for the two "action" bits. 25 | */ 26 | enum { 27 | METHOD_TRACE_ENTER = 0x00, // method entry 28 | METHOD_TRACE_EXIT = 0x01, // method exit 29 | METHOD_TRACE_UNROLL = 0x02, // method exited by exception unrolling 30 | // 0x03 currently unused 31 | }; 32 | 33 | #define TOKEN_CHAR '*' 34 | 35 | /* 36 | * Common definitions, shared with the dump tool. 37 | */ 38 | #define METHOD_ACTION_MASK 0x03 /* two bits */ 39 | #define METHOD_ID(_method) ((_method) & (~METHOD_ACTION_MASK)) 40 | #define METHOD_ACTION(_method) (((unsigned int)(_method)) & METHOD_ACTION_MASK) 41 | #define METHOD_COMBINE(_method, _action) ((_method) | (_action)) 42 | 43 | #endif // DALVIK_PROFILE_H_ 44 | -------------------------------------------------------------------------------- /tracetohtml/src/all/jni/TraceDump.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2006 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /* 18 | * Process dmtrace output. 19 | * 20 | * This is the wrong way to go about it -- C is a clumsy language for 21 | * shuffling data around. It'll do for a first pass. 22 | */ 23 | #define NOT_VM 24 | 25 | #include 26 | #include "Profile.h" // from VM header 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #ifndef __unused /* sys/cdefs.h not available on all platforms */ 38 | # define __unused __attribute__((__unused__)) 39 | #endif 40 | 41 | /* Version number in the key file. 42 | * Version 1 uses one byte for the thread id. 43 | * Version 2 uses two bytes for the thread ids. 44 | * Version 3 encodes the record size and adds an optional extra timestamp field. 45 | */ 46 | int versionNumber; 47 | 48 | /* arbitrarily limit indentation */ 49 | #define MAX_STACK_DEPTH 10000 50 | 51 | /* thread list in key file is not reliable, so just max out */ 52 | #define MAX_THREADS 32768 53 | 54 | /* Size of temporary buffers for escaping html strings */ 55 | #define HTML_BUFSIZE 10240 56 | 57 | char *htmlHeader = 58 | "\n\n\n" 59 | "\n" 79 | "\n" 95 | "\n\n"; 96 | 97 | char *htmlFooter = "\n\n\n"; 98 | char *profileSeparator = 99 | "======================================================================"; 100 | 101 | const char *tableHeader = 102 | "\n" 103 | "\n" 104 | "\n" 105 | "\n" 106 | "\n" 107 | "\n" 108 | "\n" 109 | "\n" 110 | "\n"; 111 | 112 | const char *tableHeaderMissing = 113 | "
MethodRun 1 (us)Run 2 (us)Diff (us)Diff (%%)1: # calls2: # calls
\n" 114 | "\n" 115 | "\n" 116 | "\n" 117 | "\n"; 118 | 119 | #define GRAPH_LABEL_VISITED 0x0001 120 | #define GRAPH_NODE_VISITED 0x0002 121 | 122 | /* 123 | * Values from the header of the data file. 124 | */ 125 | typedef struct DataHeader { 126 | unsigned int magic; 127 | short version; 128 | short offsetToData; 129 | long long startWhen; 130 | short recordSize; 131 | } DataHeader; 132 | 133 | /* 134 | * Entry from the thread list. 135 | */ 136 | typedef struct ThreadEntry { 137 | int threadId; 138 | const char *threadName; 139 | } ThreadEntry; 140 | 141 | struct MethodEntry; 142 | typedef struct TimedMethod { 143 | struct TimedMethod *next; 144 | uint64_t elapsedInclusive; 145 | int numCalls; 146 | struct MethodEntry *method; 147 | } TimedMethod; 148 | 149 | typedef struct ClassEntry { 150 | const char *className; 151 | uint64_t elapsedExclusive; 152 | int numMethods; 153 | struct MethodEntry **methods; 154 | /* list of methods in this class */ 155 | int numCalls[2]; /* 0=normal, 1=recursive */ 156 | } ClassEntry; 157 | 158 | typedef struct UniqueMethodEntry { 159 | uint64_t elapsedExclusive; 160 | int numMethods; 161 | struct MethodEntry **methods; 162 | /* list of methods with same name */ 163 | int numCalls[2]; /* 0=normal, 1=recursive */ 164 | } UniqueMethodEntry; 165 | 166 | /* 167 | * Entry from the method list. 168 | */ 169 | typedef struct MethodEntry { 170 | int64_t methodId; 171 | const char *className; 172 | const char *methodName; 173 | const char *signature; 174 | const char *fileName; 175 | int lineNum; 176 | uint64_t elapsedExclusive; 177 | uint64_t elapsedInclusive; 178 | uint64_t topExclusive; 179 | /* non-recursive exclusive time */ 180 | uint64_t recursiveInclusive; 181 | struct TimedMethod *parents[2]; 182 | /* 0=normal, 1=recursive */ 183 | struct TimedMethod *children[2]; 184 | /* 0=normal, 1=recursive */ 185 | int numCalls[2]; 186 | /* 0=normal, 1=recursive */ 187 | int index; 188 | /* used after sorting to number methods */ 189 | int recursiveEntries; 190 | /* number of entries on the stack */ 191 | int graphState; /* used when graphing to see if this method has been visited before */ 192 | } MethodEntry; 193 | 194 | /* 195 | * The parsed contents of the key file. 196 | */ 197 | typedef struct DataKeys { 198 | char *fileData; 199 | /* contents of the entire file */ 200 | long fileLen; 201 | int numThreads; 202 | ThreadEntry *threads; 203 | int numMethods; 204 | MethodEntry *methods; /* 2 extra methods: "toplevel" and "unknown" */ 205 | } DataKeys; 206 | 207 | #define TOPLEVEL_INDEX 0 208 | #define UNKNOWN_INDEX 1 209 | 210 | typedef struct StackEntry { 211 | MethodEntry *method; 212 | uint64_t entryTime; 213 | } StackEntry; 214 | 215 | typedef struct CallStack { 216 | int top; 217 | StackEntry calls[MAX_STACK_DEPTH]; 218 | uint64_t lastEventTime; 219 | uint64_t threadStartTime; 220 | } CallStack; 221 | 222 | typedef struct DiffEntry { 223 | MethodEntry *method1; 224 | MethodEntry *method2; 225 | int64_t differenceExclusive; 226 | int64_t differenceInclusive; 227 | double differenceExclusivePercentage; 228 | double differenceInclusivePercentage; 229 | } DiffEntry; 230 | 231 | // Global options 232 | typedef struct Options { 233 | const char *traceFileName; 234 | const char *diffFileName; 235 | const char *graphFileName; 236 | int keepDotFile; 237 | int dump; 238 | int outputHtml; 239 | const char *sortableUrl; 240 | int threshold; 241 | } Options; 242 | 243 | typedef struct TraceData { 244 | int numClasses; 245 | ClassEntry *classes; 246 | CallStack *stacks[MAX_THREADS]; 247 | int depth[MAX_THREADS]; 248 | int numUniqueMethods; 249 | UniqueMethodEntry *uniqueMethods; 250 | } TraceData; 251 | 252 | static Options gOptions; 253 | 254 | /* Escapes characters in the source string that are html special entities. 255 | * The escaped string is written to "dest" which must be large enough to 256 | * hold the result. A pointer to "dest" is returned. The characters and 257 | * their corresponding escape sequences are: 258 | * '<' < 259 | * '>' > 260 | * '&' & 261 | */ 262 | char *htmlEscape(const char *src, char *dest, int len) { 263 | char *destStart = dest; 264 | 265 | if (src == NULL) 266 | return NULL; 267 | 268 | int nbytes = 0; 269 | while (*src) { 270 | if (*src == '<') { 271 | nbytes += 4; 272 | if (nbytes >= len) 273 | break; 274 | *dest++ = '&'; 275 | *dest++ = 'l'; 276 | *dest++ = 't'; 277 | *dest++ = ';'; 278 | } else if (*src == '>') { 279 | nbytes += 4; 280 | if (nbytes >= len) 281 | break; 282 | *dest++ = '&'; 283 | *dest++ = 'g'; 284 | *dest++ = 't'; 285 | *dest++ = ';'; 286 | } else if (*src == '&') { 287 | nbytes += 5; 288 | if (nbytes >= len) 289 | break; 290 | *dest++ = '&'; 291 | *dest++ = 'a'; 292 | *dest++ = 'm'; 293 | *dest++ = 'p'; 294 | *dest++ = ';'; 295 | } else { 296 | nbytes += 1; 297 | if (nbytes >= len) 298 | break; 299 | *dest++ = *src; 300 | } 301 | src += 1; 302 | } 303 | if (nbytes >= len) { 304 | fprintf(stderr, "htmlEscape(): buffer overflow\n"); 305 | exit(1); 306 | } 307 | *dest = 0; 308 | 309 | return destStart; 310 | } 311 | 312 | /* Initializes a MethodEntry 313 | */ 314 | void initMethodEntry(MethodEntry *method, int64_t methodId, 315 | const char *className, const char *methodName, 316 | const char *signature, const char *fileName, 317 | const char *lineNumStr) { 318 | method->methodId = methodId; 319 | method->className = className; 320 | method->methodName = methodName; 321 | method->signature = signature; 322 | method->fileName = fileName; 323 | method->lineNum = (lineNumStr != NULL) ? atoi(lineNumStr) : -1; 324 | method->elapsedExclusive = 0; 325 | method->elapsedInclusive = 0; 326 | method->topExclusive = 0; 327 | method->recursiveInclusive = 0; 328 | method->parents[0] = NULL; 329 | method->parents[1] = NULL; 330 | method->children[0] = NULL; 331 | method->children[1] = NULL; 332 | method->numCalls[0] = 0; 333 | method->numCalls[1] = 0; 334 | method->index = 0; 335 | method->recursiveEntries = 0; 336 | } 337 | 338 | /* 339 | * This comparison function is called from qsort() to sort 340 | * methods into decreasing order of exclusive elapsed time. 341 | */ 342 | int compareElapsedExclusive(const void *a, const void *b) { 343 | uint64_t elapsed1, elapsed2; 344 | int result; 345 | 346 | const MethodEntry *methodA = *(const MethodEntry **) a; 347 | const MethodEntry *methodB = *(const MethodEntry **) b; 348 | elapsed1 = methodA->elapsedExclusive; 349 | elapsed2 = methodB->elapsedExclusive; 350 | if (elapsed1 < elapsed2) 351 | return 1; 352 | if (elapsed1 > elapsed2) 353 | return -1; 354 | 355 | /* If the elapsed times of two methods are equal, then sort them 356 | * into alphabetical order. 357 | */ 358 | result = strcmp(methodA->className, methodB->className); 359 | if (result == 0) { 360 | if (methodA->methodName == NULL || methodB->methodName == NULL) { 361 | int64_t idA = methodA->methodId; 362 | int64_t idB = methodB->methodId; 363 | if (idA < idB) 364 | return -1; 365 | if (idA > idB) 366 | return 1; 367 | return 0; 368 | } 369 | result = strcmp(methodA->methodName, methodB->methodName); 370 | if (result == 0) 371 | result = strcmp(methodA->signature, methodB->signature); 372 | } 373 | return result; 374 | } 375 | 376 | /* 377 | * This comparison function is called from qsort() to sort 378 | * methods into decreasing order of inclusive elapsed time. 379 | */ 380 | int compareElapsedInclusive(const void *a, const void *b) { 381 | const MethodEntry *methodA, *methodB; 382 | uint64_t elapsed1, elapsed2; 383 | int result; 384 | 385 | methodA = *(MethodEntry const **) a; 386 | methodB = *(MethodEntry const **) b; 387 | elapsed1 = methodA->elapsedInclusive; 388 | elapsed2 = methodB->elapsedInclusive; 389 | if (elapsed1 < elapsed2) 390 | return 1; 391 | if (elapsed1 > elapsed2) 392 | return -1; 393 | 394 | /* If the elapsed times of two methods are equal, then sort them 395 | * into alphabetical order. 396 | */ 397 | result = strcmp(methodA->className, methodB->className); 398 | if (result == 0) { 399 | if (methodA->methodName == NULL || methodB->methodName == NULL) { 400 | int64_t idA = methodA->methodId; 401 | int64_t idB = methodB->methodId; 402 | if (idA < idB) 403 | return -1; 404 | if (idA > idB) 405 | return 1; 406 | return 0; 407 | } 408 | result = strcmp(methodA->methodName, methodB->methodName); 409 | if (result == 0) 410 | result = strcmp(methodA->signature, methodB->signature); 411 | } 412 | return result; 413 | } 414 | 415 | /* 416 | * This comparison function is called from qsort() to sort 417 | * TimedMethods into decreasing order of inclusive elapsed time. 418 | */ 419 | int compareTimedMethod(const void *a, const void *b) { 420 | const TimedMethod *timedA, *timedB; 421 | uint64_t elapsed1, elapsed2; 422 | int result; 423 | 424 | timedA = (TimedMethod const *) a; 425 | timedB = (TimedMethod const *) b; 426 | elapsed1 = timedA->elapsedInclusive; 427 | elapsed2 = timedB->elapsedInclusive; 428 | if (elapsed1 < elapsed2) 429 | return 1; 430 | if (elapsed1 > elapsed2) 431 | return -1; 432 | 433 | /* If the elapsed times of two methods are equal, then sort them 434 | * into alphabetical order. 435 | */ 436 | MethodEntry *methodA = timedA->method; 437 | MethodEntry *methodB = timedB->method; 438 | result = strcmp(methodA->className, methodB->className); 439 | if (result == 0) { 440 | if (methodA->methodName == NULL || methodB->methodName == NULL) { 441 | int64_t idA = methodA->methodId; 442 | int64_t idB = methodB->methodId; 443 | if (idA < idB) 444 | return -1; 445 | if (idA > idB) 446 | return 1; 447 | return 0; 448 | } 449 | result = strcmp(methodA->methodName, methodB->methodName); 450 | if (result == 0) 451 | result = strcmp(methodA->signature, methodB->signature); 452 | } 453 | return result; 454 | } 455 | 456 | /* 457 | * This comparison function is called from qsort() to sort 458 | * MethodEntry pointers into alphabetical order of class names. 459 | */ 460 | int compareClassNames(const void *a, const void *b) { 461 | int result; 462 | 463 | const MethodEntry *methodA = *(const MethodEntry **) a; 464 | const MethodEntry *methodB = *(const MethodEntry **) b; 465 | result = strcmp(methodA->className, methodB->className); 466 | if (result == 0) { 467 | int64_t idA = methodA->methodId; 468 | int64_t idB = methodB->methodId; 469 | if (idA < idB) 470 | return -1; 471 | if (idA > idB) 472 | return 1; 473 | return 0; 474 | } 475 | return result; 476 | } 477 | 478 | /* 479 | * This comparison function is called from qsort() to sort 480 | * classes into decreasing order of exclusive elapsed time. 481 | */ 482 | int compareClassExclusive(const void *a, const void *b) { 483 | uint64_t elapsed1, elapsed2; 484 | int result; 485 | 486 | const ClassEntry *classA = *(const ClassEntry **) a; 487 | const ClassEntry *classB = *(const ClassEntry **) b; 488 | elapsed1 = classA->elapsedExclusive; 489 | elapsed2 = classB->elapsedExclusive; 490 | if (elapsed1 < elapsed2) 491 | return 1; 492 | if (elapsed1 > elapsed2) 493 | return -1; 494 | 495 | /* If the elapsed times of two classs are equal, then sort them 496 | * into alphabetical order. 497 | */ 498 | result = strcmp(classA->className, classB->className); 499 | if (result == 0) { 500 | /* Break ties with the first method id. This is probably not 501 | * needed. 502 | */ 503 | int64_t idA = classA->methods[0]->methodId; 504 | int64_t idB = classB->methods[0]->methodId; 505 | if (idA < idB) 506 | return -1; 507 | if (idA > idB) 508 | return 1; 509 | return 0; 510 | } 511 | return result; 512 | } 513 | 514 | /* 515 | * This comparison function is called from qsort() to sort 516 | * MethodEntry pointers into alphabetical order by method name, 517 | * then by class name. 518 | */ 519 | int compareMethodNames(const void *a, const void *b) { 520 | int result; 521 | 522 | const MethodEntry *methodA = *(const MethodEntry **) a; 523 | const MethodEntry *methodB = *(const MethodEntry **) b; 524 | if (methodA->methodName == NULL || methodB->methodName == NULL) { 525 | return compareClassNames(a, b); 526 | } 527 | result = strcmp(methodA->methodName, methodB->methodName); 528 | if (result == 0) { 529 | result = strcmp(methodA->className, methodB->className); 530 | if (result == 0) { 531 | int64_t idA = methodA->methodId; 532 | int64_t idB = methodB->methodId; 533 | if (idA < idB) 534 | return -1; 535 | if (idA > idB) 536 | return 1; 537 | return 0; 538 | } 539 | } 540 | return result; 541 | } 542 | 543 | /* 544 | * This comparison function is called from qsort() to sort 545 | * unique methods into decreasing order of exclusive elapsed time. 546 | */ 547 | int compareUniqueExclusive(const void *a, const void *b) { 548 | uint64_t elapsed1, elapsed2; 549 | int result; 550 | 551 | const UniqueMethodEntry *uniqueA = *(const UniqueMethodEntry **) a; 552 | const UniqueMethodEntry *uniqueB = *(const UniqueMethodEntry **) b; 553 | elapsed1 = uniqueA->elapsedExclusive; 554 | elapsed2 = uniqueB->elapsedExclusive; 555 | if (elapsed1 < elapsed2) 556 | return 1; 557 | if (elapsed1 > elapsed2) 558 | return -1; 559 | 560 | /* If the elapsed times of two methods are equal, then sort them 561 | * into alphabetical order. 562 | */ 563 | result = strcmp(uniqueA->methods[0]->className, 564 | uniqueB->methods[0]->className); 565 | if (result == 0) { 566 | int64_t idA = uniqueA->methods[0]->methodId; 567 | int64_t idB = uniqueB->methods[0]->methodId; 568 | if (idA < idB) 569 | return -1; 570 | if (idA > idB) 571 | return 1; 572 | return 0; 573 | } 574 | return result; 575 | } 576 | 577 | /* 578 | * Free a DataKeys struct. 579 | */ 580 | void freeDataKeys(DataKeys *pKeys) { 581 | if (pKeys == NULL) 582 | return; 583 | 584 | free(pKeys->fileData); 585 | free(pKeys->threads); 586 | free(pKeys->methods); 587 | free(pKeys); 588 | } 589 | 590 | /* 591 | * Find the offset to the next occurrence of the specified character. 592 | * 593 | * "data" should point somewhere within the current line. "len" is the 594 | * number of bytes left in the buffer. 595 | * 596 | * Returns -1 if we hit the end of the buffer. 597 | */ 598 | int findNextChar(const char *data, int len, char lookFor) { 599 | const char *start = data; 600 | 601 | while (len > 0) { 602 | if (*data == lookFor) 603 | return data - start; 604 | 605 | data++; 606 | len--; 607 | } 608 | 609 | return -1; 610 | } 611 | 612 | /* 613 | * Count the number of lines until the next token. 614 | * 615 | * Returns -1 if none found before EOF. 616 | */ 617 | int countLinesToToken(const char *data, int len) { 618 | int count = 0; 619 | int next; 620 | 621 | while (*data != TOKEN_CHAR) { 622 | next = findNextChar(data, len, '\n'); 623 | if (next < 0) 624 | return -1; 625 | count++; 626 | data += next + 1; 627 | len -= next + 1; 628 | } 629 | 630 | return count; 631 | } 632 | 633 | /* 634 | * Make sure we're at the start of the right section. 635 | * 636 | * Returns the length of the token line, or -1 if something is wrong. 637 | */ 638 | int checkToken(const char *data, int len, const char *cmpStr) { 639 | int cmpLen = strlen(cmpStr); 640 | int next; 641 | 642 | if (*data != TOKEN_CHAR) { 643 | fprintf(stderr, 644 | "ERROR: not at start of %s (found '%.10s')\n", cmpStr, data); 645 | return -1; 646 | } 647 | 648 | next = findNextChar(data, len, '\n'); 649 | if (next < cmpLen + 1) 650 | return -1; 651 | 652 | if (strncmp(data + 1, cmpStr, cmpLen) != 0) { 653 | fprintf(stderr, "ERROR: '%s' not found (got '%.7s')\n", cmpStr, data + 1); 654 | return -1; 655 | } 656 | 657 | return next + 1; 658 | } 659 | 660 | /* 661 | * Parse the "*version" section. 662 | */ 663 | long parseVersion(DataKeys *pKeys, long offset, int verbose) { 664 | char *data; 665 | char *dataEnd; 666 | int i, count, next; 667 | 668 | if (offset < 0) 669 | return -1; 670 | 671 | data = pKeys->fileData + offset; 672 | dataEnd = pKeys->fileData + pKeys->fileLen; 673 | next = checkToken(data, dataEnd - data, "version"); 674 | if (next <= 0) 675 | return -1; 676 | 677 | data += next; 678 | 679 | /* 680 | * Count the number of items in the "version" section. 681 | */ 682 | count = countLinesToToken(data, dataEnd - data); 683 | if (count <= 0) { 684 | fprintf(stderr, 685 | "ERROR: failed while reading version (found %d)\n", count); 686 | return -1; 687 | } 688 | 689 | /* find the end of the line */ 690 | next = findNextChar(data, dataEnd - data, '\n'); 691 | if (next < 0) 692 | return -1; 693 | 694 | data[next] = '\0'; 695 | versionNumber = strtoul(data, NULL, 0); 696 | if (verbose) 697 | printf("VERSION: %d\n", versionNumber); 698 | 699 | data += next + 1; 700 | 701 | /* skip over the rest of the stuff, which is "name=value" lines */ 702 | for (i = 1; i < count; i++) { 703 | next = findNextChar(data, dataEnd - data, '\n'); 704 | if (next < 0) 705 | return -1; 706 | //data[next] = '\0'; 707 | //printf("IGNORING: '%s'\n", data); 708 | data += next + 1; 709 | } 710 | 711 | return data - pKeys->fileData; 712 | } 713 | 714 | /* 715 | * Parse the "*threads" section. 716 | */ 717 | long parseThreads(DataKeys *pKeys, long offset) { 718 | char *data; 719 | char *dataEnd; 720 | int i, next, tab, count; 721 | 722 | if (offset < 0) 723 | return -1; 724 | 725 | data = pKeys->fileData + offset; 726 | dataEnd = pKeys->fileData + pKeys->fileLen; 727 | next = checkToken(data, dataEnd - data, "threads"); 728 | 729 | data += next; 730 | 731 | /* 732 | * Count the number of thread entries (one per line). 733 | */ 734 | count = countLinesToToken(data, dataEnd - data); 735 | if (count <= 0) { 736 | fprintf(stderr, 737 | "ERROR: failed while reading threads (found %d)\n", count); 738 | return -1; 739 | } 740 | 741 | //printf("+++ found %d threads\n", count); 742 | pKeys->threads = (ThreadEntry *) malloc(sizeof(ThreadEntry) * count); 743 | if (pKeys->threads == NULL) 744 | return -1; 745 | 746 | /* 747 | * Extract all entries. 748 | */ 749 | for (i = 0; i < count; i++) { 750 | next = findNextChar(data, dataEnd - data, '\n'); 751 | assert(next > 0); 752 | data[next] = '\0'; 753 | 754 | tab = findNextChar(data, next, '\t'); 755 | data[tab] = '\0'; 756 | 757 | pKeys->threads[i].threadId = atoi(data); 758 | pKeys->threads[i].threadName = data + tab + 1; 759 | 760 | data += next + 1; 761 | } 762 | 763 | pKeys->numThreads = count; 764 | return data - pKeys->fileData; 765 | } 766 | 767 | /* 768 | * Parse the "*methods" section. 769 | */ 770 | long parseMethods(DataKeys *pKeys, long offset) { 771 | char *data; 772 | char *dataEnd; 773 | int i, next, count; 774 | 775 | if (offset < 0) 776 | return -1; 777 | 778 | data = pKeys->fileData + offset; 779 | dataEnd = pKeys->fileData + pKeys->fileLen; 780 | next = checkToken(data, dataEnd - data, "methods"); 781 | if (next < 0) 782 | return -1; 783 | 784 | data += next; 785 | 786 | /* 787 | * Count the number of method entries (one per line). 788 | */ 789 | count = countLinesToToken(data, dataEnd - data); 790 | if (count <= 0) { 791 | fprintf(stderr, 792 | "ERROR: failed while reading methods (found %d)\n", count); 793 | return -1; 794 | } 795 | 796 | /* Reserve an extra method at location 0 for the "toplevel" method, 797 | * and another extra method for all other "unknown" methods. 798 | */ 799 | count += 2; 800 | pKeys->methods = (MethodEntry *) malloc(sizeof(MethodEntry) * count); 801 | if (pKeys->methods == NULL) 802 | return -1; 803 | initMethodEntry(&pKeys->methods[TOPLEVEL_INDEX], -2, "(toplevel)", 804 | NULL, NULL, NULL, NULL); 805 | initMethodEntry(&pKeys->methods[UNKNOWN_INDEX], -1, "(unknown)", 806 | NULL, NULL, NULL, NULL); 807 | 808 | /* 809 | * Extract all entries, starting with index 2. 810 | */ 811 | for (i = UNKNOWN_INDEX + 1; i < count; i++) { 812 | int tab1, tab2, tab3, tab4, tab5; 813 | int64_t id; 814 | char *endptr; 815 | 816 | next = findNextChar(data, dataEnd - data, '\n'); 817 | assert(next > 0); 818 | data[next] = '\0'; 819 | 820 | tab1 = findNextChar(data, next, '\t'); 821 | tab2 = findNextChar(data + (tab1 + 1), next - (tab1 + 1), '\t'); 822 | tab3 = findNextChar(data + (tab1 + tab2 + 2), next - (tab1 + tab2 + 2), '\t'); 823 | tab4 = findNextChar(data + (tab1 + tab2 + tab3 + 3), 824 | next - (tab1 + tab2 + tab3 + 3), '\t'); 825 | tab5 = findNextChar(data + (tab1 + tab2 + tab3 + tab4 + 4), 826 | next - (tab1 + tab2 + tab3 + tab4 + 4), '\t'); 827 | if (tab1 < 0) { 828 | fprintf(stderr, "ERROR: missing field on method line: '%s'\n", 829 | data); 830 | return -1; 831 | } 832 | assert(data[tab1] == '\t'); 833 | data[tab1] = '\0'; 834 | 835 | id = strtoul(data, &endptr, 0); 836 | if (*endptr != '\0') { 837 | fprintf(stderr, "ERROR: bad method ID '%s'\n", data); 838 | return -1; 839 | } 840 | 841 | // Allow files that specify just a function name, instead of requiring 842 | // "class \t method \t signature" 843 | if (tab2 > 0 && tab3 > 0) { 844 | tab2 += tab1 + 1; 845 | tab3 += tab2 + 1; 846 | assert(data[tab2] == '\t'); 847 | assert(data[tab3] == '\t'); 848 | data[tab2] = data[tab3] = '\0'; 849 | 850 | // This is starting to get awkward. Allow filename and line #. 851 | if (tab4 > 0 && tab5 > 0) { 852 | tab4 += tab3 + 1; 853 | tab5 += tab4 + 1; 854 | 855 | assert(data[tab4] == '\t'); 856 | assert(data[tab5] == '\t'); 857 | data[tab4] = data[tab5] = '\0'; 858 | 859 | initMethodEntry(&pKeys->methods[i], id, data + tab1 + 1, 860 | data + tab2 + 1, data + tab3 + 1, data + tab4 + 1, 861 | data + tab5 + 1); 862 | } else { 863 | initMethodEntry(&pKeys->methods[i], id, data + tab1 + 1, 864 | data + tab2 + 1, data + tab3 + 1, NULL, NULL); 865 | } 866 | } else { 867 | initMethodEntry(&pKeys->methods[i], id, data + tab1 + 1, 868 | NULL, NULL, NULL, NULL); 869 | } 870 | 871 | data += next + 1; 872 | } 873 | 874 | pKeys->numMethods = count; 875 | return data - pKeys->fileData; 876 | } 877 | 878 | /* 879 | * Parse the "*end" section. 880 | */ 881 | long parseEnd(DataKeys *pKeys, long offset) { 882 | char *data; 883 | char *dataEnd; 884 | int next; 885 | 886 | if (offset < 0) 887 | return -1; 888 | 889 | data = pKeys->fileData + offset; 890 | dataEnd = pKeys->fileData + pKeys->fileLen; 891 | next = checkToken(data, dataEnd - data, "end"); 892 | if (next < 0) 893 | return -1; 894 | 895 | data += next; 896 | 897 | return data - pKeys->fileData; 898 | } 899 | 900 | /* 901 | * Sort the thread list entries. 902 | */ 903 | static int compareThreads(const void *thread1, const void *thread2) { 904 | return ((const ThreadEntry *) thread1)->threadId - 905 | ((const ThreadEntry *) thread2)->threadId; 906 | } 907 | 908 | void sortThreadList(DataKeys *pKeys) { 909 | qsort(pKeys->threads, pKeys->numThreads, sizeof(pKeys->threads[0]), 910 | compareThreads); 911 | } 912 | 913 | /* 914 | * Sort the method list entries. 915 | */ 916 | static int compareMethods(const void *meth1, const void *meth2) { 917 | int64_t id1, id2; 918 | 919 | id1 = ((const MethodEntry *) meth1)->methodId; 920 | id2 = ((const MethodEntry *) meth2)->methodId; 921 | if (id1 < id2) 922 | return -1; 923 | if (id1 > id2) 924 | return 1; 925 | return 0; 926 | } 927 | 928 | void sortMethodList(DataKeys *pKeys) { 929 | qsort(pKeys->methods, pKeys->numMethods, sizeof(MethodEntry), 930 | compareMethods); 931 | } 932 | 933 | /* 934 | * Parse the key section, and return a copy of the parsed contents. 935 | */ 936 | DataKeys *parseKeys(FILE *fp, int verbose) { 937 | DataKeys *pKeys = NULL; 938 | long offset; 939 | int i; 940 | 941 | pKeys = (DataKeys *) calloc(1, sizeof(DataKeys)); 942 | if (pKeys == NULL) 943 | goto fail; 944 | 945 | /* 946 | * We load the entire file into memory. We do this, rather than memory- 947 | * mapping it, because we want to change some whitespace to NULs. 948 | */ 949 | if (fseek(fp, 0L, SEEK_END) != 0) { 950 | perror("fseek"); 951 | goto fail; 952 | } 953 | pKeys->fileLen = ftell(fp); 954 | if (pKeys->fileLen == 0) { 955 | fprintf(stderr, "Key file is empty.\n"); 956 | goto fail; 957 | } 958 | rewind(fp); 959 | 960 | pKeys->fileData = (char *) malloc(pKeys->fileLen); 961 | if (pKeys->fileData == NULL) { 962 | fprintf(stderr, "ERROR: unable to alloc %ld bytes\n", pKeys->fileLen); 963 | goto fail; 964 | } 965 | 966 | if (fread(pKeys->fileData, 1, pKeys->fileLen, fp) != (size_t) pKeys->fileLen) { 967 | fprintf(stderr, "ERROR: unable to read %ld bytes from trace file\n", 968 | pKeys->fileLen); 969 | goto fail; 970 | } 971 | 972 | offset = 0; 973 | 974 | offset = parseVersion(pKeys, offset, verbose); 975 | offset = parseThreads(pKeys, offset); 976 | offset = parseMethods(pKeys, offset); 977 | offset = parseEnd(pKeys, offset); 978 | if (offset < 0) 979 | goto fail; 980 | 981 | /* Reduce our allocation now that we know where the end of the key section is. */ 982 | pKeys->fileData = (char *) realloc(pKeys->fileData, offset); 983 | pKeys->fileLen = offset; 984 | /* Leave fp pointing to the beginning of the data section. */ 985 | fseek(fp, offset, SEEK_SET); 986 | 987 | sortThreadList(pKeys); 988 | sortMethodList(pKeys); 989 | 990 | /* 991 | * Dump list of threads. 992 | */ 993 | if (verbose) { 994 | printf("Threads (%d):\n", pKeys->numThreads); 995 | for (i = 0; i < pKeys->numThreads; i++) { 996 | printf("%2d %s\n", 997 | pKeys->threads[i].threadId, pKeys->threads[i].threadName); 998 | } 999 | } 1000 | 1001 | #if 0 1002 | /* 1003 | * Dump list of methods. 1004 | */ 1005 | if (verbose) { 1006 | printf("Methods (%d):\n", pKeys->numMethods); 1007 | for (i = 0; i < pKeys->numMethods; i++) { 1008 | printf("0x%08x %s : %s : %s\n", 1009 | pKeys->methods[i].methodId, pKeys->methods[i].className, 1010 | pKeys->methods[i].methodName, pKeys->methods[i].signature); 1011 | } 1012 | } 1013 | #endif 1014 | 1015 | return pKeys; 1016 | 1017 | fail: 1018 | freeDataKeys(pKeys); 1019 | return NULL; 1020 | } 1021 | 1022 | 1023 | /* 1024 | * Read values from the binary data file. 1025 | */ 1026 | 1027 | /* Make the return value "unsigned int" instead of "unsigned short" so that 1028 | * we can detect EOF. 1029 | */ 1030 | unsigned int read2LE(FILE *fp) { 1031 | unsigned int val; 1032 | 1033 | val = getc(fp); 1034 | val |= getc(fp) << 8; 1035 | return val; 1036 | } 1037 | 1038 | unsigned int read4LE(FILE *fp) { 1039 | unsigned int val; 1040 | 1041 | val = getc(fp); 1042 | val |= getc(fp) << 8; 1043 | val |= getc(fp) << 16; 1044 | val |= getc(fp) << 24; 1045 | return val; 1046 | } 1047 | 1048 | unsigned long long read8LE(FILE *fp) { 1049 | unsigned long long val; 1050 | 1051 | val = getc(fp); 1052 | val |= (unsigned long long) getc(fp) << 8; 1053 | val |= (unsigned long long) getc(fp) << 16; 1054 | val |= (unsigned long long) getc(fp) << 24; 1055 | val |= (unsigned long long) getc(fp) << 32; 1056 | val |= (unsigned long long) getc(fp) << 40; 1057 | val |= (unsigned long long) getc(fp) << 48; 1058 | val |= (unsigned long long) getc(fp) << 56; 1059 | return val; 1060 | } 1061 | 1062 | /* 1063 | * Parse the header of the data section. 1064 | * 1065 | * Returns with the file positioned at the start of the record data. 1066 | */ 1067 | int parseDataHeader(FILE *fp, DataHeader *pHeader) { 1068 | int bytesToRead; 1069 | 1070 | pHeader->magic = read4LE(fp); 1071 | pHeader->version = read2LE(fp); 1072 | pHeader->offsetToData = read2LE(fp); 1073 | pHeader->startWhen = read8LE(fp); 1074 | bytesToRead = pHeader->offsetToData - 16; 1075 | if (pHeader->version == 1) { 1076 | pHeader->recordSize = 9; 1077 | } else if (pHeader->version == 2) { 1078 | pHeader->recordSize = 10; 1079 | } else if (pHeader->version == 3) { 1080 | pHeader->recordSize = read2LE(fp); 1081 | bytesToRead -= 2; 1082 | } else { 1083 | fprintf(stderr, "Unsupported trace file version: %d\n", pHeader->version); 1084 | return -1; 1085 | } 1086 | 1087 | if (fseek(fp, bytesToRead, SEEK_CUR) != 0) { 1088 | return -1; 1089 | } 1090 | 1091 | return 0; 1092 | } 1093 | 1094 | /* 1095 | * Look up a method by it's method ID. 1096 | * 1097 | * Returns NULL if no matching method was found. 1098 | */ 1099 | MethodEntry *lookupMethod(DataKeys *pKeys, int64_t methodId) { 1100 | int hi, lo, mid; 1101 | int64_t id; 1102 | 1103 | lo = 0; 1104 | hi = pKeys->numMethods - 1; 1105 | 1106 | while (hi >= lo) { 1107 | mid = (hi + lo) / 2; 1108 | 1109 | id = pKeys->methods[mid].methodId; 1110 | if (id == methodId) /* match */ 1111 | return &pKeys->methods[mid]; 1112 | else if (id < methodId) /* too low */ 1113 | lo = mid + 1; 1114 | else /* too high */ 1115 | hi = mid - 1; 1116 | } 1117 | 1118 | return NULL; 1119 | } 1120 | 1121 | /* 1122 | * Reads the next data record, and assigns the data values to threadId, 1123 | * methodVal and elapsedTime. On end-of-file, the threadId, methodVal, 1124 | * and elapsedTime are unchanged. Returns 1 on end-of-file, otherwise 1125 | * returns 0. 1126 | */ 1127 | int readDataRecord(FILE *dataFp, DataHeader *dataHeader, 1128 | int *threadId, unsigned int *methodVal, uint64_t *elapsedTime) { 1129 | int id; 1130 | int bytesToRead; 1131 | 1132 | bytesToRead = dataHeader->recordSize; 1133 | if (dataHeader->version == 1) { 1134 | id = getc(dataFp); 1135 | bytesToRead -= 1; 1136 | } else { 1137 | id = read2LE(dataFp); 1138 | bytesToRead -= 2; 1139 | } 1140 | if (id == EOF) 1141 | return 1; 1142 | *threadId = id; 1143 | 1144 | *methodVal = read4LE(dataFp); 1145 | *elapsedTime = read4LE(dataFp); 1146 | bytesToRead -= 8; 1147 | 1148 | while (bytesToRead-- > 0) { 1149 | getc(dataFp); 1150 | } 1151 | 1152 | if (feof(dataFp)) { 1153 | fprintf(stderr, "WARNING: hit EOF mid-record\n"); 1154 | return 1; 1155 | } 1156 | return 0; 1157 | } 1158 | 1159 | /* 1160 | * Read the key file and use it to produce formatted output from the 1161 | * data file. 1162 | */ 1163 | void dumpTrace() { 1164 | static const char *actionStr[] = {"ent", "xit", "unr", "???"}; 1165 | MethodEntry bogusMethod = {0, "???", "???", "???", "???", -1, 0, 0, 0, 0, 1166 | {NULL, NULL}, {NULL, NULL}, {0, 0}, 0, 0, -1}; 1167 | char bogusBuf[80]; 1168 | char spaces[MAX_STACK_DEPTH + 1]; 1169 | FILE *dataFp = NULL; 1170 | DataHeader dataHeader; 1171 | DataKeys *pKeys = NULL; 1172 | int i; 1173 | TraceData traceData; 1174 | 1175 | //printf("Dumping '%s' '%s'\n", dataFileName, keyFileName); 1176 | 1177 | memset(spaces, '.', MAX_STACK_DEPTH); 1178 | spaces[MAX_STACK_DEPTH] = '\0'; 1179 | 1180 | for (i = 0; i < MAX_THREADS; i++) 1181 | traceData.depth[i] = 2; // adjust for return from start function 1182 | 1183 | dataFp = fopen(gOptions.traceFileName, "rb"); 1184 | if (dataFp == NULL) 1185 | goto bail; 1186 | 1187 | if ((pKeys = parseKeys(dataFp, 1)) == NULL) 1188 | goto bail; 1189 | 1190 | if (parseDataHeader(dataFp, &dataHeader) < 0) 1191 | goto bail; 1192 | 1193 | printf("Trace (threadID action usecs class.method signature):\n"); 1194 | 1195 | while (1) { 1196 | MethodEntry *method; 1197 | int threadId; 1198 | unsigned int methodVal; 1199 | uint64_t elapsedTime; 1200 | int action, printDepth; 1201 | int64_t methodId, lastEnter = 0; 1202 | int mismatch = 0; 1203 | char depthNote; 1204 | 1205 | /* 1206 | * Extract values from file. 1207 | */ 1208 | if (readDataRecord(dataFp, &dataHeader, &threadId, &methodVal, &elapsedTime)) 1209 | break; 1210 | 1211 | action = METHOD_ACTION(methodVal); 1212 | methodId = METHOD_ID(methodVal); 1213 | 1214 | /* 1215 | * Generate a line of output. 1216 | */ 1217 | if (action == METHOD_TRACE_ENTER) { 1218 | traceData.depth[threadId]++; 1219 | lastEnter = methodId; 1220 | } else { 1221 | /* quick test for mismatched adjacent enter/exit */ 1222 | if (lastEnter != 0 && lastEnter != methodId) 1223 | mismatch = 1; 1224 | } 1225 | 1226 | printDepth = traceData.depth[threadId]; 1227 | depthNote = ' '; 1228 | if (printDepth < 0) { 1229 | printDepth = 0; 1230 | depthNote = '-'; 1231 | } else if (printDepth > MAX_STACK_DEPTH) { 1232 | printDepth = MAX_STACK_DEPTH; 1233 | depthNote = '+'; 1234 | } 1235 | 1236 | method = lookupMethod(pKeys, methodId); 1237 | if (method == NULL) { 1238 | method = &bogusMethod; 1239 | sprintf(bogusBuf, "methodId: %#" PRIx64 "", methodId); 1240 | method->signature = bogusBuf; 1241 | } 1242 | 1243 | if (method->methodName) { 1244 | printf("%2d %s%c %8" PRIu64 "%c%s%s.%s %s\n", threadId, 1245 | actionStr[action], mismatch ? '!' : ' ', 1246 | elapsedTime, depthNote, 1247 | spaces + (MAX_STACK_DEPTH - printDepth), 1248 | method->className, method->methodName, method->signature); 1249 | } else { 1250 | printf("%2d %s%c %8" PRIu64 "%c%s%s\n", threadId, 1251 | actionStr[action], mismatch ? '!' : ' ', 1252 | elapsedTime, depthNote, 1253 | spaces + (MAX_STACK_DEPTH - printDepth), 1254 | method->className); 1255 | } 1256 | 1257 | if (action != METHOD_TRACE_ENTER) { 1258 | traceData.depth[threadId]--; /* METHOD_TRACE_EXIT or METHOD_TRACE_UNROLL */ 1259 | lastEnter = 0; 1260 | } 1261 | 1262 | mismatch = 0; 1263 | } 1264 | 1265 | bail: 1266 | if (dataFp != NULL) 1267 | fclose(dataFp); 1268 | if (pKeys != NULL) 1269 | freeDataKeys(pKeys); 1270 | } 1271 | 1272 | /* This routine adds the given time to the parent and child methods. 1273 | * This is called when the child routine exits, after the child has 1274 | * been popped from the stack. The elapsedTime parameter is the 1275 | * duration of the child routine, including time spent in called routines. 1276 | */ 1277 | void addInclusiveTime(MethodEntry *parent, MethodEntry *child, 1278 | uint64_t elapsedTime) { 1279 | TimedMethod *pTimed; 1280 | 1281 | #if 0 1282 | bool verbose = false; 1283 | if (strcmp(child->className, debugClassName) == 0) 1284 | verbose = true; 1285 | #endif 1286 | 1287 | int childIsRecursive = (child->recursiveEntries > 0); 1288 | int parentIsRecursive = (parent->recursiveEntries > 1); 1289 | 1290 | if (child->recursiveEntries == 0) { 1291 | child->elapsedInclusive += elapsedTime; 1292 | } else if (child->recursiveEntries == 1) { 1293 | child->recursiveInclusive += elapsedTime; 1294 | } 1295 | child->numCalls[childIsRecursive] += 1; 1296 | 1297 | #if 0 1298 | if (verbose) { 1299 | fprintf(stderr, 1300 | "%s %d elapsedTime: %lld eI: %lld, rI: %lld\n", 1301 | child->className, child->recursiveEntries, 1302 | elapsedTime, child->elapsedInclusive, 1303 | child->recursiveInclusive); 1304 | } 1305 | #endif 1306 | 1307 | /* Find the child method in the parent */ 1308 | TimedMethod *children = parent->children[parentIsRecursive]; 1309 | for (pTimed = children; pTimed; pTimed = pTimed->next) { 1310 | if (pTimed->method == child) { 1311 | pTimed->elapsedInclusive += elapsedTime; 1312 | pTimed->numCalls += 1; 1313 | break; 1314 | } 1315 | } 1316 | if (pTimed == NULL) { 1317 | /* Allocate a new TimedMethod */ 1318 | pTimed = (TimedMethod *) malloc(sizeof(TimedMethod)); 1319 | pTimed->elapsedInclusive = elapsedTime; 1320 | pTimed->numCalls = 1; 1321 | pTimed->method = child; 1322 | 1323 | /* Add it to the front of the list */ 1324 | pTimed->next = children; 1325 | parent->children[parentIsRecursive] = pTimed; 1326 | } 1327 | 1328 | /* Find the parent method in the child */ 1329 | TimedMethod *parents = child->parents[childIsRecursive]; 1330 | for (pTimed = parents; pTimed; pTimed = pTimed->next) { 1331 | if (pTimed->method == parent) { 1332 | pTimed->elapsedInclusive += elapsedTime; 1333 | pTimed->numCalls += 1; 1334 | break; 1335 | } 1336 | } 1337 | if (pTimed == NULL) { 1338 | /* Allocate a new TimedMethod */ 1339 | pTimed = (TimedMethod *) malloc(sizeof(TimedMethod)); 1340 | pTimed->elapsedInclusive = elapsedTime; 1341 | pTimed->numCalls = 1; 1342 | pTimed->method = parent; 1343 | 1344 | /* Add it to the front of the list */ 1345 | pTimed->next = parents; 1346 | child->parents[childIsRecursive] = pTimed; 1347 | } 1348 | 1349 | #if 0 1350 | if (verbose) { 1351 | fprintf(stderr, 1352 | " %s %d eI: %lld\n", 1353 | parent->className, parent->recursiveEntries, 1354 | pTimed->elapsedInclusive); 1355 | } 1356 | #endif 1357 | } 1358 | 1359 | /* Sorts a linked list and returns a newly allocated array containing 1360 | * the sorted entries. 1361 | */ 1362 | TimedMethod *sortTimedMethodList(TimedMethod *list, int *num) { 1363 | int ii; 1364 | TimedMethod *pTimed, *sorted; 1365 | 1366 | /* Count the elements */ 1367 | int num_entries = 0; 1368 | for (pTimed = list; pTimed; pTimed = pTimed->next) 1369 | num_entries += 1; 1370 | *num = num_entries; 1371 | if (num_entries == 0) 1372 | return NULL; 1373 | 1374 | /* Copy all the list elements to a new array and sort them */ 1375 | sorted = (TimedMethod *) malloc(sizeof(TimedMethod) * num_entries); 1376 | for (ii = 0, pTimed = list; pTimed; pTimed = pTimed->next, ++ii) 1377 | memcpy(&sorted[ii], pTimed, sizeof(TimedMethod)); 1378 | qsort(sorted, num_entries, sizeof(TimedMethod), compareTimedMethod); 1379 | 1380 | /* Fix up the "next" pointers so that they work. */ 1381 | for (ii = 0; ii < num_entries - 1; ++ii) 1382 | sorted[ii].next = &sorted[ii + 1]; 1383 | sorted[num_entries - 1].next = NULL; 1384 | 1385 | return sorted; 1386 | } 1387 | 1388 | /* Define flag values for printInclusiveMethod() */ 1389 | static const int kIsRecursive = 1; 1390 | 1391 | /* This prints the inclusive stats for all the parents or children of a 1392 | * method, depending on the list that is passed in. 1393 | */ 1394 | void printInclusiveMethod(MethodEntry *method, TimedMethod *list, int numCalls, 1395 | int flags) { 1396 | int num; 1397 | TimedMethod *pTimed; 1398 | char buf[80]; 1399 | char *anchor_close; 1400 | char *spaces = " "; /* 6 spaces */ 1401 | int num_spaces = strlen(spaces); 1402 | char *space_ptr = &spaces[num_spaces]; 1403 | char *className, *methodName, *signature; 1404 | char classBuf[HTML_BUFSIZE], methodBuf[HTML_BUFSIZE]; 1405 | char signatureBuf[HTML_BUFSIZE]; 1406 | 1407 | anchor_close = ""; 1408 | if (gOptions.outputHtml) 1409 | anchor_close = ""; 1410 | 1411 | TimedMethod *sorted = sortTimedMethodList(list, &num); 1412 | double methodTotal = method->elapsedInclusive; 1413 | for (pTimed = sorted; pTimed; pTimed = pTimed->next) { 1414 | MethodEntry *relative = pTimed->method; 1415 | className = (char *) (relative->className); 1416 | methodName = (char *) (relative->methodName); 1417 | signature = (char *) (relative->signature); 1418 | double per = 100.0 * pTimed->elapsedInclusive / methodTotal; 1419 | sprintf(buf, "[%d]", relative->index); 1420 | if (gOptions.outputHtml) { 1421 | int len = strlen(buf); 1422 | if (len > num_spaces) 1423 | len = num_spaces; 1424 | sprintf(buf, "[%d]", 1425 | relative->index, relative->index); 1426 | space_ptr = &spaces[len]; 1427 | className = htmlEscape(className, classBuf, HTML_BUFSIZE); 1428 | methodName = htmlEscape(methodName, methodBuf, HTML_BUFSIZE); 1429 | signature = htmlEscape(signature, signatureBuf, HTML_BUFSIZE); 1430 | } 1431 | int nCalls = numCalls; 1432 | if (nCalls == 0) 1433 | nCalls = relative->numCalls[0] + relative->numCalls[1]; 1434 | if (relative->methodName) { 1435 | if (flags & kIsRecursive) { 1436 | // Don't display percentages for recursive functions 1437 | printf("%6s %5s %6s %s%6s%s %6d/%-6d %9" PRIu64 " %s.%s %s\n", 1438 | "", "", "", 1439 | space_ptr, buf, anchor_close, 1440 | pTimed->numCalls, nCalls, 1441 | pTimed->elapsedInclusive, 1442 | className, methodName, signature); 1443 | } else { 1444 | printf("%6s %5s %5.1f%% %s%6s%s %6d/%-6d %9" PRIu64 1445 | " %s.%s %s\n", 1446 | "", "", per, 1447 | space_ptr, buf, anchor_close, 1448 | pTimed->numCalls, nCalls, 1449 | pTimed->elapsedInclusive, 1450 | className, methodName, signature); 1451 | } 1452 | } else { 1453 | if (flags & kIsRecursive) { 1454 | // Don't display percentages for recursive functions 1455 | printf("%6s %5s %6s %s%6s%s %6d/%-6d %9" PRIu64 " %s\n", 1456 | "", "", "", 1457 | space_ptr, buf, anchor_close, 1458 | pTimed->numCalls, nCalls, 1459 | pTimed->elapsedInclusive, 1460 | className); 1461 | } else { 1462 | printf("%6s %5s %5.1f%% %s%6s%s %6d/%-6d %9" PRIu64 " %s\n", 1463 | "", "", per, 1464 | space_ptr, buf, anchor_close, 1465 | pTimed->numCalls, nCalls, 1466 | pTimed->elapsedInclusive, 1467 | className); 1468 | } 1469 | } 1470 | } 1471 | } 1472 | 1473 | void countRecursiveEntries(CallStack *pStack, int top, MethodEntry *method) { 1474 | int ii; 1475 | 1476 | method->recursiveEntries = 0; 1477 | for (ii = 0; ii < top; ++ii) { 1478 | if (pStack->calls[ii].method == method) 1479 | method->recursiveEntries += 1; 1480 | } 1481 | } 1482 | 1483 | void stackDump(CallStack *pStack, int top) { 1484 | int ii; 1485 | 1486 | for (ii = 0; ii < top; ++ii) { 1487 | MethodEntry *method = pStack->calls[ii].method; 1488 | uint64_t entryTime = pStack->calls[ii].entryTime; 1489 | if (method->methodName) { 1490 | fprintf(stderr, " %2d: %8" PRIu64 " %s.%s %s\n", ii, entryTime, 1491 | method->className, method->methodName, method->signature); 1492 | } else { 1493 | fprintf(stderr, " %2d: %8" PRIu64 " %s\n", ii, entryTime, 1494 | method->className); 1495 | } 1496 | } 1497 | } 1498 | 1499 | void outputTableOfContents() { 1500 | printf("\n"); 1501 | printf("

Table of Contents

\n"); 1502 | printf("\n\n"); 1508 | } 1509 | 1510 | void outputNavigationBar() { 1511 | printf("[Top]\n"); 1512 | printf("[Exclusive]\n"); 1513 | printf("[Inclusive]\n"); 1514 | printf("[Class]\n"); 1515 | printf("[Method]\n"); 1516 | printf("

\n"); 1517 | } 1518 | 1519 | void printExclusiveProfile(MethodEntry **pMethods, int numMethods, 1520 | uint64_t sumThreadTime) { 1521 | int ii; 1522 | MethodEntry *method; 1523 | double total, sum, per, sum_per; 1524 | char classBuf[HTML_BUFSIZE], methodBuf[HTML_BUFSIZE]; 1525 | char signatureBuf[HTML_BUFSIZE]; 1526 | char anchor_buf[80]; 1527 | char *anchor_close = ""; 1528 | 1529 | total = sumThreadTime; 1530 | anchor_buf[0] = 0; 1531 | if (gOptions.outputHtml) { 1532 | anchor_close = ""; 1533 | printf("\n"); 1534 | printf("
\n"); 1535 | outputNavigationBar(); 1536 | } else { 1537 | printf("\n%s\n", profileSeparator); 1538 | } 1539 | 1540 | /* First, sort the methods into decreasing order of inclusive 1541 | * elapsed time so that we can assign the method indices. 1542 | */ 1543 | qsort(pMethods, numMethods, sizeof(MethodEntry *), compareElapsedInclusive); 1544 | 1545 | for (ii = 0; ii < numMethods; ++ii) 1546 | pMethods[ii]->index = ii; 1547 | 1548 | /* Sort the methods into decreasing order of exclusive elapsed time. 1549 | */ 1550 | qsort(pMethods, numMethods, sizeof(MethodEntry *), 1551 | compareElapsedExclusive); 1552 | 1553 | printf("Total cycles: %" PRIu64 "\n\n", sumThreadTime); 1554 | if (gOptions.outputHtml) { 1555 | printf("

\n"); 1556 | } 1557 | printf("Exclusive elapsed times for each method, not including time spent in\n"); 1558 | printf("children, sorted by exclusive time.\n\n"); 1559 | if (gOptions.outputHtml) { 1560 | printf("

\n
\n");
1561 |     }
1562 | 
1563 |     printf("    Usecs  self %%  sum %%  Method\n");
1564 |     sum = 0;
1565 | 
1566 |     for (ii = 0; ii < numMethods; ++ii) {
1567 |         char *className, *methodName, *signature;
1568 | 
1569 |         method = pMethods[ii];
1570 |         /* Don't show methods with zero cycles */
1571 |         if (method->elapsedExclusive == 0)
1572 |             break;
1573 |         className = (char *) (method->className);
1574 |         methodName = (char *) (method->methodName);
1575 |         signature = (char *) (method->signature);
1576 |         sum += method->elapsedExclusive;
1577 |         per = 100.0 * method->elapsedExclusive / total;
1578 |         sum_per = 100.0 * sum / total;
1579 |         if (gOptions.outputHtml) {
1580 |             sprintf(anchor_buf, "", method->index);
1581 |             className = htmlEscape(className, classBuf, HTML_BUFSIZE);
1582 |             methodName = htmlEscape(methodName, methodBuf, HTML_BUFSIZE);
1583 |             signature = htmlEscape(signature, signatureBuf, HTML_BUFSIZE);
1584 |         }
1585 |         if (method->methodName) {
1586 |             printf("%9" PRIu64 "  %6.2f %6.2f  %s[%d]%s %s.%s %s\n",
1587 |                    method->elapsedExclusive, per, sum_per,
1588 |                    anchor_buf, method->index, anchor_close,
1589 |                    className, methodName, signature);
1590 |         } else {
1591 |             printf("%9" PRIu64 "  %6.2f %6.2f  %s[%d]%s %s\n",
1592 |                    method->elapsedExclusive, per, sum_per,
1593 |                    anchor_buf, method->index, anchor_close,
1594 |                    className);
1595 |         }
1596 |     }
1597 |     if (gOptions.outputHtml) {
1598 |         printf("
\n"); 1599 | } 1600 | } 1601 | 1602 | /* check to make sure that the child method meets the threshold of the parent */ 1603 | int checkThreshold(MethodEntry *parent, MethodEntry *child) { 1604 | double parentTime = parent->elapsedInclusive; 1605 | double childTime = child->elapsedInclusive; 1606 | int64_t percentage = (childTime / parentTime) * 100.0; 1607 | return (percentage < gOptions.threshold) ? 0 : 1; 1608 | } 1609 | 1610 | void createLabels(FILE *file, MethodEntry *method) { 1611 | fprintf(file, "node%d[label = \"[%d] %s.%s (%" PRIu64 ", %" PRIu64 ", %d)\"]\n", 1612 | method->index, method->index, method->className, method->methodName, 1613 | method->elapsedInclusive / 1000, 1614 | method->elapsedExclusive / 1000, 1615 | method->numCalls[0]); 1616 | 1617 | method->graphState = GRAPH_LABEL_VISITED; 1618 | 1619 | TimedMethod *child; 1620 | for (child = method->children[0]; child; child = child->next) { 1621 | MethodEntry *childMethod = child->method; 1622 | 1623 | if ((childMethod->graphState & GRAPH_LABEL_VISITED) == 0 && 1624 | checkThreshold(method, childMethod)) { 1625 | createLabels(file, child->method); 1626 | } 1627 | } 1628 | } 1629 | 1630 | void createLinks(FILE *file, MethodEntry *method) { 1631 | method->graphState |= GRAPH_NODE_VISITED; 1632 | 1633 | TimedMethod *child; 1634 | for (child = method->children[0]; child; child = child->next) { 1635 | MethodEntry *childMethod = child->method; 1636 | if (checkThreshold(method, child->method)) { 1637 | fprintf(file, "node%d -> node%d\n", method->index, child->method->index); 1638 | // only visit children that haven't been visited before 1639 | if ((childMethod->graphState & GRAPH_NODE_VISITED) == 0) { 1640 | createLinks(file, child->method); 1641 | } 1642 | } 1643 | } 1644 | } 1645 | 1646 | void createInclusiveProfileGraphNew(DataKeys *dataKeys) { 1647 | 1648 | } 1649 | 1650 | void printInclusiveProfile(MethodEntry **pMethods, int numMethods, 1651 | uint64_t sumThreadTime) { 1652 | int ii; 1653 | MethodEntry *method; 1654 | double total, per; 1655 | char classBuf[HTML_BUFSIZE], methodBuf[HTML_BUFSIZE]; 1656 | char signatureBuf[HTML_BUFSIZE]; 1657 | char anchor_buf[80]; 1658 | char *anchor_close = ""; 1659 | 1660 | total = sumThreadTime; 1661 | anchor_buf[0] = 0; 1662 | if (gOptions.outputHtml) { 1663 | anchor_close = ""; 1664 | printf("\n"); 1665 | printf("
\n"); 1666 | outputNavigationBar(); 1667 | } else { 1668 | printf("\n%s\n", profileSeparator); 1669 | } 1670 | 1671 | /* Sort the methods into decreasing order of inclusive elapsed time. */ 1672 | qsort(pMethods, numMethods, sizeof(MethodEntry *), 1673 | compareElapsedInclusive); 1674 | 1675 | printf("\nInclusive elapsed times for each method and its parents and children,\n"); 1676 | printf("sorted by inclusive time.\n\n"); 1677 | 1678 | if (gOptions.outputHtml) { 1679 | printf("

\n
\n");
1680 |     }
1681 | 
1682 |     printf("index  %%/total %%/self  index     calls         usecs name\n");
1683 |     for (ii = 0; ii < numMethods; ++ii) {
1684 |         double excl_per;
1685 |         char buf[40];
1686 |         char *className, *methodName, *signature;
1687 | 
1688 |         method = pMethods[ii];
1689 |         /* Don't show methods with zero cycles */
1690 |         if (method->elapsedInclusive == 0)
1691 |             break;
1692 | 
1693 |         className = (char *) (method->className);
1694 |         methodName = (char *) (method->methodName);
1695 |         signature = (char *) (method->signature);
1696 | 
1697 |         if (gOptions.outputHtml) {
1698 |             printf("", method->index);
1699 |             className = htmlEscape(className, classBuf, HTML_BUFSIZE);
1700 |             methodName = htmlEscape(methodName, methodBuf, HTML_BUFSIZE);
1701 |             signature = htmlEscape(signature, signatureBuf, HTML_BUFSIZE);
1702 |         }
1703 |         printf("----------------------------------------------------\n");
1704 | 
1705 |         /* Sort and print the parents */
1706 |         int numCalls = method->numCalls[0] + method->numCalls[1];
1707 |         printInclusiveMethod(method, method->parents[0], numCalls, 0);
1708 |         if (method->parents[1]) {
1709 |             printf("               +++++++++++++++++++++++++\n");
1710 |             printInclusiveMethod(method, method->parents[1], numCalls,
1711 |                                  kIsRecursive);
1712 |         }
1713 | 
1714 |         per = 100.0 * method->elapsedInclusive / total;
1715 |         sprintf(buf, "[%d]", ii);
1716 |         if (method->methodName) {
1717 |             printf("%-6s %5.1f%%   %5s %6s %6d+%-6d %9" PRIu64 " %s.%s %s\n",
1718 |                    buf,
1719 |                    per, "", "", method->numCalls[0], method->numCalls[1],
1720 |                    method->elapsedInclusive,
1721 |                    className, methodName, signature);
1722 |         } else {
1723 |             printf("%-6s %5.1f%%   %5s %6s %6d+%-6d %9" PRIu64 " %s\n",
1724 |                    buf,
1725 |                    per, "", "", method->numCalls[0], method->numCalls[1],
1726 |                    method->elapsedInclusive,
1727 |                    className);
1728 |         }
1729 |         excl_per = 100.0 * method->topExclusive / method->elapsedInclusive;
1730 |         printf("%6s %5s   %5.1f%% %6s %6s %6s %9" PRIu64 "\n",
1731 |                "", "", excl_per, "excl", "", "", method->topExclusive);
1732 | 
1733 |         /* Sort and print the children */
1734 |         printInclusiveMethod(method, method->children[0], 0, 0);
1735 |         if (method->children[1]) {
1736 |             printf("               +++++++++++++++++++++++++\n");
1737 |             printInclusiveMethod(method, method->children[1], 0,
1738 |                                  kIsRecursive);
1739 |         }
1740 |     }
1741 |     if (gOptions.outputHtml) {
1742 |         printf("
\n"); 1743 | } 1744 | } 1745 | 1746 | void createClassList(TraceData *traceData, MethodEntry **pMethods, int numMethods) { 1747 | int ii; 1748 | 1749 | /* Sort the methods into alphabetical order to find the unique class 1750 | * names. 1751 | */ 1752 | qsort(pMethods, numMethods, sizeof(MethodEntry *), compareClassNames); 1753 | 1754 | /* Count the number of unique class names. */ 1755 | const char *currentClassName = ""; 1756 | const char *firstClassName = NULL; 1757 | traceData->numClasses = 0; 1758 | for (ii = 0; ii < numMethods; ++ii) { 1759 | if (pMethods[ii]->methodName == NULL) { 1760 | continue; 1761 | } 1762 | if (strcmp(pMethods[ii]->className, currentClassName) != 0) { 1763 | // Remember the first one 1764 | if (firstClassName == NULL) { 1765 | firstClassName = pMethods[ii]->className; 1766 | } 1767 | traceData->numClasses += 1; 1768 | currentClassName = pMethods[ii]->className; 1769 | } 1770 | } 1771 | 1772 | if (traceData->numClasses == 0) { 1773 | traceData->classes = NULL; 1774 | return; 1775 | } 1776 | 1777 | /* Allocate space for all of the unique class names */ 1778 | traceData->classes = (ClassEntry *) malloc(sizeof(ClassEntry) * traceData->numClasses); 1779 | 1780 | /* Initialize the classes array */ 1781 | memset(traceData->classes, 0, sizeof(ClassEntry) * traceData->numClasses); 1782 | ClassEntry *pClass = traceData->classes; 1783 | pClass->className = currentClassName = firstClassName; 1784 | int prevNumMethods = 0; 1785 | for (ii = 0; ii < numMethods; ++ii) { 1786 | if (pMethods[ii]->methodName == NULL) { 1787 | continue; 1788 | } 1789 | if (strcmp(pMethods[ii]->className, currentClassName) != 0) { 1790 | pClass->numMethods = prevNumMethods; 1791 | (++pClass)->className = currentClassName = pMethods[ii]->className; 1792 | prevNumMethods = 0; 1793 | } 1794 | prevNumMethods += 1; 1795 | } 1796 | pClass->numMethods = prevNumMethods; 1797 | 1798 | /* Create the array of MethodEntry pointers for each class */ 1799 | pClass = NULL; 1800 | currentClassName = ""; 1801 | int nextMethod = 0; 1802 | for (ii = 0; ii < numMethods; ++ii) { 1803 | if (pMethods[ii]->methodName == NULL) { 1804 | continue; 1805 | } 1806 | if (strcmp(pMethods[ii]->className, currentClassName) != 0) { 1807 | currentClassName = pMethods[ii]->className; 1808 | if (pClass == NULL) 1809 | pClass = traceData->classes; 1810 | else 1811 | pClass++; 1812 | /* Allocate space for the methods array */ 1813 | int nbytes = sizeof(MethodEntry *) * pClass->numMethods; 1814 | pClass->methods = (MethodEntry **) malloc(nbytes); 1815 | nextMethod = 0; 1816 | } 1817 | pClass->methods[nextMethod++] = pMethods[ii]; 1818 | } 1819 | } 1820 | 1821 | /* Prints a number of html non-breaking spaces according so that the length 1822 | * of the string "buf" is at least "width" characters wide. If width is 1823 | * negative, then trailing spaces are added instead of leading spaces. 1824 | */ 1825 | void printHtmlField(char *buf, int width) { 1826 | int ii; 1827 | 1828 | int leadingSpaces = 1; 1829 | if (width < 0) { 1830 | width = -width; 1831 | leadingSpaces = 0; 1832 | } 1833 | int len = strlen(buf); 1834 | int numSpaces = width - len; 1835 | if (numSpaces <= 0) { 1836 | printf("%s", buf); 1837 | return; 1838 | } 1839 | if (leadingSpaces == 0) 1840 | printf("%s", buf); 1841 | for (ii = 0; ii < numSpaces; ++ii) 1842 | printf(" "); 1843 | if (leadingSpaces == 1) 1844 | printf("%s", buf); 1845 | } 1846 | 1847 | void printClassProfiles(TraceData *traceData, uint64_t sumThreadTime) { 1848 | int ii, jj; 1849 | MethodEntry *method; 1850 | double total, sum, per, sum_per; 1851 | char classBuf[HTML_BUFSIZE], methodBuf[HTML_BUFSIZE]; 1852 | char signatureBuf[HTML_BUFSIZE]; 1853 | 1854 | total = sumThreadTime; 1855 | if (gOptions.outputHtml) { 1856 | printf("\n"); 1857 | printf("
\n"); 1858 | outputNavigationBar(); 1859 | } else { 1860 | printf("\n%s\n", profileSeparator); 1861 | } 1862 | 1863 | if (traceData->numClasses == 0) { 1864 | printf("\nNo classes.\n"); 1865 | if (gOptions.outputHtml) { 1866 | printf("

\n"); 1867 | } 1868 | return; 1869 | } 1870 | 1871 | printf("\nExclusive elapsed time for each class, summed over all the methods\n"); 1872 | printf("in the class.\n\n"); 1873 | if (gOptions.outputHtml) { 1874 | printf("

\n"); 1875 | } 1876 | 1877 | /* For each class, sum the exclusive times in all of the methods 1878 | * in that class. Also sum the number of method calls. Also 1879 | * sort the methods so the most expensive appear at the top. 1880 | */ 1881 | ClassEntry *pClass = traceData->classes; 1882 | for (ii = 0; ii < traceData->numClasses; ++ii, ++pClass) { 1883 | //printf("%s %d methods\n", pClass->className, pClass->numMethods); 1884 | int numMethods = pClass->numMethods; 1885 | for (jj = 0; jj < numMethods; ++jj) { 1886 | method = pClass->methods[jj]; 1887 | pClass->elapsedExclusive += method->elapsedExclusive; 1888 | pClass->numCalls[0] += method->numCalls[0]; 1889 | pClass->numCalls[1] += method->numCalls[1]; 1890 | } 1891 | 1892 | /* Sort the methods into decreasing order of exclusive time */ 1893 | qsort(pClass->methods, numMethods, sizeof(MethodEntry *), 1894 | compareElapsedExclusive); 1895 | } 1896 | 1897 | /* Allocate an array of pointers to the classes for more efficient 1898 | * sorting. 1899 | */ 1900 | ClassEntry **pClasses; 1901 | pClasses = (ClassEntry **) malloc(sizeof(ClassEntry *) * traceData->numClasses); 1902 | for (ii = 0; ii < traceData->numClasses; ++ii) 1903 | pClasses[ii] = &traceData->classes[ii]; 1904 | 1905 | /* Sort the classes into decreasing order of exclusive time */ 1906 | qsort(pClasses, traceData->numClasses, sizeof(ClassEntry *), compareClassExclusive); 1907 | 1908 | if (gOptions.outputHtml) { 1909 | printf("
    "); 1910 | printf("Cycles %%/total Cumul.%%  Calls+Recur  Class
\n"); 1911 | } else { 1912 | printf(" Cycles %%/total Cumul.%% Calls+Recur Class\n"); 1913 | } 1914 | 1915 | sum = 0; 1916 | for (ii = 0; ii < traceData->numClasses; ++ii) { 1917 | char *className, *methodName, *signature; 1918 | 1919 | /* Skip classes with zero cycles */ 1920 | pClass = pClasses[ii]; 1921 | if (pClass->elapsedExclusive == 0) 1922 | break; 1923 | 1924 | per = 100.0 * pClass->elapsedExclusive / total; 1925 | sum += pClass->elapsedExclusive; 1926 | sum_per = 100.0 * sum / total; 1927 | className = (char *) (pClass->className); 1928 | if (gOptions.outputHtml) { 1929 | char buf[80]; 1930 | 1931 | className = htmlEscape(className, classBuf, HTML_BUFSIZE); 1932 | printf("
+", 1933 | ii, ii); 1934 | sprintf(buf, "%" PRIu64, pClass->elapsedExclusive); 1935 | printHtmlField(buf, 9); 1936 | printf(" "); 1937 | sprintf(buf, "%.1f", per); 1938 | printHtmlField(buf, 7); 1939 | printf(" "); 1940 | sprintf(buf, "%.1f", sum_per); 1941 | printHtmlField(buf, 7); 1942 | printf(" "); 1943 | sprintf(buf, "%d", pClass->numCalls[0]); 1944 | printHtmlField(buf, 6); 1945 | printf("+"); 1946 | sprintf(buf, "%d", pClass->numCalls[1]); 1947 | printHtmlField(buf, -6); 1948 | printf(" "); 1949 | printf("%s", className); 1950 | printf("
\n"); 1951 | printf("
\n", ii); 1952 | } else { 1953 | printf("---------------------------------------------\n"); 1954 | printf("%9" PRIu64 " %7.1f %7.1f %6d+%-6d %s\n", 1955 | pClass->elapsedExclusive, per, sum_per, 1956 | pClass->numCalls[0], pClass->numCalls[1], 1957 | className); 1958 | } 1959 | 1960 | int numMethods = pClass->numMethods; 1961 | double classExclusive = pClass->elapsedExclusive; 1962 | double sumMethods = 0; 1963 | for (jj = 0; jj < numMethods; ++jj) { 1964 | method = pClass->methods[jj]; 1965 | methodName = (char *) (method->methodName); 1966 | signature = (char *) (method->signature); 1967 | per = 100.0 * method->elapsedExclusive / classExclusive; 1968 | sumMethods += method->elapsedExclusive; 1969 | sum_per = 100.0 * sumMethods / classExclusive; 1970 | if (gOptions.outputHtml) { 1971 | char buf[80]; 1972 | 1973 | methodName = htmlEscape(methodName, methodBuf, HTML_BUFSIZE); 1974 | signature = htmlEscape(signature, signatureBuf, HTML_BUFSIZE); 1975 | printf("
 "); 1976 | sprintf(buf, "%" PRIu64, method->elapsedExclusive); 1977 | printHtmlField(buf, 9); 1978 | printf(" "); 1979 | sprintf(buf, "%" PRIu64, method->elapsedInclusive); 1980 | printHtmlField(buf, 9); 1981 | printf(" "); 1982 | sprintf(buf, "%.1f", per); 1983 | printHtmlField(buf, 7); 1984 | printf(" "); 1985 | sprintf(buf, "%.1f", sum_per); 1986 | printHtmlField(buf, 7); 1987 | printf(" "); 1988 | sprintf(buf, "%d", method->numCalls[0]); 1989 | printHtmlField(buf, 6); 1990 | printf("+"); 1991 | sprintf(buf, "%d", method->numCalls[1]); 1992 | printHtmlField(buf, -6); 1993 | printf(" "); 1994 | printf("[%d] %s %s", 1995 | method->index, method->index, methodName, signature); 1996 | printf("
\n"); 1997 | } else { 1998 | printf("%9" PRIu64 " %9" PRIu64 " %7.1f %7.1f %6d+%-6d [%d] %s %s\n", 1999 | method->elapsedExclusive, 2000 | method->elapsedInclusive, 2001 | per, sum_per, 2002 | method->numCalls[0], method->numCalls[1], 2003 | method->index, methodName, signature); 2004 | } 2005 | } 2006 | if (gOptions.outputHtml) { 2007 | printf("
\n"); 2008 | } 2009 | } 2010 | } 2011 | 2012 | void createUniqueMethodList(TraceData *traceData, MethodEntry **pMethods, int numMethods) { 2013 | int ii; 2014 | 2015 | /* Sort the methods into alphabetical order of method names 2016 | * to find the unique method names. 2017 | */ 2018 | qsort(pMethods, numMethods, sizeof(MethodEntry *), compareMethodNames); 2019 | 2020 | /* Count the number of unique method names, ignoring class and 2021 | * signature. 2022 | */ 2023 | const char *currentMethodName = ""; 2024 | traceData->numUniqueMethods = 0; 2025 | for (ii = 0; ii < numMethods; ++ii) { 2026 | if (pMethods[ii]->methodName == NULL) 2027 | continue; 2028 | if (strcmp(pMethods[ii]->methodName, currentMethodName) != 0) { 2029 | traceData->numUniqueMethods += 1; 2030 | currentMethodName = pMethods[ii]->methodName; 2031 | } 2032 | } 2033 | if (traceData->numUniqueMethods == 0) 2034 | return; 2035 | 2036 | /* Allocate space for pointers to all of the unique methods */ 2037 | int nbytes = sizeof(UniqueMethodEntry) * traceData->numUniqueMethods; 2038 | traceData->uniqueMethods = (UniqueMethodEntry *) malloc(nbytes); 2039 | 2040 | /* Initialize the uniqueMethods array */ 2041 | memset(traceData->uniqueMethods, 0, nbytes); 2042 | UniqueMethodEntry *pUnique = traceData->uniqueMethods; 2043 | currentMethodName = NULL; 2044 | int prevNumMethods = 0; 2045 | for (ii = 0; ii < numMethods; ++ii) { 2046 | if (pMethods[ii]->methodName == NULL) 2047 | continue; 2048 | if (currentMethodName == NULL) 2049 | currentMethodName = pMethods[ii]->methodName; 2050 | if (strcmp(pMethods[ii]->methodName, currentMethodName) != 0) { 2051 | currentMethodName = pMethods[ii]->methodName; 2052 | pUnique->numMethods = prevNumMethods; 2053 | pUnique++; 2054 | prevNumMethods = 0; 2055 | } 2056 | prevNumMethods += 1; 2057 | } 2058 | pUnique->numMethods = prevNumMethods; 2059 | 2060 | /* Create the array of MethodEntry pointers for each unique method */ 2061 | pUnique = NULL; 2062 | currentMethodName = ""; 2063 | int nextMethod = 0; 2064 | for (ii = 0; ii < numMethods; ++ii) { 2065 | if (pMethods[ii]->methodName == NULL) 2066 | continue; 2067 | if (strcmp(pMethods[ii]->methodName, currentMethodName) != 0) { 2068 | currentMethodName = pMethods[ii]->methodName; 2069 | if (pUnique == NULL) 2070 | pUnique = traceData->uniqueMethods; 2071 | else 2072 | pUnique++; 2073 | /* Allocate space for the methods array */ 2074 | int nbytes = sizeof(MethodEntry *) * pUnique->numMethods; 2075 | pUnique->methods = (MethodEntry **) malloc(nbytes); 2076 | nextMethod = 0; 2077 | } 2078 | pUnique->methods[nextMethod++] = pMethods[ii]; 2079 | } 2080 | } 2081 | 2082 | void printMethodProfiles(TraceData *traceData, uint64_t sumThreadTime) { 2083 | int ii, jj; 2084 | MethodEntry *method; 2085 | double total, sum, per, sum_per; 2086 | char classBuf[HTML_BUFSIZE], methodBuf[HTML_BUFSIZE]; 2087 | char signatureBuf[HTML_BUFSIZE]; 2088 | 2089 | if (traceData->numUniqueMethods == 0) 2090 | return; 2091 | 2092 | total = sumThreadTime; 2093 | if (gOptions.outputHtml) { 2094 | printf("\n"); 2095 | printf("
\n"); 2096 | outputNavigationBar(); 2097 | } else { 2098 | printf("\n%s\n", profileSeparator); 2099 | } 2100 | 2101 | printf("\nExclusive elapsed time for each method, summed over all the classes\n"); 2102 | printf("that contain a method with the same name.\n\n"); 2103 | if (gOptions.outputHtml) { 2104 | printf("

\n"); 2105 | } 2106 | 2107 | /* For each unique method, sum the exclusive times in all of the methods 2108 | * with the same name. Also sum the number of method calls. Also 2109 | * sort the methods so the most expensive appear at the top. 2110 | */ 2111 | UniqueMethodEntry *pUnique = traceData->uniqueMethods; 2112 | for (ii = 0; ii < traceData->numUniqueMethods; ++ii, ++pUnique) { 2113 | int numMethods = pUnique->numMethods; 2114 | for (jj = 0; jj < numMethods; ++jj) { 2115 | method = pUnique->methods[jj]; 2116 | pUnique->elapsedExclusive += method->elapsedExclusive; 2117 | pUnique->numCalls[0] += method->numCalls[0]; 2118 | pUnique->numCalls[1] += method->numCalls[1]; 2119 | } 2120 | 2121 | /* Sort the methods into decreasing order of exclusive time */ 2122 | qsort(pUnique->methods, numMethods, sizeof(MethodEntry *), 2123 | compareElapsedExclusive); 2124 | } 2125 | 2126 | /* Allocate an array of pointers to the methods for more efficient 2127 | * sorting. 2128 | */ 2129 | UniqueMethodEntry **pUniqueMethods; 2130 | int nbytes = sizeof(UniqueMethodEntry *) * traceData->numUniqueMethods; 2131 | pUniqueMethods = (UniqueMethodEntry **) malloc(nbytes); 2132 | for (ii = 0; ii < traceData->numUniqueMethods; ++ii) 2133 | pUniqueMethods[ii] = &traceData->uniqueMethods[ii]; 2134 | 2135 | /* Sort the methods into decreasing order of exclusive time */ 2136 | qsort(pUniqueMethods, traceData->numUniqueMethods, sizeof(UniqueMethodEntry *), 2137 | compareUniqueExclusive); 2138 | 2139 | if (gOptions.outputHtml) { 2140 | printf("
    "); 2141 | printf("Cycles %%/total Cumul.%%  Calls+Recur  Method
\n"); 2142 | } else { 2143 | printf(" Cycles %%/total Cumul.%% Calls+Recur Method\n"); 2144 | } 2145 | 2146 | sum = 0; 2147 | for (ii = 0; ii < traceData->numUniqueMethods; ++ii) { 2148 | char *className, *methodName, *signature; 2149 | 2150 | /* Skip methods with zero cycles */ 2151 | pUnique = pUniqueMethods[ii]; 2152 | if (pUnique->elapsedExclusive == 0) 2153 | break; 2154 | 2155 | per = 100.0 * pUnique->elapsedExclusive / total; 2156 | sum += pUnique->elapsedExclusive; 2157 | sum_per = 100.0 * sum / total; 2158 | methodName = (char *) (pUnique->methods[0]->methodName); 2159 | if (gOptions.outputHtml) { 2160 | char buf[80]; 2161 | 2162 | methodName = htmlEscape(methodName, methodBuf, HTML_BUFSIZE); 2163 | printf("
+", 2164 | ii, ii); 2165 | sprintf(buf, "%" PRIu64, pUnique->elapsedExclusive); 2166 | printHtmlField(buf, 9); 2167 | printf(" "); 2168 | sprintf(buf, "%.1f", per); 2169 | printHtmlField(buf, 7); 2170 | printf(" "); 2171 | sprintf(buf, "%.1f", sum_per); 2172 | printHtmlField(buf, 7); 2173 | printf(" "); 2174 | sprintf(buf, "%d", pUnique->numCalls[0]); 2175 | printHtmlField(buf, 6); 2176 | printf("+"); 2177 | sprintf(buf, "%d", pUnique->numCalls[1]); 2178 | printHtmlField(buf, -6); 2179 | printf(" "); 2180 | printf("%s", methodName); 2181 | printf("
\n"); 2182 | printf("
\n", ii); 2183 | } else { 2184 | printf("---------------------------------------------\n"); 2185 | printf("%9" PRIu64 " %7.1f %7.1f %6d+%-6d %s\n", 2186 | pUnique->elapsedExclusive, per, sum_per, 2187 | pUnique->numCalls[0], pUnique->numCalls[1], 2188 | methodName); 2189 | } 2190 | int numMethods = pUnique->numMethods; 2191 | double methodExclusive = pUnique->elapsedExclusive; 2192 | double sumMethods = 0; 2193 | for (jj = 0; jj < numMethods; ++jj) { 2194 | method = pUnique->methods[jj]; 2195 | className = (char *) (method->className); 2196 | signature = (char *) (method->signature); 2197 | per = 100.0 * method->elapsedExclusive / methodExclusive; 2198 | sumMethods += method->elapsedExclusive; 2199 | sum_per = 100.0 * sumMethods / methodExclusive; 2200 | if (gOptions.outputHtml) { 2201 | char buf[80]; 2202 | 2203 | className = htmlEscape(className, classBuf, HTML_BUFSIZE); 2204 | signature = htmlEscape(signature, signatureBuf, HTML_BUFSIZE); 2205 | printf("
 "); 2206 | sprintf(buf, "%" PRIu64, method->elapsedExclusive); 2207 | printHtmlField(buf, 9); 2208 | printf(" "); 2209 | sprintf(buf, "%" PRIu64, method->elapsedInclusive); 2210 | printHtmlField(buf, 9); 2211 | printf(" "); 2212 | sprintf(buf, "%.1f", per); 2213 | printHtmlField(buf, 7); 2214 | printf(" "); 2215 | sprintf(buf, "%.1f", sum_per); 2216 | printHtmlField(buf, 7); 2217 | printf(" "); 2218 | sprintf(buf, "%d", method->numCalls[0]); 2219 | printHtmlField(buf, 6); 2220 | printf("+"); 2221 | sprintf(buf, "%d", method->numCalls[1]); 2222 | printHtmlField(buf, -6); 2223 | printf(" "); 2224 | printf("[%d] %s.%s %s", 2225 | method->index, method->index, 2226 | className, methodName, signature); 2227 | printf("
\n"); 2228 | } else { 2229 | printf("%9" PRIu64 " %9" PRIu64 " %7.1f %7.1f %6d+%-6d [%d] %s.%s %s\n", 2230 | method->elapsedExclusive, 2231 | method->elapsedInclusive, 2232 | per, sum_per, 2233 | method->numCalls[0], method->numCalls[1], 2234 | method->index, className, methodName, signature); 2235 | } 2236 | } 2237 | if (gOptions.outputHtml) { 2238 | printf("
\n"); 2239 | } 2240 | } 2241 | } 2242 | 2243 | /* 2244 | * Read the key and data files and return the MethodEntries for those files 2245 | */ 2246 | DataKeys *parseDataKeys(TraceData *traceData, const char *traceFileName, uint64_t *threadTime) { 2247 | DataKeys *dataKeys = NULL; 2248 | MethodEntry *method; 2249 | FILE *dataFp = NULL; 2250 | DataHeader dataHeader; 2251 | int ii; 2252 | uint64_t currentTime; 2253 | MethodEntry *caller; 2254 | 2255 | dataFp = fopen(traceFileName, "rb"); 2256 | if (dataFp == NULL) 2257 | goto bail; 2258 | 2259 | if ((dataKeys = parseKeys(dataFp, 0)) == NULL) 2260 | goto bail; 2261 | 2262 | if (parseDataHeader(dataFp, &dataHeader) < 0) 2263 | goto bail; 2264 | 2265 | #if 0 2266 | FILE *dumpStream = fopen("debug", "w"); 2267 | #endif 2268 | while (1) { 2269 | int threadId; 2270 | unsigned int methodVal; 2271 | int action; 2272 | int64_t methodId; 2273 | CallStack *pStack; 2274 | /* 2275 | * Extract values from file. 2276 | */ 2277 | if (readDataRecord(dataFp, &dataHeader, &threadId, &methodVal, ¤tTime)) 2278 | break; 2279 | 2280 | action = METHOD_ACTION(methodVal); 2281 | methodId = METHOD_ID(methodVal); 2282 | 2283 | /* Get the call stack for this thread */ 2284 | pStack = traceData->stacks[threadId]; 2285 | 2286 | /* If there is no call stack yet for this thread, then allocate one */ 2287 | if (pStack == NULL) { 2288 | pStack = malloc(sizeof(CallStack)); 2289 | pStack->top = 0; 2290 | pStack->lastEventTime = currentTime; 2291 | pStack->threadStartTime = currentTime; 2292 | traceData->stacks[threadId] = pStack; 2293 | } 2294 | 2295 | /* Lookup the current method */ 2296 | method = lookupMethod(dataKeys, methodId); 2297 | if (method == NULL) 2298 | method = &dataKeys->methods[UNKNOWN_INDEX]; 2299 | 2300 | #if 0 2301 | if (method->methodName) { 2302 | fprintf(dumpStream, "%2d %-8llu %d %8llu r %d c %d %s.%s %s\n", 2303 | threadId, currentTime, action, pStack->threadStartTime, 2304 | method->recursiveEntries, 2305 | pStack->top, method->className, method->methodName, 2306 | method->signature); 2307 | } else { 2308 | fprintf(dumpStream, "%2d %-8llu %d %8llu r %d c %d %s\n", 2309 | threadId, currentTime, action, pStack->threadStartTime, 2310 | method->recursiveEntries, 2311 | pStack->top, method->className); 2312 | } 2313 | #endif 2314 | 2315 | if (action == METHOD_TRACE_ENTER) { 2316 | /* This is a method entry */ 2317 | if (pStack->top >= MAX_STACK_DEPTH) { 2318 | fprintf(stderr, "Stack overflow (exceeded %d frames)\n", 2319 | MAX_STACK_DEPTH); 2320 | exit(1); 2321 | } 2322 | 2323 | /* Get the caller method */ 2324 | if (pStack->top >= 1) 2325 | caller = pStack->calls[pStack->top - 1].method; 2326 | else 2327 | caller = &dataKeys->methods[TOPLEVEL_INDEX]; 2328 | countRecursiveEntries(pStack, pStack->top, caller); 2329 | caller->elapsedExclusive += currentTime - pStack->lastEventTime; 2330 | #if 0 2331 | if (caller->elapsedExclusive > 10000000) 2332 | fprintf(dumpStream, "%llu current %llu last %llu diff %llu\n", 2333 | caller->elapsedExclusive, currentTime, 2334 | pStack->lastEventTime, 2335 | currentTime - pStack->lastEventTime); 2336 | #endif 2337 | if (caller->recursiveEntries <= 1) { 2338 | caller->topExclusive += currentTime - pStack->lastEventTime; 2339 | } 2340 | 2341 | /* Push the method on the stack for this thread */ 2342 | pStack->calls[pStack->top].method = method; 2343 | pStack->calls[pStack->top++].entryTime = currentTime; 2344 | } else { 2345 | /* This is a method exit */ 2346 | uint64_t entryTime = 0; 2347 | 2348 | /* Pop the method off the stack for this thread */ 2349 | if (pStack->top > 0) { 2350 | pStack->top -= 1; 2351 | entryTime = pStack->calls[pStack->top].entryTime; 2352 | if (method != pStack->calls[pStack->top].method) { 2353 | if (method->methodName) { 2354 | fprintf(stderr, 2355 | "Exit from method %s.%s %s does not match stack:\n", 2356 | method->className, method->methodName, 2357 | method->signature); 2358 | } else { 2359 | fprintf(stderr, 2360 | "Exit from method %s does not match stack:\n", 2361 | method->className); 2362 | } 2363 | stackDump(pStack, pStack->top + 1); 2364 | exit(1); 2365 | } 2366 | } 2367 | 2368 | /* Get the caller method */ 2369 | if (pStack->top >= 1) 2370 | caller = pStack->calls[pStack->top - 1].method; 2371 | else 2372 | caller = &dataKeys->methods[TOPLEVEL_INDEX]; 2373 | countRecursiveEntries(pStack, pStack->top, caller); 2374 | countRecursiveEntries(pStack, pStack->top, method); 2375 | uint64_t elapsed = currentTime - entryTime; 2376 | addInclusiveTime(caller, method, elapsed); 2377 | method->elapsedExclusive += currentTime - pStack->lastEventTime; 2378 | if (method->recursiveEntries == 0) { 2379 | method->topExclusive += currentTime - pStack->lastEventTime; 2380 | } 2381 | } 2382 | /* Remember the time of the last entry or exit event */ 2383 | pStack->lastEventTime = currentTime; 2384 | } 2385 | 2386 | /* If we have calls on the stack when the trace ends, then clean 2387 | * up the stack and add time to the callers by pretending that we 2388 | * are exiting from their methods now. 2389 | */ 2390 | CallStack *pStack; 2391 | int threadId; 2392 | uint64_t sumThreadTime = 0; 2393 | for (threadId = 0; threadId < MAX_THREADS; ++threadId) { 2394 | pStack = traceData->stacks[threadId]; 2395 | 2396 | /* If this thread never existed, then continue with next thread */ 2397 | if (pStack == NULL) 2398 | continue; 2399 | 2400 | /* Also, add up the time taken by all of the threads */ 2401 | sumThreadTime += pStack->lastEventTime - pStack->threadStartTime; 2402 | 2403 | for (ii = 0; ii < pStack->top; ++ii) { 2404 | if (ii == 0) 2405 | caller = &dataKeys->methods[TOPLEVEL_INDEX]; 2406 | else 2407 | caller = pStack->calls[ii - 1].method; 2408 | method = pStack->calls[ii].method; 2409 | countRecursiveEntries(pStack, ii, caller); 2410 | countRecursiveEntries(pStack, ii, method); 2411 | 2412 | uint64_t entryTime = pStack->calls[ii].entryTime; 2413 | uint64_t elapsed = pStack->lastEventTime - entryTime; 2414 | addInclusiveTime(caller, method, elapsed); 2415 | } 2416 | } 2417 | caller = &dataKeys->methods[TOPLEVEL_INDEX]; 2418 | caller->elapsedInclusive = sumThreadTime; 2419 | 2420 | #if 0 2421 | fclose(dumpStream); 2422 | #endif 2423 | 2424 | if (threadTime != NULL) { 2425 | *threadTime = sumThreadTime; 2426 | } 2427 | 2428 | bail: 2429 | if (dataFp != NULL) 2430 | fclose(dataFp); 2431 | 2432 | return dataKeys; 2433 | } 2434 | 2435 | MethodEntry **parseMethodEntries(DataKeys *dataKeys) { 2436 | int ii; 2437 | /* Create a new array of pointers to the methods and sort the pointers 2438 | * instead of the actual MethodEntry structs. We need to do this 2439 | * because there are other lists that contain pointers to the 2440 | * MethodEntry structs. 2441 | */ 2442 | MethodEntry **pMethods = (MethodEntry **) malloc(sizeof(MethodEntry *) * dataKeys->numMethods); 2443 | for (ii = 0; ii < dataKeys->numMethods; ++ii) { 2444 | MethodEntry *entry = &dataKeys->methods[ii]; 2445 | pMethods[ii] = entry; 2446 | } 2447 | 2448 | return pMethods; 2449 | } 2450 | 2451 | /* 2452 | * Produce a function profile from the following methods 2453 | */ 2454 | void profileTrace(TraceData *traceData, MethodEntry **pMethods, int numMethods, 2455 | uint64_t sumThreadTime) { 2456 | /* Print the html header, if necessary */ 2457 | if (gOptions.outputHtml) { 2458 | printf(htmlHeader, gOptions.sortableUrl); 2459 | outputTableOfContents(); 2460 | } 2461 | 2462 | printExclusiveProfile(pMethods, numMethods, sumThreadTime); 2463 | printInclusiveProfile(pMethods, numMethods, sumThreadTime); 2464 | 2465 | createClassList(traceData, pMethods, numMethods); 2466 | printClassProfiles(traceData, sumThreadTime); 2467 | 2468 | createUniqueMethodList(traceData, pMethods, numMethods); 2469 | printMethodProfiles(traceData, sumThreadTime); 2470 | 2471 | if (gOptions.outputHtml) { 2472 | printf("%s", htmlFooter); 2473 | } 2474 | } 2475 | 2476 | int compareMethodNamesForDiff(const void *a, const void *b) { 2477 | int result; 2478 | 2479 | const MethodEntry *methodA = *(const MethodEntry **) a; 2480 | const MethodEntry *methodB = *(const MethodEntry **) b; 2481 | if (methodA->methodName == NULL || methodB->methodName == NULL) { 2482 | return compareClassNames(a, b); 2483 | } 2484 | result = strcmp(methodA->methodName, methodB->methodName); 2485 | if (result == 0) { 2486 | result = strcmp(methodA->signature, methodB->signature); 2487 | if (result == 0) { 2488 | return strcmp(methodA->className, methodB->className); 2489 | } 2490 | } 2491 | return result; 2492 | } 2493 | 2494 | int findMatch(MethodEntry **methods, int size, MethodEntry *matchThis) { 2495 | int i; 2496 | 2497 | for (i = 0; i < size; i++) { 2498 | MethodEntry *method = methods[i]; 2499 | 2500 | if (method != NULL && !compareMethodNamesForDiff(&method, &matchThis)) { 2501 | // printf("%s.%s == %s.%s
\n", matchThis->className, matchThis->methodName, 2502 | // method->className, method->methodName); 2503 | 2504 | return i; 2505 | /* if (!compareMethodNames(&method, &matchThis)) { 2506 | return i; 2507 | } 2508 | */ } 2509 | } 2510 | 2511 | return -1; 2512 | } 2513 | 2514 | int compareDiffEntriesExculsive(const void *a, const void *b) { 2515 | const DiffEntry *entryA = (const DiffEntry *) a; 2516 | const DiffEntry *entryB = (const DiffEntry *) b; 2517 | 2518 | if (entryA->differenceExclusive < entryB->differenceExclusive) { 2519 | return 1; 2520 | } else if (entryA->differenceExclusive > entryB->differenceExclusive) { 2521 | return -1; 2522 | } 2523 | 2524 | return 0; 2525 | } 2526 | 2527 | int compareDiffEntriesInculsive(const void *a, const void *b) { 2528 | const DiffEntry *entryA = (const DiffEntry *) a; 2529 | const DiffEntry *entryB = (const DiffEntry *) b; 2530 | 2531 | if (entryA->differenceInclusive < entryB->differenceInclusive) { 2532 | return 1; 2533 | } else if (entryA->differenceInclusive > entryB->differenceInclusive) { 2534 | return -1; 2535 | } 2536 | 2537 | return 0; 2538 | } 2539 | 2540 | void printMissingMethod(MethodEntry *method) { 2541 | char classBuf[HTML_BUFSIZE]; 2542 | char methodBuf[HTML_BUFSIZE]; 2543 | char *className; 2544 | char *methodName; 2545 | 2546 | className = htmlEscape(method->className, classBuf, HTML_BUFSIZE); 2547 | methodName = htmlEscape(method->methodName, methodBuf, HTML_BUFSIZE); 2548 | 2549 | if (gOptions.outputHtml) printf("\n"); 2671 | 2672 | ptr++; 2673 | } 2674 | 2675 | if (gOptions.outputHtml) printf("
MethodExclusiveInclusive# calls
\n"); 2550 | 2551 | printf("%s.%s ", className, methodName); 2552 | if (gOptions.outputHtml) printf(""); 2553 | 2554 | printf("%" PRIu64 " ", method->elapsedExclusive); 2555 | if (gOptions.outputHtml) printf(""); 2556 | 2557 | printf("%" PRIu64 " ", method->elapsedInclusive); 2558 | if (gOptions.outputHtml) printf(""); 2559 | 2560 | printf("%d\n", method->numCalls[0]); 2561 | if (gOptions.outputHtml) printf("\n"); 2562 | } 2563 | 2564 | 2565 | void createDiff(DataKeys *d1, uint64_t sum1 __unused, 2566 | DataKeys *d2, uint64_t sum2 __unused) { 2567 | MethodEntry **methods1 = parseMethodEntries(d1); 2568 | MethodEntry **methods2 = parseMethodEntries(d2); 2569 | 2570 | // sort and assign the indicies 2571 | int i; 2572 | qsort(methods1, d1->numMethods, sizeof(MethodEntry *), compareElapsedInclusive); 2573 | for (i = 0; i < d1->numMethods; ++i) { 2574 | methods1[i]->index = i; 2575 | } 2576 | 2577 | qsort(methods2, d2->numMethods, sizeof(MethodEntry *), compareElapsedInclusive); 2578 | for (i = 0; i < d2->numMethods; ++i) { 2579 | methods2[i]->index = i; 2580 | } 2581 | 2582 | int max = (d1->numMethods < d2->numMethods) ? d2->numMethods : d1->numMethods; 2583 | max++; 2584 | DiffEntry *diffs = (DiffEntry *) malloc(max * sizeof(DiffEntry)); 2585 | memset(diffs, 0, max * sizeof(DiffEntry)); 2586 | DiffEntry *ptr = diffs; 2587 | 2588 | // printf("
d1->numMethods: %d d1->numMethods: %d
\n", d1->numMethods, d2->numMethods); 2589 | 2590 | int matches = 0; 2591 | 2592 | for (i = 0; i < d1->numMethods; i++) { 2593 | int match = findMatch(methods2, d2->numMethods, methods1[i]); 2594 | if (match >= 0) { 2595 | ptr->method1 = methods1[i]; 2596 | ptr->method2 = methods2[match]; 2597 | 2598 | uint64_t e1 = ptr->method1->elapsedExclusive; 2599 | uint64_t e2 = ptr->method2->elapsedExclusive; 2600 | if (e1 > 0) { 2601 | ptr->differenceExclusive = e2 - e1; 2602 | ptr->differenceExclusivePercentage = ((double) e2 / (double) e1) * 100.0; 2603 | } 2604 | 2605 | uint64_t i1 = ptr->method1->elapsedInclusive; 2606 | uint64_t i2 = ptr->method2->elapsedInclusive; 2607 | if (i1 > 0) { 2608 | ptr->differenceInclusive = i2 - i1; 2609 | ptr->differenceInclusivePercentage = ((double) i2 / (double) i1) * 100.0; 2610 | } 2611 | 2612 | // clear these out so we don't find them again and we know which ones 2613 | // we have left over 2614 | methods1[i] = NULL; 2615 | methods2[match] = NULL; 2616 | ptr++; 2617 | 2618 | matches++; 2619 | } 2620 | } 2621 | ptr->method1 = NULL; 2622 | ptr->method2 = NULL; 2623 | 2624 | qsort(diffs, matches, sizeof(DiffEntry), compareDiffEntriesExculsive); 2625 | ptr = diffs; 2626 | 2627 | if (gOptions.outputHtml) { 2628 | printf(htmlHeader, gOptions.sortableUrl); 2629 | printf("

Table of Contents

\n"); 2630 | printf("\n"); 2634 | printf("Run 1: %s
\n", gOptions.diffFileName); 2635 | printf("Run 2: %s
\n", gOptions.traceFileName); 2636 | printf("

Exclusive

\n"); 2637 | printf(tableHeader, "exclusive_table"); 2638 | } 2639 | 2640 | char classBuf[HTML_BUFSIZE]; 2641 | char methodBuf[HTML_BUFSIZE]; 2642 | char *className; 2643 | char *methodName; 2644 | 2645 | while (ptr->method1 != NULL && ptr->method2 != NULL) { 2646 | if (gOptions.outputHtml) printf("
\n"); 2647 | 2648 | className = htmlEscape(ptr->method1->className, classBuf, HTML_BUFSIZE); 2649 | methodName = htmlEscape(ptr->method1->methodName, methodBuf, HTML_BUFSIZE); 2650 | 2651 | printf("%s.%s ", className, methodName); 2652 | if (gOptions.outputHtml) printf(""); 2653 | 2654 | printf("%" PRIu64 " ", ptr->method1->elapsedExclusive); 2655 | if (gOptions.outputHtml) printf(""); 2656 | 2657 | printf("%" PRIu64 " ", ptr->method2->elapsedExclusive); 2658 | if (gOptions.outputHtml) printf(""); 2659 | 2660 | printf("%" PRIu64 " ", ptr->differenceExclusive); 2661 | if (gOptions.outputHtml) printf(""); 2662 | 2663 | printf("%.2f\n", ptr->differenceExclusivePercentage); 2664 | if (gOptions.outputHtml) printf("\n"); 2665 | 2666 | printf("%d\n", ptr->method1->numCalls[0]); 2667 | if (gOptions.outputHtml) printf("\n"); 2668 | 2669 | printf("%d\n", ptr->method2->numCalls[0]); 2670 | if (gOptions.outputHtml) printf("
\n"); 2676 | 2677 | if (gOptions.outputHtml) { 2678 | printf(htmlHeader, gOptions.sortableUrl); 2679 | printf("Run 1: %s
\n", gOptions.diffFileName); 2680 | printf("Run 2: %s
\n", gOptions.traceFileName); 2681 | printf("

Inclusive

\n"); 2682 | printf(tableHeader, "inclusive_table"); 2683 | } 2684 | 2685 | qsort(diffs, matches, sizeof(DiffEntry), compareDiffEntriesInculsive); 2686 | ptr = diffs; 2687 | 2688 | while (ptr->method1 != NULL && ptr->method2 != NULL) { 2689 | if (gOptions.outputHtml) printf("\n"); 2690 | 2691 | className = htmlEscape(ptr->method1->className, classBuf, HTML_BUFSIZE); 2692 | methodName = htmlEscape(ptr->method1->methodName, methodBuf, HTML_BUFSIZE); 2693 | 2694 | printf("%s.%s ", className, methodName); 2695 | if (gOptions.outputHtml) printf(""); 2696 | 2697 | printf("%" PRIu64 " ", ptr->method1->elapsedInclusive); 2698 | if (gOptions.outputHtml) printf(""); 2699 | 2700 | printf("%" PRIu64 " ", ptr->method2->elapsedInclusive); 2701 | if (gOptions.outputHtml) printf(""); 2702 | 2703 | printf("%" PRIu64 " ", ptr->differenceInclusive); 2704 | if (gOptions.outputHtml) printf(""); 2705 | 2706 | printf("%.2f\n", ptr->differenceInclusivePercentage); 2707 | if (gOptions.outputHtml) printf("\n"); 2708 | 2709 | printf("%d\n", ptr->method1->numCalls[0]); 2710 | if (gOptions.outputHtml) printf("\n"); 2711 | 2712 | printf("%d\n", ptr->method2->numCalls[0]); 2713 | if (gOptions.outputHtml) printf("\n"); 2714 | 2715 | ptr++; 2716 | } 2717 | 2718 | if (gOptions.outputHtml) { 2719 | printf("\n"); 2720 | printf("

Run 1 methods not found in Run 2

"); 2721 | printf(tableHeaderMissing, "?"); 2722 | } 2723 | 2724 | for (i = 0; i < d1->numMethods; ++i) { 2725 | if (methods1[i] != NULL) { 2726 | printMissingMethod(methods1[i]); 2727 | } 2728 | } 2729 | 2730 | if (gOptions.outputHtml) { 2731 | printf("\n"); 2732 | printf("

Run 2 methods not found in Run 1

"); 2733 | printf(tableHeaderMissing, "?"); 2734 | } 2735 | 2736 | for (i = 0; i < d2->numMethods; ++i) { 2737 | if (methods2[i] != NULL) { 2738 | printMissingMethod(methods2[i]); 2739 | } 2740 | } 2741 | 2742 | if (gOptions.outputHtml) printf("numMethods, sumThreadTime); 2843 | if (gOptions.graphFileName != NULL) { 2844 | createInclusiveProfileGraphNew(dataKeys); 2845 | } 2846 | free(methods); 2847 | } 2848 | 2849 | freeDataKeys(dataKeys); 2850 | 2851 | return 0; 2852 | } 2853 | 2854 | /** 2855 | * 2856 | */ 2857 | JNIEXPORT jint JNICALL 2858 | Java_dodola_watcher_utils_TraceUtils_analysisTraceFile(JNIEnv *env, jclass type, jstring path_) { 2859 | const char *path = (*env)->GetStringUTFChars(env, path_, 0); 2860 | gOptions.outputHtml = 1;//默认打开html输出 2861 | 2862 | char *end = ".html"; 2863 | char *outputPath = (char *) malloc(1 + strlen(path) + strlen(end));//.. 2864 | strcpy(outputPath, path); 2865 | strcat(outputPath, end); 2866 | 2867 | //查看output文件是否存在,存在即删除 2868 | int status = remove(outputPath); 2869 | 2870 | //重定向流 2871 | if (freopen(outputPath, "w", stdout) == NULL)//error("重定向出错"); 2872 | { 2873 | return -1; 2874 | } 2875 | 2876 | gOptions.traceFileName = path; 2877 | gOptions.threshold = 20; 2878 | 2879 | uint64_t sumThreadTime = 0; 2880 | 2881 | TraceData data1; 2882 | DataKeys *dataKeys = parseDataKeys(&data1, gOptions.traceFileName, 2883 | &sumThreadTime); 2884 | if (dataKeys == NULL) { 2885 | fprintf(stderr, "Cannot read \"%s\".\n", gOptions.traceFileName); 2886 | exit(1); 2887 | } 2888 | 2889 | MethodEntry **methods = parseMethodEntries(dataKeys); 2890 | profileTrace(&data1, methods, dataKeys->numMethods, sumThreadTime); 2891 | 2892 | free(methods); 2893 | freeDataKeys(dataKeys); 2894 | 2895 | 2896 | (*env)->ReleaseStringUTFChars(env, path_, path); 2897 | return 0; 2898 | } -------------------------------------------------------------------------------- /tracetohtml/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tracetohtml/src/main/java/dodola/traceutil/MainActivity.java: -------------------------------------------------------------------------------- 1 | package dodola.traceutil; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.os.AsyncTask; 7 | import android.os.Bundle; 8 | import android.support.v7.app.AppCompatActivity; 9 | 10 | import java.io.File; 11 | 12 | import dodola.watcher.utils.TraceUtils; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | 16 | private ProgressDialog progressDialog; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_main); 22 | progressDialog = new ProgressDialog(this); 23 | 24 | Intent intent = getIntent(); 25 | String action = intent.getAction(); 26 | String type = intent.getType(); 27 | 28 | if (Intent.ACTION_SEND.equals(action) || Intent.ACTION_VIEW.equals(action)) { 29 | Uri data = intent.getData(); 30 | if (data != null) { 31 | processTrace(data.getEncodedPath()); 32 | } 33 | } 34 | 35 | } 36 | 37 | private void processTrace(final String filePath) { 38 | AsyncTask task = new AsyncTask() { 39 | 40 | @Override 41 | protected Void doInBackground(Void...params) { 42 | TraceUtils.analysisTraceFile(filePath); 43 | return null; 44 | } 45 | 46 | @Override 47 | protected void onPostExecute(Void aVoid) { 48 | super.onPostExecute(aVoid); 49 | progressDialog.hide(); 50 | Uri uri = Uri.fromFile(new File(filePath + ".html")); 51 | Intent intent = new Intent(MainActivity.this, WebViewActivity.class); 52 | intent.putExtra("html", uri.toString()); 53 | startActivity(intent); 54 | finish(); 55 | } 56 | 57 | @Override 58 | protected void onPreExecute() { 59 | super.onPreExecute(); 60 | progressDialog.show(); 61 | } 62 | }; 63 | task.execute(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tracetohtml/src/main/java/dodola/traceutil/WebViewActivity.java: -------------------------------------------------------------------------------- 1 | package dodola.traceutil; 2 | 3 | import android.content.Intent; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.os.Bundle; 6 | import android.webkit.WebChromeClient; 7 | import android.webkit.WebView; 8 | import android.webkit.WebViewClient; 9 | 10 | public class WebViewActivity extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_web_view); 16 | WebView webView = (WebView) this.findViewById(R.id.webView); 17 | webView.setWebChromeClient(new WebChromeClient()); 18 | webView.setWebViewClient(new WebViewClient()); 19 | Intent intent = getIntent(); 20 | if (intent != null) { 21 | String html = intent.getStringExtra("html"); 22 | webView.loadUrl(html); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tracetohtml/src/main/java/dodola/watcher/utils/TraceUtils.java: -------------------------------------------------------------------------------- 1 | package dodola.watcher.utils; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by sunpengfei on 16/1/26. 7 | */ 8 | public class TraceUtils { 9 | static { 10 | System.loadLibrary("ndk"); 11 | } 12 | 13 | public static native int analysisTraceFile(String path); 14 | } 15 | -------------------------------------------------------------------------------- /tracetohtml/src/main/res/layout/activity_anr_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tracetohtml/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 12 | -------------------------------------------------------------------------------- /tracetohtml/src/main/res/layout/activity_scroll.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tracetohtml/src/main/res/layout/activity_web_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /tracetohtml/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dodola/TraceToHtml/391cde2a63219481c5770688c74eca7b61746825/tracetohtml/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /tracetohtml/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dodola/TraceToHtml/391cde2a63219481c5770688c74eca7b61746825/tracetohtml/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /tracetohtml/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dodola/TraceToHtml/391cde2a63219481c5770688c74eca7b61746825/tracetohtml/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /tracetohtml/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dodola/TraceToHtml/391cde2a63219481c5770688c74eca7b61746825/tracetohtml/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /tracetohtml/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dodola/TraceToHtml/391cde2a63219481c5770688c74eca7b61746825/tracetohtml/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /tracetohtml/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /tracetohtml/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /tracetohtml/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /tracetohtml/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | TraceToHtml 3 | 4 | -------------------------------------------------------------------------------- /tracetohtml/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tracetohtml/tracetohtml.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | --------------------------------------------------------------------------------