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,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.jmeter.visualizers;
020:
021: import java.awt.BorderLayout;
022: import java.awt.Color;
023: import java.awt.Component;
024: import java.awt.Dimension;
025: import java.awt.FlowLayout;
026: import java.awt.Image;
027: import java.awt.event.ItemEvent;
028: import java.awt.event.ItemListener;
029: import java.text.NumberFormat;
030:
031: import javax.swing.BorderFactory;
032: import javax.swing.Box;
033: import javax.swing.JCheckBox;
034: import javax.swing.JLabel;
035: import javax.swing.JPanel;
036: import javax.swing.JScrollPane;
037: import javax.swing.JTextField;
038: import javax.swing.border.Border;
039: import javax.swing.border.EmptyBorder;
040:
041: import org.apache.jmeter.gui.util.JMeterColor;
042: import org.apache.jmeter.samplers.Clearable;
043: import org.apache.jmeter.samplers.SampleResult;
044: import org.apache.jmeter.util.JMeterUtils;
045: import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
046:
047: /**
048: * This class implements a statistical analyser that calculates both the average
049: * and the standard deviation of the sampling process and outputs them as
050: * autoscaling plots.
051: *
052: * Created February 8, 2001
053: *
054: */
055: public class GraphVisualizer extends AbstractVisualizer implements
056: ImageVisualizer, ItemListener, Clearable {
057:
058: private static final String ZERO = "0"; //$NON-NLS-1$
059:
060: private NumberFormat nf = NumberFormat.getInstance(); // OK, because used in synchronised method
061:
062: private SamplingStatCalculator model;
063:
064: private JTextField maxYField = null;
065:
066: private JTextField minYField = null;
067:
068: private JTextField noSamplesField = null;
069:
070: private String minute = JMeterUtils.getResString("minute"); // $NON-NLS-1$
071:
072: private Graph graph;
073:
074: private JCheckBox data;
075:
076: private JCheckBox average;
077:
078: private JCheckBox deviation;
079:
080: private JCheckBox throughput;
081:
082: private JCheckBox median;
083:
084: private JTextField dataField;
085:
086: private JTextField averageField;
087:
088: private JTextField deviationField;
089:
090: private JTextField throughputField;
091:
092: private JTextField medianField;
093:
094: /**
095: * Constructor for the GraphVisualizer object.
096: */
097: public GraphVisualizer() {
098: model = new SamplingStatCalculator("Graph");
099: graph = new Graph(model);
100: init();
101: }
102:
103: /**
104: * Gets the Image attribute of the GraphVisualizer object.
105: *
106: * @return the Image value
107: */
108: public Image getImage() {
109: Image result = graph.createImage(graph.getWidth(), graph
110: .getHeight());
111:
112: graph.paintComponent(result.getGraphics());
113:
114: return result;
115: }
116:
117: public synchronized void updateGui(Sample s) {
118: // We have received one more sample
119: graph.updateGui(s);
120: noSamplesField.setText(Long.toString(s.getCount()));
121: dataField.setText(Long.toString(s.getData()));
122: averageField.setText(Long.toString(s.getAverage()));
123: deviationField.setText(Long.toString(s.getDeviation()));
124: throughputField.setText(nf.format(60 * s.getThroughput()) + "/"
125: + minute); // $NON-NLS-1$
126: medianField.setText(Long.toString(s.getMedian()));
127: updateYAxis();
128: }
129:
130: public void add(SampleResult res) {
131: updateGui(model.addSample(res));
132: }
133:
134: public String getLabelResource() {
135: return "graph_results_title"; // $NON-NLS-1$
136: }
137:
138: public void itemStateChanged(ItemEvent e) {
139: if (e.getItem() == data) {
140: this .graph
141: .enableData(e.getStateChange() == ItemEvent.SELECTED);
142: } else if (e.getItem() == average) {
143: this .graph
144: .enableAverage(e.getStateChange() == ItemEvent.SELECTED);
145: } else if (e.getItem() == deviation) {
146: this .graph
147: .enableDeviation(e.getStateChange() == ItemEvent.SELECTED);
148: } else if (e.getItem() == throughput) {
149: this .graph
150: .enableThroughput(e.getStateChange() == ItemEvent.SELECTED);
151: } else if (e.getItem() == median) {
152: this .graph
153: .enableMedian(e.getStateChange() == ItemEvent.SELECTED);
154: }
155: this .graph.repaint();
156: }
157:
158: public void clearData() {
159: graph.clearData();
160: model.clear();
161: dataField.setText(ZERO);
162: averageField.setText(ZERO);
163: deviationField.setText(ZERO);
164: throughputField.setText("0/" + minute); //$NON-NLS-1$
165: medianField.setText(ZERO);
166: noSamplesField.setText(ZERO);
167: updateYAxis();
168: repaint();
169: }
170:
171: public String toString() {
172: return "Show the samples analysis as dot plots";
173: }
174:
175: /**
176: * Update the max and min value of the Y axis.
177: */
178: private void updateYAxis() {
179: maxYField.setText(Long.toString(graph.getGraphMax()));
180: minYField.setText(ZERO);
181: }
182:
183: /**
184: * Initialize the GUI.
185: */
186: private void init() {
187: this .setLayout(new BorderLayout());
188:
189: // MAIN PANEL
190: Border margin = new EmptyBorder(10, 10, 5, 10);
191:
192: this .setBorder(margin);
193:
194: // Set up the graph with header, footer, Y axis and graph display
195: JPanel graphPanel = new JPanel(new BorderLayout());
196: graphPanel.add(createYAxis(), BorderLayout.WEST);
197: graphPanel.add(createChoosePanel(), BorderLayout.NORTH);
198: graphPanel.add(createGraphPanel(), BorderLayout.CENTER);
199: graphPanel.add(createGraphInfoPanel(), BorderLayout.SOUTH);
200:
201: // Add the main panel and the graph
202: this .add(makeTitlePanel(), BorderLayout.NORTH);
203: this .add(graphPanel, BorderLayout.CENTER);
204: }
205:
206: // Methods used in creating the GUI
207:
208: /**
209: * Creates the panel containing the graph's Y axis labels.
210: *
211: * @return the Y axis panel
212: */
213: private JPanel createYAxis() {
214: JPanel graphYAxisPanel = new JPanel();
215:
216: graphYAxisPanel.setLayout(new BorderLayout());
217:
218: maxYField = createYAxisField(5);
219: minYField = createYAxisField(3);
220:
221: graphYAxisPanel.add(createYAxisPanel("graph_results_ms",
222: maxYField), BorderLayout.NORTH); // $NON-NLS-1$
223: graphYAxisPanel.add(createYAxisPanel("graph_results_ms",
224: minYField), BorderLayout.SOUTH); // $NON-NLS-1$
225:
226: return graphYAxisPanel;
227: }
228:
229: /**
230: * Creates a text field to be used for the value of a Y axis label. These
231: * fields hold the minimum and maximum values for the graph. The units are
232: * kept in a separate label outside of this field.
233: *
234: * @param length
235: * the number of characters which the field will use to calculate
236: * its preferred width. This should be set to the maximum number
237: * of digits that are expected to be necessary to hold the label
238: * value.
239: *
240: * @see #createYAxisPanel(String, JTextField)
241: *
242: * @return a text field configured to be used in the Y axis
243: */
244: private JTextField createYAxisField(int length) {
245: JTextField field = new JTextField(length);
246: field.setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
247: field.setEditable(false);
248: field.setForeground(Color.black);
249: field.setBackground(getBackground());
250: field.setHorizontalAlignment(JTextField.RIGHT);
251: return field;
252: }
253:
254: /**
255: * Creates a panel for an entire Y axis label. This includes the dynamic
256: * value as well as the unit label.
257: *
258: * @param labelResourceName
259: * the name of the label resource. This is used to look up the
260: * label text using {@link JMeterUtils#getResString(String)}.
261: *
262: * @return a panel containing both the dynamic and static parts of a Y axis
263: * label
264: */
265: private JPanel createYAxisPanel(String labelResourceName,
266: JTextField field) {
267: JPanel panel = new JPanel(new FlowLayout());
268: JLabel label = new JLabel(JMeterUtils
269: .getResString(labelResourceName));
270:
271: panel.add(field);
272: panel.add(label);
273: return panel;
274: }
275:
276: /**
277: * Creates a panel which allows the user to choose which graphs to display.
278: * This panel consists of a check box for each type of graph (current
279: * sample, average, deviation, and throughput).
280: *
281: * @return a panel allowing the user to choose which graphs to display
282: */
283: private JPanel createChoosePanel() {
284: JPanel chooseGraphsPanel = new JPanel();
285:
286: chooseGraphsPanel.setLayout(new FlowLayout());
287: JLabel selectGraphsLabel = new JLabel(JMeterUtils
288: .getResString("graph_choose_graphs")); //$NON-NLS-1$
289: data = createChooseCheckBox("graph_results_data", Color.black); // $NON-NLS-1$
290: average = createChooseCheckBox("graph_results_average",
291: Color.blue); // $NON-NLS-1$
292: deviation = createChooseCheckBox("graph_results_deviation",
293: Color.red); // $NON-NLS-1$
294: throughput = createChooseCheckBox("graph_results_throughput",
295: JMeterColor.dark_green); // $NON-NLS-1$
296: median = createChooseCheckBox("graph_results_median",
297: JMeterColor.purple); // $NON-NLS-1$
298:
299: chooseGraphsPanel.add(selectGraphsLabel);
300: chooseGraphsPanel.add(data);
301: chooseGraphsPanel.add(average);
302: chooseGraphsPanel.add(median);
303: chooseGraphsPanel.add(deviation);
304: chooseGraphsPanel.add(throughput);
305: return chooseGraphsPanel;
306: }
307:
308: /**
309: * Creates a check box configured to be used to in the choose panel allowing
310: * the user to select whether or not a particular kind of graph data will be
311: * displayed.
312: *
313: * @param labelResourceName
314: * the name of the label resource. This is used to look up the
315: * label text using {@link JMeterUtils#getResString(String)}.
316: * @param color
317: * the color used for the checkbox text. By convention this is
318: * the same color that is used to draw the graph and for the
319: * corresponding info field.
320: *
321: * @return a checkbox allowing the user to select whether or not a kind of
322: * graph data will be displayed
323: */
324: private JCheckBox createChooseCheckBox(String labelResourceName,
325: Color color) {
326: JCheckBox checkBox = new JCheckBox(JMeterUtils
327: .getResString(labelResourceName));
328: checkBox.setSelected(true);
329: checkBox.addItemListener(this );
330: checkBox.setForeground(color);
331: return checkBox;
332: }
333:
334: /**
335: * Creates a scroll pane containing the actual graph of the results.
336: *
337: * @return a scroll pane containing the graph
338: */
339: private Component createGraphPanel() {
340: JScrollPane graphScrollPanel = makeScrollPane(graph,
341: JScrollPane.VERTICAL_SCROLLBAR_NEVER,
342: JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
343: graphScrollPanel.setViewportBorder(BorderFactory
344: .createEmptyBorder(2, 2, 2, 2));
345: graphScrollPanel.setPreferredSize(graphScrollPanel
346: .getMinimumSize());
347:
348: return graphScrollPanel;
349: }
350:
351: /**
352: * Creates a panel which numerically displays the current graph values.
353: *
354: * @return a panel showing the current graph values
355: */
356: private Box createGraphInfoPanel() {
357: Box graphInfoPanel = Box.createHorizontalBox();
358:
359: noSamplesField = createInfoField(Color.black, 6);
360: dataField = createInfoField(Color.black, 5);
361: averageField = createInfoField(Color.blue, 5);
362: deviationField = createInfoField(Color.red, 5);
363: throughputField = createInfoField(JMeterColor.dark_green, 15);
364: medianField = createInfoField(JMeterColor.purple, 5);
365:
366: graphInfoPanel.add(createInfoColumn(
367: createInfoLabel("graph_results_no_samples",
368: noSamplesField), // $NON-NLS-1$
369: noSamplesField, createInfoLabel(
370: "graph_results_deviation", deviationField),
371: deviationField)); // $NON-NLS-1$
372: graphInfoPanel.add(Box.createHorizontalGlue());
373:
374: graphInfoPanel.add(createInfoColumn(createInfoLabel(
375: "graph_results_latest_sample", dataField), dataField, // $NON-NLS-1$
376: createInfoLabel("graph_results_throughput",
377: throughputField), throughputField)); // $NON-NLS-1$
378: graphInfoPanel.add(Box.createHorizontalGlue());
379:
380: graphInfoPanel.add(createInfoColumn(createInfoLabel(
381: "graph_results_average", averageField),
382: averageField, // $NON-NLS-1$
383: createInfoLabel("graph_results_median", medianField),
384: medianField)); // $NON-NLS-1$
385: graphInfoPanel.add(Box.createHorizontalGlue());
386: return graphInfoPanel;
387: }
388:
389: /**
390: * Creates one of the fields used to display the graph's current values.
391: *
392: * @param color
393: * the color used to draw the value. By convention this is the
394: * same color that is used to draw the graph for this value and
395: * in the choose panel.
396: * @param length
397: * the number of digits which the field should be able to display
398: *
399: * @return a text field configured to display one of the current graph
400: * values
401: */
402: private JTextField createInfoField(Color color, int length) {
403: JTextField field = new JTextField(length);
404: field.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
405: field.setEditable(false);
406: field.setForeground(color);
407: field.setBackground(getBackground());
408:
409: // The text field should expand horizontally, but have
410: // a fixed height
411: field.setMaximumSize(new Dimension(
412: field.getMaximumSize().width,
413: field.getPreferredSize().height));
414: return field;
415: }
416:
417: /**
418: * Creates a label for one of the fields used to display the graph's current
419: * values. Neither the label created by this method or the
420: * <code>field</code> passed as a parameter is added to the GUI here.
421: *
422: * @param labelResourceName
423: * the name of the label resource. This is used to look up the
424: * label text using {@link JMeterUtils#getResString(String)}.
425: * @param field
426: * the field this label is being created for.
427: */
428: private JLabel createInfoLabel(String labelResourceName,
429: JTextField field) {
430: JLabel label = new JLabel(JMeterUtils
431: .getResString(labelResourceName));
432: label.setForeground(field.getForeground());
433: label.setLabelFor(field);
434: return label;
435: }
436:
437: /**
438: * Creates a panel containing two pairs of labels and fields for displaying
439: * the current graph values. This method exists to help with laying out the
440: * fields in columns. If one or more components are null then these
441: * components will be represented by blank space.
442: *
443: * @param label1
444: * the label for the first field. This label will be placed in
445: * the upper left section of the panel. If this parameter is
446: * null, this section of the panel will be left blank.
447: * @param field1
448: * the field corresponding to the first label. This field will be
449: * placed in the upper right section of the panel. If this
450: * parameter is null, this section of the panel will be left
451: * blank.
452: * @param label2
453: * the label for the second field. This label will be placed in
454: * the lower left section of the panel. If this parameter is
455: * null, this section of the panel will be left blank.
456: * @param field2
457: * the field corresponding to the second label. This field will
458: * be placed in the lower right section of the panel. If this
459: * parameter is null, this section of the panel will be left
460: * blank.
461: */
462: private Box createInfoColumn(JLabel label1, JTextField field1,
463: JLabel label2, JTextField field2) {
464: // This column actually consists of a row with two sub-columns
465: // The first column contains the labels, and the second
466: // column contains the fields.
467: Box row = Box.createHorizontalBox();
468: Box col = Box.createVerticalBox();
469: col.add(label1 != null ? label1 : Box.createVerticalGlue());
470: col.add(label2 != null ? label2 : Box.createVerticalGlue());
471: row.add(col);
472:
473: row.add(Box.createHorizontalStrut(5));
474:
475: col = Box.createVerticalBox();
476: col.add(field1 != null ? field1 : Box.createVerticalGlue());
477: col.add(field2 != null ? field2 : Box.createVerticalGlue());
478: row.add(col);
479:
480: row.add(Box.createHorizontalStrut(5));
481:
482: return row;
483: }
484:
485: }
|