從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保存。而後最終顯示到測試報告中就以下圖所示: 工具
須要注意的是,自定義的監控數據的圖表名字是 「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裏面,看到這些監控數據。
(由於這個例子中的GC不是很頻繁,因此看到的基本上就是一條直線。)
使用這樣的方式,咱們就能夠在咱們的測試結果中添加任意的監控數據,來幫助咱們對target服務器上的某些運行狀態有一個更好的展現。並保存便於之後查看。