001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
013: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
014: * License for the specific language governing permissions and limitations
015: * under the License.
016: *
017: */
018:
019: package org.apache.jmeter.visualizers;
020:
021: import java.awt.BorderLayout;
022: import java.awt.Dimension;
023: import java.awt.event.ActionEvent;
024: import java.awt.event.ActionListener;
025: import java.io.File;
026: import java.io.FileNotFoundException;
027: import java.io.FileWriter;
028: import java.io.IOException;
029: import java.util.Collections;
030: import java.util.HashMap;
031: import java.util.Map;
032: import java.util.Vector;
033:
034: import javax.swing.BoxLayout;
035: import javax.swing.JButton;
036: import javax.swing.JComponent;
037: import javax.swing.JFileChooser;
038: import javax.swing.JLabel;
039: import javax.swing.JPanel;
040: import javax.swing.JScrollPane;
041: import javax.swing.JSplitPane;
042: import javax.swing.JTable;
043: import javax.swing.border.Border;
044: import javax.swing.border.EmptyBorder;
045: import javax.swing.table.TableCellRenderer;
046:
047: import org.apache.jmeter.gui.action.ActionNames;
048: import org.apache.jmeter.gui.action.ActionRouter;
049: import org.apache.jmeter.gui.action.SaveGraphics;
050: import org.apache.jmeter.gui.util.FileDialoger;
051: import org.apache.jmeter.gui.util.HorizontalPanel;
052: import org.apache.jmeter.gui.util.VerticalPanel;
053: import org.apache.jmeter.samplers.Clearable;
054: import org.apache.jmeter.samplers.SampleResult;
055: import org.apache.jmeter.save.CSVSaveService;
056: import org.apache.jmeter.testelement.TestElement;
057: import org.apache.jmeter.util.JMeterUtils;
058: import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
059: import org.apache.jorphan.gui.JLabeledChoice;
060: import org.apache.jorphan.gui.JLabeledTextField;
061: import org.apache.jorphan.gui.NumberRenderer;
062: import org.apache.jorphan.gui.ObjectTableModel;
063: import org.apache.jorphan.gui.RateRenderer;
064: import org.apache.jorphan.gui.RendererUtils;
065: import org.apache.jorphan.logging.LoggingManager;
066: import org.apache.jorphan.reflect.Functor;
067: import org.apache.jorphan.util.JOrphanUtils;
068: import org.apache.log.Logger;
069:
070: /**
071: * Aggregrate Table-Based Reporting Visualizer for JMeter. Props to the people
072: * who've done the other visualizers ahead of me (Stefano Mazzocchi), who I
073: * borrowed code from to start me off (and much code may still exist). Thank
074: * you!
075: *
076: */
077: public class StatGraphVisualizer extends AbstractVisualizer implements
078: Clearable, ActionListener {
079: private static final Logger log = LoggingManager
080: .getLoggerForClass();
081:
082: private final String[] COLUMNS = { JMeterUtils.getResString("url"), //$NON-NLS-1$
083: JMeterUtils.getResString("aggregate_report_count"), //$NON-NLS-1$
084: JMeterUtils.getResString("average"), //$NON-NLS-1$
085: JMeterUtils.getResString("aggregate_report_median"), //$NON-NLS-1$
086: JMeterUtils.getResString("aggregate_report_90%_line"), //$NON-NLS-1$
087: JMeterUtils.getResString("aggregate_report_min"), //$NON-NLS-1$
088: JMeterUtils.getResString("aggregate_report_max"), //$NON-NLS-1$
089: JMeterUtils.getResString("aggregate_report_error%"), //$NON-NLS-1$
090: JMeterUtils.getResString("aggregate_report_rate"), //$NON-NLS-1$
091: JMeterUtils.getResString("aggregate_report_bandwidth") }; //$NON-NLS-1$
092:
093: private final String[] GRAPH_COLUMNS = {
094: JMeterUtils.getResString("average"),//$NON-NLS-1$
095: JMeterUtils.getResString("aggregate_report_median"), //$NON-NLS-1$
096: JMeterUtils.getResString("aggregate_report_90%_line"), //$NON-NLS-1$
097: JMeterUtils.getResString("aggregate_report_min"), //$NON-NLS-1$
098: JMeterUtils.getResString("aggregate_report_max") }; //$NON-NLS-1$
099:
100: private final String TOTAL_ROW_LABEL = JMeterUtils
101: .getResString("aggregate_report_total_label"); //$NON-NLS-1$
102:
103: protected JTable myJTable;
104:
105: protected JScrollPane myScrollPane;
106:
107: transient private ObjectTableModel model;
108:
109: Map tableRows = Collections.synchronizedMap(new HashMap());
110:
111: protected AxisGraph graphPanel = null;
112:
113: protected VerticalPanel graph = null;
114:
115: protected JScrollPane graphScroll = null;
116:
117: protected JSplitPane spane = null;
118:
119: protected JLabeledChoice columns = new JLabeledChoice(JMeterUtils
120: .getResString("aggregate_graph_column"), GRAPH_COLUMNS);//$NON-NLS-1$
121:
122: //NOT USED protected double[][] data = null;
123:
124: protected JButton displayButton = new JButton(JMeterUtils
125: .getResString("aggregate_graph_display")); //$NON-NLS-1$
126:
127: protected JButton saveGraph = new JButton(JMeterUtils
128: .getResString("aggregate_graph_save")); //$NON-NLS-1$
129:
130: protected JButton saveTable = new JButton(JMeterUtils
131: .getResString("aggregate_graph_save_table")); //$NON-NLS-1$
132:
133: JLabeledTextField graphTitle = new JLabeledTextField(JMeterUtils
134: .getResString("aggregate_graph_user_title")); //$NON-NLS-1$
135:
136: JLabeledTextField maxLengthXAxisLabel = new JLabeledTextField(
137: JMeterUtils
138: .getResString("aggregate_graph_max_length_xaxis_label"));//$NON-NLS-1$
139:
140: JLabeledTextField graphWidth = new JLabeledTextField(JMeterUtils
141: .getResString("aggregate_graph_width")); //$NON-NLS-1$
142: JLabeledTextField graphHeight = new JLabeledTextField(JMeterUtils
143: .getResString("aggregate_graph_height")); //$NON-NLS-1$
144:
145: protected String yAxisLabel = JMeterUtils
146: .getResString("aggregate_graph_response_time");//$NON-NLS-1$
147:
148: protected String yAxisTitle = JMeterUtils
149: .getResString("aggregate_graph_ms"); //$NON-NLS-1$
150:
151: protected boolean saveGraphToFile = false;
152:
153: protected int defaultWidth = 400;
154:
155: protected int defaultHeight = 300;
156:
157: public StatGraphVisualizer() {
158: super ();
159: model = new ObjectTableModel(COLUMNS,
160: SamplingStatCalculator.class, new Functor[] {
161: new Functor("getLabel"), //$NON-NLS-1$
162: new Functor("getCount"), //$NON-NLS-1$
163: new Functor("getMeanAsNumber"), //$NON-NLS-1$
164: new Functor("getMedian"), //$NON-NLS-1$
165: new Functor("getPercentPoint", //$NON-NLS-1$
166: new Object[] { new Float(.900) }),
167: new Functor("getMin"), //$NON-NLS-1$
168: new Functor("getMax"), //$NON-NLS-1$
169: new Functor("getErrorPercentage"), //$NON-NLS-1$
170: new Functor("getRate"), //$NON-NLS-1$
171: new Functor("getKBPerSecond") }, //$NON-NLS-1$
172: new Functor[] { null, null, null, null, null, null,
173: null, null, null, null }, new Class[] {
174: String.class, Long.class, Long.class,
175: Long.class, Long.class, Long.class, Long.class,
176: String.class, String.class, String.class });
177: clearData();
178: init();
179: }
180:
181: // Column renderers
182: private static final TableCellRenderer[] RENDERERS = new TableCellRenderer[] {
183: null, // Label
184: null, // count
185: null, // Mean
186: null, // median
187: null, // 90%
188: null, // Min
189: null, // Max
190: new NumberRenderer("#0.00%"), // Error %age
191: new RateRenderer("#.0"), // Throughpur
192: new NumberRenderer("#.0"), // pageSize
193: };
194:
195: public static boolean testFunctors() {
196: StatGraphVisualizer instance = new StatGraphVisualizer();
197: return instance.model.checkFunctors(null, instance.getClass());
198: }
199:
200: public String getLabelResource() {
201: return "aggregate_graph_title"; //$NON-NLS-1$
202: }
203:
204: public void add(SampleResult res) {
205: SamplingStatCalculator row = null;
206: synchronized (tableRows) {
207: row = (SamplingStatCalculator) tableRows.get(res
208: .getSampleLabel());
209: if (row == null) {
210: row = new SamplingStatCalculator(res.getSampleLabel());
211: tableRows.put(row.getLabel(), row);
212: model.insertRow(row, model.getRowCount() - 1);
213: }
214: }
215: row.addSample(res);
216: ((SamplingStatCalculator) tableRows.get(TOTAL_ROW_LABEL))
217: .addSample(res);
218: model.fireTableDataChanged();
219: }
220:
221: /**
222: * Clears this visualizer and its model, and forces a repaint of the table.
223: */
224: public void clearData() {
225: model.clearData();
226: tableRows.clear();
227: tableRows.put(TOTAL_ROW_LABEL, new SamplingStatCalculator(
228: TOTAL_ROW_LABEL));
229: model.addRow(tableRows.get(TOTAL_ROW_LABEL));
230: }
231:
232: // overrides AbstractVisualizer
233: // forces GUI update after sample file has been read
234: public TestElement createTestElement() {
235: TestElement t = super .createTestElement();
236:
237: // sleepTill = 0;
238: return t;
239: }
240:
241: /**
242: * Main visualizer setup.
243: */
244: private void init() {
245: this .setLayout(new BorderLayout());
246:
247: // MAIN PANEL
248: JPanel mainPanel = new JPanel();
249: Border margin = new EmptyBorder(10, 10, 5, 10);
250: Border margin2 = new EmptyBorder(10, 10, 5, 10);
251:
252: mainPanel.setBorder(margin);
253: mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
254: mainPanel.add(makeTitlePanel());
255:
256: myJTable = new JTable(model);
257: myJTable.setPreferredScrollableViewportSize(new Dimension(500,
258: 80));
259: RendererUtils.applyRenderers(myJTable, RENDERERS);
260: myScrollPane = new JScrollPane(myJTable);
261:
262: graph = new VerticalPanel();
263: graph.setBorder(margin2);
264:
265: JLabel graphLabel = new JLabel(JMeterUtils
266: .getResString("aggregate_graph")); //$NON-NLS-1$
267: graphPanel = new AxisGraph();
268: graphPanel.setPreferredSize(new Dimension(defaultWidth,
269: defaultHeight));
270:
271: // horizontal panel for the buttons
272: HorizontalPanel buttonpanel = new HorizontalPanel();
273: buttonpanel.add(columns);
274: buttonpanel.add(displayButton);
275: buttonpanel.add(saveGraph);
276: buttonpanel.add(saveTable);
277:
278: graph.add(graphLabel);
279: graph.add(graphTitle);
280: graph.add(maxLengthXAxisLabel);
281: graph.add(graphWidth);
282: graph.add(graphHeight);
283: graph.add(buttonpanel);
284: graph.add(graphPanel);
285:
286: displayButton.addActionListener(this );
287: saveGraph.addActionListener(this );
288: saveTable.addActionListener(this );
289: graphScroll = new JScrollPane(graph);
290: graphScroll.setAutoscrolls(true);
291:
292: spane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
293: spane.setLeftComponent(myScrollPane);
294: spane.setRightComponent(graphScroll);
295: spane.setResizeWeight(.2);
296: spane.setContinuousLayout(true);
297:
298: this .add(mainPanel, BorderLayout.NORTH);
299: this .add(spane, BorderLayout.CENTER);
300: }
301:
302: public void makeGraph() {
303: String wstr = graphWidth.getText();
304: String hstr = graphHeight.getText();
305: String lstr = maxLengthXAxisLabel.getText();
306: if (wstr.length() == 0) {
307: wstr = "450";//$NON-NLS-1$
308: }
309: if (hstr.length() == 0) {
310: hstr = "250";//$NON-NLS-1$
311: }
312: if (lstr.length() == 0) {
313: lstr = "20";//$NON-NLS-1$
314: }
315: int width = Integer.parseInt(wstr);
316: int height = Integer.parseInt(hstr);
317: int maxLength = Integer.parseInt(lstr);
318:
319: graphPanel.setData(this .getData());
320: graphPanel.setHeight(height);
321: graphPanel.setWidth(width);
322: graphPanel.setTitle(graphTitle.getText());
323: graphPanel.setMaxLength(maxLength);
324: graphPanel.setXAxisLabels(getAxisLabels());
325: graphPanel.setXAxisTitle(columns.getText());
326: graphPanel.setYAxisLabels(this .yAxisLabel);
327: graphPanel.setYAxisTitle(this .yAxisTitle);
328:
329: graphPanel.setPreferredSize(new Dimension(width, height));
330: graph.setSize(new Dimension(graph.getWidth(), height + 120));
331: spane.repaint();
332: }
333:
334: public double[][] getData() {
335: if (model.getRowCount() > 1) {
336: int count = model.getRowCount() - 1;
337: int col = model.findColumn(columns.getText());
338: double[][] data = new double[1][count];
339: for (int idx = 0; idx < count; idx++) {
340: data[0][idx] = ((Number) model.getValueAt(idx, col))
341: .doubleValue();
342: }
343: return data;
344: }
345: return new double[][] { { 250, 45, 36, 66, 145, 80, 55 } };
346: }
347:
348: public String[] getAxisLabels() {
349: if (model.getRowCount() > 1) {
350: int count = model.getRowCount() - 1;
351: String[] labels = new String[count];
352: for (int idx = 0; idx < count; idx++) {
353: labels[idx] = (String) model.getValueAt(idx, 0);
354: }
355: return labels;
356: }
357: return new String[] { "/", "/samples", "/jsp-samples",
358: "/manager", "/manager/status", "/hello", "/world" };
359: }
360:
361: /**
362: * We use this method to get the data, since we are using
363: * ObjectTableModel, so the calling getDataVector doesn't
364: * work as expected.
365: * @return the data from the model
366: */
367: public Vector getAllTableData() {
368: Vector data = new Vector();
369: if (model.getRowCount() > 0) {
370: for (int rw = 0; rw < model.getRowCount(); rw++) {
371: int cols = model.getColumnCount();
372: Vector column = new Vector();
373: data.add(column);
374: for (int idx = 0; idx < cols; idx++) {
375: Object val = model.getValueAt(rw, idx);
376: column.add(val);
377: }
378: }
379: }
380: return data;
381: }
382:
383: public void actionPerformed(ActionEvent event) {
384: if (event.getSource() == displayButton) {
385: makeGraph();
386: } else if (event.getSource() == saveGraph) {
387: saveGraphToFile = true;
388: try {
389: ActionRouter.getInstance().getAction(
390: ActionNames.SAVE_GRAPHICS,
391: SaveGraphics.class.getName()).doAction(
392: new ActionEvent(this , 1,
393: ActionNames.SAVE_GRAPHICS));
394: } catch (Exception e) {
395: e.printStackTrace();
396: }
397: } else if (event.getSource() == saveTable) {
398: JFileChooser chooser = FileDialoger
399: .promptToSaveFile("statistics.csv"); //$NON-NLS-1$
400: File output = chooser.getSelectedFile();
401: FileWriter writer = null;
402: try {
403: writer = new FileWriter(output);
404: Vector data = this .getAllTableData();
405: CSVSaveService.saveCSVStats(data, writer);
406: } catch (FileNotFoundException e) {
407: log.warn(e.getMessage());
408: } catch (IOException e) {
409: log.warn(e.getMessage());
410: } finally {
411: JOrphanUtils.closeQuietly(writer);
412: }
413: }
414: }
415:
416: public JComponent getPrintableComponent() {
417: if (saveGraphToFile == true) {
418: saveGraphToFile = false;
419: graphPanel.setBounds(graphPanel.getLocation().x, graphPanel
420: .getLocation().y, graphPanel.width,
421: graphPanel.height);
422: return graphPanel;
423: }
424: return this;
425: }
426: }
|