如何为Apache JMeter开发插件(五)——监听器之Report (报告)

来源:互联网 发布:刷指数软件 编辑:程序博客网 时间:2024/06/04 23:54

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,运行效果如下:
这里写图片描述

1 0