This repository has been archived by the owner on Mar 14, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 218
/
Trace.scala
206 lines (164 loc) · 7.79 KB
/
Trace.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/*
* Copyright 2013 zhongl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.zhongl.housemd.command
import instrument.Instrumentation
import management.ManagementFactory
import com.cedarsoftware.util.io.JsonWriter
import com.github.zhongl.housemd.misc.ObjUtils
import com.github.zhongl.yascli.PrintOut
import com.github.zhongl.housemd.misc.ReflectionUtils._
import java.util.Date
import collection.immutable.SortedSet
import com.github.zhongl.housemd.instrument.{Hook, Context}
import org.objectweb.asm.Type
import java.io.{BufferedWriter, FileWriter, File}
/**
* @author <a href="mailto:zhong.lunfu@gmail.com">zhongl<a>
*/
class Trace(val inst: Instrumentation, out: PrintOut)
extends TransformCommand("trace", "display or output infomation of method invocaton.", inst, out)
with MethodFilterCompleter {
val outputRoot = {
val dir = new File("/tmp/" + name + "/" + ManagementFactory.getRuntimeMXBean.getName)
dir.mkdirs()
dir
}
val detailFile = new File(outputRoot, "detail")
val stackFile = new File(outputRoot, "stack")
private val detailable = flag("-d" :: "--detail" :: Nil, "enable append invocation detail to " + detailFile + ".")
private val detailInJson = flag("-j" :: "--json" :: Nil, "convert detail info into json format.")
private val stackable = flag("-s" :: "--stack" :: Nil, "enable append invocation calling stack to " + stackFile + ".")
private val methodFilters = parameter[Array[MethodFilter]]("method-filter", "method filter pattern like \"ClassSimpleName.methodName\" or \"ClassSimpleName\".")
protected def isCandidate(klass: Class[_]) = methodFilters().find(_.filter(klass)).isDefined
protected def isDecorating(klass: Class[_], methodName: String) =
methodFilters().find(_.filter(klass, methodName)).isDefined
override protected def hook = new Hook() {
val enableDetail = detailable()
val enableStack = stackable()
val showDetailInJson = detailInJson()
implicit val statisticOrdering = Ordering.by((_: Statistic).methodSign)
var statistics = SortedSet.empty[Statistic]
var maxMethodSignLength = 0
var maxClassLoaderLength = 0
if (showDetailInJson) ObjUtils.useJsonFormat() else ObjUtils.useToStringFormat()
lazy val detailWriter = new DetailWriter(new BufferedWriter(new FileWriter(detailFile, true)))
lazy val stackWriter = new StackWriter(new BufferedWriter(new FileWriter(stackFile, true)))
override def enterWith(context: Context) {}
override def exitWith(context: Context) {
if (enableDetail) detailWriter.write(context)
if (enableStack) stackWriter.write(context)
val statistic = new Statistic(context, 1L, context.stopped.get - context.started)
maxClassLoaderLength = math.max(maxClassLoaderLength, statistic.loader.length)
maxMethodSignLength = math.max(maxMethodSignLength, statistic.methodSign.length)
statistics = statistics find { _.filter(context) } match {
case Some(s) => (statistics - s) + (s + statistic)
case None => statistics + statistic
}
}
override def heartbeat(now: Long) {
if (statistics.isEmpty) println("No traced method invoked")
else statistics foreach { s => println(s.reps(maxMethodSignLength, maxClassLoaderLength)) }
println()
}
override def finalize(throwable: Option[Throwable]) {
heartbeat(0L) // last print
if (enableDetail) {
detailWriter.close()
info("You can get invocation detail from " + detailFile)
}
if (enableStack) {
stackWriter.close()
info("You can get invocation stack from " + stackFile)
}
}
}
class Statistic(val context: Context, val totalTimes: Long, val totalElapseMills: Long) {
lazy val methodSign = "%1$s.%2$s(%3$s)".format(
simpleNameOf(context.className),
context.methodName,
Type.getArgumentTypes(context.descriptor).map(t => simpleNameOf(t.getClassName)).mkString(", ")
)
lazy val loader = if (context.loader == null) "BootClassLoader" else getOrForceToNativeString(context.loader)
private val NaN = "-"
private lazy val thisObjectString = context match {
case c if c.thisObject == null => "[Static Method]"
case c if isInit(c.methodName) => "[Initialize Method]"
case _ => getOrForceToNativeString(context.thisObject)
}
private lazy val avgElapseMillis =
if (totalTimes == 0) NaN else if (totalElapseMills < totalTimes) "<1" else totalElapseMills / totalTimes
def +(s: Statistic) = new Statistic(context, totalTimes + s.totalTimes, totalElapseMills + s.totalElapseMills)
def filter(context: Context) = this.context.loader == context.loader &&
this.context.className == context.className &&
this.context.methodName == context.methodName &&
this.context.arguments.size == context.arguments.size &&
this.context.descriptor == context.descriptor &&
(isInit(context.methodName) || this.context.thisObject == context.thisObject)
def reps(maxMethodSignLength: Int, maxClassLoaderLength: Int) =
"%1$-" + maxMethodSignLength + "s %2$-" + maxClassLoaderLength + "s %3$9s %4$9sms %5$s" format(
methodSign,
loader,
totalTimes,
avgElapseMillis,
thisObjectString)
private def isInit(method: String) = method == "<init>"
}
}
class DetailWriter(writer: BufferedWriter) {
def write(context: Context) {
val started = "%1$tF %1$tT" format (new Date(context.started))
val elapse = "%,dms" format (context.stopped.get - context.started)
val thread = "[" + context.thread.getName + "]"
val thisObject = if (context.thisObject == null) "null" else context.thisObject.toString
val method = context.className + "." + context.methodName
val arguments = context.arguments.mkString("[" + ObjUtils.parameterSeparator, ObjUtils.parameterSeparator, "]")
val resultOrExcption = context.resultOrException match {
case Some(x) => ObjUtils.toString(x)
case None if context.isVoidReturn => "void"
case None => "null"
}
val argumentsAndResult = "Arguments: " + arguments + ObjUtils.parameterSeparator + "Result: " + resultOrExcption
val line = (started :: elapse :: thread :: thisObject :: method :: argumentsAndResult :: Nil)
.mkString(" ")
writer.write(line)
writer.newLine()
context.resultOrException match {
case Some(x) if x.isInstanceOf[Throwable] => x.asInstanceOf[Throwable].getStackTrace.foreach {
s =>
writer.write("\tat " + s)
writer.newLine()
}
case _ =>
}
}
def close() {
try {writer.close()} catch {case _ => }
}
}
class StackWriter(writer: BufferedWriter) {
def write(context: Context) {
// TODO Avoid duplicated stack
val head = "%1$s.%2$s%3$s call by thread [%4$s]"
.format(context.className, context.methodName, context.descriptor, context.thread.getName)
writer.write(head)
writer.newLine()
context.stack foreach { s => writer.write("\t" + s); writer.newLine() }
writer.newLine()
}
def close() {
try {writer.close()} catch {case _ => }
}
}