You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Profiler.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. /*
  2. * Copyright 2000-2014 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client;
  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.Comparator;
  20. import java.util.HashMap;
  21. import java.util.LinkedHashMap;
  22. import java.util.LinkedList;
  23. import java.util.Map;
  24. import java.util.logging.Logger;
  25. import com.google.gwt.core.client.Duration;
  26. import com.google.gwt.core.client.JavaScriptObject;
  27. import com.google.gwt.core.client.JsArray;
  28. import com.google.gwt.core.shared.GWT;
  29. import com.vaadin.client.debug.internal.ProfilerSection.Node;
  30. import com.vaadin.client.debug.internal.ProfilerSection.ProfilerResultConsumer;
  31. /**
  32. * Lightweight profiling tool that can be used to collect profiling data with
  33. * zero overhead unless enabled. To enable profiling, add
  34. * <code>&lt;set-property name="vaadin.profiler" value="true" /&gt;</code> to
  35. * your .gwt.xml file.
  36. *
  37. * @author Vaadin Ltd
  38. * @since 7.0.0
  39. */
  40. public class Profiler {
  41. /**
  42. * Class to include using deferred binding to enable the profiling.
  43. *
  44. * @author Vaadin Ltd
  45. * @since 7.0.0
  46. */
  47. public static class EnabledProfiler extends Profiler {
  48. @Override
  49. protected boolean isImplEnabled() {
  50. return true;
  51. }
  52. }
  53. private static final String evtGroup = "VaadinProfiler";
  54. private static final class GwtStatsEvent extends JavaScriptObject {
  55. protected GwtStatsEvent() {
  56. // JSO constructor
  57. }
  58. private native String getEvtGroup()
  59. /*-{
  60. return this.evtGroup;
  61. }-*/;
  62. private native double getMillis()
  63. /*-{
  64. return this.millis;
  65. }-*/;
  66. private native String getSubSystem()
  67. /*-{
  68. return this.subSystem;
  69. }-*/;
  70. private native String getType()
  71. /*-{
  72. return this.type;
  73. }-*/;
  74. private native String getModuleName()
  75. /*-{
  76. return this.moduleName;
  77. }-*/;
  78. public final String getEventName() {
  79. String group = getEvtGroup();
  80. if (evtGroup.equals(group)) {
  81. return getSubSystem();
  82. } else {
  83. return group + "." + getSubSystem();
  84. }
  85. }
  86. }
  87. private static ProfilerResultConsumer consumer;
  88. /**
  89. * Checks whether the profiling gathering is enabled.
  90. *
  91. * @return <code>true</code> if the profiling is enabled, else
  92. * <code>false</code>
  93. */
  94. public static boolean isEnabled() {
  95. // This will be fully inlined by the compiler
  96. Profiler create = GWT.create(Profiler.class);
  97. return create.isImplEnabled();
  98. }
  99. /**
  100. * Enters a named block. There should always be a matching invocation of
  101. * {@link #leave(String)} when leaving the block. Calls to this method will
  102. * be removed by the compiler unless profiling is enabled.
  103. *
  104. * @param name
  105. * the name of the entered block
  106. */
  107. public static void enter(String name) {
  108. if (isEnabled()) {
  109. logGwtEvent(name, "begin");
  110. }
  111. }
  112. /**
  113. * Leaves a named block. There should always be a matching invocation of
  114. * {@link #enter(String)} when entering the block. Calls to this method will
  115. * be removed by the compiler unless profiling is enabled.
  116. *
  117. * @param name
  118. * the name of the left block
  119. */
  120. public static void leave(String name) {
  121. if (isEnabled()) {
  122. logGwtEvent(name, "end");
  123. }
  124. }
  125. private static native final void logGwtEvent(String name, String type)
  126. /*-{
  127. $wnd.__gwtStatsEvent({
  128. evtGroup: @com.vaadin.client.Profiler::evtGroup,
  129. moduleName: @com.google.gwt.core.client.GWT::getModuleName()(),
  130. millis: (new Date).getTime(),
  131. sessionId: undefined,
  132. subSystem: name,
  133. type: type
  134. });
  135. }-*/;
  136. /**
  137. * Resets the collected profiler data. Calls to this method will be removed
  138. * by the compiler unless profiling is enabled.
  139. */
  140. public static void reset() {
  141. if (isEnabled()) {
  142. /*
  143. * Old implementations might call reset for initialization, so
  144. * ensure it is initialized here as well. Initialization has no side
  145. * effects if already done.
  146. */
  147. initialize();
  148. clearEventsList();
  149. }
  150. }
  151. /**
  152. * Initializes the profiler. This should be done before calling any other
  153. * function in this class. Failing to do so might cause undesired behavior.
  154. * This method has no side effects if the initialization has already been
  155. * done.
  156. * <p>
  157. * Please note that this method should be called even if the profiler is not
  158. * enabled because it will then remove a logger function that might have
  159. * been included in the HTML page and that would leak memory unless removed.
  160. * </p>
  161. *
  162. * @since 7.0.2
  163. */
  164. public static void initialize() {
  165. if (isEnabled()) {
  166. ensureLogger();
  167. } else {
  168. ensureNoLogger();
  169. }
  170. }
  171. /**
  172. * Outputs the gathered profiling data to the debug console.
  173. */
  174. public static void logTimings() {
  175. if (!isEnabled()) {
  176. getLogger().warning(
  177. "Profiler is not enabled, no data has been collected.");
  178. return;
  179. }
  180. LinkedList<Node> stack = new LinkedList<Node>();
  181. Node rootNode = new Node(null);
  182. stack.add(rootNode);
  183. JsArray<GwtStatsEvent> gwtStatsEvents = getGwtStatsEvents();
  184. if (gwtStatsEvents.length() == 0) {
  185. getLogger()
  186. .warning(
  187. "No profiling events recorded, this might happen if another __gwtStatsEvent handler is installed.");
  188. return;
  189. }
  190. for (int i = 0; i < gwtStatsEvents.length(); i++) {
  191. GwtStatsEvent gwtStatsEvent = gwtStatsEvents.get(i);
  192. String eventName = gwtStatsEvent.getEventName();
  193. String type = gwtStatsEvent.getType();
  194. boolean isBeginEvent = "begin".equals(type);
  195. Node stackTop = stack.getLast();
  196. boolean inEvent = eventName.equals(stackTop.getName())
  197. && !isBeginEvent;
  198. if (!inEvent && stack.size() >= 2
  199. && eventName.equals(stack.get(stack.size() - 2).getName())
  200. && !isBeginEvent) {
  201. // back out of sub event
  202. stackTop.leave(gwtStatsEvent.getMillis());
  203. stack.removeLast();
  204. stackTop = stack.getLast();
  205. inEvent = true;
  206. }
  207. if (type.equals("end")) {
  208. if (!inEvent) {
  209. getLogger().severe(
  210. "Got end event for " + eventName
  211. + " but is currently in "
  212. + stackTop.getName());
  213. return;
  214. }
  215. Node previousStackTop = stack.removeLast();
  216. previousStackTop.leave(gwtStatsEvent.getMillis());
  217. } else {
  218. if (!inEvent) {
  219. stackTop = stackTop.enterChild(eventName,
  220. gwtStatsEvent.getMillis());
  221. stack.add(stackTop);
  222. }
  223. if (!isBeginEvent) {
  224. // Create sub event
  225. stack.add(stackTop.enterChild(eventName + "." + type,
  226. gwtStatsEvent.getMillis()));
  227. }
  228. }
  229. }
  230. if (stack.size() != 1) {
  231. getLogger().warning(
  232. "Not all nodes are left, the last node is "
  233. + stack.getLast().getName());
  234. return;
  235. }
  236. Map<String, Node> totals = new HashMap<String, Node>();
  237. rootNode.sumUpTotals(totals);
  238. ArrayList<Node> totalList = new ArrayList<Node>(totals.values());
  239. Collections.sort(totalList, new Comparator<Node>() {
  240. @Override
  241. public int compare(Node o1, Node o2) {
  242. return (int) (o2.getTimeSpent() - o1.getTimeSpent());
  243. }
  244. });
  245. if (getConsumer() != null) {
  246. getConsumer().addProfilerData(stack.getFirst(), totalList);
  247. }
  248. }
  249. /**
  250. * Overridden in {@link EnabledProfiler} to make {@link #isEnabled()} return
  251. * true if GWT.create returns that class.
  252. *
  253. * @return <code>true</code> if the profiling is enabled, else
  254. * <code>false</code>
  255. */
  256. protected boolean isImplEnabled() {
  257. return false;
  258. }
  259. /**
  260. * Outputs the time passed since various events recored in
  261. * performance.timing if supported by the browser.
  262. */
  263. public static void logBootstrapTimings() {
  264. if (isEnabled()) {
  265. double now = Duration.currentTimeMillis();
  266. StringBuilder stringBuilder = new StringBuilder(
  267. "Time since window.performance.timing events");
  268. SimpleTree tree = new SimpleTree(stringBuilder.toString());
  269. String[] keys = new String[] { "navigationStart",
  270. "unloadEventStart", "unloadEventEnd", "redirectStart",
  271. "redirectEnd", "fetchStart", "domainLookupStart",
  272. "domainLookupEnd", "connectStart", "connectEnd",
  273. "requestStart", "responseStart", "responseEnd",
  274. "domLoading", "domInteractive",
  275. "domContentLoadedEventStart", "domContentLoadedEventEnd",
  276. "domComplete", "loadEventStart", "loadEventEnd" };
  277. LinkedHashMap<String, Double> timings = new LinkedHashMap<String, Double>();
  278. for (String key : keys) {
  279. double value = getPerformanceTiming(key);
  280. if (value == 0) {
  281. // Ignore missing value
  282. continue;
  283. }
  284. timings.put(key, Double.valueOf(now - value));
  285. }
  286. if (timings.isEmpty()) {
  287. getLogger()
  288. .info("Bootstrap timings not supported, please ensure your browser supports performance.timing");
  289. return;
  290. }
  291. if (getConsumer() != null) {
  292. getConsumer().addBootstrapData(timings);
  293. }
  294. }
  295. }
  296. private static final native double getPerformanceTiming(String name)
  297. /*-{
  298. if ($wnd.performance && $wnd.performance.timing && $wnd.performance.timing[name]) {
  299. return $wnd.performance.timing[name];
  300. } else {
  301. return 0;
  302. }
  303. }-*/;
  304. private static native JsArray<GwtStatsEvent> getGwtStatsEvents()
  305. /*-{
  306. return $wnd.vaadin.gwtStatsEvents || [];
  307. }-*/;
  308. /**
  309. * Add logger if it's not already there, also initializing the event array
  310. * if needed.
  311. */
  312. private static native void ensureLogger()
  313. /*-{
  314. if (typeof $wnd.__gwtStatsEvent != 'function') {
  315. if (typeof $wnd.vaadin.gwtStatsEvents != 'object') {
  316. $wnd.vaadin.gwtStatsEvents = [];
  317. }
  318. $wnd.__gwtStatsEvent = function(event) {
  319. $wnd.vaadin.gwtStatsEvents.push(event);
  320. return true;
  321. }
  322. }
  323. }-*/;
  324. /**
  325. * Remove logger function and event array if it seems like the function has
  326. * been added by us.
  327. */
  328. private static native void ensureNoLogger()
  329. /*-{
  330. if (typeof $wnd.vaadin.gwtStatsEvents == 'object') {
  331. delete $wnd.vaadin.gwtStatsEvents;
  332. if (typeof $wnd.__gwtStatsEvent == 'function') {
  333. $wnd.__gwtStatsEvent = function(){};
  334. }
  335. }
  336. }-*/;
  337. private static native JsArray<GwtStatsEvent> clearEventsList()
  338. /*-{
  339. $wnd.vaadin.gwtStatsEvents = [];
  340. }-*/;
  341. /**
  342. * Sets the profiler result consumer that is used to output the profiler
  343. * data to the user.
  344. * <p>
  345. * <b>Warning!</b> This is internal API and should not be used by
  346. * applications or add-ons.
  347. *
  348. * @since 7.1.4
  349. * @param profilerResultConsumer
  350. * the consumer that gets profiler data
  351. */
  352. public static void setProfilerResultConsumer(
  353. ProfilerResultConsumer profilerResultConsumer) {
  354. if (consumer != null) {
  355. throw new IllegalStateException("The consumer has already been set");
  356. }
  357. consumer = profilerResultConsumer;
  358. }
  359. private static ProfilerResultConsumer getConsumer() {
  360. return consumer;
  361. }
  362. private static Logger getLogger() {
  363. return Logger.getLogger(Profiler.class.getName());
  364. }
  365. }