Package com.google.speedtracer.client.visualizations.view

Source Code of com.google.speedtracer.client.visualizations.view.JavaScriptProfileRenderer

/*
* Copyright 2010 Google Inc.
*
* 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.google.speedtracer.client.visualizations.view;

import com.google.gwt.dom.client.AnchorElement;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.dom.client.TableCellElement;
import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.events.client.Event;
import com.google.gwt.graphics.client.Color;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.topspin.ui.client.Anchor;
import com.google.gwt.topspin.ui.client.ClickEvent;
import com.google.gwt.topspin.ui.client.ClickListener;
import com.google.gwt.topspin.ui.client.Container;
import com.google.gwt.topspin.ui.client.DefaultContainerImpl;
import com.google.gwt.topspin.ui.client.Div;
import com.google.gwt.topspin.ui.client.Table;
import com.google.speedtracer.client.ClientConfig;
import com.google.speedtracer.client.Logging;
import com.google.speedtracer.client.SourceViewerServer;
import com.google.speedtracer.client.SymbolServerController;
import com.google.speedtracer.client.SourceViewer.SourcePresenter;
import com.google.speedtracer.client.SymbolServerController.Resymbolizeable;
import com.google.speedtracer.client.model.JavaScriptProfile;
import com.google.speedtracer.client.model.JavaScriptProfileModel;
import com.google.speedtracer.client.model.JavaScriptProfileNode;
import com.google.speedtracer.client.model.JsSymbol;
import com.google.speedtracer.client.util.Command;
import com.google.speedtracer.client.util.TimeStampFormatter;
import com.google.speedtracer.client.util.dom.ManagesEventListeners;

import java.util.Collections;
import java.util.List;

/**
* Presents a UI for a JavaScript profile.
*/
public class JavaScriptProfileRenderer {
  /**
   * Css.
   */
  public interface Css extends CssResource {
    String resymbolizedLink();

    String treeItemBottomDiv();

    String treeItemTopDiv();
  }

  /**
   * Callback invoked when the screen dimensions of the profile changes.
   */
  public interface ResizeCallback {
    void onResize();
  }

  /**
   * Resources.
   */
  public interface Resources extends Tree.Resources {
    @Source("resources/JavaScriptProfileRenderer.css")
    Css javaScriptProfileRendererCss();
  }

  private class FlatChildRowRenderer implements Resymbolizeable {
    private final JavaScriptProfileNode profileNode;
    private final TableRowElement row;
    private TableCellElement symbolNameCell;

    FlatChildRowRenderer(JavaScriptProfileNode profileNode, TableRowElement row) {
      this.profileNode = profileNode;
      this.row = row;
    }

    public void reSymbolize(final String sourceServer,
        final SourceViewerServer sourceViewerServer,
        final JsSymbol sourceSymbol, final SourcePresenter sourcePresenter) {
      AnchorElement resymbolizedSymbol = symbolNameCell.getOwnerDocument().createAnchorElement();
      resymbolizedSymbol.setClassName(css.resymbolizedLink());
      resymbolizedSymbol.setInnerText(sourceSymbol.getSymbolName());
      resymbolizedSymbol.setHref("javascript:;");

      symbolNameCell.appendChild(resymbolizedSymbol);
      listenerManager.manageEventListener(ClickEvent.addClickListener(
          resymbolizedSymbol, resymbolizedSymbol, new ClickListener() {
            public void onClick(ClickEvent event) {
              sourcePresenter.showSource(sourceServer
                  + sourceSymbol.getResourceUrl().getPath(),
                  sourceViewerServer, sourceSymbol.getLineNumber(), 0,
                  sourceSymbol.getAbsoluteFilePath());
            }
          }));

      if (resizeCallback != null) {
        resizeCallback.onResize();
      }
    }

    void render() {
      final JsSymbol childSymbol = profileNode.getSymbol();
      symbolNameCell = row.insertCell(-1);
      symbolNameCell.setInnerText(formatSymbolName(childSymbol));
      final TableCellElement resourceCell = row.insertCell(-1);
      renderResourceLocation(resourceCell, childSymbol);
      row.insertCell(-1).setInnerHTML(
          "<b>" + formatSelfTime(profileNode) + "%</b></td>");
      row.insertCell(-1).setInnerHTML(
          "<b>" + formatTime(profileNode) + "%</td>");

      Color rowColor = rowEvenOdd ? Color.WHITE : Color.CHROME_BLUE;
      row.getStyle().setBackgroundColor(rowColor.toString());
      rowEvenOdd = !rowEvenOdd;
    }
  }

  private class ProfileTree extends Tree {
    private class ProfileItem extends Tree.Item implements Resymbolizeable {

      private DivElement bottomDiv;

      /**
       * New child item.
       */
      public ProfileItem(ProfileItem parent, JavaScriptProfileNode profileNode) {
        super(parent);
        setItemTarget(profileNode);
        initItem(profileNode);
      }

      /**
       * New root item.
       */
      public ProfileItem(Tree tree, JavaScriptProfileNode profileNode) {
        super(tree);
        setItemTarget(profileNode);
        initItem(profileNode);
      }

      @Override
      public void handleEvent(Event event) {
        super.handleEvent(event);
        if (resizeCallback != null) {
          resizeCallback.onResize();
        }
      }

      public void reSymbolize(final String sourceServer,
          final SourceViewerServer sourceViewerServer,
          final JsSymbol sourceSymbol, final SourcePresenter sourcePresenter) {
        AnchorElement resymbolizedSymbol = bottomDiv.getOwnerDocument().createAnchorElement();
        resymbolizedSymbol.setClassName(css.resymbolizedLink());
        resymbolizedSymbol.setInnerText(sourceSymbol.getSymbolName());
        resymbolizedSymbol.setHref("javascript:;");

        bottomDiv.appendChild(resymbolizedSymbol);
        listenerManager.manageEventListener(ClickEvent.addClickListener(
            resymbolizedSymbol, resymbolizedSymbol, new ClickListener() {
              public void onClick(ClickEvent event) {
                sourcePresenter.showSource(sourceServer
                    + sourceSymbol.getResourceUrl().getPath(),
                    sourceViewerServer, sourceSymbol.getLineNumber(), 0,
                    sourceSymbol.getAbsoluteFilePath());
              }
            }));

        if (resizeCallback != null) {
          resizeCallback.onResize();
        }
      }

      private void initItem(JavaScriptProfileNode profileNode) {
        Container container = new DefaultContainerImpl(
            this.getItemLabelElement());
        DivElement topDiv = container.getDocument().createDivElement();
        topDiv.setClassName(css.treeItemTopDiv());
        this.getItemLabelElement().appendChild(topDiv);
        // The bottom div is reserved for the resymbolized link.
        bottomDiv = container.getDocument().createDivElement();
        bottomDiv.setClassName(css.treeItemBottomDiv());
        this.getItemLabelElement().appendChild(bottomDiv);

        final JsSymbol symbol = profileNode.getSymbol();
        SpanElement symbolNameElement = container.getDocument().createSpanElement();
        symbolNameElement.setInnerText(formatSymbolName(symbol));
        topDiv.appendChild(symbolNameElement);
        renderResourceLocation(topDiv, symbol);
        SpanElement timeValue = container.getDocument().createSpanElement();
        topDiv.appendChild(timeValue);
        timeValue.setInnerHTML(" <b>self: " + formatSelfTime(profileNode)
            + "%</b> (" + formatTime(profileNode) + "%)");
      }
    }

    /**
     * Creates a node that is not expanded, and children are to be created
     * lazily.
     */
    private class UnexpandedProfileItem extends ProfileItem {
      private boolean dirtyChildren;

      public UnexpandedProfileItem(final ProfileItem parent,
          final JavaScriptProfileNode profileNode) {
        super(parent, profileNode);
        List<JavaScriptProfileNode> children = profileNode.getChildren();
        if (children == null || children.size() == 0) {
          dirtyChildren = false;
        } else {
          this.setExpandIconVisible(true);
          this.setExpansionIcon(false);
          dirtyChildren = true;
        }
      }

      @Override
      public void handleEvent(Event event) {
        // if we have clicked on the expansion control and we havn't yet
        // generated
        // the DOM for our children.
        if (!isSelectionEvent(event) && dirtyChildren) {
          // Build the children.
          expand();
          event.cancelBubble(true);
        } else {
          super.handleEvent(event);
        }
        if (resizeCallback != null) {
          resizeCallback.onResize();
        }
      }

      private void expand() {
        JavaScriptProfileNode profileNode = (JavaScriptProfileNode) getItemTarget();
        addChildrenRecursive(this, resources, profileNode, 1);
        this.setExpansionIcon(true);
      }
    }

    public ProfileTree(Container container, Resources resources,
        JavaScriptProfileNode profileRoot) {
      super(container, resources);
      List<JavaScriptProfileNode> children = profileRoot.getChildren();
      Collections.sort(children, JavaScriptProfileModel.nodeTimeComparator);
      for (int i = 0, length = children.size(); i < length; ++i) {
        final JavaScriptProfileNode profileChild = children.get(i);
        // add root nodes
        final ProfileItem item = new ProfileItem(this, profileChild);
        // Add resymbolized data to frame/profile if it is available.
        Command.defer(new Command.Method() {
          public void execute() {
            if (ssController != null) {
              JsSymbol jsSymbol = profileChild.getSymbol();
              ssController.attemptResymbolization(
                  jsSymbol.getResourceUrl().getUrl(), jsSymbol.getSymbolName(),
                  item, sourcePresenter);
            }
          }
        });
        addChildrenRecursive(item, resources, children.get(i), 1);
      }
    }

    /**
     * Displays a hierarchical profile, top-down or bottom-up.
     */
    private void addChildrenRecursive(ProfileItem item,
        Tree.Resources resources, JavaScriptProfileNode profileParent, int depth) {

      List<JavaScriptProfileNode> children = profileParent.getChildren();

      Collections.sort(children, JavaScriptProfileModel.nodeTimeComparator);
      for (int i = 0, length = children.size(); i < length; ++i) {
        final JavaScriptProfileNode profileChild = children.get(i);
        if (depth < 4 || profileChild.hasTwoOrMoreChildren() == false) {
          final ProfileItem childItem = new ProfileItem(item, profileChild);
          // Add resymbolized data to frame/profile if it is available.
          Command.defer(new Command.Method() {
            public void execute() {
              if (ssController != null) {
                final JsSymbol childSymbol = profileChild.getSymbol();
                ssController.attemptResymbolization(
                    childSymbol.getResourceUrl().getUrl(),
                    childSymbol.getSymbolName(), childItem, sourcePresenter);
              }
            }
          });
          addChildrenRecursive(childItem, resources, children.get(i), depth + 1);
        } else {
          new UnexpandedProfileItem(item, profileChild);
        }
      }
    }
  }

  // Constant for limiting the number of symbols we show initially for the flat
  // profile.
  private static final int FLAT_PROFILE_PAGE_SIZE = 15;

  // Flips between true and false depending on when a flat profile row gets
  // rendered.
  private static boolean rowEvenOdd = true;

  private final Tree.Resources resources;

  private final Div profileDiv;

  private final JavaScriptProfile profile;

  private final ResizeCallback resizeCallback;

  private final SourceSymbolClickListener sourceClickCallback;

  private final SourcePresenter sourcePresenter;

  private final Css css;

  private final SymbolServerController ssController;

  private final ManagesEventListeners listenerManager;

  public JavaScriptProfileRenderer(Container container, Resources resources,
      ManagesEventListeners listenerManager,
      SymbolServerController ssController, SourcePresenter sourcePresenter,
      JavaScriptProfile profile, SourceSymbolClickListener sourceClickCallback,
      ResizeCallback resizeCallback) {
    this.profileDiv = new Div(container);
    this.resources = resources;
    this.profile = profile;
    this.sourceClickCallback = sourceClickCallback;
    this.resizeCallback = resizeCallback;
    this.ssController = ssController;
    this.sourcePresenter = sourcePresenter;
    this.css = resources.javaScriptProfileRendererCss();
    this.listenerManager = listenerManager;
  }

  /**
   * Display the specified profile.
   *
   * @param profileType one of JavaScriptProfile.PROFILE_TYPE_XXX values
   */
  public void show(int profileType) {
    JavaScriptProfileNode profileRoot = profile.getProfile(profileType);
    if (profileRoot == null) {
      profileDiv.setHtml("Profile is empty.");
      return;
    }
    profileDiv.setHtml("");
    Container container = new DefaultContainerImpl(profileDiv.getElement());
    // show VM states (Garbage Collect, etc)
    StringBuilder result = new StringBuilder();
    result.append("<h3>VM States</h3>");
    result.append("<br/>");
    profile.getVmStateHtml(result);
    result.append("<br/>");
    DivElement vmStatesDiv = container.getDocument().createDivElement();
    vmStatesDiv.setInnerHTML(result.toString());
    profileDiv.getElement().appendChild(vmStatesDiv);

    profileDiv.setHtml(result.toString());
    switch (profileType) {
      case JavaScriptProfile.PROFILE_TYPE_FLAT:
        dumpNodeChildrenFlat(container, profileRoot);
        break;
      case JavaScriptProfile.PROFILE_TYPE_BOTTOM_UP:
      case JavaScriptProfile.PROFILE_TYPE_TOP_DOWN:
        new ProfileTree(container, resources, profileRoot);
        break;
      default:
        if (ClientConfig.isDebugMode()) {
          Logging.getLogger().logTextError("Unknown Profile type: " + profileType);
        }
        assert false : "Unknown profile type: " + profileType;
    }
  }

  private void addFlatChild(final JavaScriptProfileNode child,
      Table profileTable) {

    TableRowElement row = profileTable.appendRow();
    final FlatChildRowRenderer childRenderer = new FlatChildRowRenderer(child,
        row);
    childRenderer.render();

    // Add resymbolized data to frame/profile if it is available.
    Command.defer(new Command.Method() {
      public void execute() {
        if (ssController != null) {
          final JsSymbol childSymbol = child.getSymbol();
          ssController.attemptResymbolization(
              childSymbol.getResourceUrl().getUrl(),
              childSymbol.getSymbolName(), childRenderer, sourcePresenter);
        }
      }
    });
  }

  private void dumpNodeChildrenFlat(Container container,
      JavaScriptProfileNode profileRoot) {
    final List<JavaScriptProfileNode> children = profileRoot.getChildren();
    if (children == null) {
      return;
    }
    Collections.sort(children, JavaScriptProfileModel.nodeTimeComparator);
    final Table profileTable = new Table(container);
    int rowIndex = 0;
    profileTable.appendRow();
    profileTable.appendCell(rowIndex).setInnerHTML("<b>Symbol</b>");
    profileTable.appendCell(rowIndex).setInnerHTML("<b>Resource</b>");
    profileTable.appendCell(rowIndex).setInnerHTML("<b>Self Time</b>");
    profileTable.appendCell(rowIndex).setInnerHTML("<b>Time</b>");

    for (int length = children.size(); rowIndex < length; ++rowIndex) {
      JavaScriptProfileNode child = children.get(rowIndex);
      // Truncate the display by default to show only nodes where self time
      // occurred.
      if (child.getSelfTime() <= 0 && (length - rowIndex > 4)
          || rowIndex > FLAT_PROFILE_PAGE_SIZE) {
        break;
      }
      addFlatChild(child, profileTable);
    }

    // Profile terminated early, add a "show more" indicator
    if (rowIndex < children.size()) {
      profileTable.appendRow();
      TableCellElement cell = profileTable.appendCell(rowIndex + 1);
      cell.setColSpan(4);
      Anchor anchor = new Anchor(new DefaultContainerImpl(cell));
      anchor.setText("More...");
      anchor.setHref("javascript:;");
      cell.appendChild(anchor.getElement());
      final int moreRowIndex = rowIndex;
      listenerManager.manageEventListener(anchor.addClickListener(new ClickListener() {

        public void onClick(ClickEvent event) {
          profileTable.deleteRow(moreRowIndex + 1);
          for (int i = moreRowIndex; i < children.size(); ++i) {
            JavaScriptProfileNode child = children.get(i);
            addFlatChild(child, profileTable);
          }
          if (resizeCallback != null) {
            resizeCallback.onResize();
          }
        }
      }));
    }
  }

  /**
   * Return a string with the self time formatted as a decimal percentage.
   */
  private String formatSelfTime(JavaScriptProfileNode profileNode) {
    int relativeSelfTime = (int) (profile.getTotalTime() > 0
        ? (profileNode.getSelfTime() / profile.getTotalTime()) * 100 : 0);
    return TimeStampFormatter.formatToFixedDecimalPoint(relativeSelfTime, 1);
  }

  /**
   * Return the symbol name formatted as a human readable string. If the symbol
   * is blank, the string [unknown] is substituted.
   */
  private String formatSymbolName(JsSymbol jsSymbol) {
    return "".equals(jsSymbol.getSymbolName()) ? "[unknown] "
        : jsSymbol.getSymbolName() + "() ";
  }

  /**
   * Return a string with the time formatted as a decimal percentage.
   */
  private String formatTime(JavaScriptProfileNode profileNode) {
    int relativeTime = (int) (profile.getTotalTime() > 0
        ? (profileNode.getTime() / profile.getTotalTime()) * 100 : 0);
    return TimeStampFormatter.formatToFixedDecimalPoint(relativeTime, 1);
  }

  /**
   * Create a resource location as an anchor in the parent element.
   */
  private void renderResourceLocation(Element parent, final JsSymbol jsSymbol) {
    final Anchor anchor = new Anchor(new DefaultContainerImpl(parent));

    String resourceLocation = jsSymbol.getResourceUrl().getLastPathComponent();
    if (!jsSymbol.isNativeSymbol()) {
      resourceLocation = "".equals(resourceLocation) ? "" : resourceLocation
          + ":" + jsSymbol.getLineNumber();
      anchor.setHref("javascript:;");
      listenerManager.manageEventListener(anchor.addClickListener(new ClickListener() {
        public void onClick(ClickEvent event) {
          String resourceUrl = jsSymbol.getResourceUrl().getUrl();
          if (ClientConfig.isDebugMode()) {
            Logging.getLogger().logText(
                "opening resource " + resourceUrl + " line: "
                    + jsSymbol.getLineNumber());
          }
          sourceClickCallback.onSymbolClicked(resourceUrl, null,
              jsSymbol.getLineNumber(), 0, null);
        }
      }));
    } else {
      resourceLocation = "native " + resourceLocation;
    }
    anchor.setText(resourceLocation);
  }
}
TOP

Related Classes of com.google.speedtracer.client.visualizations.view.JavaScriptProfileRenderer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.