Java tutorial
/* * Copyright 2015 AT&T * * 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.att.aro.ui.view.waterfalltab; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.DecimalFormat; import java.text.MessageFormat; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JPanel; import javax.swing.JScrollBar; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.jfree.chart.ChartMouseEvent; import org.jfree.chart.ChartMouseListener; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.CategoryAnchor; import org.jfree.chart.axis.CategoryAxis; import org.jfree.chart.axis.CategoryLabelPosition; import org.jfree.chart.axis.CategoryLabelPositions; import org.jfree.chart.axis.CategoryLabelWidthType; import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.NumberTickUnit; import org.jfree.chart.axis.TickUnit; import org.jfree.chart.axis.TickUnits; import org.jfree.chart.entity.CategoryItemEntity; import org.jfree.chart.labels.CategoryToolTipGenerator; import org.jfree.chart.labels.ItemLabelAnchor; import org.jfree.chart.labels.ItemLabelPosition; import org.jfree.chart.labels.StandardCategoryItemLabelGenerator; import org.jfree.chart.plot.CategoryPlot; import org.jfree.chart.plot.PlotOrientation; import org.jfree.chart.renderer.category.CategoryItemRenderer; import org.jfree.chart.renderer.category.StackedBarRenderer; import org.jfree.data.Range; import org.jfree.data.category.CategoryDataset; import org.jfree.data.category.DefaultCategoryDataset; import org.jfree.data.category.SlidingCategoryDataset; import org.jfree.text.TextBlockAnchor; import org.jfree.ui.RectangleAnchor; import org.jfree.ui.RectangleInsets; import org.jfree.ui.TextAnchor; import com.att.aro.core.packetanalysis.pojo.HttpDirection; import com.att.aro.core.packetanalysis.pojo.HttpRequestResponseInfo; import com.att.aro.core.packetanalysis.pojo.RequestResponseTimeline; import com.att.aro.core.packetanalysis.pojo.Session; import com.att.aro.core.pojo.AROTraceData; import com.att.aro.ui.commonui.TabPanelJPanel; import com.att.aro.ui.model.waterfall.WaterfallCategory; import com.att.aro.ui.utils.ResourceBundleHelper; import com.att.aro.view.images.Images; /** * * Panel that displays a waterfall view of the analysis data * * */ public class WaterfallPanel extends TabPanelJPanel { private static final long serialVersionUID = 1L; private static final int DEFAULT_TIMELINE = 100; private static final int CATEGORY_MAX_COUNT = 25; private static final double ZOOM_FACTOR = 2; private Color noneColor = new Color(0, 0, 0, 0); private Color dnsLoolupColor = new Color(0, 128, 128); private Color initiaConnColor = new Color(255, 140, 0); private Color sslNegColor = new Color(199, 21, 133); private Color requestTimeColor = new Color(255, 255, 0); private Color firstByteTimeColor = new Color(0, 255, 0); private Color contentDownloadColor = new Color(70, 130, 180); private Color threexColor = Color.BLUE; private Color fourxColor = Color.RED; private SlidingCategoryDataset dataset; private JButton zoomInButton; private JButton zoomOutButton; private ChartPanel chartPanel; private JScrollBar verticalScroll; private JScrollBar horizontalScroll; private NumberAxis timeAxis; private CategoryAxis categoryAxis; private double traceDuration = DEFAULT_TIMELINE; private WaterfallPopup popup; private StackedBarRenderer renderer; private static final NumberFormat format = new DecimalFormat(); private static final TickUnits units = new TickUnits(); static { units.add(new NumberTickUnit(500000, format, 5)); units.add(new NumberTickUnit(250000, format, 5)); units.add(new NumberTickUnit(100000, format, 10)); units.add(new NumberTickUnit(50000, format, 5)); units.add(new NumberTickUnit(25000, format, 5)); units.add(new NumberTickUnit(10000, format, 10)); units.add(new NumberTickUnit(5000, format, 5)); units.add(new NumberTickUnit(2500, format, 5)); units.add(new NumberTickUnit(1000, format, 5)); units.add(new NumberTickUnit(500, format, 5)); units.add(new NumberTickUnit(250, format, 5)); units.add(new NumberTickUnit(100, format, 10)); units.add(new NumberTickUnit(50, format, 10)); units.add(new NumberTickUnit(25, format, 5)); units.add(new NumberTickUnit(10, format, 10)); units.add(new NumberTickUnit(5, format, 5)); units.add(new NumberTickUnit(2, format, 4)); units.add(new NumberTickUnit(1, format, 10)); units.add(new NumberTickUnit(.5, format, 5)); units.add(new NumberTickUnit(.25, format, 5)); units.add(new NumberTickUnit(.1, format, 10)); units.add(new NumberTickUnit(.05, format, 5)); units.add(new NumberTickUnit(.01, format, 10)); } private WaterfallTab waterfallTab; public WaterfallPanel(WaterfallTab waterfallTab) { super(); this.waterfallTab = waterfallTab; } public JPanel layoutDataPanel() { this.setLayout(new BorderLayout()); this.dataset = new SlidingCategoryDataset(new DefaultCategoryDataset(), 0, CATEGORY_MAX_COUNT); this.popup = new WaterfallPopup(); JPanel graphPanel = new JPanel(new BorderLayout()); graphPanel.add(getChartPanel(), BorderLayout.CENTER); graphPanel.add(getVerticalScroll(), BorderLayout.EAST); graphPanel.add(getHorizontalScroll(), BorderLayout.SOUTH); this.add(graphPanel, BorderLayout.CENTER); JPanel buttonsPanel = new JPanel(); buttonsPanel.add(getZoomInButton()); buttonsPanel.add(getZoomOutButton()); this.add(buttonsPanel, BorderLayout.SOUTH); return this; } /** * * @return */ private ChartPanel getChartPanel() { if (chartPanel == null) { renderer = new StackedBarRenderer(); renderer.setMaximumBarWidth(0.05); renderer.setShadowVisible(false); renderer.setBaseToolTipGenerator(new CategoryToolTipGenerator() { @Override public String generateToolTip(CategoryDataset dataset, int row, int column) { WaterfallCategory wfCategory = (WaterfallCategory) dataset.getColumnKey(column); return wfCategory.getTooltip(); } }); renderer.setBaseItemLabelsVisible(true); renderer.setBasePositiveItemLabelPosition( new ItemLabelPosition(ItemLabelAnchor.INSIDE9, TextAnchor.CENTER_LEFT)); renderer.setPositiveItemLabelPositionFallback( new ItemLabelPosition(ItemLabelAnchor.INSIDE9, TextAnchor.CENTER_LEFT)); // Set up plot CategoryPlot plot = new CategoryPlot(new DefaultCategoryDataset(), getCategoryAxis(), getTimeAxis(), renderer); plot.setOrientation(PlotOrientation.HORIZONTAL); plot.setDomainGridlinesVisible(true); plot.setDomainGridlinePosition(CategoryAnchor.END); JFreeChart chart = new JFreeChart(plot); chartPanel = new ChartPanel(chart, 400, 200, 200, 200, 2000, 5000, true, false, false, false, false, true); chartPanel.setMouseZoomable(false); chartPanel.setRangeZoomable(false); chartPanel.setDomainZoomable(false); chartPanel.addChartMouseListener(new ChartMouseListener() { @Override public void chartMouseMoved(ChartMouseEvent arg0) { // Do Nothing } @Override public void chartMouseClicked(ChartMouseEvent event) { //TODO Add listner info or separate the listener. if (event.getEntity() instanceof CategoryItemEntity) { CategoryItemEntity xyitem = (CategoryItemEntity) event.getEntity(); WaterfallCategory wfCategory = (WaterfallCategory) xyitem.getColumnKey(); if (wfCategory != null && wfCategory.getReqResp() != null) { int count = event.getTrigger().getClickCount(); if (count > 1) { waterfallTab.updateMainFrame(wfCategory.getSession()); } else { popup.refresh(wfCategory.getReqResp(), wfCategory.getIndex()); if (!popup.getPopupDialog().isVisible()) { popup.getPopupDialog().setVisible(true); } } } } } }); } return chartPanel; } /** * @return the categoryAxis */ private CategoryAxis getCategoryAxis() { if (categoryAxis == null) { categoryAxis = new CategoryAxis(); categoryAxis.setMaximumCategoryLabelWidthRatio(0.2f); categoryAxis.setCategoryLabelPositions(CategoryLabelPositions.replaceLeftPosition( CategoryLabelPositions.STANDARD, new CategoryLabelPosition(RectangleAnchor.LEFT, TextBlockAnchor.CENTER_LEFT, CategoryLabelWidthType.RANGE, 1.0f))); } return categoryAxis; } /** * @return the timeAxis */ private NumberAxis getTimeAxis() { if (timeAxis == null) { timeAxis = new NumberAxis(ResourceBundleHelper.getMessageString("waterfall.time")) { private static final long serialVersionUID = 1L; /** * This override prevents the tick units from changing * as the timeline is scrolled to numbers with more digits */ @Override protected double estimateMaximumTickLabelWidth(Graphics2D g2d, TickUnit unit) { if (isVerticalTickLabels()) { return super.estimateMaximumTickLabelWidth(g2d, unit); } else { RectangleInsets tickLabelInsets = getTickLabelInsets(); double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight(); // look at lower and upper bounds... FontMetrics fMetrics = g2d.getFontMetrics(getTickLabelFont()); double upper = traceDuration; String upperStr = ""; NumberFormat formatter = getNumberFormatOverride(); if (formatter == null) { upperStr = unit.valueToString(upper); } else { upperStr = formatter.format(upper); } double width2 = fMetrics.stringWidth(upperStr); result += width2; return result; } } }; timeAxis.setRange(new Range(0, DEFAULT_TIMELINE)); timeAxis.setStandardTickUnits(units); } return timeAxis; } /** * @return the verticalScroll */ private JScrollBar getVerticalScroll() { if (verticalScroll == null) { verticalScroll = new JScrollBar(JScrollBar.VERTICAL, 0, 100, 0, 100); verticalScroll.getModel().addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent arg0) { if (dataset.getColumnCount() > 0) { dataset.setFirstCategoryIndex(verticalScroll.getValue()); } } }); } return verticalScroll; } /** * @return the horizontalScroll */ private JScrollBar getHorizontalScroll() { if (horizontalScroll == null) { horizontalScroll = new JScrollBar(JScrollBar.HORIZONTAL, 0, DEFAULT_TIMELINE, 0, DEFAULT_TIMELINE); horizontalScroll.getModel().addChangeListener(new ChangeListener() { @Override public void stateChanged(ChangeEvent arg0) { int scrollStart = horizontalScroll.getValue(); int scrollEnd = scrollStart + horizontalScroll.getVisibleAmount(); //logger.log(Level.FINE, "Change Event: Setting time range to {0} - {1}", new Object[] {scrollStart, scrollEnd}); // Set time axis range based on scroll bar new position timeAxis.setRange(scrollStart, scrollEnd); } }); } return horizontalScroll; } /** * Implements the graph zoom out functionality. */ private JButton getZoomOutButton() { if (zoomOutButton == null) { ImageIcon zoomOutButtonIcon = Images.DEMAGNIFY.getIcon(); zoomOutButton = new JButton(zoomOutButtonIcon); zoomOutButton.setEnabled(false); zoomOutButton.setPreferredSize(new Dimension(60, 30)); zoomOutButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent aEvent) { zoomOut(); } }); zoomOutButton.setToolTipText(ResourceBundleHelper.getMessageString("chart.tooltip.zoomout")); } return zoomOutButton; } /** * Button for zoom in * @return */ private JButton getZoomInButton() { if (zoomInButton == null) { ImageIcon zoomInButtonIcon = Images.MAGNIFY.getIcon(); zoomInButton = new JButton(zoomInButtonIcon); zoomInButton.setEnabled(true); zoomInButton.setPreferredSize(new Dimension(60, 30)); zoomInButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent aEvent) { zoomIn(); } }); zoomInButton.setToolTipText(ResourceBundleHelper.getMessageString("chart.tooltip.zoomin")); } return zoomInButton; } /** * This method implements the graph zoom in functionality. */ private void zoomIn() { Range range = timeAxis.getRange(); double lowl = range.getLowerBound(); double high = lowl + (range.getUpperBound() - lowl) / ZOOM_FACTOR; setTimeRange(lowl, high); } /** * This method implements the graph zoom out functionality. */ private void zoomOut() { Range r = timeAxis.getRange(); double low = r.getLowerBound(); double high = low + (r.getUpperBound() - low) * ZOOM_FACTOR; setTimeRange(low, high); } /** * Setting the time range for the graph. * @param low * @param high */ private void setTimeRange(double low, double high) { double lTime = low; double hTime = high; boolean zoomInEnabled = true; boolean zoomOutEnabled = true; JScrollBar scrollBarr = getHorizontalScroll(); if (hTime > traceDuration) { double delta = hTime - traceDuration; lTime = lTime - delta; hTime = hTime - delta; if (lTime < 0) { lTime = 0.0; } } if (hTime - lTime <= 1.0) { hTime = lTime + 1.0; zoomInEnabled = false; } if ((hTime - lTime) < traceDuration) { zoomOutEnabled = true; } else { zoomOutEnabled = false; } // logger.log(Level.FINE, "Range set to {0} - {1}", new Object[] {low, high}); scrollBarr.setValue((int) lTime); scrollBarr.setVisibleAmount((int) Math.ceil(hTime - lTime)); scrollBarr.setBlockIncrement(scrollBarr.getVisibleAmount()); // Enable zoom buttons appropriately zoomOutButton.setEnabled(zoomOutEnabled); zoomInButton.setEnabled(zoomInEnabled); } /** * Refreshes the waterfall display with the specified analysis data * @param Analyzed data from aro core. */ public void refresh(AROTraceData aModel) { this.popup.refresh(null, 0); this.popup.setVisible(false); double range = DEFAULT_TIMELINE; // Create sorted list of request/response pairs List<WaterfallCategory> categoryList = new ArrayList<WaterfallCategory>(); if (aModel != null && aModel.getAnalyzerResult() != null) { this.traceDuration = aModel.getAnalyzerResult().getTraceresult().getTraceDuration(); // add 20% to make sure labels close to the right edge of the screen are visible this.traceDuration *= 1.2; range = Math.min(this.traceDuration, DEFAULT_TIMELINE); for (Session tcpSession : aModel.getAnalyzerResult().getSessionlist()) { Session thisSession = tcpSession; if (!tcpSession.isUDP()) { for (HttpRequestResponseInfo reqResInfo : tcpSession.getRequestResponseInfo()) { if (reqResInfo.getDirection() == HttpDirection.REQUEST && reqResInfo.getWaterfallInfos() != null) { categoryList.add(new WaterfallCategory(reqResInfo, thisSession)); } } } } // Sort and set index Collections.sort(categoryList); int index = 0; for (WaterfallCategory wCategory : categoryList) { wCategory.setIndex(++index); } } // Horizontal scroll bar used to scroll through trace duration JScrollBar hScrollBar = getHorizontalScroll(); hScrollBar.setMaximum((int) Math.ceil(this.traceDuration)); // Set the visible time range setTimeRange(0, range); CategoryAxis cAxis = getCategoryAxis(); cAxis.clearCategoryLabelToolTips(); // Build the dataset DefaultCategoryDataset underlying = new DefaultCategoryDataset(); for (WaterfallCategory wfc : categoryList) { RequestResponseTimeline rrTimeLine = wfc.getReqResp().getWaterfallInfos(); underlying.addValue(rrTimeLine.getStartTime(), Waterfall.BEFORE, wfc); underlying.addValue(rrTimeLine.getDnsLookupDuration(), Waterfall.DNS_LOOKUP, wfc); underlying.addValue(rrTimeLine.getInitialConnDuration(), Waterfall.INITIAL_CONNECTION, wfc); underlying.addValue(rrTimeLine.getSslNegotiationDuration(), Waterfall.SSL_NEGOTIATION, wfc); underlying.addValue(rrTimeLine.getRequestDuration(), Waterfall.REQUEST_TIME, wfc); underlying.addValue(rrTimeLine.getTimeToFirstByte(), Waterfall.TIME_TO_FIRST_BYTE, wfc); underlying.addValue(rrTimeLine.getContentDownloadDuration(), Waterfall.CONTENT_DOWNLOAD, wfc); underlying.addValue(null, Waterfall.HTTP_3XX_REDIRECTION, wfc); underlying.addValue(null, Waterfall.HTTP_4XX_CLIENTERROR, wfc); int code = wfc.getReqResp().getAssocReqResp().getStatusCode(); double endTime = this.traceDuration - rrTimeLine.getStartTime() - rrTimeLine.getTotalTime(); if (code >= 300 && code < 400) { underlying.addValue(endTime, Waterfall.AFTER_3XX, wfc); } else if (code >= 400) { underlying.addValue(endTime, Waterfall.AFTER_4XX, wfc); } else { underlying.addValue(endTime, Waterfall.AFTER, wfc); } cAxis.addCategoryLabelToolTip(wfc, wfc.getTooltip()); } // Vertical scroll bar is used to scroll through data JScrollBar vScrollBar = getVerticalScroll(); int count = underlying.getColumnCount(); vScrollBar.setValue(0); vScrollBar.setMaximum(count); vScrollBar.setVisibleAmount(count > 0 ? this.dataset.getMaximumCategoryCount() - 1 / count : 1); // Add the dataset to the plot CategoryPlot plot = getChartPanel().getChart().getCategoryPlot(); this.dataset = new SlidingCategoryDataset(underlying, 0, CATEGORY_MAX_COUNT); plot.setDataset(this.dataset); // Place proper colors on renderer for waterfall states final CategoryItemRenderer renderer = plot.getRenderer(); for (Object obj : underlying.getRowKeys()) { Waterfall wFall = (Waterfall) obj; int index = underlying.getRowIndex(wFall); Color paint; switch (wFall) { case DNS_LOOKUP: paint = dnsLoolupColor; break; case INITIAL_CONNECTION: paint = initiaConnColor; break; case SSL_NEGOTIATION: paint = sslNegColor; break; case REQUEST_TIME: paint = requestTimeColor; break; case TIME_TO_FIRST_BYTE: paint = firstByteTimeColor; break; case CONTENT_DOWNLOAD: paint = contentDownloadColor; break; case AFTER_3XX: paint = noneColor; renderer.setSeriesItemLabelPaint(index, threexColor); renderer.setSeriesVisibleInLegend(index, false); break; case AFTER_4XX: paint = noneColor; renderer.setSeriesItemLabelPaint(index, fourxColor); renderer.setSeriesVisibleInLegend(index, false); break; case HTTP_3XX_REDIRECTION: paint = threexColor; break; case HTTP_4XX_CLIENTERROR: paint = fourxColor; break; default: renderer.setSeriesItemLabelPaint(index, Color.black); renderer.setSeriesVisibleInLegend(index, false); paint = noneColor; } renderer.setSeriesPaint(index, paint); } // Adding the label at the end of bars renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator() { private static final long serialVersionUID = 1L; @Override public String generateLabel(CategoryDataset dataset, int row, int column) { if (Waterfall.AFTER == dataset.getRowKey(row) || Waterfall.AFTER_3XX == dataset.getRowKey(row) || Waterfall.AFTER_4XX == dataset.getRowKey(row)) { WaterfallCategory waterfallItem = (WaterfallCategory) dataset.getColumnKey(column); RequestResponseTimeline waterfallInfos = waterfallItem.getReqResp().getWaterfallInfos(); DecimalFormat formatter = new DecimalFormat("#.##"); int code = waterfallItem.getReqResp().getAssocReqResp().getStatusCode(); return MessageFormat.format(ResourceBundleHelper.getMessageString("waterfall.totalTime"), formatter.format(waterfallInfos.getTotalTime()), code > 0 ? waterfallItem.getReqResp().getScheme() + " " + code : ResourceBundleHelper.getMessageString("waterfall.unknownCode")); } return null; } }); } }