在nGrinder测试报告中使用自定义的监控数据

从nGrinder3.1.3版本开始,就能够添加自定义的监控数据并显示到最终的测试报告中。咱们能够在测试对象所在的服务器上,建立一个文件,叫“custom.data”,而后使用任意的程序或脚本,每隔必定时间将监控信息写进这个文件里面,那个在target服务器上运行的monitor就会获取到这些数据,并传送给controller保存,而后在最终的测试报告中,以图表的形式显示出来。 java

这一特性能够用来给目标服务器上的添加任意的运行数据,并最终显示到测试报告中。例如能够添加系统的I/O,java的VM状态等等。 git

这个"custom.data"文件保存的位置是: github

${ngrinder_agent}/monitor/custom.data tomcat

它的内容就是当前的监控数据,多条数据以逗号隔开,而且都在一行,最多只能5条数据,多于的数据不会被保存。例以下面所示: 服务器

315630613,1123285602,1106612131 app

有了这个文件,target服务器上运行的监控程序nGrinder monitor就会读取文件的内容,并在测试运行过程当中把他发送给controller保存。而后最终显示到测试报告中就以下图所示: 工具

image

 

须要注意的是,自定义的监控数据的图表名字是 “CUSTOM MONITOR DATA 1”, “CUSTOM MONITOR DATA 2” .., 直到“CUSTOM MONITOR DATA 5”。并且最多只能有5条数据,因此也最多有5个自定义监控数据的图。因为这个图的名字很不直观,可是又没法自定义,用户能够把这些字段的意义做为测试的注释(comment)保存在这个测试的属性里面。 测试

接下来,咱们就须要利用一下工具来获取并生成监控数据。咱们须要定时的获取系统的某一个属性并保存在文件中。 ui

说到这里,可能不少人就会想到用Linux的cron,例如建立一个脚本用来获取监控数据并保存的文件中,而后用cron定时的调用。可是,cron最低只能设置每分钟执行,可是,nGrinder的监控数据基本都是每秒钟获取一次。 url

因此,在这个例子中,我要使用java来实现。例如,我要作的是获取Tomcat的GC执行状况,用Java JMX来链接Tomcat进程,用获取GC的执行状况,并保存在文件中。

要使用JMX链接本地服务器上的其它进程,通常状况下,须要那个Java进程启动了JMX服务,可是,通常状况下,咱们使用Tomcat是不启动这个服务的。那咱们要怎么才能使用JMX链接呢?Attach API。在本地服务器上,咱们可使用attach API来绑定到目标Java进程,而后启动目标进程上的“management agent”。这样就可使用JMX链接到了。

有关使用attach API和JMX链接到其余Java进程的程序,能够参考这个。有一点须要特别说明的是,咱们是使用JMX对象名来获取远程进程的属性,因此咱们须要知道GC的名称来获取。可是,在不一样的Java版本已经不一样的GC配置下,GC的名字也是不同的,因此,在这个例子中,我先获取了一下本地JVM的GC名称,而后经过这个名字来获取目标进程中GC属性。这就要求,咱们容许这个代码的Java环境和运行目标java进程的java环境必须同样,而后使用的VM参数也必须同样。

在这个代码中,我整理了sun和bea的JVM的GC名称,以及它们对应的minor GC或者full GC。并且对于其余的JVM例如IBM的就没有,若是大家须要其余的,请参考相关文档本身添加。

下面,咱们就仿照这个事例,来编写一个类,来每隔一秒钟,获取一次目标java进程的GC信息,并写到文件中。其代码以下:

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;

/**
 * Class description.
 * 
 * @author Mavlarn
 */
public class GCMonitor {

    public static Set<String> youngGCNames = new HashSet<String>();
    public static Set<String> oldGCNames = new HashSet<String>();

    static {
        // Oracle (Sun) HotSpot
        youngGCNames.add("Copy"); // -XX:+UseSerialGC
        youngGCNames.add("ParNew"); // -XX:+UseParNewGC
        youngGCNames.add("PS Scavenge"); // -XX:+UseParallelGC

        // Oracle (BEA) JRockit
        youngGCNames.add("Garbage collection optimized for short pausetimes Young Collector"); // -XgcPrio:pausetime
        youngGCNames.add("Garbage collection optimized for throughput Young Collector"); // -XgcPrio:throughput
        youngGCNames.add("Garbage collection optimized for deterministic pausetimes Young Collector"); // -XgcPrio:deterministic

        // Oracle (Sun) HotSpot
        oldGCNames.add("MarkSweepCompact"); // -XX:+UseSerialGC
        oldGCNames.add("PS MarkSweep"); // -XX:+UseParallelGC and
                                        // (-XX:+UseParallelOldGC or -XX:+UseParallelOldGCCompacting)
        oldGCNames.add("ConcurrentMarkSweep"); // -XX:+UseConcMarkSweepGC

        // Oracle (BEA) JRockit
        oldGCNames.add("Garbage collection optimized for short pausetimes Old Collector"); // -XgcPrio:pausetime
        oldGCNames.add("Garbage collection optimized for throughput Old Collector"); // -XgcPrio:throughput
        oldGCNames.add("Garbage collection optimized for deterministic pausetimes Old Collector"); // -XgcPrio:deterministic
    }

    static final String CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress";

    public static void main(String[] args) throws InterruptedException {
        if (args == null || args.length == 0) {
            System.err.println("Please specify the target PID to attach.");
            return;
        }

        // attach to the target application
        VirtualMachine vm;
        try {
            vm = VirtualMachine.attach(args[0]);
        } catch (AttachNotSupportedException e) {
            System.err.println("Target application doesn't support attach API.");
            e.printStackTrace();
            return;
        } catch (IOException e) {
            System.err.println("Error during attaching to target application.");
            e.printStackTrace();
            return;
        }

        try {
            // get the connector address
            String connectorAddress = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS);
            MBeanServerConnection serverConn;
            // no connector address, so we start the JMX agent
            if (connectorAddress == null) {
                String agent = vm.getSystemProperties().getProperty("java.home") + File.separator + "lib"
                        + File.separator + "management-agent.jar";
                vm.loadAgent(agent);
                // agent is started, get the connector address
                connectorAddress = vm.getAgentProperties().getProperty(CONNECTOR_ADDRESS);
            }

            // establish connection to connector server
            JMXServiceURL url = new JMXServiceURL(connectorAddress);
            JMXConnector connector = JMXConnectorFactory.connect(url);
            serverConn = connector.getMBeanServerConnection();
            ObjectName objName = new ObjectName(ManagementFactory.RUNTIME_MXBEAN_NAME);

            // Get standard attribute "VmVendor"
            String vendor = (String) serverConn.getAttribute(objName, "VmVendor");
            System.out.println("vendor:" + vendor);

            String[] gcNames = getGCNames();
            while(true) {
                long minorGCCount = 0;
                long minorGCTime = 0;
                long fullGCCount = 0;
                long fullGCTime = 0;
                
                for (String currName : gcNames) {
                    objName = new ObjectName("java.lang:type=GarbageCollector,name=" + currName);
                    Long collectionCount = (Long) serverConn.getAttribute(objName, "CollectionCount");
                    Long collectionTime = (Long) serverConn.getAttribute(objName, "CollectionTime");
                    if (youngGCNames.contains(currName)) {
                        minorGCCount = collectionCount;
                        minorGCTime = collectionTime;
                    } else if (oldGCNames.contains(currName)) {
                        fullGCCount = collectionCount;
                        fullGCTime = collectionTime;
                    }
                    StringBuilder sb = new StringBuilder("[");
                    sb.append(getGCType(currName)).append("\t: ");
                    sb.append("Count=" + collectionCount);
                    sb.append(" \tGCTime=" + collectionTime);
                    sb.append("]");
                    System.out.println(sb.toString());
                }
                StringBuilder valueStr = new StringBuilder();
                //custom data format is:
                //minorGCCount,minorGCTime,fullGCCount,fullGCTime
                valueStr.append(minorGCCount);
                valueStr.append(",");
                valueStr.append(minorGCTime);
                valueStr.append(",");
                valueStr.append(fullGCCount);
                valueStr.append(",");
                valueStr.append(fullGCTime);
                writeToFile(valueStr.toString());
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static String getGCType(String name) {
        if (youngGCNames.contains(name)) {
            return "Minor GC";
        } else if (oldGCNames.contains(name)) {
            return "Full GC";
        } else {
            return name;
        }
    }

    public static String[] getGCNames() {
        List<GarbageCollectorMXBean> gcmbeans = ManagementFactory.getGarbageCollectorMXBeans();
        String[] rtnName = new String[gcmbeans.size()];
        int index = 0;
        for (GarbageCollectorMXBean gc : gcmbeans) {
            rtnName[index] = gc.getName();
            index++;
        }
        return rtnName;
    }
    
    public static void writeToFile(String gcData) {
        String currDir = System.getProperty("user.dir");
        BufferedWriter writer = null;
        
        try {
            File customFile = new File(currDir + File.separator + "custom.data");
            if (!customFile.exists()) {
                customFile.createNewFile();
            }
            writer = new BufferedWriter(new FileWriter(customFile));
            writer.write(gcData);
            writer.flush();
        } catch (IOException e) {
            System.err.println("Error to read custom monitor data:" + e.getMessage());
        } finally {
            if (writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

有关这个代码,有几个须要注意的:

a) 咱们须要知道目标进程的ID,并把它做为运行参数。

b) 运行这个java程序的环境必须和目标Tomcat服务器的java环境一致,例如"-server"和其余VM的配置必须同样。

c) 自定义的监控数据的格式是“minorGCCount,minorGCTime,fullGCCount,fullGCTime”.

d) 运行这个java程序时,必须在 “${ngrinder_agent}/monitor/” 目录中,由于在代码中,我将在当前目录中建立和更新custom.data文件。

e) 编译这段代码须要JDK的 “tools.jar”。你须要用相似下面的方式来编译和运行:

javac -cp/home/ngrinder/jdk1.6.0_38/lib/tools.jar GCMonitor.java 
  
#get target tomcat process ID, it is 24003 
java -cp/home/ngrinder/jdk1.6.0_38/lib/tools.jar: GCMonitor  24003
 

运行之后,应该在控制台看到相似下面的结果:

current dir:/home/ngrinder/.ngrinder_agent/monitor 
[Minor GC       : Count=3564    GCTime=27850] 
[Full GC        : Count=166     GCTime=65525] 
[Minor GC       : Count=3564    GCTime=27850] 
[Full GC        : Count=166     GCTime=65525]
 

 

而后在当前目录中会生成custom.data文件,其内容是:

3564,27850,166,65525

而后,建立一个测试,在这个测试的属性中,设置合适的target服务器,而后运行,当运行完成后,就能够在测试报告中的target monitor里面,看到这些监控数据。

image

(由于这个例子中的GC不是很频繁,因此看到的基本上就是一条直线。)

 

使用这样的方式,咱们就能够在咱们的测试结果中添加任意的监控数据,来帮助咱们对target服务器上的某些运行状态有一个更好的展现。并保存便于之后查看。

相关文章
相关标签/搜索