您的位置:首页 > 运维架构 > Apache

如何为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,运行效果如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: