如何为Apache JMeter开发插件(五)——监听器之Report (报告)
2016-09-28 17:29
751 查看
JMeter监听器组件
前文介绍过,我们可以从实际用途上将JMeter的Listener(监听器)组件分为两大类Report (报告)和Vizualizers(监视器)。Report (报告)主要用于收集来自于线程组内各个Sampler在进行sample时所产生的结果数据;Vizualizers(监视器)主要用于主动采集一些我们所关心的,最终用于结合Sampler结果数据进行性能瓶颈分析与调优的性能计数(如操作系统CPU利用率、内存使用情况等)。另外,为达到监听效果在GUI层常常会以图表形式对监听到的结果数据进行直观展现,在文件系统层通过格式化后将结果数据进行本地化文件保存。Report插件开发
Report (报告)继承AbstractListenerElement抽象类,通过实现sampleOccurred(SampleEvent e)方法,对所有采集事件中所产生的SampleResult进行处理,从而生成报告。一个用于产生结果的SleepTestSampler插件
Sampler插件非常简单,需要的功能是根据配置生成一个sample标签并产生一段sample消耗时间,核心代码参考如下:public SampleResult sample(Entry entry) { SampleResult res = new SampleResult(); res.sampleStart(); res.setSampleLabel(this.getPropertyAsString(NAME) + this.getThreadName()); Random rand = new Random(); try { long defaultValue = (rand.nextInt(5) + 1) * 1000; if(this.getPropertyAsString(SLEEP).trim().equals("")){ Thread.sleep(defaultValue); } else { Thread.sleep(this.getPropertyAsLong(SLEEP, defaultValue)); } } catch (InterruptedException e) { e.printStackTrace(); } res.sampleEnd(); res.setResponseCodeOK(); res.setSuccessful(true); return res; }
完成后,效果如下图:
从前面的代码可以看出,第一个输入框为产生一个NAME属性,第一个输入框为产生一个SLEEP属性,当SLEEP属性未设置时,将产生一个随机时间。
Report插件
Report插件会监听到所有Sampler在sample时所产生的SampleResult对象,为了甄别我们真正想要收集的结果数据,可以定义一个isSampleWanted方法,收集器Collector的代码参考如下:public class SamplerResultCollector extends AbstractListenerElement implements SampleListener, Clearable, Serializable, TestStateListener, Remoteable, NoThreadClone { public boolean isSampleWanted(boolean success){ if(success){ return true; } return false; } @Override public void testEnded() { } @Override public void testEnded(String host) { } @Override public void testStarted() { } @Override public void testStarted(String host) { } @Override public void clearData() { } @Override public void sampleOccurred(SampleEvent event) { SampleResult result = event.getResult(); if(isSampleWanted(result.isSuccessful())){ long during = result.getEndTime() - result.getStartTime(); System.out.println(result.getSampleLabel() + ":" + during + "ms"); } } @Override public void sampleStarted(SampleEvent event) { } @Override public void sampleStopped(SampleEvent event) { } }
我们收集所有状态为成功的SampleResult,并在收集的同时输出其对应的sample标签和响应时间。
Report的GUI代码参考如下:
public class SamplerResultReporter extends AbstractListenerGui{ @Override public TestElement createTestElement() { SamplerResultCollector collector = new SamplerResultCollector(); modifyTestElement(collector); return collector; } @Override public String getLabelResource() { return this.getClass().getSimpleName(); } @Override public String getStaticLabel() {//设置显示名称 return JMeterPluginUtils.prefixLabel("SamplerResultReport"); } @Override public void modifyTestElement(TestElement element) { super.configureTestElement(element); } @Override public void configure(TestElement element) { super.configure(element); } }
完成后,我们创建两个SleepTestSampler,第一个取名为A,固定产生3000毫秒响应时间,第二个取名为B,随机产生响应时间,使用SamplerResultReporter进行监听,10个线程循环2次,效果如下:
开发一个类似于LoadRunner事务的Sampler组及配套的事务监听器Report
众所周知,LoadRunner提供了一种定义事务的方法,即使用int lr_start_transaction( const char *transaction_name )函数和int lr_end_transaction( const char *transaction_name, int status ) 函数组合形成一个封闭的代码段,此代码段便称之为一个事务。JMeter同样为我们提供了一种定义事务的方法,利用“事务控制器”来定义事务。我们能否为JMeter开发一个类似于LoadRunner的事务定义方法呢?答案是肯定的,利用一组Sampler便可完成,另外,我们为了对事务的执行情况进行监测,还需要配套开发一个事务监听器Report。
自定义一个TransactionSampleResult
我们需要自定义一个TransactionSampleResult用于专门保存我们所定义Transaction所产生的结果,它继承SampleResult,参考代码如下:public class TransactionSampleResult extends SampleResult{ private String name = null; public TransactionSampleResult(long start){ setStartTime(start); setEndTime(System.currentTimeMillis()); } public String getName() { return name; } public void setName(String name) { this.name = name; } }
通过name保存事务名称,实例化同时设置整个事务的开发和完成时间。
开发TransactionBeginSampler插件
TransactionBeginSampler非常简单,要点是不需要产生任何sample,只需要将开始时间保存到全局的startTimeMap中,参考代码如下:public class TransactionBeginSampler extends AbstractSampler{ public final static String TRANS_NAME = "trans_name"; public static Map<String, Long> startTimeMap = new HashMap<String, Long>(); @Override public SampleResult sample(Entry entry) { String name = this.getProperty(TRANS_NAME).toString().trim(); long startTime = System.currentTimeMillis(); String key = name + this.getThreadName(); startTimeMap.put(key, startTime); return null; } }
GUI参考代码如下:
public class TransactionBeginSamplerGUI extends AbstractSamplerGui{ private JTextField nameTextField = null; public TransactionBeginSamplerGUI(){ init(); } @Override public void configure(TestElement element) { super.configure(element); nameTextField.setText(element.getPropertyAsString(TransactionBeginSampler.TRANS_NAME)); } private void init() { JPanel mainPanel = new JPanel(new GridBagLayout()); nameTextField = new JTextField(20); mainPanel.add(nameTextField); add(mainPanel); } @Override public TestElement createTestElement() { // TODO Auto-generated method stub TestElement sampler = new TransactionBeginSampler(); sampler.setName(nameTextField.getText()); modifyTestElement(sampler); return sampler; } @Override public String getLabelResource() { return this.getClass().getSimpleName(); } @Override public void modifyTestElement(TestElement sampler) { super.configureTestElement(sampler); if (sampler instanceof TransactionBeginSampler) { TransactionBeginSampler tSampler = (TransactionBeginSampler) sampler; tSampler.setProperty(TransactionBeginSampler.TRANS_NAME, nameTextField.getText()); } } @Override public String getStaticLabel() {//设置显示名称 return JMeterPluginUtils.prefixLabel("TransactionBegin"); } private void initFields(){ nameTextField.setText(""); } @Override public void clearGui() { super.clearGui(); initFields(); } }
开发TransactionEndSampler插件
TransactionBeginSampler通过事务名称从startTimeMap中取得事务开始时间,实例化一个TransactionSampleResult对象并通过sample返回,参考代码如下:public class TransactionEndSampler extends AbstractSampler { public final static String TRANS_NAME = "trans_name"; @Override public SampleResult sample(Entry entry) { String name = this.getProperty(TRANS_NAME).toString().trim(); String key = name + this.getThreadName(); long startTime = TransactionBeginSampler.startTimeMap.get(key); TransactionSampleResult res = new TransactionSampleResult(startTime); res.setName(name); res.setSuccessful(true); return res; } }
GUI参考代码如下:
public class TransactionEndSamplerGUI extends AbstractSamplerGui{ private JTextField nameTextField = null; public TransactionEndSamplerGUI(){ init(); } @Override public void configure(TestElement element) { super.configure(element); nameTextField.setText(element.getPropertyAsString(TransactionEndSampler.TRANS_NAME)); } private void init() { JPanel mainPanel = new JPanel(new GridBagLayout()); nameTextField = new JTextField(20); mainPanel.add(nameTextField); add(mainPanel); } @Override public TestElement createTestElement() { TestElement sampler = new TransactionEndSampler(); sampler.setName(nameTextField.getText()); modifyTestElement(sampler); return sampler; } @Override public String getLabelResource() { return this.getClass().getSimpleName(); } @Override public void modifyTestElement(TestElement sampler) { super.configureTestElement(sampler); if (sampler instanceof TransactionEndSampler) { TransactionEndSampler tSampler = (TransactionEndSampler) sampler; tSampler.setProperty(TransactionEndSampler.TRANS_NAME, nameTextField.getText()); } } @Override public String getStaticLabel() {//设置显示名称 return JMeterPluginUtils.prefixLabel("TransactionEnd"); } private void initFields(){ nameTextField.setText(""); } @Override public void clearGui() { super.clearGui(); initFields(); } }
开发TransactionResultReporter插件
TransactionResultReporter插件为了在GUI层通过表格形式显示统计结果,需要构建一个结果数据结构和一个结果统计计算器,并且利用firePropertyChange这一奇技淫巧完成实时绘制,参考代码如下:public class TransactionResult { private long min; private long max; private long time90; private long avg; private long last; public long getMin() { return min; } public void setMin(long min) { this.min = min; } public long getMax() { return max; } public void setMax(long max) { this.max = max; } public long getTime90() { return time90; } public void setTime90(long time90) { this.time90 = time90; } public long getAvg() { return avg; } public void setAvg(long avg) { this.avg = avg; } public long getLast() { return last; } public void setLast(long last) { this.last = last; } }
public class TransactionTimeComputer { private PropertyChangeSupport pcs = new PropertyChangeSupport(this); private Map<String, Vector<Long>> result = new HashMap<String, Vector<Long>>(); public Map<String, Vector<Long>> getResult() { return result; } public void reset(){ result.clear(); } public TransactionTimeResult compute(String transName){ TransactionTimeResult ttr = new TransactionTimeResult(); Vector<Long> times = result.get(transName); ttr.setLast(times.get(times.size() - 1)); ttr.setAvg(sum(times) / times.size()); Object[] sort = times.toArray(); Arrays.sort(sort); ttr.setMin(Long.valueOf(sort[0].toString())); ttr.setMax(Long.valueOf(sort[sort.length - 1].toString())); ttr.setTime90(Long.valueOf(sort[(int)Math.floor(sort.length * 0.9)].toString())); return ttr; } private long sum(Vector<Long> times){ long value = 0; for(long time : times){ value += time; } return value; } public void setResult(Map<String, Vector<Long>> result) { pcs.firePropertyChange("result", null, result); } public void addPropertyChangeListener(PropertyChangeListener pcl) { pcs.addPropertyChangeListener(pcl); } public void removePropertyChangeListener(PropertyChangeListener pcl) { pcs.removePropertyChangeListener(pcl); } }
接下来就非常简单了,创建TransactionResultCollector对结果进行采集,在isSampleWanted中甄别是否为TransactionSampleResult,参考代码如下:
public class TransactionResultCollector extends AbstractListenerElement implements SampleListener, Clearable, Serializable, TestStateListener, Remoteable, NoThreadClone { private TransactionTimeComputer ttc = new TransactionTimeComputer(); public TransactionResultCollector(TransactionTimeComputer ttc){ super(); this.ttc = ttc; } @Override public TransactionResultCollector clone(){ TransactionResultCollector collector = new TransactionResultCollector(ttc); collector.ttc = ttc; return collector; } public boolean isSampleWanted(SampleResult result){ if((result instanceof TransactionSampleResult)){ return true; } return false; } @Override public void testEnded() { TransactionBeginSampler.startTimeMap.clear(); ttc.reset(); } @Override public void testEnded(String host) { TransactionBeginSampler.startTimeMap.clear(); ttc.reset(); } @Override public void testStarted() { TransactionBeginSampler.startTimeMap.clear(); ttc.reset(); } @Override public void testStarted(String host) { TransactionBeginSampler.startTimeMap.clear(); ttc.reset(); } @Override public void clearData() { } @Override public void sampleOccurred(SampleEvent event) { SampleResult result = event.getResult(); if(isSampleWanted(result)){ TransactionSampleResult tsr = (TransactionSampleResult)result; long during = tsr.getEndTime() - tsr.getStartTime(); Map<String, Vector<Long>> map = ttc.getResult(); if(map.get(tsr.getName()) != null){ map.get(tsr.getName()).add(during); } else { Vector<Long> v = new Vector<Long>(); v.add(during); map.put(tsr.getName(), v); } ttc.setResult(map); } } @Override public void sampleStarted(SampleEvent event) { } @Override public void sampleStopped(SampleEvent event) { } }
GUI层利用Table的形式对统计结果进行输出,代码参考如下:
public class TransactionResultReporter extends AbstractListenerGui implements Clearable, PropertyChangeListener, TestStateListener{ private TransactionTimeComputer ttc = new TransactionTimeComputer(); private final String[] COLUMNS = new String[] { "事务名称", "平均响应时间", "90%响应时间","最小响应时间","最大响应时间","最后一次响应时间"}; private Object[][] rows = new Object[][] {{"N/A","N/A","N/A","N/A","N/A","N/A"}}; private DefaultTableModel model = new DefaultTableModel(rows, COLUMNS); private JTable table = new JTable(model); private int num = 1; public TransactionResultReporter(){ super(); init(); } private void init() { this.setLayout(new BorderLayout()); JPanel mainPanel = new JPanel(); DefaultTableCellRenderer renderer = new DefaultTableCellRenderer(); table.setDefaultRenderer(Object.class, renderer); table.setAutoscrolls(true); JScrollPane pane = new JScrollPane(); pane.setPreferredSize(new Dimension(500, 50)); pane.setViewportView(table); mainPanel.add(pane); add(mainPanel); } @Override public void clearData() { model = new DefaultTableModel(rows, COLUMNS); table = new JTable(model); init(); num = 1; } @Override public TestElement createTestElement() { ttc.addPropertyChangeListener(this); TransactionResultCollector collector = new TransactionResultCollector(ttc); modifyTestElement(collector); return collector; } @Override public String getLabelResource() { return this.getClass().getSimpleName(); } @Override public String getStaticLabel() {//设置显示名称 return JMeterPluginUtils.prefixLabel("TransactionReport"); } @Override public void modifyTestElement(TestElement element) { super.configureTestElement(element); } @Override public void configure(TestElement element) { super.configure(element); } class TimeUpdate implements Runnable { @Override public void run() { int i = 0; Set<Entry<String, Vector<Long>>> set = ttc.getResult().entrySet(); if(num == set.size()){ num++; Object[][] rows = new Object[set.size()][6]; for(Entry<String, Vector<Long>> entry : set){ TransactionTimeResult ttr = ttc.compute(entry.getKey()); rows[i][0] = entry.getKey(); rows[i][1] = ttr.getAvg(); rows[i][2] = ttr.getTime90(); rows[i][3] = ttr.getMin(); rows[i][4] = ttr.getMax(); rows[i][5] = ttr.getLast(); i++; } model.setDataVector(rows, COLUMNS); } else { for(Entry<String, Vector<Long>> entry : set){ TransactionTimeResult ttr = ttc.compute(entry.getKey()); model.setValueAt(ttr.getAvg(), i, 1); model.setValueAt(ttr.getTime90(), i, 2); model.setValueAt(ttr.getMin(), i, 3); model.setValueAt(ttr.getMax(), i, 4); model.setValueAt(ttr.getLast(), i, 5); i++; } } } } @Override public void propertyChange(PropertyChangeEvent pce) { try { SwingUtilities.invokeLater(new TimeUpdate()); } catch(Exception e) { e.printStackTrace(); } } @Override public void testEnded() { num = 1; } @Override public void testEnded(String host) { num = 1; } @Override public void testStarted() { clearData(); } @Override public void testStarted(String host) { clearData(); } @Override public void clearGui() { num = 1; } }
我们定义两组事务T1,T2其间夹杂一些SleepTestSampler,运行效果如下:
相关文章推荐
- 如何为Apache JMeter开发插件(六)——监听器之Vizualizer(监视器)
- 如何为Apache JMeter开发插件(二)——第一个JMeter插件
- 如何为Apache JMeter开发插件(四)——几种类型的Sampler
- 如何为Apache JMeter开发插件(一)
- 如何为Apache JMeter开发插件(三)——冲破图片验证码的束缚
- 如何为Apache JMeter开发插件(二)—第一个JMeter插件
- 如何为Apache JMeter开发插件(一)
- 我是如何利用插件赚钱的(开发delphi6,VB 2008,Java ME等流行软件的第三方插件)
- eclipse 插件开发中如何实现刷新和重编译
- eclipse 插件开发中如何实现刷新和重编译
- 我是如何利用插件赚钱的(开发delphi6,VB 2008,Java ME等流行软件的第三方插件)
- word2007插件开发经验备忘1--如何开发word插件
- word2007插件开发经验备忘2--如何操作word
- 理解 JMeter 聚合报告(Aggregate Report)
- 如何开发一个Joomla插件 【转】
- VS 2010 : 如何开发和部署Outlook 2010插件(Add-in)
- php 网站添加视频,如何实现这种效果,自己开发,还是找视频插件商买
- (转贴的)如何开发Firefox插件
- word2007插件开发经验备忘3--如何操作文本字体
- PDF教程 - 如何开发Firefox插件