|
1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- |
|
2 * This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 package org.mozilla.gecko; |
|
7 |
|
8 import android.os.SystemClock; |
|
9 import android.util.Log; |
|
10 import android.util.SparseArray; |
|
11 |
|
12 import org.mozilla.gecko.mozglue.generatorannotations.WrapElementForJNI; |
|
13 |
|
14 import java.lang.Thread; |
|
15 import java.util.Set; |
|
16 |
|
17 public class GeckoJavaSampler { |
|
18 private static final String LOGTAG = "JavaSampler"; |
|
19 private static Thread sSamplingThread = null; |
|
20 private static SamplingThread sSamplingRunnable = null; |
|
21 private static Thread sMainThread = null; |
|
22 private static volatile boolean sLibsLoaded = false; |
|
23 |
|
24 // Use the same timer primitive as the profiler |
|
25 // to get a perfect sample syncing. |
|
26 private static native double getProfilerTime(); |
|
27 |
|
28 private static class Sample { |
|
29 public Frame[] mFrames; |
|
30 public double mTime; |
|
31 public long mJavaTime; // non-zero if Android system time is used |
|
32 public Sample(StackTraceElement[] aStack) { |
|
33 mFrames = new Frame[aStack.length]; |
|
34 if (sLibsLoaded) { |
|
35 mTime = getProfilerTime(); |
|
36 } |
|
37 if (mTime == 0.0d) { |
|
38 // getProfilerTime is not available yet; either libs are not loaded, |
|
39 // or profiling hasn't started on the Gecko side yet |
|
40 mJavaTime = SystemClock.elapsedRealtime(); |
|
41 } |
|
42 for (int i = 0; i < aStack.length; i++) { |
|
43 mFrames[aStack.length - 1 - i] = new Frame(); |
|
44 mFrames[aStack.length - 1 - i].fileName = aStack[i].getFileName(); |
|
45 mFrames[aStack.length - 1 - i].lineNo = aStack[i].getLineNumber(); |
|
46 mFrames[aStack.length - 1 - i].methodName = aStack[i].getMethodName(); |
|
47 mFrames[aStack.length - 1 - i].className = aStack[i].getClassName(); |
|
48 } |
|
49 } |
|
50 } |
|
51 private static class Frame { |
|
52 public String fileName; |
|
53 public int lineNo; |
|
54 public String methodName; |
|
55 public String className; |
|
56 } |
|
57 |
|
58 private static class SamplingThread implements Runnable { |
|
59 private final int mInterval; |
|
60 private final int mSampleCount; |
|
61 |
|
62 private boolean mPauseSampler = false; |
|
63 private boolean mStopSampler = false; |
|
64 |
|
65 private SparseArray<Sample[]> mSamples = new SparseArray<Sample[]>(); |
|
66 private int mSamplePos; |
|
67 |
|
68 public SamplingThread(final int aInterval, final int aSampleCount) { |
|
69 // If we sample faster then 10ms we get to many missed samples |
|
70 mInterval = Math.max(10, aInterval); |
|
71 mSampleCount = aSampleCount; |
|
72 } |
|
73 |
|
74 public void run() { |
|
75 synchronized (GeckoJavaSampler.class) { |
|
76 mSamples.put(0, new Sample[mSampleCount]); |
|
77 mSamplePos = 0; |
|
78 |
|
79 // Find the main thread |
|
80 Set<Thread> threadSet = Thread.getAllStackTraces().keySet(); |
|
81 for (Thread t : threadSet) { |
|
82 if (t.getName().compareToIgnoreCase("main") == 0) { |
|
83 sMainThread = t; |
|
84 break; |
|
85 } |
|
86 } |
|
87 |
|
88 if (sMainThread == null) { |
|
89 Log.e(LOGTAG, "Main thread not found"); |
|
90 return; |
|
91 } |
|
92 } |
|
93 |
|
94 while (true) { |
|
95 try { |
|
96 Thread.sleep(mInterval); |
|
97 } catch (InterruptedException e) { |
|
98 e.printStackTrace(); |
|
99 } |
|
100 synchronized (GeckoJavaSampler.class) { |
|
101 if (!mPauseSampler) { |
|
102 StackTraceElement[] bt = sMainThread.getStackTrace(); |
|
103 mSamples.get(0)[mSamplePos] = new Sample(bt); |
|
104 mSamplePos = (mSamplePos+1) % mSamples.get(0).length; |
|
105 } |
|
106 if (mStopSampler) { |
|
107 break; |
|
108 } |
|
109 } |
|
110 } |
|
111 } |
|
112 |
|
113 private Sample getSample(int aThreadId, int aSampleId) { |
|
114 if (aThreadId < mSamples.size() && aSampleId < mSamples.get(aThreadId).length && |
|
115 mSamples.get(aThreadId)[aSampleId] != null) { |
|
116 int startPos = 0; |
|
117 if (mSamples.get(aThreadId)[mSamplePos] != null) { |
|
118 startPos = mSamplePos; |
|
119 } |
|
120 int readPos = (startPos + aSampleId) % mSamples.get(aThreadId).length; |
|
121 return mSamples.get(aThreadId)[readPos]; |
|
122 } |
|
123 return null; |
|
124 } |
|
125 } |
|
126 |
|
127 |
|
128 @WrapElementForJNI(allowMultithread = true, stubName = "GetThreadNameJavaProfilingWrapper") |
|
129 public synchronized static String getThreadName(int aThreadId) { |
|
130 if (aThreadId == 0 && sMainThread != null) { |
|
131 return sMainThread.getName(); |
|
132 } |
|
133 return null; |
|
134 } |
|
135 |
|
136 private synchronized static Sample getSample(int aThreadId, int aSampleId) { |
|
137 return sSamplingRunnable.getSample(aThreadId, aSampleId); |
|
138 } |
|
139 |
|
140 @WrapElementForJNI(allowMultithread = true, stubName = "GetSampleTimeJavaProfiling") |
|
141 public synchronized static double getSampleTime(int aThreadId, int aSampleId) { |
|
142 Sample sample = getSample(aThreadId, aSampleId); |
|
143 if (sample != null) { |
|
144 if (sample.mJavaTime != 0) { |
|
145 return (double)(sample.mJavaTime - |
|
146 SystemClock.elapsedRealtime()) + getProfilerTime(); |
|
147 } |
|
148 System.out.println("Sample: " + sample.mTime); |
|
149 return sample.mTime; |
|
150 } |
|
151 return 0; |
|
152 } |
|
153 |
|
154 @WrapElementForJNI(allowMultithread = true, stubName = "GetFrameNameJavaProfilingWrapper") |
|
155 public synchronized static String getFrameName(int aThreadId, int aSampleId, int aFrameId) { |
|
156 Sample sample = getSample(aThreadId, aSampleId); |
|
157 if (sample != null && aFrameId < sample.mFrames.length) { |
|
158 Frame frame = sample.mFrames[aFrameId]; |
|
159 if (frame == null) { |
|
160 return null; |
|
161 } |
|
162 return frame.className + "." + frame.methodName + "()"; |
|
163 } |
|
164 return null; |
|
165 } |
|
166 |
|
167 @WrapElementForJNI(allowMultithread = true, stubName = "StartJavaProfiling") |
|
168 public static void start(int aInterval, int aSamples) { |
|
169 synchronized (GeckoJavaSampler.class) { |
|
170 if (sSamplingRunnable != null) { |
|
171 return; |
|
172 } |
|
173 sSamplingRunnable = new SamplingThread(aInterval, aSamples); |
|
174 sSamplingThread = new Thread(sSamplingRunnable, "Java Sampler"); |
|
175 sSamplingThread.start(); |
|
176 } |
|
177 } |
|
178 |
|
179 @WrapElementForJNI(allowMultithread = true, stubName = "PauseJavaProfiling") |
|
180 public static void pause() { |
|
181 synchronized (GeckoJavaSampler.class) { |
|
182 sSamplingRunnable.mPauseSampler = true; |
|
183 } |
|
184 } |
|
185 |
|
186 @WrapElementForJNI(allowMultithread = true, stubName = "UnpauseJavaProfiling") |
|
187 public static void unpause() { |
|
188 synchronized (GeckoJavaSampler.class) { |
|
189 sSamplingRunnable.mPauseSampler = false; |
|
190 } |
|
191 } |
|
192 |
|
193 @WrapElementForJNI(allowMultithread = true, stubName = "StopJavaProfiling") |
|
194 public static void stop() { |
|
195 synchronized (GeckoJavaSampler.class) { |
|
196 if (sSamplingThread == null) { |
|
197 return; |
|
198 } |
|
199 |
|
200 sSamplingRunnable.mStopSampler = true; |
|
201 try { |
|
202 sSamplingThread.join(); |
|
203 } catch (InterruptedException e) { |
|
204 e.printStackTrace(); |
|
205 } |
|
206 sSamplingThread = null; |
|
207 sSamplingRunnable = null; |
|
208 } |
|
209 } |
|
210 |
|
211 public static void setLibsLoaded() { |
|
212 sLibsLoaded = true; |
|
213 } |
|
214 } |
|
215 |
|
216 |
|
217 |