├── .github └── workflows │ ├── build.yml │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src ├── main └── kotlin │ └── io │ └── johnsonlee │ └── android │ └── trace │ ├── ByteSize.kt │ ├── Hashing.kt │ ├── JavaStackFrame.kt │ ├── JavaStackTraceParser.kt │ ├── KernelStackFrame.kt │ ├── NativeStackFrame.kt │ ├── RootCause.kt │ ├── StackFrame.kt │ ├── ThreadInfo.kt │ ├── ThreadState.kt │ ├── ThreadStatus.kt │ ├── TombstoneFile.kt │ ├── TombstoneFileParser.kt │ ├── TraceFile.kt │ └── TraceFileParser.kt └── test ├── kotlin └── io │ └── johnsonlee │ └── android │ └── trace │ ├── JavaStackTraceParserTest.kt │ ├── NativeStackFrameTest.kt │ ├── RootCauseIdentificationTest.kt │ ├── TombstoneFileParserTest.kt │ └── TraceFileParserTest.kt └── resources ├── tombstone.txt └── trace.txt /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Run Unit Tests 2 | 3 | on: 4 | push: 5 | branches: [ '*' ] 6 | pull_request: 7 | branches: [ '*' ] 8 | 9 | repository_dispatch: 10 | types: [test] 11 | 12 | jobs: 13 | run-unit-test: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | 20 | - name: Setup Java 21 | uses: actions/setup-java@v2 22 | with: 23 | distribution: 'adopt' 24 | java-version: '8' 25 | 26 | - name: Build 27 | run: ./gradlew check -S --no-daemon 28 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Build To Sonatype 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | repository_dispatch: 8 | types: [publish] 9 | 10 | jobs: 11 | publish: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | 18 | - name: Setup Java 19 | uses: actions/setup-java@v2 20 | with: 21 | distribution: 'adopt' 22 | java-version: '11' 23 | 24 | - name: Publish 25 | run: | 26 | echo "Create GPG private key" 27 | echo $GPG_KEY_ARMOR | base64 --decode > $GITHUB_WORKSPACE/secring.gpg 28 | ./gradlew clean initializeSonatypeStagingRepository publishToSonatype closeAndReleaseRepository -S --no-daemon \ 29 | -Pversion=${GITHUB_REF/refs\/tags\/v/} \ 30 | -POSSRH_USERNAME=${OSSRH_USERNAME} \ 31 | -POSSRH_PASSWORD=${OSSRH_PASSWORD} \ 32 | -POSSRH_PACKAGE_GROUP=${OSSRH_PACKAGE_GROUP} \ 33 | -Psigning.keyId=${GPG_KEY_ID} \ 34 | -Psigning.password=${GPG_PASSPHRASE} \ 35 | -Psigning.secretKeyRingFile=${GITHUB_WORKSPACE}/secring.gpg 36 | shred -u $GITHUB_WORKSPACE/secring.gpg 37 | env: 38 | GPG_KEY_ARMOR: ${{ secrets.GPG_KEY_ARMOR }} 39 | GPG_KEY_ID: ${{ secrets.GPG_KEY_ID }} 40 | GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} 41 | OSSRH_PASSWORD: ${{ secrets.OSSRH_PASSWORD }} 42 | OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }} 43 | OSSRH_PACKAGE_GROUP: ${{ secrets.OSSRH_PACKAGE_GROUP }} 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .gradle 3 | .idea 4 | build/ 5 | 6 | # Ignore Gradle GUI config 7 | gradle-app.setting 8 | 9 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 10 | !gradle-wrapper.jar 11 | 12 | # Cache of project 13 | .gradletasknamecache 14 | 15 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 16 | # gradle/wrapper/gradle-wrapper.properties 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | A parser for Java stack trace, Android trace.txt and tombstone file parsing, usually used for issue aggregation in APM system. 4 | 5 | ## Usage 6 | 7 | ### Parse Java stack trace 8 | 9 | ```kotlin 10 | val trace = JavaStackTraceParser().parse(Log.getStackTrace(e)) 11 | val rootCause = trace.rootCause 12 | ``` 13 | 14 | ### Parse trace file 15 | 16 | ```kotlin 17 | val trace = TraceFile.from(path) 18 | val rootCause = trace.rootCause 19 | ``` 20 | 21 | or 22 | 23 | ```kotlin 24 | val trace = FileReader(file).use { 25 | TraceFileParser(it).parse() 26 | } 27 | val rootCause = trace.rootCause 28 | ``` 29 | 30 | ### Parse Android tombstone file 31 | 32 | ```kotlin 33 | val tombstone = TombstoneFile.from(path) 34 | val rootCause = tombstone.rootCause 35 | ``` 36 | 37 | or 38 | 39 | ```kotlin 40 | val tombstone = FileReader(file).use { 41 | TombstoneFileParser(it).parse() 42 | } 43 | val rootCause = tombstone.rootCause 44 | ``` 45 | 46 | ### Gradle 47 | 48 | ```kotlin 49 | implementation("io.johnsonlee:trace-parser:$trace_parser_version") 50 | ``` 51 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | systemProp.org.gradle.internal.publish.checksums.insecure=true 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnsonlee/trace-parser/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | # Determine the Java command to use to start the JVM. 86 | if [ -n "$JAVA_HOME" ] ; then 87 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 88 | # IBM's JDK on AIX uses strange locations for the executables 89 | JAVACMD="$JAVA_HOME/jre/sh/java" 90 | else 91 | JAVACMD="$JAVA_HOME/bin/java" 92 | fi 93 | if [ ! -x "$JAVACMD" ] ; then 94 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 95 | 96 | Please set the JAVA_HOME variable in your environment to match the 97 | location of your Java installation." 98 | fi 99 | else 100 | JAVACMD="java" 101 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 102 | 103 | Please set the JAVA_HOME variable in your environment to match the 104 | location of your Java installation." 105 | fi 106 | 107 | # Increase the maximum file descriptors if we can. 108 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 109 | MAX_FD_LIMIT=`ulimit -H -n` 110 | if [ $? -eq 0 ] ; then 111 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 112 | MAX_FD="$MAX_FD_LIMIT" 113 | fi 114 | ulimit -n $MAX_FD 115 | if [ $? -ne 0 ] ; then 116 | warn "Could not set maximum file descriptor limit: $MAX_FD" 117 | fi 118 | else 119 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 120 | fi 121 | fi 122 | 123 | # For Darwin, add options to specify how the application appears in the dock 124 | if $darwin; then 125 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 126 | fi 127 | 128 | # For Cygwin or MSYS, switch paths to Windows format before running java 129 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 130 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 131 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 132 | JAVACMD=`cygpath --unix "$JAVACMD"` 133 | 134 | # We build the pattern for arguments to be converted via cygpath 135 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 136 | SEP="" 137 | for dir in $ROOTDIRSRAW ; do 138 | ROOTDIRS="$ROOTDIRS$SEP$dir" 139 | SEP="|" 140 | done 141 | OURCYGPATTERN="(^($ROOTDIRS))" 142 | # Add a user-defined pattern to the cygpath arguments 143 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 144 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 145 | fi 146 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 147 | i=0 148 | for arg in "$@" ; do 149 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 150 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 151 | 152 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 153 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 154 | else 155 | eval `echo args$i`="\"$arg\"" 156 | fi 157 | i=`expr $i + 1` 158 | done 159 | case $i in 160 | 0) set -- ;; 161 | 1) set -- "$args0" ;; 162 | 2) set -- "$args0" "$args1" ;; 163 | 3) set -- "$args0" "$args1" "$args2" ;; 164 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 165 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 166 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 167 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 168 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 169 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 170 | esac 171 | fi 172 | 173 | # Escape application args 174 | save () { 175 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 176 | echo " " 177 | } 178 | APP_ARGS=`save "$@"` 179 | 180 | # Collect all arguments for the java command, following the shell quoting and substitution rules 181 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 182 | 183 | exec "$JAVACMD" "$@" 184 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 33 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 34 | 35 | @rem Find java.exe 36 | if defined JAVA_HOME goto findJavaFromJavaHome 37 | 38 | set JAVA_EXE=java.exe 39 | %JAVA_EXE% -version >NUL 2>&1 40 | if "%ERRORLEVEL%" == "0" goto init 41 | 42 | echo. 43 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 44 | echo. 45 | echo Please set the JAVA_HOME variable in your environment to match the 46 | echo location of your Java installation. 47 | 48 | goto fail 49 | 50 | :findJavaFromJavaHome 51 | set JAVA_HOME=%JAVA_HOME:"=% 52 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 53 | 54 | if exist "%JAVA_EXE%" goto init 55 | 56 | echo. 57 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 58 | echo. 59 | echo Please set the JAVA_HOME variable in your environment to match the 60 | echo location of your Java installation. 61 | 62 | goto fail 63 | 64 | :init 65 | @rem Get command-line arguments, handling Windows variants 66 | 67 | if not "%OS%" == "Windows_NT" goto win9xME_args 68 | 69 | :win9xME_args 70 | @rem Slurp the command line arguments. 71 | set CMD_LINE_ARGS= 72 | set _SKIP=2 73 | 74 | :win9xME_args_slurp 75 | if "x%~1" == "x" goto execute 76 | 77 | set CMD_LINE_ARGS=%* 78 | 79 | :execute 80 | @rem Setup the command line 81 | 82 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 83 | 84 | @rem Execute Gradle 85 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 86 | 87 | :end 88 | @rem End local scope for the variables with windows NT shell 89 | if "%ERRORLEVEL%"=="0" goto mainEnd 90 | 91 | :fail 92 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 93 | rem the _cmd.exe /c_ return code! 94 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 95 | exit /b 1 96 | 97 | :mainEnd 98 | if "%OS%"=="Windows_NT" endlocal 99 | 100 | :omega 101 | -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/ByteSize.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | import java.util.Locale 4 | 5 | internal const val KB = 1024L 6 | internal const val MB = KB * 1024L 7 | internal const val GB = MB * 1024L 8 | internal const val TB = GB * 1024L 9 | 10 | inline class ByteSize(val value: Long) { 11 | 12 | constructor(str: String) : this(fromHumanReadable(str)) 13 | 14 | 15 | override fun toString(): String = when { 16 | value >= TB -> "${value / TB}TB" 17 | value >= GB -> "${value / GB}GB" 18 | value >= MB -> "${value / MB}MB" 19 | value >= KB -> "${value / KB}KB" 20 | else -> "${value}B" 21 | } 22 | 23 | } 24 | 25 | private fun fromHumanReadable(str: String): Long { 26 | val hrs = str.toUpperCase(Locale.getDefault()) 27 | val b = hrs.indexOf('B').takeIf { it > 0 } ?: return 0 28 | val num: (Int) -> Long = { 29 | str.substring(0, it).takeIf(String::isNotEmpty)?.toLong() ?: 0 30 | } 31 | 32 | return when (hrs[b - 1]) { 33 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> num(b) 34 | 'K' -> num(b - 1) * KB 35 | 'M' -> num(b - 1) * MB 36 | 'G' -> num(b - 1) * GB 37 | else -> num(b - 1) * TB 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/Hashing.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | import java.security.MessageDigest 4 | 5 | private const val HEX = "0123456789abcdef" 6 | 7 | fun ByteArray.hexify(): String { 8 | val chars = CharArray(this.size * 2) 9 | for (i in this.indices) { 10 | val v = this[i].toInt() and 0xff 11 | chars[i * 2] = HEX[v ushr 4] 12 | chars[i * 2 + 1] = HEX[v and 0x0f] 13 | } 14 | return String(chars) 15 | } 16 | 17 | fun ByteArray.sha256(): String = MessageDigest.getInstance("SHA-256").digest(this).hexify() 18 | 19 | fun String.sha256(): String = MessageDigest.getInstance("SHA-256").digest(this.toByteArray()).hexify() 20 | 21 | fun String.md5(): String = MessageDigest.getInstance("MD5").digest(this.toByteArray()).hexify() 22 | -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/JavaStackFrame.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | 4 | private const val INVALID_FILE_NAME: String = "" 5 | 6 | private const val INVALID_LINE_NUMBER: Int = -1 7 | 8 | private const val INVALID_CLASS_NAME: String = "" 9 | 10 | private const val INVALID_METHOD_NAME: String = "" 11 | 12 | private val systemPackages = setOf( 13 | "java.", 14 | "kotlin.", 15 | "kotlinx.", 16 | "android.", 17 | "androidx.", 18 | "com.android.", 19 | "dalvik." 20 | ) 21 | 22 | class JavaStackFrame(snapshot: String) : StackFrame(snapshot) { 23 | 24 | private val at: Int? by lazy { 25 | snapshot.indexOf("at ").takeIf { it > -1 } 26 | } 27 | 28 | private val rbrace: Int? by lazy { 29 | snapshot.lastIndexOf(')').takeIf { it > -1 } 30 | } 31 | 32 | private val colon: Int? by lazy { 33 | rbrace?.let { r -> 34 | snapshot.lastIndexOf(':', r).takeIf { it > -1 } 35 | } 36 | } 37 | 38 | private val lbrace: Int? by lazy { 39 | (colon ?: rbrace)?.let { r -> 40 | snapshot.lastIndexOf('(', r).takeIf { it > -1 } 41 | } 42 | } 43 | 44 | private val dot: Int? by lazy { 45 | lbrace?.let { l -> 46 | snapshot.lastIndexOf('.', l).takeIf { it > -1 } 47 | } 48 | } 49 | 50 | override val isFromUser: Boolean by lazy { 51 | at ?: return@lazy false 52 | systemPackages.none { 53 | className.startsWith(it) 54 | } 55 | } 56 | 57 | val className: String by lazy { 58 | val at = this.at ?: return@lazy INVALID_CLASS_NAME 59 | val dot = this.dot ?: return@lazy INVALID_CLASS_NAME 60 | val slash = snapshot.lastIndexOf('/', dot).takeIf { it > -1 } 61 | snapshot.substring(slash ?: (at + 3), dot) 62 | } 63 | 64 | val methodName: String by lazy { 65 | val lbrace = this.lbrace ?: return@lazy INVALID_METHOD_NAME 66 | val dot = this.dot ?: return@lazy INVALID_METHOD_NAME 67 | snapshot.substring(dot + 1, lbrace) 68 | } 69 | 70 | val sourceFile: String by lazy { 71 | val lbrace = this.lbrace ?: return@lazy INVALID_FILE_NAME 72 | val rbrace = this.rbrace ?: return@lazy INVALID_FILE_NAME 73 | snapshot.substring(lbrace + 1, this.colon ?: rbrace).takeIf { 74 | it != "Native Method" && it != "Unknown Source" 75 | } ?: INVALID_FILE_NAME 76 | } 77 | 78 | val lineNumber: Int by lazy { 79 | val rbrace = this.rbrace ?: return@lazy INVALID_LINE_NUMBER 80 | this.colon?.let { 81 | snapshot.substring(it + 1, rbrace).toIntOrNull() 82 | } ?: INVALID_LINE_NUMBER 83 | } 84 | 85 | } 86 | 87 | internal val REGEX_CAUSED_BY = Regex("\\s*Caused by: (.*)") 88 | 89 | internal val REGEX_JAVA_STACK_FRAME = Regex("\\s*at (.*)") 90 | 91 | internal fun isCausedBy(line: String): Boolean = line matches REGEX_CAUSED_BY 92 | 93 | internal fun isJavaStackTraceElement(line: String): Boolean = line matches REGEX_JAVA_STACK_FRAME 94 | -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/JavaStackTraceParser.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | /** 4 | * 5 | * Plain java stack trace parser 6 | * 7 | * @author johnsonlee 8 | */ 9 | class JavaStackTraceParser { 10 | 11 | fun parse(stackTrace: String): List = parse(stackTrace.split("\n")) 12 | 13 | fun parse(stackTrace: List): List { 14 | val frames = mutableListOf() 15 | val iterator = stackTrace.findRootCause().iterator() 16 | 17 | while (iterator.hasNext()) { 18 | iterator.next().takeIf(::isJavaStackTraceElement)?.let(frames::add) ?: if (frames.isNotEmpty()) break 19 | } 20 | 21 | return frames.map(::JavaStackFrame) 22 | } 23 | 24 | } 25 | 26 | private fun List.findRootCause(): List { 27 | val lines = toMutableList() 28 | var last = lines.size 29 | for (i in lines.indices.reversed()) { 30 | if (isCausedBy(lines[i])) { 31 | val causedBy = lines.subList(i, last) 32 | if (causedBy.map(::JavaStackFrame).any(JavaStackFrame::isFromUser)) { 33 | return causedBy 34 | } 35 | last = i 36 | } 37 | } 38 | return this 39 | } 40 | -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/KernelStackFrame.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | class KernelStackFrame(snapshot: String) : StackFrame(snapshot) { 4 | 5 | override val isFromUser: Boolean = false 6 | 7 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/NativeStackFrame.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | private const val INVALID_ADDRESS: Long = -1 4 | 5 | private const val INVALID_MAP_NAME: String = "" 6 | 7 | private const val INVALID_INDEX: Int = -1 8 | 9 | private const val INVALID_FUNCTION_NAME: String = "" 10 | 11 | private const val INVALID_FUNCTION_OFFSET: Int = -1 12 | 13 | private const val PREFIX_DATA = "/data/" 14 | 15 | private val systemLdPathPrefixes = setOf("/system/", "/apex/", "/vendor/", "/data/dalvik-cache/", "/data/app/com.android.chrome", "com.google.android.webview", "com.google.android.trichromelibrary") 16 | 17 | class NativeStackFrame(snapshot: String) : StackFrame(snapshot) { 18 | 19 | private val pound: Int? by lazy { 20 | snapshot.indexOf('#').takeIf { it > -1 } 21 | } 22 | 23 | private val _pc: Int? by lazy { 24 | snapshot.indexOf(" pc ").takeIf { it > -1 } 25 | } 26 | 27 | private val sp3: Int? by lazy { 28 | _pc?.let { pc -> 29 | snapshot.indexOf(' ', pc + 4).takeIf { it > -1 } 30 | } 31 | } 32 | 33 | private val slash: Int? by lazy { 34 | sp3?.let { sp -> 35 | snapshot.indexOf('/', sp).takeIf { it > -1 } 36 | } 37 | } 38 | 39 | private val sp4: Int? by lazy { 40 | slash?.let { sp -> 41 | snapshot.indexOf(' ', sp).takeIf { it > -1 } 42 | } 43 | } 44 | 45 | private val rbrace: Int? by lazy { 46 | snapshot.lastIndexOf(')').takeIf { it > -1 } 47 | } 48 | 49 | private val lbrace: Int? by lazy { 50 | sp4?.let { sp -> 51 | snapshot.indexOf('(', sp).takeIf { it > -1 } 52 | } 53 | } 54 | 55 | private val plus: Int? by lazy { 56 | rbrace?.let { r -> 57 | snapshot.lastIndexOf('+', r).takeIf { it > -1 } 58 | } 59 | } 60 | 61 | private val map: Int? by lazy { 62 | if (isFromUser) { 63 | snapshot.lastIndexOf('/', lbrace ?: snapshot.length) + 1 64 | } else { 65 | slash 66 | } 67 | } 68 | 69 | override val isFromUser: Boolean by lazy { 70 | if (index <= 0) return@lazy false 71 | val slash = this.slash ?: return@lazy false 72 | snapshot.indexOf(PREFIX_DATA, slash) == slash || systemLdPathPrefixes.none { 73 | snapshot.indexOf(it, slash) == slash 74 | } 75 | } 76 | 77 | override val fingerprint: String by lazy { 78 | (map?.let(snapshot::substring) ?: snapshot).md5() 79 | } 80 | 81 | val index: Int by lazy { 82 | val pound = this.pound ?: return@lazy INVALID_INDEX 83 | val pc = _pc ?: return@lazy INVALID_INDEX 84 | snapshot.substring(pound + 1, pc).trim().toIntOrNull() ?: INVALID_INDEX 85 | } 86 | 87 | val pc: Long by lazy { 88 | val pc = _pc ?: return@lazy INVALID_ADDRESS 89 | val sp3 = this.sp3 ?: return@lazy INVALID_ADDRESS 90 | snapshot.substring(pc + 4, sp3).toLongOrNull(16) ?: INVALID_ADDRESS 91 | } 92 | 93 | val mapName: String by lazy { 94 | map?.let { 95 | snapshot.substring(it, lbrace ?: snapshot.length).trim() 96 | } ?: INVALID_MAP_NAME 97 | } 98 | 99 | val functionName: String by lazy { 100 | val lbrace = this.lbrace ?: return@lazy INVALID_FUNCTION_NAME 101 | val plus = this.plus ?: return@lazy INVALID_FUNCTION_NAME 102 | snapshot.substring(lbrace + 1, plus) 103 | } 104 | 105 | val functionOffset: Int by lazy { 106 | val plus = this.plus ?: return@lazy INVALID_FUNCTION_OFFSET 107 | val rbrace = this.rbrace ?: return@lazy INVALID_FUNCTION_OFFSET 108 | snapshot.substring(plus + 1, rbrace).toIntOrNull() ?: INVALID_FUNCTION_OFFSET 109 | } 110 | 111 | } 112 | 113 | internal val REGEX_NATIVE_STACK_FRAME = Regex("\\s*native:\\s+#\\d+\\s+pc\\s+[\\da-fA-F]+\\s+.+") 114 | 115 | internal val REGEX_BACKTRACE_FRAME = Regex("#\\d+\\s+pc\\s+[\\da-fA-F]+\\s+.+") 116 | 117 | internal fun isNativeBacktraceElement(line: String): Boolean { 118 | return line matches REGEX_NATIVE_STACK_FRAME || line matches REGEX_BACKTRACE_FRAME 119 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/RootCause.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | fun identifyRootCause(stackTrace: String): StackFrame? = identifyRootCause(stackTrace.split("\n")) 4 | 5 | fun identifyRootCause(stackTrace: List): StackFrame? { 6 | val hasJavaStackFrame = stackTrace.any { it matches REGEX_JAVA_STACK_FRAME } 7 | val hasCausedBy = stackTrace.any { it matches REGEX_CAUSED_BY } 8 | val hasNativeStackFrame = stackTrace.any { it matches REGEX_NATIVE_STACK_FRAME } 9 | val hasBacktraceFrame = stackTrace.any { it matches REGEX_BACKTRACE_FRAME } 10 | 11 | return when { 12 | hasCausedBy -> { 13 | JavaStackTraceParser().parse(stackTrace).rootCause 14 | } 15 | hasBacktraceFrame -> { 16 | val native = stackTrace.filter(::isNativeBacktraceElement).map(::NativeStackFrame) 17 | native.rootCause ?: native.firstOrNull() 18 | } 19 | hasNativeStackFrame || hasJavaStackFrame -> { 20 | val java = stackTrace.filter(::isJavaStackTraceElement).map(::JavaStackFrame) 21 | val native = stackTrace.filter(::isNativeBacktraceElement).map(::NativeStackFrame) 22 | native.rootCause ?: java.rootCause ?: native.firstOrNull() ?: java.firstOrNull() 23 | } 24 | else -> { 25 | JavaStackTraceParser().parse(stackTrace).rootCause 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/StackFrame.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | abstract class StackFrame(private val snapshot: String) { 4 | 5 | init { 6 | if ('\n' in snapshot) { 7 | throw IllegalArgumentException(snapshot) 8 | } 9 | } 10 | 11 | abstract val isFromUser: Boolean 12 | 13 | open val fingerprint: String by lazy(snapshot.trim()::md5) 14 | 15 | override fun equals(other: Any?): Boolean { 16 | return other === this || (other is StackFrame && other.fingerprint == fingerprint) 17 | } 18 | 19 | override fun hashCode(): Int = snapshot.hashCode() 20 | 21 | override fun toString(): String = snapshot 22 | 23 | } 24 | 25 | val Iterable.rootCause: T? 26 | get() = firstOrNull { 27 | it.isFromUser 28 | } 29 | -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/ThreadInfo.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | abstract class ThreadInfo( 4 | val name: String, 5 | val priority: Int, 6 | val stackTrace: List 7 | ) { 8 | 9 | abstract val sysTid: Int 10 | abstract val nice: Int 11 | abstract val cgrp: String 12 | abstract val state: Char 13 | abstract val schedstat: Triple 14 | abstract val utm: Int 15 | abstract val stm: Int 16 | abstract val core: Int 17 | abstract val hz: Int 18 | } 19 | 20 | data class MutexInfo(val name: String, val shared: Boolean) { 21 | override fun toString(): String = "\"${name}\"(${if (shared) "shared held" else "exclusive held"})" 22 | } 23 | 24 | @ExperimentalUnsignedTypes 25 | class DalvikThreadInfo constructor( 26 | name: String, 27 | val daemon: Boolean, 28 | priority: Int, 29 | val tid: Int, 30 | val status: ThreadStatus, 31 | val group: String, 32 | val sCount: Int, 33 | val dsCount: Int, 34 | val flags: Int, 35 | val obj: ULong, 36 | val self: ULong, 37 | override val sysTid: Int, 38 | override val nice: Int, 39 | override val cgrp: String, 40 | val sched: Pair, 41 | val handle: ULong, 42 | override val state: Char, 43 | override val schedstat: Triple, 44 | override val utm: Int, 45 | override val stm: Int, 46 | override val core: Int, 47 | override val hz: Int, 48 | val stack: Pair, 49 | val stackSize: Long, 50 | val heldMutexes: List, 51 | stackTrace: List 52 | ) : ThreadInfo(name, priority, stackTrace) { 53 | 54 | override fun toString() = stackTrace.joinToString("\n", """ 55 | |"$name"${if (daemon) " daemon" else ""} prio=${priority} tid=${tid} $status 56 | | | group="$group" sCount=$sCount dsCount=$dsCount flags=$flags obj=0x${obj.toString(16)} self=0x${self.toString(16)} 57 | | | sysTid=$sysTid nice=$nice cgrp=$cgrp sched=${sched.first}/${sched.second} handle=0x${handle.toString(16)} 58 | | | state=$state schedstat=( ${schedstat.first} ${schedstat.second} ${schedstat.third} ) utm=$utm stm=$stm core=$core HZ=$hz 59 | | | stack=0x${stack.first}-0x${stack.second} stackSize=${ByteSize(stackSize)} 60 | | | held mutexes=${heldMutexes.joinToString(" ", " ")} 61 | | 62 | """.trimMargin()) { " $it" } 63 | } 64 | 65 | class NativeThreadInfo( 66 | name: String, 67 | priority: Int, 68 | override val sysTid: Int, 69 | override val nice: Int, 70 | override val cgrp: String, 71 | override val state: Char, 72 | override val schedstat: Triple, 73 | override val utm: Int, 74 | override val stm: Int, 75 | override val core: Int, 76 | override val hz: Int, 77 | stackTrace: List 78 | ) : ThreadInfo(name, priority, stackTrace) { 79 | 80 | override fun toString() = stackTrace.joinToString("\n", """ 81 | |"$name" prio=${priority} (not attached) 82 | | | sysTid=$sysTid nice=$nice cgrp=$cgrp 83 | | | state=$state schedstat=( ${schedstat.first} ${schedstat.second} ${schedstat.third} ) utm=$utm stm=$stm core=$core HZ=$hz 84 | | 85 | """.trimMargin()) { " $it" } 86 | 87 | } 88 | 89 | private fun Long.prettySize(): String = when { 90 | this >= TB -> "${this / TB}TB" 91 | this >= GB -> "${this / GB}GB" 92 | this >= MB -> "${this / MB}MB" 93 | this >= KB -> "${this / KB}KB" 94 | else -> "${this}B" 95 | } 96 | 97 | -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/ThreadState.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | import kotlin.reflect.KClass 4 | 5 | sealed class ThreadState(internal val value: String) { 6 | 7 | companion object { 8 | 9 | private val STATES = ThreadState::class.sealedSubclasses 10 | .mapNotNull(KClass::objectInstance) 11 | .map { it.value to it } 12 | .toMap() 13 | 14 | fun valueOf(state: String) = STATES[state] ?: throw IllegalArgumentException("invalid state `${state}`") 15 | 16 | } 17 | 18 | object Terminated : ThreadState("Terminated") 19 | object Runnable : ThreadState("Runnable") 20 | object TimedWaiting : ThreadState("TimedWaiting") 21 | object Sleeping : ThreadState("Sleeping") 22 | object Blocked : ThreadState("Blocked") 23 | object Waiting : ThreadState("Waiting") 24 | object WaitingForLockInflation : ThreadState("WaitingForLockInflation") 25 | object WaitingForTaskProcessor : ThreadState("WaitingForTaskProcessor") 26 | object WaitingForGcToComplete : ThreadState("WaitingForGcToComplete") 27 | object WaitingForCheckPointsToRun : ThreadState("WaitingForCheckPointsToRun") 28 | object WaitingPerformingGc : ThreadState("WaitingPerformingGc") 29 | object WaitingForDebuggerSend : ThreadState("WaitingForDebuggerSend") 30 | object WaitingForDebuggerToAttach : ThreadState("WaitingForDebuggerToAttach") 31 | object WaitingInMainDebuggerLoop : ThreadState("WaitingInMainDebuggerLoop") 32 | object WaitingForDebuggerSuspension : ThreadState("WaitingForDebuggerSuspension") 33 | object WaitingForJniOnLoad : ThreadState("WaitingForJniOnLoad") 34 | object WaitingForSignalCatcherOutput : ThreadState("WaitingForSignalCatcherOutput") 35 | object WaitingInMainSignalCatcherLoop : ThreadState("WaitingInMainSignalCatcherLoop") 36 | object WaitingForDeoptimization : ThreadState("WaitingForDeoptimization") 37 | object WaitingForMethodTracingStart : ThreadState("WaitingForMethodTracingStart") 38 | object WaitingForVisitObjects : ThreadState("WaitingForVisitObjects") 39 | object WaitingForGetObjectsAllocated : ThreadState("WaitingForGetObjectsAllocated") 40 | object WaitingWeakGcRootRead : ThreadState("WaitingWeakGcRootRead") 41 | object WaitingForGcThreadFlip : ThreadState("WaitingForGcThreadFlip") 42 | object NativeForAbort : ThreadState("NativeForAbort") 43 | object Starting : ThreadState("Starting") 44 | object Native : ThreadState("Native") 45 | object Suspended : ThreadState("Suspended") 46 | 47 | override fun toString() = value 48 | 49 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/ThreadStatus.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | import kotlin.reflect.KClass 4 | 5 | sealed class ThreadStatus(internal val value: String) { 6 | 7 | companion object { 8 | 9 | private val STATES = ThreadStatus::class.sealedSubclasses 10 | .mapNotNull(KClass::objectInstance) 11 | .map { it.value to it } 12 | .toMap() 13 | 14 | fun valueOf(state: String) = STATES[state] ?: throw IllegalArgumentException("invalid state `${state}`") 15 | 16 | } 17 | 18 | object Terminated : ThreadStatus("Terminated") 19 | object Runnable : ThreadStatus("Runnable") 20 | object TimedWaiting : ThreadStatus("TimedWaiting") 21 | object Sleeping : ThreadStatus("Sleeping") 22 | object Blocked : ThreadStatus("Blocked") 23 | object Waiting : ThreadStatus("Waiting") 24 | object WaitingForLockInflation : ThreadStatus("WaitingForLockInflation") 25 | object WaitingForTaskProcessor : ThreadStatus("WaitingForTaskProcessor") 26 | object WaitingForGcToComplete : ThreadStatus("WaitingForGcToComplete") 27 | object WaitingForCheckPointsToRun : ThreadStatus("WaitingForCheckPointsToRun") 28 | object WaitingPerformingGc : ThreadStatus("WaitingPerformingGc") 29 | object WaitingForDebuggerSend : ThreadStatus("WaitingForDebuggerSend") 30 | object WaitingForDebuggerToAttach : ThreadStatus("WaitingForDebuggerToAttach") 31 | object WaitingInMainDebuggerLoop : ThreadStatus("WaitingInMainDebuggerLoop") 32 | object WaitingForDebuggerSuspension : ThreadStatus("WaitingForDebuggerSuspension") 33 | object WaitingForJniOnLoad : ThreadStatus("WaitingForJniOnLoad") 34 | object WaitingForSignalCatcherOutput : ThreadStatus("WaitingForSignalCatcherOutput") 35 | object WaitingInMainSignalCatcherLoop : ThreadStatus("WaitingInMainSignalCatcherLoop") 36 | object WaitingForDeoptimization : ThreadStatus("WaitingForDeoptimization") 37 | object WaitingForMethodTracingStart : ThreadStatus("WaitingForMethodTracingStart") 38 | object WaitingForVisitObjects : ThreadStatus("WaitingForVisitObjects") 39 | object WaitingForGetObjectsAllocated : ThreadStatus("WaitingForGetObjectsAllocated") 40 | object WaitingWeakGcRootRead : ThreadStatus("WaitingWeakGcRootRead") 41 | object WaitingForGcThreadFlip : ThreadStatus("WaitingForGcThreadFlip") 42 | object NativeForAbort : ThreadStatus("NativeForAbort") 43 | object Starting : ThreadStatus("Starting") 44 | object Native : ThreadStatus("Native") 45 | object Suspended : ThreadStatus("Suspended") 46 | 47 | override fun toString() = value 48 | 49 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/TombstoneFile.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | import java.io.File 4 | 5 | @ExperimentalUnsignedTypes 6 | class TombstoneFile constructor( 7 | val fingerprint: String, 8 | val revision: Int, 9 | val abi: String, 10 | val pid: Long, 11 | val tid: Long, 12 | val threadName: String, 13 | val processName: String, 14 | val signal: Int, 15 | val code: Int, 16 | val faultAddress: ULong, 17 | val backtrace: List 18 | ) { 19 | 20 | companion object { 21 | 22 | fun from(file: File): TombstoneFile = file.reader().use { 23 | TombstoneFileParser(it).parse() 24 | } 25 | 26 | } 27 | 28 | val rootCause: NativeStackFrame? by lazy { 29 | backtrace.rootCause ?: backtrace.firstOrNull() 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/TombstoneFileParser.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | import io.johnsonlee.LookAheadReader 4 | import java.io.InputStream 5 | import java.io.Reader 6 | import java.io.StringReader 7 | 8 | private const val PREFIX_FINGERPRINT = "Build fingerprint: " 9 | private const val PREFIX_REVISION = "Revision: " 10 | private const val PREFIX_ABI = "ABI: " 11 | private const val PREFIX_PID = "pid: " 12 | private const val PREFIX_TID = "tid: " 13 | private const val PREFIX_THREAD_NAME = "name: " 14 | private const val PREFIX_PROCESS_NAME = ">>> " 15 | private const val PREFIX_SIGNAL = "signal " 16 | private const val PREFIX_CODE = "code " 17 | private const val PREFIX_FAULT_ADDR = "fault addr 0x" 18 | private const val PREFIX_BACKTRACE = "backtrace:" 19 | private const val PREFIX_FRAME = " #" 20 | 21 | /** 22 | * Android tombstone file parser 23 | * 24 | * @author johnsonlee 25 | */ 26 | @ExperimentalUnsignedTypes 27 | class TombstoneFileParser(source: Reader) { 28 | 29 | private val reader: LookAheadReader = LookAheadReader(source) 30 | 31 | constructor(source: InputStream) : this(source.bufferedReader()) 32 | 33 | fun parse(): TombstoneFile { 34 | var fingerprint = "" 35 | var revision = Int.MIN_VALUE 36 | var abi = "" 37 | var pid = Long.MIN_VALUE 38 | var tid = Long.MIN_VALUE 39 | var threadName = "" 40 | var processName = "" 41 | var signal = Int.MIN_VALUE 42 | var code = Int.MIN_VALUE 43 | var faultAddress = ULong.MAX_VALUE 44 | val backtrace = mutableListOf() 45 | 46 | loop@ while (true) { 47 | val line = reader.readLine() ?: break@loop 48 | 49 | when { 50 | line.startsWith(PREFIX_FINGERPRINT) -> { 51 | fingerprint = LookAheadReader(StringReader(line.substringAfter(PREFIX_FINGERPRINT))).use { 52 | it.readSingleQuotedString() 53 | } ?: "" 54 | } 55 | line.startsWith(PREFIX_REVISION) -> { 56 | revision = LookAheadReader(StringReader(line.substringAfter(PREFIX_REVISION))).use { 57 | it.readSingleQuotedString()?.toInt() 58 | } ?: Int.MIN_VALUE 59 | } 60 | line.startsWith(PREFIX_ABI) -> { 61 | abi = LookAheadReader(StringReader(line.substringAfter(PREFIX_ABI))).use { 62 | it.readSingleQuotedString() 63 | } ?: "" 64 | } 65 | line.startsWith(PREFIX_PID) -> { 66 | pid = line.indexOf(PREFIX_PID).takeIf { it > -1 }?.let { index -> 67 | LookAheadReader(StringReader(line.substring(index + PREFIX_PID.length))).use { 68 | it.readDigits()?.toLong() 69 | } 70 | } ?: Long.MIN_VALUE 71 | tid = line.indexOf(PREFIX_TID).takeIf { it > -1 }?.let { index -> 72 | LookAheadReader(StringReader(line.substring(index + PREFIX_TID.length))).use { 73 | it.readDigits()?.toLong() 74 | } 75 | } ?: Long.MIN_VALUE 76 | threadName = line.indexOf(PREFIX_THREAD_NAME).takeIf { it > -1 }?.let { index -> 77 | LookAheadReader(StringReader(line.substring(index + PREFIX_THREAD_NAME.length))).use { 78 | it.readToken(" \t\r\n\u000C>") 79 | } 80 | } ?: "" 81 | processName = line.indexOf(PREFIX_PROCESS_NAME).takeIf { it > -1 }?.let { index -> 82 | LookAheadReader(StringReader(line.substring(index + PREFIX_PROCESS_NAME.length))).use { 83 | it.readToken(" \t\r\n\u000C<") 84 | } 85 | } ?: "" 86 | } 87 | line.startsWith(PREFIX_SIGNAL) -> { 88 | signal = line.indexOf(PREFIX_SIGNAL).takeIf { it > -1 }?.let { index -> 89 | LookAheadReader(StringReader(line.substring(index + PREFIX_SIGNAL.length))).use { 90 | it.readSignedInt() 91 | } 92 | } ?: Int.MIN_VALUE 93 | code = line.indexOf(PREFIX_CODE).takeIf { it > -1 }?.let { index -> 94 | LookAheadReader(StringReader(line.substring(index + PREFIX_CODE.length))).use { 95 | it.readSignedInt() 96 | } 97 | } ?: Int.MIN_VALUE 98 | faultAddress = line.indexOf(PREFIX_FAULT_ADDR).takeIf { it > -1 }?.let { index -> 99 | LookAheadReader(StringReader(line.substring(index + PREFIX_CODE.length))).use { 100 | it.readDigits()?.toULong(16) 101 | } 102 | } ?: ULong.MAX_VALUE 103 | } 104 | line.startsWith(PREFIX_BACKTRACE) -> { 105 | backtrace@ while (true) { 106 | val frame = reader.readLine() ?: break@loop 107 | when { 108 | frame.isBlank() -> continue@backtrace 109 | frame.startsWith(PREFIX_FRAME) -> backtrace += frame 110 | else -> { 111 | if (backtrace.isNotEmpty()) { 112 | break@loop 113 | } 114 | } 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | return TombstoneFile( 122 | fingerprint = fingerprint, 123 | revision = revision, 124 | abi = abi, 125 | pid = pid, 126 | tid = tid, 127 | threadName = threadName, 128 | processName = processName, 129 | signal = signal, 130 | code = code, 131 | faultAddress = faultAddress, 132 | backtrace = backtrace.map(::NativeStackFrame) 133 | ) 134 | } 135 | 136 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/TraceFile.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | import java.io.File 4 | import java.util.Date 5 | 6 | /** 7 | * Abstraction of `trace.txt` file 8 | * 9 | * @author johnsonlee 10 | */ 11 | class TraceFile( 12 | val pid: Long, 13 | val date: Date, 14 | val threads: List 15 | ) { 16 | 17 | @ExperimentalUnsignedTypes 18 | companion object { 19 | 20 | fun from(file: File): TraceFile = file.reader().use { 21 | TraceFileParser(it).parse() 22 | } 23 | 24 | } 25 | 26 | val mainThreadInfo: ThreadInfo by lazy { 27 | threads.first(this::isMainThread) 28 | } 29 | 30 | val rootCause: StackFrame? by lazy { 31 | mainThreadInfo.stackTrace.rootCause ?: mainThreadInfo.stackTrace.firstOrNull() 32 | } 33 | 34 | private fun isMainThread(thread: ThreadInfo): Boolean = pid == thread.sysTid.toLong() 35 | 36 | } -------------------------------------------------------------------------------- /src/main/kotlin/io/johnsonlee/android/trace/TraceFileParser.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | import io.johnsonlee.LookAheadReader 4 | import io.johnsonlee.LookAheadReader.Companion.EOF 5 | import java.io.InputStream 6 | import java.io.Reader 7 | import java.io.StringReader 8 | import java.text.ParseException 9 | import java.text.SimpleDateFormat 10 | import java.util.Date 11 | 12 | private val REGEX_DALVIK_THREADS = Regex("DALVIK THREADS \\((\\d+)\\):") 13 | 14 | /** 15 | * Android trace file parser 16 | * 17 | * @author johnsonlee 18 | */ 19 | @ExperimentalUnsignedTypes 20 | class TraceFileParser(source: Reader) { 21 | 22 | private val reader: LookAheadReader = LookAheadReader(source) 23 | 24 | constructor(source: InputStream) : this(source.bufferedReader()) 25 | 26 | fun parse(): TraceFile { 27 | var pid = 0L 28 | var date = 0L 29 | 30 | while (true) { 31 | val line = reader.readLine() ?: break 32 | if (line.startsWith("----- pid ") && line.indexOf(" at ", 10) != -1 && line.endsWith(" -----")) { 33 | val lar = LookAheadReader(StringReader(line.substring(10, line.length - 6))) 34 | // pid 35 | pid = lar.readDigits()?.toLong() ?: continue 36 | lar.skipWhitespace() 37 | 38 | // at 39 | val at = CharArray(2) 40 | lar.read(at) 41 | if (at[0] != 'a' || at[1] != 't') { 42 | continue 43 | } 44 | lar.skipWhitespace() 45 | 46 | // date and time 47 | val s = lar.readLine() ?: continue 48 | try { 49 | date = SimpleDateFormat("YYYY-MM-DD HH:mm:ss").parse(s).time 50 | } catch (e: ParseException) { 51 | continue 52 | } 53 | break 54 | } 55 | } 56 | 57 | return TraceFile(pid, Date(date), parseThreads()) 58 | } 59 | 60 | private fun parseThreads(): List { 61 | var count = 0 62 | 63 | do { 64 | val line = reader.readLine() ?: break 65 | if (!line.startsWith("DALVIK THREADS (")) { 66 | continue 67 | } 68 | count = REGEX_DALVIK_THREADS.matchEntire(line)?.groupValues?.get(1)?.toInt() ?: 0 69 | } while (count == 0) 70 | 71 | return if (count <= 0) emptyList() else parseThreads(count) 72 | } 73 | 74 | 75 | private fun parseThreads(n: Int): List { 76 | reader.skipBlankLines() 77 | 78 | val threads = mutableListOf() 79 | repeat(n) { 80 | threads += parseDalvikThread() ?: return@repeat 81 | } 82 | 83 | reader.skipBlankLines() 84 | 85 | while (true) { 86 | threads += parseNativeThread() ?: break 87 | } 88 | 89 | return threads 90 | } 91 | 92 | private fun parseThreadAttributes(): Map { 93 | val attrs = mutableMapOf() 94 | 95 | lines@ while (true) { 96 | reader.skipWhitespace() 97 | when (reader.peek().toChar()) { 98 | '|' -> { 99 | reader.read() 100 | reader.skipWhitespace() 101 | val line = reader.readLine()?.takeIf(String::isNotBlank) ?: break@lines 102 | val lar = LookAheadReader(StringReader(line), line.length) 103 | attrs@ while (true) { 104 | attrs += parseThreadAttribute(lar) ?: break@attrs 105 | } 106 | } 107 | else -> break@lines 108 | } 109 | } 110 | 111 | return attrs 112 | } 113 | 114 | private fun parseThreadAttribute(reader: LookAheadReader): Pair? { 115 | val key = parseThreadAttributeKey(reader) ?: return null 116 | val value = parseThreadAttributeValue(reader) ?: "" 117 | return key to value 118 | } 119 | 120 | private fun parseThreadAttributeKey(reader: LookAheadReader): String? { 121 | val s = StringBuilder() 122 | 123 | while (true) { 124 | val c = reader.read() 125 | if (EOF == c || c == 61 /* = */) { 126 | break 127 | } 128 | s.append(c.toChar()) 129 | } 130 | 131 | return s.takeIf(StringBuilder::isNotEmpty)?.trim()?.toString() 132 | } 133 | 134 | private fun parseThreadAttributeValue(reader: LookAheadReader): String? { 135 | reader.skipWhitespace() 136 | return when (reader.peek()) { 137 | 34 /* " */ -> reader.readDoubleQuotedString() 138 | 39 /* ' */ -> reader.readSingleQuotedString() 139 | 40 /* ( */ -> reader.readWrappedString(40, 41)?.trim() 140 | EOF -> null 141 | else -> { 142 | val s = StringBuilder() 143 | while (true) { 144 | val c = reader.read() 145 | if (EOF == c || Character.isWhitespace(c)) { 146 | break 147 | } 148 | s.append(c.toChar()) 149 | } 150 | s.takeIf(StringBuilder::isNotEmpty)?.toString() 151 | } 152 | } 153 | } 154 | 155 | @ExperimentalUnsignedTypes 156 | private fun parseDalvikThread(): DalvikThreadInfo? { 157 | val name = reader.readDoubleQuotedString() ?: return null 158 | val daemon = parseThreadDaemon() 159 | val priority = parseThreadIntLabel("prio=") 160 | val tid = parseThreadIntLabel("tid=") 161 | val status = parseThreadStatus() 162 | readLine() // skip remaining chars in line 163 | val attrs = parseThreadAttributes() 164 | val (sched0, sched1) = attrs["sched"]?.split('/')?.map(String::toInt) ?: listOf(0, 0) 165 | val (schedstat0, schedstat1, schedstat2) = attrs["schedstat"]?.split(' ')?.map(String::toLong) ?: listOf(0L, 0L, 0L) 166 | val (stack0, stack1) = attrs["stack"]?.split('-')?.map { it.substringAfter("0x").toLong(16) } ?: listOf(0L, 0L) 167 | val heldMutexes = attrs["held mutexes"]?.split(' ')?.mapNotNull { 168 | val lar = LookAheadReader(StringReader(it), it.length) 169 | val mutexName = lar.readDoubleQuotedString() ?: return@mapNotNull null 170 | val shared = lar.readLine()?.trim() == "(shared held)" 171 | MutexInfo(mutexName, shared) 172 | } ?: emptyList() 173 | 174 | return DalvikThreadInfo( 175 | name, 176 | daemon, 177 | priority, 178 | tid, 179 | status, 180 | attrs["group"] ?: "", 181 | attrs["sCount"]?.toInt() ?: 0, 182 | attrs["dsCount"]?.toInt() ?: 0, 183 | attrs["flags"]?.toInt() ?: 0, 184 | attrs["obj"]?.substringAfter("0x")?.toULong(16) ?: 0uL, 185 | attrs["self"]?.substringAfter("0x")?.toULong(16) ?: 0uL, 186 | attrs["sysTid"]?.toInt() ?: 0, 187 | attrs["nice"]?.toInt() ?: 0, 188 | attrs["cgrp"] ?: "default", 189 | sched0 to sched1, 190 | attrs["handle"]?.substringAfter("0x")?.toULong(16) ?: 0uL, 191 | attrs["state"]?.firstOrNull() ?: '?', 192 | Triple(schedstat0, schedstat1, schedstat2), 193 | attrs["utm"]?.toInt() ?: 0, 194 | attrs["stm"]?.toInt() ?: 0, 195 | attrs["core"]?.toInt() ?: 0, 196 | attrs["HZ"]?.toInt() ?: 0, 197 | stack0 to stack1, 198 | attrs["stackSize"]?.let(::ByteSize)?.value ?: 0, 199 | heldMutexes, 200 | parseStackFrames()) 201 | } 202 | 203 | private fun parseNativeThread(): NativeThreadInfo? { 204 | val name = reader.readDoubleQuotedString() ?: return null 205 | val priority = parseThreadIntLabel("prio=") 206 | reader.readLine() // skip remaining chars in line 207 | val attrs = parseThreadAttributes() 208 | val (schedstat0, schedstat1, schedstat2) = attrs["schedstat"]?.split(' ')?.map(String::toLong) ?: listOf(0L, 0L, 0L) 209 | 210 | return NativeThreadInfo( 211 | name, 212 | priority, 213 | attrs["sysTid"]?.toInt() ?: 0, 214 | attrs["nice"]?.toInt() ?: 0, 215 | attrs["cgrp"] ?: "default", 216 | attrs["state"]?.firstOrNull() ?: '?', 217 | Triple(schedstat0, schedstat1, schedstat2), 218 | attrs["utm"]?.toInt() ?: 0, 219 | attrs["stm"]?.toInt() ?: 0, 220 | attrs["core"]?.toInt() ?: 0, 221 | attrs["HZ"]?.toInt() ?: 0, 222 | parseStackFrames() 223 | ) 224 | } 225 | 226 | private fun parseStackFrames(): List = readLinesWithoutBlank().mapNotNull { 227 | val line = it.trim() 228 | when { 229 | line.startsWith("kernel: ") -> KernelStackFrame(line) 230 | line.startsWith("native: ") -> NativeStackFrame(line) 231 | line.startsWith("at ") -> JavaStackFrame(line) 232 | else -> null 233 | } 234 | } 235 | 236 | 237 | private fun parseThreadDaemon(): Boolean { 238 | reader.skipWhitespace() 239 | 240 | val daemon = CharArray(6) 241 | val n = reader.read(daemon, 0, daemon.size) 242 | if ("daemon" == String(daemon)) { 243 | return true 244 | } 245 | reader.unread(daemon, 0, n) 246 | return false 247 | } 248 | 249 | 250 | private fun parseThreadIntLabel(prefix: String): Int { 251 | reader.skipWhitespace() 252 | 253 | val buf = CharArray(prefix.length) 254 | val n = reader.read(buf, 0, buf.size) 255 | if (n < buf.size || prefix != String(buf)) { 256 | reader.unread(buf, 0, n) 257 | return -1 258 | } 259 | return reader.readUnsignedInt() ?: 0 260 | } 261 | 262 | private fun parseThreadStatus(): ThreadStatus { 263 | reader.skipWhitespace() 264 | return ThreadStatus.valueOf(reader.readToken()!!) 265 | } 266 | 267 | private fun readLinesWithoutBlank(): List { 268 | val lines = mutableListOf() 269 | while (true) { 270 | lines += reader.readLine()?.takeIf(String::isNotBlank) ?: break 271 | } 272 | return lines 273 | } 274 | 275 | } 276 | 277 | private fun String.toBytes(): Long { 278 | val hrs = toUpperCase() 279 | val b = hrs.indexOf("B").takeIf { it > 0 } ?: return 0 280 | val num: (Int) -> Long = { 281 | substring(0, it).takeIf(String::isNotEmpty)?.toLong() ?: 0 282 | } 283 | return when (hrs[b - 1]) { 284 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' -> num(b) 285 | 'K' -> num(b - 1) * KB 286 | 'M' -> num(b - 1) * MB 287 | 'G' -> num(b - 1) * GB 288 | else -> num(b - 1) * TB 289 | } 290 | } 291 | 292 | -------------------------------------------------------------------------------- /src/test/kotlin/io/johnsonlee/android/trace/JavaStackTraceParserTest.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | import kotlin.test.assertNotNull 6 | import kotlin.test.assertTrue 7 | 8 | class JavaStackTraceParserTest { 9 | 10 | @Test 11 | fun `parse java stack trace`() { 12 | val stackTrace = """ 13 | android.view.WindowManager${'$'}BadTokenException: Unable to add window -- token android.os.BinderProxy@e2815e is not valid; is your activity running? 14 | at android.view.ViewRootImpl.setView(ViewRootImpl.java:679) 15 | at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342) 16 | at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93) 17 | at android.widget.Toast${'$'}TN.handleShow(Toast.java:459) 18 | at android.widget.Toast${'$'}TN${'$'}2.handleMessage(Toast.java:342) 19 | at android.os.Handler.dispatchMessage(Handler.java:102) 20 | at android.os.Looper.loop(Looper.java:154) 21 | at android.app.ActivityThread.main(ActivityThread.java:6119) 22 | at java.lang.reflect.Method.invoke(Native Method)""" 23 | val frames = JavaStackTraceParser().parse(stackTrace) 24 | assertTrue(frames.isNotEmpty()) 25 | assertEquals(9, frames.size) 26 | val rootCause = frames.rootCause ?: frames.firstOrNull() 27 | assertNotNull(rootCause) 28 | assertEquals("ViewRootImpl.java", rootCause.sourceFile) 29 | assertEquals("setView", rootCause.methodName) 30 | assertEquals(679, rootCause.lineNumber) 31 | } 32 | 33 | @Test 34 | fun `parse android stack trace`() { 35 | val stackTrace = """ 36 | java.lang.RuntimeException: Fatal Crash 37 | at com.example.foo.CrashyClass.sendMessage(CrashyClass.java:10) 38 | at com.example.foo.CrashyClass.crash(CrashyClass.java:6) 39 | at com.bugsnag.android.example.ExampleActivity.crashUnhandled(ExampleActivity.kt:55) 40 | at com.bugsnag.android.example.ExampleActivity${'$'}onCreate${'$'}1.invoke(ExampleActivity.kt:33) 41 | at com.bugsnag.android.example.ExampleActivity${'$'}onCreate${'$'}1.invoke(ExampleActivity.kt:14) 42 | at com.bugsnag.android.example.ExampleActivity${'$'}sam${'$'}android_view_View_OnClickListener${'$'}0.onClick(ExampleActivity.kt) 43 | at android.view.View.performClick(View.java:5637) 44 | at android.view.View${'$'}PerformClick.run(View.java:22429) 45 | at android.os.Handler.handleCallback(Handler.java:751) 46 | at android.os.Handler.dispatchMessage(Handler.java:95) 47 | at android.os.Looper.loop(Looper.java:154) 48 | at android.app.ActivityThread.main(ActivityThread.java:6119) 49 | at java.lang.reflect.Method.invoke(Native Method) 50 | at com.android.internal.os.ZygoteInit${'$'}MethodAndArgsCaller.run(ZygoteInit.java:886) 51 | at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)""" 52 | 53 | val frames = JavaStackTraceParser().parse(stackTrace) 54 | assertTrue(frames.isNotEmpty()) 55 | assertEquals(15, frames.size) 56 | val rootCause = frames.rootCause ?: frames.firstOrNull() 57 | assertNotNull(rootCause) 58 | assertEquals("CrashyClass.java", rootCause.sourceFile) 59 | assertEquals("sendMessage", rootCause.methodName) 60 | assertEquals(10, rootCause.lineNumber) 61 | } 62 | 63 | 64 | @Test 65 | fun `parse android stack trace with causes`() { 66 | val stackTrace = """ 67 | Fatal Exception: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.labpixies.flood/com.labpixies.flood.GameFinishedActivity}: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation 68 | at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3303) 69 | at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3411) 70 | at android.app.ActivityThread.-wrap12(ActivityThread.java) 71 | at android.app.ActivityThread${'$'}H.handleMessage(ActivityThread.java:1994) 72 | at android.os.Handler.dispatchMessage(Handler.java:108) 73 | at android.os.Looper.loop(Looper.java:166) 74 | at android.app.ActivityThread.main(ActivityThread.java:7529) 75 | at java.lang.reflect.Method.invoke(Method.java) 76 | at com.android.internal.os.Zygote${'$'}MethodAndArgsCaller.run(Zygote.java:245) 77 | at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921) 78 | Caused by: java.lang.IllegalStateException: Only fullscreen opaque activities can request orientation 79 | at android.app.Activity.onCreate(Activity.java:1081) 80 | at com.labpixies.flood.GameFinishedActivity.onCreate(GameFinishedActivity.java:81) 81 | at android.app.Activity.performCreate(Activity.java:7383) 82 | at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1218) 83 | at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3256) 84 | at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3411) 85 | at android.app.ActivityThread.-wrap12(ActivityThread.java) 86 | at android.app.ActivityThread${'$'}H.handleMessage(ActivityThread.java:1994) 87 | at android.os.Handler.dispatchMessage(Handler.java:108) 88 | at android.os.Looper.loop(Looper.java:166) 89 | at android.app.ActivityThread.main(ActivityThread.java:7529) 90 | at java.lang.reflect.Method.invoke(Method.java) 91 | at com.android.internal.os.Zygote${'$'}MethodAndArgsCaller.run(Zygote.java:245) 92 | at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921)""" 93 | 94 | val frames = JavaStackTraceParser().parse(stackTrace) 95 | assertTrue(frames.isNotEmpty()) 96 | assertEquals(14, frames.size) 97 | val rootCause = frames.rootCause ?: frames.firstOrNull() 98 | assertNotNull(rootCause) 99 | assertEquals("GameFinishedActivity.java", rootCause.sourceFile) 100 | assertEquals("onCreate", rootCause.methodName) 101 | assertEquals(81, rootCause.lineNumber) 102 | } 103 | 104 | @Test 105 | fun `parse android stack trace with multiple causes`() { 106 | val stackTrace = """ 107 | io.reactivex.exceptions.OnErrorNotImplementedException: Error occurred during subscription 108 | at android.os.Handler.handleCallback(Handler.java:883) 109 | at android.os.Handler.dispatchMessage(Handler.java:108) 110 | at android.os.Looper.loop(Looper.java:166) 111 | at android.app.ActivityThread.main(ActivityThread.java:7529) 112 | at java.lang.reflect.Method.invoke(Method.java) 113 | at com.android.internal.os.Zygote${'$'}MethodAndArgsCaller.run(Zygote.java:245) 114 | at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921) 115 | Caused by: android.util.AndroidRuntimeException: java.lang.reflect.InvocationTargetException 116 | at android.webkit.WebViewFactory.getProvider(WebViewFactory.java:271) 117 | at android.webkit.WebView.getFactory(WebViewFactory.java:2551) 118 | at io.johnsonlee.example.SimpleWebView.(SimpleWebView.java:80) 119 | at android.webkit.WebView.ensureProviderCreated(WebView.java:2545) 120 | at android.webkit.WebView.setOverScrollMode(WebView.java:2634) 121 | at android.view.View.(View.java:5433) 122 | at android.view.View.(View.java:5624) 123 | at android.view.ViewGroup.(ViewGroup.java:687) 124 | at android.widget.AbsoluteLayout.(AbsoluteLayout.java:58) 125 | at android.webview.WebView.(WebView.java:410) 126 | at android.webview.WebView.(WebView.java:353) 127 | at android.webview.WebView.(WebView.java:336) 128 | Caused by: java.lang.reflect.InvocationTargetException 129 | at java.lang.reflect.Method.invoke(Native Method) 130 | at android.webkit.WebViewFactory.getProvider(WebViewFactory.java:271) 131 | ... 32 more""" 132 | val frames = JavaStackTraceParser().parse(stackTrace) 133 | assertTrue(frames.isNotEmpty()) 134 | val rootCause = frames.rootCause ?: frames.firstOrNull() 135 | assertNotNull(rootCause) 136 | assertEquals("SimpleWebView.java", rootCause.sourceFile) 137 | assertEquals("", rootCause.methodName) 138 | assertEquals(80, rootCause.lineNumber) 139 | } 140 | 141 | } -------------------------------------------------------------------------------- /src/test/kotlin/io/johnsonlee/android/trace/NativeStackFrameTest.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | 6 | class NativeStackFrameTest { 7 | 8 | @Test 9 | fun `parse native backtrace 0`() { 10 | val frame = NativeStackFrame(" native: #00 pc 00000000003ccb5c /system/lib64/libart.so (art::DumpNativeStack(std::__1::basic_ostream>&, int, BacktraceMap*, char const*, art::ArtMethod*, void*)+208)") 11 | assertEquals(0, frame.index) 12 | assertEquals(0x00000000003ccb5c, frame.pc) 13 | assertEquals("/system/lib64/libart.so", frame.mapName) 14 | assertEquals("art::DumpNativeStack(std::__1::basic_ostream>&, int, BacktraceMap*, char const*, art::ArtMethod*, void*)", frame.functionName) 15 | assertEquals(208, frame.functionOffset) 16 | } 17 | 18 | @Test 19 | fun `parse native backtrace 1`() { 20 | val frame = NativeStackFrame(" native: #01 pc 000000000049cdf4 /system/lib64/libart.so (art::Thread::DumpStack(std::__1::basic_ostream>&, bool, BacktraceMap*, bool) const+348)") 21 | assertEquals(0x000000000049cdf4, frame.pc) 22 | assertEquals("/system/lib64/libart.so", frame.mapName) 23 | assertEquals("art::Thread::DumpStack(std::__1::basic_ostream>&, bool, BacktraceMap*, bool) const", frame.functionName) 24 | assertEquals(348, frame.functionOffset) 25 | } 26 | 27 | @Test 28 | fun `parse native backtrace 8`() { 29 | val frame = NativeStackFrame(" native: #08 pc 000000000000255c /data/app/io.johnsonlee.graffito-8HsPPY4tZ-aNtI2TLJBmeQ==/lib/arm64/libgraffito.so (???)") 30 | assertEquals(0x000000000000255c, frame.pc) 31 | assertEquals("libgraffito.so", frame.mapName) 32 | assertEquals("", frame.functionName) 33 | assertEquals(-1, frame.functionOffset) 34 | } 35 | 36 | @Test 37 | fun `parse native backtrace without function`() { 38 | val frame = NativeStackFrame(" native: #08 pc 000000000000255c /data/app/io.johnsonlee.graffito-8HsPPY4tZ-aNtI2TLJBmeQ==/lib/arm64/libgraffito.so") 39 | assertEquals(0x000000000000255c, frame.pc) 40 | assertEquals("libgraffito.so", frame.mapName) 41 | assertEquals("", frame.functionName) 42 | assertEquals(-1, frame.functionOffset) 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/test/kotlin/io/johnsonlee/android/trace/RootCauseIdentificationTest.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | import kotlin.test.assertNotNull 6 | import kotlin.test.assertTrue 7 | 8 | class RootCauseIdentificationTest { 9 | 10 | @Test 11 | fun `identify root cause of ANR with java stack frames`() { 12 | val stackTrace = """ 13 | "main" prio=5 tid=1 Sleeping 14 | | group="main" sCount=1 dsCount=0 flags=1 obj=0x73e015f0 self=0x6fdacbea00 15 | | sysTid=27873 nice=-10 cgrp=default sched=0/0 handle=0x705f7d49a8 16 | | state=S schedstat=( 485151184 25248967 249 ) utm=40 stm=7 core=3 HZ=100 17 | | stack=0x7ffc56a000-0x7ffc56c000 stackSize=8MB 18 | | held mutexes= 19 | at java.lang.Thread.sleep(Native method) 20 | - sleeping on <0x048bde26> (a java.lang.Object) 21 | at java.lang.Thread.sleep(Thread.java:373) 22 | - locked <0x048bde26> (a java.lang.Object) 23 | at java.lang.Thread.sleep(Thread.java:314) 24 | at io.johnsonlee.graffito.MainActivity${'$'}onCreate${'$'}1.onClick(MainActivity.kt:13) 25 | at android.view.View.performClick(View.java:6294) 26 | at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992) 27 | at android.view.View${'$'}PerformClick.run(View.java:24770) 28 | at android.os.Handler.handleCallback(Handler.java:790) 29 | at android.os.Handler.dispatchMessage(Handler.java:99) 30 | at android.os.Looper.loop(Looper.java:164) 31 | at android.app.ActivityThread.main(ActivityThread.java:6494) 32 | at java.lang.reflect.Method.invoke(Native method) 33 | at com.android.internal.os.RuntimeInit${'$'}MethodAndArgsCaller.run(RuntimeInit.java:438) 34 | at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 35 | """.trimIndent() 36 | val rootCause = identifyRootCause(stackTrace) 37 | assertNotNull(rootCause) 38 | assertTrue(rootCause is JavaStackFrame) 39 | assertEquals("MainActivity.kt", rootCause.sourceFile) 40 | assertEquals("onClick", rootCause.methodName) 41 | } 42 | 43 | @Test 44 | fun `identify root cause of ANR with java stack frames and system native stack frames`() { 45 | val stackTrace = """ 46 | "main" prio=5 tid=1 Sleeping 47 | | group="main" sCount=1 dsCount=0 flags=1 obj=0x73e015f0 self=0x6fdacbea00 48 | | sysTid=27873 nice=-10 cgrp=default sched=0/0 handle=0x705f7d49a8 49 | | state=S schedstat=( 485151184 25248967 249 ) utm=40 stm=7 core=3 HZ=100 50 | | stack=0x7ffc56a000-0x7ffc56c000 stackSize=8MB 51 | | held mutexes= 52 | native: #00 pc 0004793e /system/lib/libc.so (pthread_mutex_lock+1) 53 | native: #01 pc 0001aa1b /system/lib/libc.so (readdir+10) 54 | native: #02 pc 00001b91 /system/xbin/crasher (readdir_null+20) 55 | native: #03 pc 0000184b /system/xbin/crasher (do_action+978) 56 | native: #04 pc 00001459 /system/xbin/crasher (thread_callback+24) 57 | native: #05 pc 00047317 /system/lib/libc.so (_ZL15__pthread_startPv+22) 58 | native: #06 pc 0001a7e5 /system/lib/libc.so (__start_thread+34) 59 | at java.lang.Thread.sleep(Native method) 60 | - sleeping on <0x048bde26> (a java.lang.Object) 61 | at java.lang.Thread.sleep(Thread.java:373) 62 | - locked <0x048bde26> (a java.lang.Object) 63 | at java.lang.Thread.sleep(Thread.java:314) 64 | at io.johnsonlee.graffito.MainActivity${'$'}onCreate${'$'}1.onClick(MainActivity.kt:13) 65 | at android.view.View.performClick(View.java:6294) 66 | at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992) 67 | at android.view.View${'$'}PerformClick.run(View.java:24770) 68 | at android.os.Handler.handleCallback(Handler.java:790) 69 | at android.os.Handler.dispatchMessage(Handler.java:99) 70 | at android.os.Looper.loop(Looper.java:164) 71 | at android.app.ActivityThread.main(ActivityThread.java:6494) 72 | at java.lang.reflect.Method.invoke(Native method) 73 | at com.android.internal.os.RuntimeInit${'$'}MethodAndArgsCaller.run(RuntimeInit.java:438) 74 | at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 75 | """.trimIndent() 76 | val rootCause = identifyRootCause(stackTrace) 77 | assertNotNull(rootCause) 78 | assertTrue(rootCause is JavaStackFrame) 79 | assertEquals("MainActivity.kt", rootCause.sourceFile) 80 | assertEquals("onClick", rootCause.methodName) 81 | } 82 | 83 | @Test 84 | fun `identify root cause of ANR with java stack frames and user native stack frames`() { 85 | val stackTrace = """ 86 | "main" prio=5 tid=1 Sleeping 87 | | group="main" sCount=1 dsCount=0 flags=1 obj=0x73e015f0 self=0x6fdacbea00 88 | | sysTid=27873 nice=-10 cgrp=default sched=0/0 handle=0x705f7d49a8 89 | | state=S schedstat=( 485151184 25248967 249 ) utm=40 stm=7 core=3 HZ=100 90 | | stack=0x7ffc56a000-0x7ffc56c000 stackSize=8MB 91 | | held mutexes= 92 | native: #00 pc 0004793e /system/lib/libc.so (pthread_mutex_lock+1) 93 | native: #01 pc 0001aa1b /system/lib/libc.so (readdir+10) 94 | native: #02 pc 00001b91 /data/app/com.example.app-SfGgHWrZgkDTZ437L1I_cQ==/lib/arm/libgraffito.so 95 | native: #05 pc 00047317 /system/lib/libc.so (_ZL15__pthread_startPv+22) 96 | native: #06 pc 0001a7e5 /system/lib/libc.so (__start_thread+34) 97 | at java.lang.Thread.sleep(Native method) 98 | - sleeping on <0x048bde26> (a java.lang.Object) 99 | at java.lang.Thread.sleep(Thread.java:373) 100 | - locked <0x048bde26> (a java.lang.Object) 101 | at java.lang.Thread.sleep(Thread.java:314) 102 | at io.johnsonlee.graffito.MainActivity${'$'}onCreate${'$'}1.onClick(MainActivity.kt:13) 103 | at android.view.View.performClick(View.java:6294) 104 | at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992) 105 | at android.view.View${'$'}PerformClick.run(View.java:24770) 106 | at android.os.Handler.handleCallback(Handler.java:790) 107 | at android.os.Handler.dispatchMessage(Handler.java:99) 108 | at android.os.Looper.loop(Looper.java:164) 109 | at android.app.ActivityThread.main(ActivityThread.java:6494) 110 | at java.lang.reflect.Method.invoke(Native method) 111 | at com.android.internal.os.RuntimeInit${'$'}MethodAndArgsCaller.run(RuntimeInit.java:438) 112 | at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 113 | """.trimIndent() 114 | val rootCause = identifyRootCause(stackTrace) 115 | assertNotNull(rootCause) 116 | assertTrue(rootCause is NativeStackFrame) 117 | assertEquals("libgraffito.so", rootCause.mapName) 118 | } 119 | 120 | @Test 121 | fun `identify root cause of native crash`() { 122 | val stackTrace = """ 123 | #00 pc 0004793e /system/lib/libc.so (pthread_mutex_lock+1) 124 | #01 pc 0001aa1b /system/lib/libc.so (readdir+10) 125 | #02 pc 00001b91 /system/xbin/crasher (readdir_null+20) 126 | #03 pc 0000184b /system/xbin/crasher (do_action+978) 127 | #04 pc 00001459 /system/xbin/crasher (thread_callback+24) 128 | #05 pc 00047317 /system/lib/libc.so (_ZL15__pthread_startPv+22) 129 | #06 pc 0001a7e5 /system/lib/libc.so (__start_thread+34) 130 | """.trimIndent() 131 | val rootCause = identifyRootCause(stackTrace) 132 | assertNotNull(rootCause) 133 | assertTrue(rootCause is NativeStackFrame) 134 | assertEquals("/system/lib/libc.so", rootCause.mapName) 135 | assertEquals("pthread_mutex_lock", rootCause.functionName) 136 | } 137 | 138 | @Test 139 | fun `identify root cause of java crash`() { 140 | val stackTrace = """ 141 | io.reactivex.exceptions.OnErrorNotImplementedException: Error occurred during subscription 142 | at android.os.Handler.handleCallback(Handler.java:883) 143 | at android.os.Handler.dispatchMessage(Handler.java:108) 144 | at android.os.Looper.loop(Looper.java:166) 145 | at android.app.ActivityThread.main(ActivityThread.java:7529) 146 | at java.lang.reflect.Method.invoke(Method.java) 147 | at com.android.internal.os.Zygote${'$'}MethodAndArgsCaller.run(Zygote.java:245) 148 | at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:921) 149 | Caused by: android.util.AndroidRuntimeException: java.lang.reflect.InvocationTargetException 150 | at android.webkit.WebViewFactory.getProvider(WebViewFactory.java:271) 151 | at android.webkit.WebView.getFactory(WebViewFactory.java:2551) 152 | at io.johnsonlee.example.SimpleWebView.(SimpleWebView.java:80) 153 | at android.webkit.WebView.ensureProviderCreated(WebView.java:2545) 154 | at android.webkit.WebView.setOverScrollMode(WebView.java:2634) 155 | at android.view.View.(View.java:5433) 156 | at android.view.View.(View.java:5624) 157 | at android.view.ViewGroup.(ViewGroup.java:687) 158 | at android.widget.AbsoluteLayout.(AbsoluteLayout.java:58) 159 | at android.webview.WebView.(WebView.java:410) 160 | at android.webview.WebView.(WebView.java:353) 161 | at android.webview.WebView.(WebView.java:336) 162 | Caused by: java.lang.reflect.InvocationTargetException 163 | at java.lang.reflect.Method.invoke(Native Method) 164 | at android.webkit.WebViewFactory.getProvider(WebViewFactory.java:271) 165 | ... 32 more""" 166 | val rootCause = identifyRootCause(stackTrace) 167 | assertNotNull(rootCause) 168 | assertTrue(rootCause is JavaStackFrame) 169 | assertEquals("SimpleWebView.java", rootCause.sourceFile) 170 | assertEquals("", rootCause.methodName) 171 | } 172 | 173 | } -------------------------------------------------------------------------------- /src/test/kotlin/io/johnsonlee/android/trace/TombstoneFileParserTest.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | import kotlin.test.assertNotNull 6 | 7 | @ExperimentalUnsignedTypes 8 | class TombstoneFileParserTest { 9 | 10 | @Test 11 | fun `parse tombstone file`() { 12 | val tombstone = javaClass.getResourceAsStream("/tombstone.txt").use { 13 | TombstoneFileParser(it).parse() 14 | } 15 | assertNotNull(tombstone) 16 | assertEquals(7, tombstone.backtrace.size) 17 | val rootCause = tombstone.rootCause 18 | assertNotNull(rootCause) 19 | assertEquals(0, rootCause.index) 20 | assertEquals("/system/lib/libc.so", rootCause.mapName) 21 | assertEquals("pthread_mutex_lock", rootCause.functionName) 22 | assertEquals(1, rootCause.functionOffset) 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/test/kotlin/io/johnsonlee/android/trace/TraceFileParserTest.kt: -------------------------------------------------------------------------------- 1 | package io.johnsonlee.android.trace 2 | 3 | import kotlin.test.Test 4 | import kotlin.test.assertEquals 5 | import kotlin.test.assertNotNull 6 | import kotlin.test.assertTrue 7 | 8 | @ExperimentalUnsignedTypes 9 | class TraceFileParserTest { 10 | 11 | @Test 12 | fun `parse trace file`() { 13 | val trace = javaClass.getResourceAsStream("/trace.txt").use { 14 | TraceFileParser(it).parse() 15 | } 16 | assertTrue(trace.threads.isNotEmpty()) 17 | assertTrue(trace.threads.size == 16) 18 | assertNotNull(trace.mainThreadInfo) 19 | 20 | val rootCause = trace.rootCause as JavaStackFrame 21 | assertNotNull(rootCause) 22 | assertEquals("io.johnsonlee.graffito.MainActivity\$onCreate\$1", rootCause.className) 23 | assertEquals("onClick", rootCause.methodName) 24 | assertEquals("MainActivity.kt", rootCause.sourceFile) 25 | assertEquals(13, rootCause.lineNumber) 26 | 27 | val jit0 = trace.threads.first { thread -> 28 | thread.name == "Jit thread pool worker thread 0" 29 | } 30 | assertNotNull(jit0) 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /src/test/resources/tombstone.txt: -------------------------------------------------------------------------------- 1 | *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 2 | Build fingerprint: 'Android/aosp_angler/angler:7.1.1/NYC/enh12211018:eng/test-keys' 3 | Revision: '0' 4 | ABI: 'arm' 5 | pid: 17946, tid: 17949, name: crasher >>> crasher <<< 6 | signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xc 7 | r0 0000000c r1 00000000 r2 00000000 r3 00000000 8 | r4 00000000 r5 0000000c r6 eccdd920 r7 00000078 9 | r8 0000461a r9 ffc78c19 sl ab209441 fp fffff924 10 | ip ed01b834 sp eccdd800 lr ecfa9a1f pc ecfd693e cpsr 600e0030 11 | 12 | backtrace: 13 | #00 pc 0004793e /system/lib/libc.so (pthread_mutex_lock+1) 14 | #01 pc 0001aa1b /system/lib/libc.so (readdir+10) 15 | #02 pc 00001b91 /system/xbin/crasher (readdir_null+20) 16 | #03 pc 0000184b /system/xbin/crasher (do_action+978) 17 | #04 pc 00001459 /system/xbin/crasher (thread_callback+24) 18 | #05 pc 00047317 /system/lib/libc.so (_ZL15__pthread_startPv+22) 19 | #06 pc 0001a7e5 /system/lib/libc.so (__start_thread+34) 20 | Tombstone written to: /data/tombstones/tombstone_06 -------------------------------------------------------------------------------- /src/test/resources/trace.txt: -------------------------------------------------------------------------------- 1 | ----- pid 27873 at 2021-01-25 17:33:59 ----- 2 | Cmd line: io.johnsonlee.graffito 3 | Zygote loaded classes=5026 post zygote classes=397 4 | Intern table: 44163 strong; 135 weak 5 | JNI: CheckJNI is on; globals=508 (plus 24 weak) 6 | Libraries: /data/app/io.johnsonlee.graffito-8HsPPY4tZ-aNtI2TLJBmeQ==/lib/arm64/libgraffito.so /system/lib64/libandroid.so /system/lib64/libcompiler_rt.so /system/lib64/libjavacrypto.so /system/lib64/libjnigraphics.so /system/lib64/libmedia_jni.so /system/lib64/libsoundpool.so /system/lib64/libwebviewchromium_loader.so libjavacore.so libopenjdk.so (10) 7 | Heap: 19% free, 2MB/2MB; 19426 objects 8 | Dumping cumulative Gc timings 9 | Start Dumping histograms for 1 iterations for concurrent copying 10 | ProcessMarkStack: Sum: 8.853ms 99% C.I. 8.853ms-8.853ms Avg: 8.853ms Max: 8.853ms 11 | VisitConcurrentRoots: Sum: 1.995ms 99% C.I. 1.995ms-1.995ms Avg: 1.995ms Max: 1.995ms 12 | ScanImmuneSpaces: Sum: 711us 99% C.I. 711us-711us Avg: 711us Max: 711us 13 | FlipThreadRoots: Sum: 373us 99% C.I. 373us-373us Avg: 373us Max: 373us 14 | RecordFree: Sum: 321us 99% C.I. 321us-321us Avg: 321us Max: 321us 15 | GrayAllDirtyImmuneObjects: Sum: 255us 99% C.I. 255us-255us Avg: 255us Max: 255us 16 | ClearFromSpace: Sum: 217us 99% C.I. 217us-217us Avg: 217us Max: 217us 17 | SweepSystemWeaks: Sum: 161us 99% C.I. 161us-161us Avg: 161us Max: 161us 18 | InitializePhase: Sum: 149us 99% C.I. 149us-149us Avg: 149us Max: 149us 19 | FlipOtherThreads: Sum: 94us 99% C.I. 94us-94us Avg: 94us Max: 94us 20 | ReclaimPhase: Sum: 61us 99% C.I. 61us-61us Avg: 61us Max: 61us 21 | ForwardSoftReferences: Sum: 60us 99% C.I. 60us-60us Avg: 60us Max: 60us 22 | (Paused)SetFromSpace: Sum: 59us 99% C.I. 59us-59us Avg: 59us Max: 59us 23 | VisitNonThreadRoots: Sum: 45us 99% C.I. 45us-45us Avg: 45us Max: 45us 24 | EnqueueFinalizerReferences: Sum: 38us 99% C.I. 38us-38us Avg: 38us Max: 38us 25 | (Paused)ClearCards: Sum: 35us 99% C.I. 0.250us-18us Avg: 2.187us Max: 18us 26 | SweepAllocSpace: Sum: 26us 99% C.I. 26us-26us Avg: 26us Max: 26us 27 | MarkZygoteLargeObjects: Sum: 25us 99% C.I. 25us-25us Avg: 25us Max: 25us 28 | (Paused)GrayAllNewlyDirtyImmuneObjects: Sum: 24us 99% C.I. 24us-24us Avg: 24us Max: 24us 29 | EmptyRBMarkBitStack: Sum: 19us 99% C.I. 19us-19us Avg: 19us Max: 19us 30 | SweepLargeObjects: Sum: 18us 99% C.I. 18us-18us Avg: 18us Max: 18us 31 | MarkingPhase: Sum: 17us 99% C.I. 17us-17us Avg: 17us Max: 17us 32 | MarkStackAsLive: Sum: 16us 99% C.I. 16us-16us Avg: 16us Max: 16us 33 | ProcessReferences: Sum: 14us 99% C.I. 1us-13us Avg: 7us Max: 13us 34 | SwapBitmaps: Sum: 5us 99% C.I. 5us-5us Avg: 5us Max: 5us 35 | (Paused)FlipCallback: Sum: 3us 99% C.I. 3us-3us Avg: 3us Max: 3us 36 | ResumeOtherThreads: Sum: 1us 99% C.I. 1us-1us Avg: 1us Max: 1us 37 | Done Dumping histograms 38 | concurrent copying paused: Sum: 142us 99% C.I. 142us-142us Avg: 142us Max: 142us 39 | concurrent copying total time: 13.667ms mean time: 13.667ms 40 | concurrent copying freed: 3523 objects with total size 1280KB 41 | concurrent copying throughput: 271000/s / 96MB/s 42 | Cumulative bytes moved 2714360 43 | Cumulative objects moved 44802 44 | Total time spent in GC: 13.667ms 45 | Mean GC size throughput: 91MB/s 46 | Mean GC object throughput: 257774 objects/s 47 | Total number of allocations 22949 48 | Total bytes allocated 3MB 49 | Total bytes freed 1280KB 50 | Free memory 522KB 51 | Free memory until GC 522KB 52 | Free memory until OOME 189MB 53 | Total memory 2MB 54 | Max memory 192MB 55 | Zygote space size 496KB 56 | Total mutator paused time: 142us 57 | Total time waiting for GC to complete: 5.207us 58 | Total GC count: 1 59 | Total GC time: 13.667ms 60 | Total blocking GC count: 0 61 | Total blocking GC time: 0 62 | Registered native bytes allocated: 233020 63 | /data/app/io.johnsonlee.graffito-8HsPPY4tZ-aNtI2TLJBmeQ==/oat/arm64/base.odex: quicken 64 | Current JIT code cache size: 12KB 65 | Current JIT data cache size: 10KB 66 | Current JIT capacity: 64KB 67 | Current number of JIT code cache entries: 32 68 | Total number of JIT compilations: 32 69 | Total number of JIT compilations for on stack replacement: 0 70 | Total number of JIT code cache collections: 0 71 | Memory used for stack maps: Avg: 103B Max: 608B Min: 24B 72 | Memory used for compiled code: Avg: 372B Max: 3KB Min: 4B 73 | Memory used for profiling info: Avg: 99B Max: 1256B Min: 32B 74 | Start Dumping histograms for 32 iterations for JIT timings 75 | Compiling: Sum: 44.013ms 99% C.I. 0.273ms-11.456ms Avg: 1.375ms Max: 12.713ms 76 | TrimMaps: Sum: 1.955ms 99% C.I. 20us-695.999us Avg: 61.093us Max: 759us 77 | Done Dumping histograms 78 | Memory used for compilation: Avg: 98KB Max: 745KB Min: 22KB 79 | ProfileSaver total_bytes_written=0 80 | ProfileSaver total_number_of_writes=0 81 | ProfileSaver total_number_of_code_cache_queries=0 82 | ProfileSaver total_number_of_skipped_writes=0 83 | ProfileSaver total_number_of_failed_writes=0 84 | ProfileSaver total_ms_of_sleep=5000 85 | ProfileSaver total_ms_of_work=0 86 | ProfileSaver max_number_profile_entries_cached=0 87 | ProfileSaver total_number_of_hot_spikes=0 88 | ProfileSaver total_number_of_wake_ups=0 89 | 90 | suspend all histogram: Sum: 1.581ms 99% C.I. 1us-252.799us Avg: 17.566us Max: 354us 91 | DALVIK THREADS (15): 92 | "TraceDumper" prio=10 tid=12 Runnable 93 | | group="main" sCount=0 dsCount=0 flags=0 obj=0x13c80bd0 self=0x6fd283cc00 94 | | sysTid=27904 nice=-10 cgrp=default sched=0/0 handle=0x6fc2dce4f0 95 | | state=R schedstat=( 16204895 139010 4 ) utm=0 stm=1 core=5 HZ=100 96 | | stack=0x6fc2cd4000-0x6fc2cd6000 stackSize=1005KB 97 | | held mutexes= "mutator lock"(shared held) 98 | native: #00 pc 00000000003ccb5c /system/lib64/libart.so (art::DumpNativeStack(std::__1::basic_ostream>&, int, BacktraceMap*, char const*, art::ArtMethod*, void*)+208) 99 | native: #01 pc 000000000049cdf4 /system/lib64/libart.so (art::Thread::DumpStack(std::__1::basic_ostream>&, bool, BacktraceMap*, bool) const+348) 100 | native: #02 pc 00000000004b4b68 /system/lib64/libart.so (art::DumpCheckpoint::Run(art::Thread*)+884) 101 | native: #03 pc 00000000004ad374 /system/lib64/libart.so (art::ThreadList::RunCheckpoint(art::Closure*, art::Closure*)+472) 102 | native: #04 pc 00000000004accf0 /system/lib64/libart.so (art::ThreadList::Dump(std::__1::basic_ostream>&, bool)+792) 103 | native: #05 pc 00000000004ac8f4 /system/lib64/libart.so (art::ThreadList::DumpForSigQuit(std::__1::basic_ostream>&)+912) 104 | native: #06 pc 000000000048145c /system/lib64/libart.so (art::Runtime::DumpForSigQuit(std::__1::basic_ostream>&)+196) 105 | native: #07 pc 0000000000003800 /data/app/io.johnsonlee.graffito-8HsPPY4tZ-aNtI2TLJBmeQ==/lib/arm64/libgraffito.so (art_dump+192) 106 | native: #08 pc 000000000000255c /data/app/io.johnsonlee.graffito-8HsPPY4tZ-aNtI2TLJBmeQ==/lib/arm64/libgraffito.so (???) 107 | native: #09 pc 0000000000067d0c /system/lib64/libc.so (__pthread_start(void*)+36) 108 | native: #10 pc 000000000001eba4 /system/lib64/libc.so (__start_thread+68) 109 | (no managed stack frames) 110 | 111 | "main" prio=5 tid=1 Sleeping 112 | | group="main" sCount=1 dsCount=0 flags=1 obj=0x73e015f0 self=0x6fdacbea00 113 | | sysTid=27873 nice=-10 cgrp=default sched=0/0 handle=0x705f7d49a8 114 | | state=S schedstat=( 485151184 25248967 249 ) utm=40 stm=7 core=3 HZ=100 115 | | stack=0x7ffc56a000-0x7ffc56c000 stackSize=8MB 116 | | held mutexes= 117 | at java.lang.Thread.sleep(Native method) 118 | - sleeping on <0x048bde26> (a java.lang.Object) 119 | at java.lang.Thread.sleep(Thread.java:373) 120 | - locked <0x048bde26> (a java.lang.Object) 121 | at java.lang.Thread.sleep(Thread.java:314) 122 | at io.johnsonlee.graffito.MainActivity$onCreate$1.onClick(MainActivity.kt:13) 123 | at android.view.View.performClick(View.java:6294) 124 | at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992) 125 | at android.view.View$PerformClick.run(View.java:24770) 126 | at android.os.Handler.handleCallback(Handler.java:790) 127 | at android.os.Handler.dispatchMessage(Handler.java:99) 128 | at android.os.Looper.loop(Looper.java:164) 129 | at android.app.ActivityThread.main(ActivityThread.java:6494) 130 | at java.lang.reflect.Method.invoke(Native method) 131 | at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 132 | at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 133 | 134 | "Jit thread pool worker thread 0" daemon prio=5 tid=2 Native (still starting up) 135 | | group="main" sCount=1 dsCount=0 flags=1 obj=0x13c80000 self=0x6fd280e000 136 | | sysTid=27884 nice=9 cgrp=default sched=0/0 handle=0x6fd27ff4f0 137 | | state=S schedstat=( 43669383 4613905 53 ) utm=3 stm=0 core=5 HZ=100 138 | | stack=0x6fd2701000-0x6fd2703000 stackSize=1021KB 139 | | held mutexes= 140 | kernel: __switch_to+0x94/0xa0 141 | kernel: futex_wait_queue_me+0xe0/0x148 142 | kernel: futex_wait+0x104/0x224 143 | kernel: do_futex+0xe0/0x928 144 | kernel: SyS_futex+0x11c/0x1b0 145 | kernel: __sys_trace+0x4c/0x50 146 | native: #00 pc 000000000001d4ac /system/lib64/libc.so (syscall+28) 147 | native: #01 pc 00000000000e663c /system/lib64/libart.so (art::ConditionVariable::WaitHoldingLocks(art::Thread*)+152) 148 | native: #02 pc 00000000004b6324 /system/lib64/libart.so (art::ThreadPool::GetTask(art::Thread*)+256) 149 | native: #03 pc 00000000004b5a18 /system/lib64/libart.so (art::ThreadPoolWorker::Run()+124) 150 | native: #04 pc 00000000004b54ec /system/lib64/libart.so (art::ThreadPoolWorker::Callback(void*)+148) 151 | native: #05 pc 0000000000067d0c /system/lib64/libc.so (__pthread_start(void*)+36) 152 | native: #06 pc 000000000001eba4 /system/lib64/libc.so (__start_thread+68) 153 | (no managed stack frames) 154 | 155 | "Signal Catcher" daemon prio=5 tid=3 WaitingInMainSignalCatcherLoop 156 | | group="system" sCount=1 dsCount=0 flags=1 obj=0x13c80260 self=0x6fdacbf400 157 | | sysTid=27885 nice=0 cgrp=default sched=0/0 handle=0x6fd26fe4f0 158 | | state=S schedstat=( 368854 0 2 ) utm=0 stm=0 core=5 HZ=100 159 | | stack=0x6fd2604000-0x6fd2606000 stackSize=1005KB 160 | | held mutexes= 161 | kernel: __switch_to+0x94/0xa0 162 | kernel: do_sigtimedwait+0xf0/0x1e4 163 | kernel: SyS_rt_sigtimedwait+0xe0/0x13c 164 | kernel: __sys_trace+0x4c/0x50 165 | native: #00 pc 000000000006a5d8 /system/lib64/libc.so (__rt_sigtimedwait+8) 166 | native: #01 pc 0000000000028468 /system/lib64/libc.so (sigwait+60) 167 | native: #02 pc 000000000048ba54 /system/lib64/libart.so (art::SignalSet::Wait()+44) 168 | native: #03 pc 000000000048b54c /system/lib64/libart.so (art::SignalCatcher::WaitForSignal(art::Thread*, art::SignalSet&)+252) 169 | native: #04 pc 0000000000489d28 /system/lib64/libart.so (art::SignalCatcher::Run(void*)+268) 170 | native: #05 pc 0000000000067d0c /system/lib64/libc.so (__pthread_start(void*)+36) 171 | native: #06 pc 000000000001eba4 /system/lib64/libc.so (__start_thread+68) 172 | (no managed stack frames) 173 | 174 | "JDWP" daemon prio=5 tid=4 WaitingInMainDebuggerLoop 175 | | group="system" sCount=1 dsCount=0 flags=1 obj=0x13c802e8 self=0x6fd281b400 176 | | sysTid=27886 nice=0 cgrp=default sched=0/0 handle=0x6fc3e6e4f0 177 | | state=S schedstat=( 7318541 988906 11 ) utm=0 stm=0 core=2 HZ=100 178 | | stack=0x6fc3d74000-0x6fc3d76000 stackSize=1005KB 179 | | held mutexes= 180 | kernel: __switch_to+0x94/0xa0 181 | kernel: poll_schedule_timeout+0x5c/0xd4 182 | kernel: do_select+0x45c/0x4c8 183 | kernel: core_sys_select+0x210/0x330 184 | kernel: SyS_pselect6+0x180/0x234 185 | kernel: __sys_trace+0x4c/0x50 186 | native: #00 pc 000000000006a530 /system/lib64/libc.so (__pselect6+8) 187 | native: #01 pc 0000000000026574 /system/lib64/libc.so (select+144) 188 | native: #02 pc 00000000005584d0 /system/lib64/libart.so (art::JDWP::JdwpAdbState::ProcessIncoming()+336) 189 | native: #03 pc 0000000000312d90 /system/lib64/libart.so (art::JDWP::JdwpState::Run()+448) 190 | native: #04 pc 0000000000312464 /system/lib64/libart.so (art::JDWP::StartJdwpThread(void*)+40) 191 | native: #05 pc 0000000000067d0c /system/lib64/libc.so (__pthread_start(void*)+36) 192 | native: #06 pc 000000000001eba4 /system/lib64/libc.so (__start_thread+68) 193 | (no managed stack frames) 194 | 195 | "ReferenceQueueDaemon" daemon prio=5 tid=5 Waiting 196 | | group="system" sCount=1 dsCount=0 flags=1 obj=0x13c80370 self=0x6fdacd2000 197 | | sysTid=27887 nice=4 cgrp=default sched=0/0 handle=0x6fc3d714f0 198 | | state=S schedstat=( 1563540 197551 10 ) utm=0 stm=0 core=1 HZ=100 199 | | stack=0x6fc3c6f000-0x6fc3c71000 stackSize=1037KB 200 | | held mutexes= 201 | at java.lang.Object.wait(Native method) 202 | - waiting on <0x04a15a67> (a java.lang.Class) 203 | at java.lang.Daemons$ReferenceQueueDaemon.runInternal(Daemons.java:178) 204 | - locked <0x04a15a67> (a java.lang.Class) 205 | at java.lang.Daemons$Daemon.run(Daemons.java:103) 206 | at java.lang.Thread.run(Thread.java:764) 207 | 208 | "FinalizerDaemon" daemon prio=5 tid=6 Waiting 209 | | group="system" sCount=1 dsCount=0 flags=1 obj=0x13c803f8 self=0x6fdacd2a00 210 | | sysTid=27888 nice=4 cgrp=default sched=0/0 handle=0x6fc3c6c4f0 211 | | state=S schedstat=( 1257189 595622 6 ) utm=0 stm=0 core=5 HZ=100 212 | | stack=0x6fc3b6a000-0x6fc3b6c000 stackSize=1037KB 213 | | held mutexes= 214 | at java.lang.Object.wait(Native method) 215 | - waiting on <0x04e91014> (a java.lang.Object) 216 | at java.lang.Object.wait(Object.java:422) 217 | at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:188) 218 | - locked <0x04e91014> (a java.lang.Object) 219 | at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:209) 220 | at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:232) 221 | at java.lang.Daemons$Daemon.run(Daemons.java:103) 222 | at java.lang.Thread.run(Thread.java:764) 223 | 224 | "FinalizerWatchdogDaemon" daemon prio=5 tid=7 Waiting 225 | | group="system" sCount=1 dsCount=0 flags=1 obj=0x13c80588 self=0x6fdacd3400 226 | | sysTid=27889 nice=4 cgrp=default sched=0/0 handle=0x6fc3b674f0 227 | | state=S schedstat=( 490990 353333 3 ) utm=0 stm=0 core=6 HZ=100 228 | | stack=0x6fc3a65000-0x6fc3a67000 stackSize=1037KB 229 | | held mutexes= 230 | at java.lang.Object.wait(Native method) 231 | - waiting on <0x0d9a20bd> (a java.lang.Daemons$FinalizerWatchdogDaemon) 232 | at java.lang.Daemons$FinalizerWatchdogDaemon.sleepUntilNeeded(Daemons.java:297) 233 | - locked <0x0d9a20bd> (a java.lang.Daemons$FinalizerWatchdogDaemon) 234 | at java.lang.Daemons$FinalizerWatchdogDaemon.runInternal(Daemons.java:277) 235 | at java.lang.Daemons$Daemon.run(Daemons.java:103) 236 | at java.lang.Thread.run(Thread.java:764) 237 | 238 | "HeapTaskDaemon" daemon prio=5 tid=8 Blocked 239 | | group="system" sCount=1 dsCount=0 flags=1 obj=0x13c80d10 self=0x6fdadbb200 240 | | sysTid=27890 nice=4 cgrp=default sched=0/0 handle=0x6fc3a624f0 241 | | state=S schedstat=( 8116721 5748439 18 ) utm=0 stm=0 core=5 HZ=100 242 | | stack=0x6fc3960000-0x6fc3962000 stackSize=1037KB 243 | | held mutexes= 244 | kernel: __switch_to+0x94/0xa0 245 | kernel: futex_wait_queue_me+0xe0/0x148 246 | kernel: futex_wait+0x104/0x224 247 | kernel: do_futex+0xe0/0x928 248 | kernel: SyS_futex+0x11c/0x1b0 249 | kernel: __sys_trace+0x4c/0x50 250 | native: #00 pc 000000000001d4ac /system/lib64/libc.so (syscall+28) 251 | native: #01 pc 00000000000e663c /system/lib64/libart.so (art::ConditionVariable::WaitHoldingLocks(art::Thread*)+152) 252 | native: #02 pc 0000000000253ac4 /system/lib64/libart.so (art::gc::TaskProcessor::GetTask(art::Thread*)+368) 253 | native: #03 pc 000000000025438c /system/lib64/libart.so (art::gc::TaskProcessor::RunAllTasks(art::Thread*)+92) 254 | native: #04 pc 00000000001f2f2c /system/framework/arm64/boot-core-libart.oat (Java_dalvik_system_VMRuntime_runHeapTasks__+124) 255 | at dalvik.system.VMRuntime.runHeapTasks(Native method) 256 | - waiting to lock an unknown object 257 | at java.lang.Daemons$HeapTaskDaemon.runInternal(Daemons.java:461) 258 | at java.lang.Daemons$Daemon.run(Daemons.java:103) 259 | at java.lang.Thread.run(Thread.java:764) 260 | 261 | "Binder:27873_1" prio=5 tid=9 Native 262 | | group="main" sCount=1 dsCount=0 flags=1 obj=0x13c80658 self=0x6fdac6f600 263 | | sysTid=27891 nice=0 cgrp=default sched=0/0 handle=0x6fc385f4f0 264 | | state=S schedstat=( 10941043 70365 20 ) utm=0 stm=0 core=5 HZ=100 265 | | stack=0x6fc3765000-0x6fc3767000 stackSize=1005KB 266 | | held mutexes= 267 | kernel: __switch_to+0x94/0xa0 268 | kernel: binder_thread_read+0xf9c/0x10e8 269 | kernel: binder_ioctl+0x478/0x958 270 | kernel: do_vfs_ioctl+0x4c4/0x5c0 271 | kernel: SyS_ioctl+0x74/0xbc 272 | kernel: __sys_trace+0x4c/0x50 273 | native: #00 pc 000000000006a4e4 /system/lib64/libc.so (__ioctl+4) 274 | native: #01 pc 0000000000023e48 /system/lib64/libc.so (ioctl+136) 275 | native: #02 pc 00000000000549d0 /system/lib64/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+260) 276 | native: #03 pc 0000000000054b40 /system/lib64/libbinder.so (android::IPCThreadState::getAndExecuteCommand()+24) 277 | native: #04 pc 0000000000055204 /system/lib64/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+60) 278 | native: #05 pc 00000000000770f8 /system/lib64/libbinder.so (???) 279 | native: #06 pc 0000000000011478 /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+280) 280 | native: #07 pc 00000000000a9814 /system/lib64/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+140) 281 | native: #08 pc 0000000000067d0c /system/lib64/libc.so (__pthread_start(void*)+36) 282 | native: #09 pc 000000000001eba4 /system/lib64/libc.so (__start_thread+68) 283 | (no managed stack frames) 284 | 285 | "Binder:27873_2" prio=5 tid=10 Native 286 | | group="main" sCount=1 dsCount=0 flags=1 obj=0x13c80700 self=0x6fd2836800 287 | | sysTid=27892 nice=0 cgrp=default sched=0/0 handle=0x6fc37624f0 288 | | state=S schedstat=( 2231720 0 11 ) utm=0 stm=0 core=4 HZ=100 289 | | stack=0x6fc3668000-0x6fc366a000 stackSize=1005KB 290 | | held mutexes= 291 | kernel: __switch_to+0x94/0xa0 292 | kernel: binder_thread_read+0xf9c/0x10e8 293 | kernel: binder_ioctl+0x478/0x958 294 | kernel: do_vfs_ioctl+0x4c4/0x5c0 295 | kernel: SyS_ioctl+0x74/0xbc 296 | kernel: __sys_trace+0x4c/0x50 297 | native: #00 pc 000000000006a4e4 /system/lib64/libc.so (__ioctl+4) 298 | native: #01 pc 0000000000023e48 /system/lib64/libc.so (ioctl+136) 299 | native: #02 pc 00000000000549d0 /system/lib64/libbinder.so (android::IPCThreadState::talkWithDriver(bool)+260) 300 | native: #03 pc 0000000000054b40 /system/lib64/libbinder.so (android::IPCThreadState::getAndExecuteCommand()+24) 301 | native: #04 pc 0000000000055204 /system/lib64/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+60) 302 | native: #05 pc 00000000000770f8 /system/lib64/libbinder.so (???) 303 | native: #06 pc 0000000000011478 /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+280) 304 | native: #07 pc 00000000000a9814 /system/lib64/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+140) 305 | native: #08 pc 0000000000067d0c /system/lib64/libc.so (__pthread_start(void*)+36) 306 | native: #09 pc 000000000001eba4 /system/lib64/libc.so (__start_thread+68) 307 | (no managed stack frames) 308 | 309 | "Profile Saver" daemon prio=5 tid=11 Native (still starting up) 310 | | group="system" sCount=1 dsCount=0 flags=1 obj=0x13c80a28 self=0x6fcfb0ea00 311 | | sysTid=27898 nice=9 cgrp=default sched=0/0 handle=0x6fc2f894f0 312 | | state=S schedstat=( 11989222 12306145 10 ) utm=0 stm=0 core=3 HZ=100 313 | | stack=0x6fc2e8f000-0x6fc2e91000 stackSize=1005KB 314 | | held mutexes= 315 | kernel: __switch_to+0x94/0xa0 316 | kernel: futex_wait_queue_me+0xe0/0x148 317 | kernel: futex_wait+0x104/0x224 318 | kernel: do_futex+0xe0/0x928 319 | kernel: SyS_futex+0x11c/0x1b0 320 | kernel: __sys_trace+0x4c/0x50 321 | native: #00 pc 000000000001d4ac /system/lib64/libc.so (syscall+28) 322 | native: #01 pc 00000000000e663c /system/lib64/libart.so (art::ConditionVariable::WaitHoldingLocks(art::Thread*)+152) 323 | native: #02 pc 00000000003367c8 /system/lib64/libart.so (art::ProfileSaver::Run()+360) 324 | native: #03 pc 00000000003393b4 /system/lib64/libart.so (art::ProfileSaver::RunProfileSaverThread(void*)+92) 325 | native: #04 pc 0000000000067d0c /system/lib64/libc.so (__pthread_start(void*)+36) 326 | native: #05 pc 000000000001eba4 /system/lib64/libc.so (__start_thread+68) 327 | (no managed stack frames) 328 | 329 | "RenderThread" prio=5 tid=13 Native 330 | | group="main" sCount=1 dsCount=0 flags=1 obj=0x12d00020 self=0x6fc2a23800 331 | | sysTid=27943 nice=-10 cgrp=default sched=0/0 handle=0x6fc29ff4f0 332 | | state=S schedstat=( 163711624 4666927 129 ) utm=12 stm=3 core=4 HZ=100 333 | | stack=0x6fc2905000-0x6fc2907000 stackSize=1005KB 334 | | held mutexes= 335 | kernel: __switch_to+0x94/0xa0 336 | kernel: SyS_epoll_wait+0x2d8/0x36c 337 | kernel: SyS_epoll_pwait+0xc8/0x164 338 | kernel: __sys_trace+0x4c/0x50 339 | native: #00 pc 000000000006a3f8 /system/lib64/libc.so (__epoll_pwait+8) 340 | native: #01 pc 000000000001f344 /system/lib64/libc.so (epoll_pwait+52) 341 | native: #02 pc 0000000000015d48 /system/lib64/libutils.so (android::Looper::pollInner(int)+144) 342 | native: #03 pc 0000000000015c28 /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+108) 343 | native: #04 pc 0000000000072c60 /system/lib64/libhwui.so (android::uirenderer::renderthread::RenderThread::threadLoop()+856) 344 | native: #05 pc 0000000000011478 /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+280) 345 | native: #06 pc 00000000000a9814 /system/lib64/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+140) 346 | native: #07 pc 0000000000067d0c /system/lib64/libc.so (__pthread_start(void*)+36) 347 | native: #08 pc 000000000001eba4 /system/lib64/libc.so (__start_thread+68) 348 | (no managed stack frames) 349 | 350 | "hwuiTask1" prio=5 tid=14 Native 351 | | group="main" sCount=1 dsCount=0 flags=1 obj=0x12dc0020 self=0x6fd2884000 352 | | sysTid=27946 nice=-2 cgrp=default sched=0/0 handle=0x6fc13bf4f0 353 | | state=S schedstat=( 3064688 1420155 20 ) utm=0 stm=0 core=1 HZ=100 354 | | stack=0x6fc12c5000-0x6fc12c7000 stackSize=1005KB 355 | | held mutexes= 356 | kernel: __switch_to+0x94/0xa0 357 | kernel: futex_wait_queue_me+0xe0/0x148 358 | kernel: futex_wait+0x104/0x224 359 | kernel: do_futex+0xe0/0x928 360 | kernel: SyS_futex+0x11c/0x1b0 361 | kernel: __sys_trace+0x4c/0x50 362 | native: #00 pc 000000000001d4ac /system/lib64/libc.so (syscall+28) 363 | native: #01 pc 0000000000067388 /system/lib64/libc.so (pthread_cond_wait+96) 364 | native: #02 pc 0000000000075504 /system/lib64/libhwui.so (???) 365 | native: #03 pc 0000000000011478 /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+280) 366 | native: #04 pc 00000000000a9814 /system/lib64/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+140) 367 | native: #05 pc 0000000000067d0c /system/lib64/libc.so (__pthread_start(void*)+36) 368 | native: #06 pc 000000000001eba4 /system/lib64/libc.so (__start_thread+68) 369 | (no managed stack frames) 370 | 371 | "hwuiTask2" prio=5 tid=15 Native 372 | | group="main" sCount=1 dsCount=0 flags=1 obj=0x12e00020 self=0x6fc2a2ec00 373 | | sysTid=27947 nice=-2 cgrp=default sched=0/0 handle=0x6fc12c24f0 374 | | state=S schedstat=( 947970 446355 8 ) utm=0 stm=0 core=5 HZ=100 375 | | stack=0x6fc11c8000-0x6fc11ca000 stackSize=1005KB 376 | | held mutexes= 377 | kernel: __switch_to+0x94/0xa0 378 | kernel: futex_wait_queue_me+0xe0/0x148 379 | kernel: futex_wait+0x104/0x224 380 | kernel: do_futex+0xe0/0x928 381 | kernel: SyS_futex+0x11c/0x1b0 382 | kernel: __sys_trace+0x4c/0x50 383 | native: #00 pc 000000000001d4ac /system/lib64/libc.so (syscall+28) 384 | native: #01 pc 0000000000067388 /system/lib64/libc.so (pthread_cond_wait+96) 385 | native: #02 pc 0000000000075504 /system/lib64/libhwui.so (???) 386 | native: #03 pc 0000000000011478 /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+280) 387 | native: #04 pc 00000000000a9814 /system/lib64/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+140) 388 | native: #05 pc 0000000000067d0c /system/lib64/libc.so (__pthread_start(void*)+36) 389 | native: #06 pc 000000000001eba4 /system/lib64/libc.so (__start_thread+68) 390 | (no managed stack frames) 391 | 392 | "RenderThread" prio=10 (not attached) 393 | | sysTid=27945 nice=-10 cgrp=default 394 | | state=S schedstat=( 249218 702709 6 ) utm=0 stm=0 core=3 HZ=100 395 | kernel: __switch_to+0x94/0xa0 396 | kernel: futex_wait_queue_me+0xe0/0x148 397 | kernel: futex_wait+0x104/0x224 398 | kernel: do_futex+0xe0/0x928 399 | kernel: SyS_futex+0x11c/0x1b0 400 | kernel: __sys_trace+0x4c/0x50 401 | 402 | --------------------------------------------------------------------------------