GreysJava線上問題診斷工具

oldmanpushcart發表於2016-01-06
greys-logo-readme.png

線上系統為何經常出錯?資料庫為何屢遭黑手?業務呼叫為何頻頻失敗?連環異常堆疊案,究竟是那次呼叫所為?
數百臺伺服器意外雪崩背後又隱藏著什麼?是軟體的扭曲還是硬體的淪喪?
走進科學帶你瞭解Greys, Java線上問題診斷工具。

main-pic

Greys的誕生

greys-logo.png

很早的時候,我們使用BTrace排查問題,在感嘆BTrace的強大之餘,也曾好幾次將線上系統折騰掛掉。2012年淘寶的聚石寫了HouseMD,將常用的幾個Btrace指令碼整合在一起形成一個獨立風格的應用,但其核心程式碼用的是Scala,我們沒這方面的程式設計維護經驗,所以只好豔羨HouseMD的才思敏捷而無法在其上增加功能。

於是乎,Greys誕生了!

PS:目前Greys僅支援Linux/Unix/Mac上的Java6+,Windows暫時無法支援

Greys是一個JVM程式執行過程中的異常診斷工具。 在不中斷程式執行的情況下輕鬆完成JVM相關問題排查工作。

和HouseMD一樣,Greys-Anatomy取名同名美劇“實習醫生格蕾”,目的是向前輩致敬。程式碼編寫的時候參考了BTrace和HouseMD兩個前輩的思路。

目標群體

  • 有時候突然一個問題反饋上來,需要入參才能完成定位,但恰恰沒有任何日誌。回去加上重新部署,一杯咖啡時間過去了,是不是很崩潰?

  • 當你經過反覆這樣幾次折騰之後變得聰明瞭,在自己的程式碼的所有入參和出參地方都加上debug日誌,但這次問題似乎暴露在別人的程式碼中了…是不是很無奈?

  • 突然遇到線上一個效能問題無法確定到底是哪個環節的耗時,只能反覆抓jstack猜,還有沒有辦法可以好好的過日子啦?

遇到以上問題時,你就是我們這類工具的目標客戶,此類工具能利用Java6的Instrumentation特性,動態增強你所指定的類,獲取你想要到的資訊。

軟體特點

  • ClassLoader隔離

    在設計和實現這款程式的時候,花費了非常多的精力在隔離目標類與Greys的ClassLoader隔離上。你可以放心大膽的使用Greys,而不用擔心Greys會干擾到現有業務程式碼所使用的三方類庫。

  • 執行時載入

    要求目標JVM在JDK6+的基礎上,且當前執行人擁有與目標JVM相同許可權。可以做到不中斷當前JVM而動態進行載入、問題分析定位。

  • 常用問題定位命令化

    Greys與BTrace、HouseMD等同類軟體最大的不同在於,她擁有我多年來業務程式碼疑難雜症定位的常用技術手段,並將這些排查思路和技巧命令化,將我的問題定位經驗Share給大家。

  • 表示式支援

    HouseMD相比BTrace最強大的地方就在於能快速指定攔截的類與方法,但卻無法支援對觀察到的物件進行展開、條件過濾等操作。BTrace的指令碼是自己所編寫,可以實現此類功能。但編寫學習成本很高,且容易出錯。

    表示式的引用能綜合這兩款軟體的特長同時彌補他們的不足。目前Greys所採用的是OGNL表示式。

  • 多使用者同時訪問

    遠端DEBUG最大的問題就在於,只允許一個人訪問DEBUG埠,而且一單斷點條件設定不當,很有可能將其他正常的業務請求攔下,影響其他使用者的使用。

    Greys採用的思路是做觀察者,其所設定的斷點不允許阻塞正常業務的流程,但你可以觀察到斷點所攔截到的所有資訊。

  • 高效能

    精心用ASM設計了位元組碼增強,核心的資料結構用陣列針對實際場景做裁剪優化。可以放心的用在高負載有求下的JVM環境。

  • 純Java編寫

    Greys定位是專業的JVM的業務問題定位工具,既然是JVM那我們所面對的大部分就是Java程式設計師。我希望能Share自己在編寫軟體時候的所有技巧與思路,讓更多的Java程式設計師能參與開發或從中受益。

    目前已經有非常多的熱心網友在給我的程式碼挑錯,非常感謝這些朋友的支援!

不適合的場景

Greys並不萬能,我也沒有計劃讓她能成為萬能的問題定位工具。所以如果你在某些場合能用上更專業的工具,我會非常樂意推薦你使用。

  • 效能環境下的效能損耗定位

    效能分析需要有更專業的軟體,我自己常用的則是JProfiler(當然是付費的了)。Greys雖然效能損耗很小,但其分析的維度太少,所以只適合做簡單的效能損耗定位。當你用過專業的效能分析軟體之後,就會發現什麼叫專業!

  • 開發環境下的遠端DEBUG

    雖然Greys能取代部分的遠端DEBUG行為,但畢竟沒不像DEBUG工具那樣可以看到區域性變數的值,而且可操作性上也沒有JVM下Eclipse/IDEA等優秀的IDE自帶的DEBUG工具這麼人性化操作。

  • 線上環境大規模部署

    與BTrace一樣,Greys獲取到的許可權太高,如果線上大規模部署會遭受黑客的攻擊,而今天我為了實現簡單是沒有做過多的鑑權控制。

  • JDK類庫分析

    JDK的類庫存放在rt.jar中,啟動時載入到BootstrapClassLoader中(Hotspot-JVM),但由於Greys也是用Java語言所編寫,所以自身也用到了這些基礎類庫,預設情況下關閉了對這些類的增強。

    當然,對於Spring、ibatis、Tomcat等三方類庫是可以放心大膽使用的。

  • 其它不適合場景

    BTrace、HouseMD、Greys、JavOSize此類工具都會對Perm區、CodeCache(影響JIT)產生干擾,如果你的程式對這兩塊非常敏感,也請不要在這些場合下使用。

我們的座右銘

讓程式解決繁瑣的事情

特性功能

互動方式

  • 命令列互動

    因為很多場景下我們都是用在遠端問題分析中(本地我就直接DEBUG了)。一般Java都會使用在Linux/BSD等類UNIX作業系統下。所以命令列是我最開始不二的選擇,也是目前支援最成熟的互動方案。

  • 圖形介面互動

    2.x.x.x版本中將會支援WEB方式訪問,HTTP採用websocket與後臺服務進行互動,預計過年之後能釋出上線。

內建主要功能

  • 檢視已被JVM所載入的類、方法資訊

  • 方法執行監控

    呼叫量,成功失敗率,響應時間

  • 方法執行資料操作

    入參、返回值、異常資訊記錄與檢視;支援動作回放

  • 效能開銷渲染

    跟蹤指定路徑中的方法呼叫軌跡、耗時

  • 檢視方法呼叫堆疊

軟體特點

  • 純Java實現的開源專案
  • 安裝使用便捷,僅一個jar包
  • 可無需重啟JVM進行CT式診斷
  • 觀察變數的出入參
  • OGNL表示式展開變數、過濾條件,方便你檢視入參、出參、異常、當前物件的各種屬性細節
  • 常用分析命令整合,monitortrace
  • 時間隧道,tt命令能以時間維度紀錄下監控期內的每一次呼叫環境
  • 多人並行協作

    基於C/S架構的任務模式甚至能讓多人同時遠端到同一程式上執行不同的指令、指令碼,非常適合團隊一起進行線上問題排查與跟蹤。Greys採用純Java編寫並留有良好的擴充套件,如果你有需求,只要你會Java,就可以為你自己編寫想要的功能。 Greys最有利的武器是他的表示式,能讓你在感受到HouseMD整合功能便利的同時,也能發揮出自定義Btrace指令碼的靈活。

    多人協作

1. 應用管理員擁有JVM程式許可權,由他來首先在目標JVM上啟動Greys
2. 技術專家A和B平時沒有對應機器的許可權,但只要網路能訪問,他們可以通過指定ip:port直接訪問目標機器的JVM程式,彷彿在本地一般

Greys入門

軟體安裝

Greys支援線上安裝和本地安裝兩種安裝方案,安裝即可用,推薦使用線上安裝。

  • 線上安裝(推薦)

    請複製以下內容,並貼上到命令列中。

    curl -sLk http://ompc.oss.aliyuncs.com/greys/install.sh|sh
    

    命令將會下載的啟動指令碼檔案greys.sh到當前目錄,你可以放在任何地方或加入到$PATH

  • 本地安裝

    在某些情況下,目標伺服器無法訪問遠端阿里雲主機,此時你需要自行下載greys的安裝檔案。

  1. 下載最新版本的GREYS

    http://ompc.oss.aliyuncs.com/greys/release/greys-VERSION-bin.zip

    最新的***VERSION***版本請參考主頁資訊

  2. 解壓zip檔案後,執行以下命令

     cd greys
     sh ./install-local.sh
    

    即完成本地安裝。

常見安裝問題

  • 下載失敗

    通常這樣的原因你需要檢查你的網路是否暢通,核對是否能正確訪問這個網址

    http://ompc.oss.aliyuncs.com/greys/greys.sh

    downloading...
    download failed!
    
  • 沒有許可權

    安裝指令碼首先會將greys檔案從阿里雲伺服器上下載到當前執行指令碼的目錄,所以你必須要擁有當前目錄的寫許可權。

    permission denied, target directory is not writable.
    

啟動Greys

  • 引數說明

    ./greys.sh <PID>[@IP:PORT]
    
    • PID:目標Java程式ID(請確保執行當前執行命令的使用者必須有足夠的許可權操作對應的Java程式)
    • IP:目標伺服器IP地址,當遠端服務開啟之後,其他人可以通過指定IP的形式載入到對應目標機器的Java程式中,從而實現遠端協助。專門用於解決目標主機賬號沒有許可權,但對方兄弟卻非常需要你支援的時候。Greys允許多個使用者同時訪問,並且各自的命令不會相互干擾執行。
    • PORT:目標伺服器埠號,設計埠號的初心則是希望解決同臺機器上存在多個Java程式需要被Greys分析的情況,預設的埠號是3658,如果不做區分則會引起埠衝突。
  • 啟動範例

    • 如果不指定**IP**和**PORT**,預設是**127.0.0.1**和**3658**

      ./greys.sh 12345
      

      等價於

      ./greys.sh 12345@127.0.0.1
      

      等價於

      ./greys.sh 12356@127.0.0.1:3658
      
  • sudo支援

    成熟的線上管理環境一般都不會直接開放JVM部署使用者許可權給你,而是通過sudo-list來控制和監控使用者的越權操作。由於greys.sh指令碼中會對當前使用者的環境變數產生感知,所以需要加上-H引數

    sudo -u admin -H ./greys.sh 12345
    
  • TELNET的支援

    Greys支援通過telnet來訪問服務端,如果當你手頭的機器沒有安裝Greys的客戶端,你可以簡單的通過telnet命令來進行訪問。

    telnet 10.232.12.113 3658
    

    當然了,telnet命令和Greys自帶的Console在使用友好度上還是有一定的差距,不過解決應急之需沒有問題。

會話與任務

Greys是一個C/S架構的程式,所以當Client訪問到Server時,Server會維護一個session(會話),以及session的心跳、超時機制。事務(Tx)機制則是建立在session的基礎上,所有的命令互動都會建立一個事務,並且產生對應的佇列進行輸出緩衝。

事務伴隨著命令的生命週期而存在,命令分兩種:

  • 立即返回

    立即返回的命令定義是:敲下命令後Server端立即返回最終結果,後續無持續反饋資訊,釋放Client對輸入的鎖定,重新開放讓使用者輸入資訊,比如versionscsm等。

  • 等待中止

    等待中止的命令則是需要使用者主動輸入Ctrl+D完成的命令中止操作。命令執行後無法立即返回最終結果,而是不斷的將中間產生的輸出源源不斷的輸出到客戶端中,這種命令比如stackmonitor等。

    當session關閉時,所有掛在session的事務也會立即被關閉。

表示式

Greys相對於HouseMD、BTrace而言最靈活的地方就是在用表示式來靈活的支援不同的問題排查、分析場景。

表示式分兩種:條件表示式觀察表示式

  • 條件表示式

    條件表示式用在**使用表示式表達TRUE或FALSE**的場景,從1.6.0.6版本開始,tracestackttwatch命令都增加了條件表示式支援。

    條件表示式將會使用greys內建的表示式解析引擎,識別OGNL語法。

    特別指出的是,如果你書寫了一個錯誤的條件表示式,greys為了相容錯誤會解析為FALSE。

    以下是一些條件表示式使用的例子和預測結果

    條件表示式 預測結果 解析結果說明
    1==1 TRUE 條件表示式為真
    true TRUE 條件表示式為真
    @@@ FALSE 非法的條件表示式
    params==null FALSE 條件表示式為假
    false FALSE 條件表示式為假
    1!=1 FALSE 條件表示式為假
  • 觀察表示式

    觀察表示式用在**使用表示式表達輸出內容**的場景,尤其在watchtt命令中,觀察表示式非常至關重要。

    條件表示式將會使用greys內建的表示式解析引擎,識別OGNL語法,將表示式轉換為待輸出的物件。

    以下是一些觀察表示式使用的例子

    • 字串拼接

      clazz.name+"."+method.name
      
    • 數字運算

      clazz.name.length()+method.name.length()
      

表示式核心變數

無論是匹配表示式也好、觀察表示式也罷,他們核心判斷變數都是圍繞著一個greys中的通用通知物件Advice進行。

它的簡略程式碼結構如下

public class Advice {

    private final ClassLoader loader;
    private final Class<?> clazz;
    private final GaMethod method;
    private final Object target;
    private final Object[] params;
    private final Object returnObj;
    private final Throwable throwExp;
    private final boolean isBefore;
    private final boolean isThrow;
    private final boolean isReturn;

    // getter/setter  
}  

這裡列一個表格來說明不同變數的含義

變數名 變數解釋
loader 本次呼叫類所在的ClassLoader
clazz 本次呼叫類的Class引用
method 本次呼叫方法反射引用
target 本次呼叫類的例項
params 本次呼叫引數列表,這是一個陣列,如果方法是無參方法則為空陣列
returnObj 本次呼叫返回的物件。當且僅當isReturn==true成立時候有效,表明方法呼叫是以正常返回的方式結束。如果當前方法無返回值void,則值為null
throwExp 本次呼叫丟擲的異常。當且僅當isThrow==true成立時有效,表明方法呼叫是以丟擲異常的方式結束。
isBefore 輔助判斷標記,當前的通知節點有可能是在方法一開始就通知,此時isBefore==true成立,同時isThrow==falseisReturn==false,因為在方法剛開始時,還無法確定方法呼叫將會如何結束。
isThrow 輔助判斷標記,當前的方法呼叫以拋異常的形式結束。
isReturn 輔助判斷標記,當前的方法呼叫以正常返回的形式結束。

所有變數都可以在表示式中直接使用,如果在表示式中編寫了不符合OGNL指令碼語法或者引入了不在表格中的變數,

  • 條件表示式檢索表示式而言,則一律當成false來處理

  • 觀察表示式而言,則放棄當前方法呼叫的處理(不輸出)

JDK類支援

JDK的類預設由BootstrapClassLoader負責載入,由於Greys自己也適用了大量的JDK類,所以我不建議使用Greys直接對JDK相關類進行增強、代理。

預設而言,Greys會拒絕執行關於JDK類的操作命令。你需顯式用options命令開啟。

ga?>options unsafe true
+--------+--------------+-------------+
| NAME   | BEFORE-VALUE | AFTER-VALUE |
+--------+--------------+-------------+
| unsafe | false        | true        |
+--------+--------------+-------------+
Affect(row-cnt:1) cost in 4 ms.
ga?>

模式匹配

一些命令需要對類、方法進行模式匹配過濾,從1.5.4.6及其之後的版本之後,Greys預設支援萬用字元匹配,目前僅支援*?兩個萬用字元,正規表示式需要顯式指定-E引數啟用。

  • 模式匹配舉例

    • sc命令的正規表示式匹配

      sc com.apache.commons.lang.StringUtils
      
    • 1.5.4.6及其之後的版本中將會預設使用萬用字元表示式

      sc com.apache.commons.lang.StringUtils
      sc *lang.StringUtils
      
    • 若想繼續使用正規表示式匹配,需要顯式-E引數啟用

      sc -E com.apache.commons.lang.StringUtils
      sc -E com..*StringUtils
      
  • 支援模式匹配的命令

    所有需要模式匹配的命令都支援引數-E,他們分別是:scsmstackmonitorwatchtttrace

Greys命令詳解

命令清單

命令 說明
help 檢視命令的幫助文件,每個命令和引數都有很詳細的說明
sc 檢視JVM已載入的類資訊
sm 檢視已載入的方法資訊
monitor 方法執行監控
trace 渲染方法內部呼叫路徑,並輸出方法路徑上的每個節點上耗時
ptrace 強化版的trace命令。通過指定渲染路徑,並可記錄下路徑中所有方法的入參、返值;與tt命令聯動。
watch 方法執行資料觀測
tt 方法執行資料的時空隧道,記錄下指定方法每次呼叫的入參和返回資訊,並能對這些不同的時間下呼叫進行觀測
stack 輸出當前方法被呼叫的呼叫路徑
version 輸出當前目標Java程式所載入的Greys版本號
quit 退出greys客戶端
shutdown 關閉greys服務端
reset 重置增強類,將被greys增強過的類全部還原
session 檢視當前會話
jvm 檢視當前JVM的資訊

help命令

help命令會是你第一個在Greys中使用的命令,也會是今後使用最頻繁的命令之一,當你在使用的過程中有任何不熟悉的疑問,請直接help吧~

  • 檢視命令清單

    進入Greys的歡迎介面後,所有命令都可以通過help獲取幫助。此時你直接輸入一個help,Greys則會返回所有命令的大概用途介紹。

    ga?>help
    +----------+----------------------------------------------------------------------------------+
    |       sc | Search all the classes loaded by JVM                                             |
    +----------+----------------------------------------------------------------------------------+
    |       sm | Search the method of classes loaded by JVM                                       |
    +----------+----------------------------------------------------------------------------------+
    |  monitor | Monitor the execution of specified Class and its method                          |
    +----------+----------------------------------------------------------------------------------+
    |    watch | Display the details of specified class and method                                |
    +----------+----------------------------------------------------------------------------------+
    |       tt | Time Tunnel                                                                      |
    +----------+----------------------------------------------------------------------------------+
    |    stack | Display the stack trace of specified class and method                            |
    +----------+----------------------------------------------------------------------------------+
    |   ptrace | Display the detailed thread path stack of specified class and method             |
    +----------+----------------------------------------------------------------------------------+
    |    trace | Display the detailed thread stack of specified class and method                  |
    +----------+----------------------------------------------------------------------------------+
    |  session | Display current session information                                              |
    +----------+----------------------------------------------------------------------------------+
    |     quit | Quit Greys console                                                               |
    +----------+----------------------------------------------------------------------------------+
    |  version | Display Greys version                                                            |
    +----------+----------------------------------------------------------------------------------+
    |      jvm | Display the target JVM information                                               |
    +----------+----------------------------------------------------------------------------------+
    |    reset | Reset all the enhanced classes                                                   |
    +----------+----------------------------------------------------------------------------------+
    | shutdown | Shut down Greys server and exit the console                                      |
    +----------+----------------------------------------------------------------------------------+
    |     help | Display Greys Help                                                               |
    +----------+----------------------------------------------------------------------------------+
    Affect(row-cnt:1) cost in 9 ms.
    ga?>
    

    嗯,囋囋,我知道我的英文翻譯很爛,就不用吐槽了。期望能有人能幫我重新打理英文的幫助介面和英文xwiki,小生感激不盡!

  • 檢視命令詳細幫助

    help命令同時也支援對其他命令的一個解釋說明,比如我們鍵入help watch,greys將會返回watch命令的所有引數解釋、用法介紹等詳細資訊。

    ga?>help watch
    +---------+----------------------------------------------------------------------------------+
    |   USAGE | -[bfesx:En:] class-pattern method-pattern express condition-express              |
    |         | Display the details of specified class and method                                |
    +---------+----------------------------------------------------------------------------------+
    | OPTIONS |              [b] | Watch before invocation                                       |
    |         | -----------------+-------------------------------------------------------------- |
    |         |              [f] | Watch after invocation                                        |
    |         | -----------------+-------------------------------------------------------------- |
    |         |              [e] | Watch after throw exception                                   |
    |         | -----------------+-------------------------------------------------------------- |
    |         |              [s] | Watch after successful invocation                             |
    |         | -----------------+-------------------------------------------------------------- |
    |         |             [x:] | Expand level of object (0 by default)                         |
    |         | -----------------+-------------------------------------------------------------- |
    |         |              [E] | Enable regular expression to match (wildcard matching by def  |
    |         |                  | ault)                                                         |
    |         | -----------------+-------------------------------------------------------------- |
    |         |             [n:] | Threshold of execution times                                  |
    |         | -----------------+-------------------------------------------------------------- |
    |         |    class-pattern | Path and classname of Pattern Matching                        |
    |         | -----------------+-------------------------------------------------------------- |
    |         |   method-pattern | Method of Pattern Matching                                    |
    |         | -----------------+-------------------------------------------------------------- |
    |         |          express | express, write by OGNL.                                       |
    |         |                  |                                                               |
    |         |                  | FOR EXAMPLE    params[0]                                      |
    |         |                  |     params[0]+params[1]                                       |
    |         |                  |     returnObj                                                 |
    |         |                  |     throwExp                                                  |
    |         |                  |     target                                                    |
    |         |                  |     clazz                                                     |
    |         |                  |     method                                                    |
    |         |                  |                                                               |
    |         |                  | THE STRUCTURE                                                 |
    |         |                  |           target : the object                                 |
    |         |                  |            clazz : the object`s class                         |
    |         |                  |           method : the constructor or method                  |
    |         |                  |     params[0..n] : the parameters of method                   |
    |         |                  |        returnObj : the returned object of method              |
    |         |                  |         throwExp : the throw exception of method              |
    |         |                  |         isReturn : the method ended by return                 |
    |         |                  |          isThrow : the method ended by throwing exception     |
    |         | -----------------+-------------------------------------------------------------- |
    |         |  condition-expre | Conditional expression by OGNL                                |
    |         |               ss |                                                               |
    |         |                  | FOR EXAMPLE                                                   |
    |         |                  |      TRUE : 1==1                                              |
    |         |                  |      TRUE : true                                              |
    |         |                  |     FALSE : false                                             |
    |         |                  |      TRUE : params.length>=0                                  |
    |         |                  |     FALSE : 1==2                                              |
    |         |                  |                                                               |
    |         |                  | THE STRUCTURE                                                 |
    |         |                  |           target : the object                                 |
    |         |                  |            clazz : the object`s class                         |
    |         |                  |           method : the constructor or method                  |
    |         |                  |     params[0..n] : the parameters of method                   |
    |         |                  |        returnObj : the returned object of method              |
    |         |                  |         throwExp : the throw exception of method              |
    |         |                  |         isReturn : the method ended by return                 |
    |         |                  |          isThrow : the method ended by throwing exception     |
    +---------+----------------------------------------------------------------------------------+
    | EXAMPLE | watch -Eb org.apache.commons.lang.StringUtils isBlank params[0]              |
    |         | watch -b org.apache.commons.lang.StringUtils isBlank params[0]                   |
    |         | watch -f org.apache.commons.lang.StringUtils isBlank returnObj                   |
    |         | watch -bf *StringUtils isBlank params[0]                                         |
    |         | watch *StringUtils isBlank params[0]                                             |
    |         | watch *StringUtils isBlank params[0] params[0].length==1                         |
    +---------+----------------------------------------------------------------------------------+
    Affect(row-cnt:1) cost in 15 ms.
    ga?>
    
  • 幫助資訊組成

    幫助文件分成**Useage**、**Options**、**Example**三個區域,分別是**用途說明**、**引數列表**、**實際例子**

    ga?>help session
    +---------+----------------------------------------------------------------------------------+
    |   USAGE | -[c:]                                                                            |
    |         | Display current session information                                              |
    +---------+----------------------------------------------------------------------------------+
    | OPTIONS |             [c:] | Modify the character set of session                           |
    +---------+----------------------------------------------------------------------------------+
    | EXAMPLE | session                                                                          |
    |         | session -c GBK                                                                   |
    |         | session -c UTF-8                                                                 |
    +---------+----------------------------------------------------------------------------------+
    Affect(row-cnt:1) cost in 2 ms.
    ga?>
    
  • 引數選項說明

    • []中的引數為選填項,比如[d],意思是該命令可接受一個名稱為d的選填引數,且不用引數值。
    • [:]中的引數則為選填,但有值的引數,比如[c:]
    • class-patternmethod-pattern,這兩個引數為隱性引數,即在輸入的時候不需要特意宣告引數。class-pattern類路徑.類名稱的表示式匹配,method-pattern則為方法名的表示式匹配。

sc命令

“Search-Class”的簡寫,這個命令能搜尋出所有已經載入到JVM中的Class資訊。

  • 引數說明

    引數名稱 引數說明
    class-pattern 類名錶達式匹配
    method-pattern 方法名錶達式匹配
    [d] 輸出當前類的詳細資訊,包括這個類所載入的原始檔案來源、類的宣告、載入的e2ClassLoader等詳細資訊。
    如果一個類被多個ClassLoader所載入,則會出現多次
    [f] 輸出當前類的成員變數資訊
    [E] 支援正規表示式匹配
  • 使用參考

    ga?>sc -df *alibaba.Address
    +------------------+-----------------------------------------------+
    |       class-info | com.alibaba.Address                           |
    +------------------+-----------------------------------------------+
    |      code-source | /Users/vlinux/temp/agent-test/target/         |
    +------------------+-----------------------------------------------+
    |             name | com.alibaba.Address                           |
    +------------------+-----------------------------------------------+
    |      isInterface | false                                         |
    +------------------+-----------------------------------------------+
    |     isAnnotation | false                                         |
    +------------------+-----------------------------------------------+
    |           isEnum | false                                         |
    +------------------+-----------------------------------------------+
    | isAnonymousClass | false                                         |
    +------------------+-----------------------------------------------+
    |          isArray | false                                         |
    +------------------+-----------------------------------------------+
    |     isLocalClass | false                                         |
    +------------------+-----------------------------------------------+
    |    isMemberClass | false                                         |
    +------------------+-----------------------------------------------+
    |      isPrimitive | false                                         |
    +------------------+-----------------------------------------------+
    |      isSynthetic | false                                         |
    +------------------+-----------------------------------------------+
    |      simple-name | Address                                       |
    +------------------+-----------------------------------------------+
    |         modifier | public                                        |
    +------------------+-----------------------------------------------+
    |       annotation |                                               |
    +------------------+-----------------------------------------------+
    |       interfaces |                                               |
    +------------------+-----------------------------------------------+
    |      super-class | com.alibaba.CountObject                       |
    |                  |   `-java.lang.Object                          |
    +------------------+-----------------------------------------------+
    |     class-loader | sun.misc.Launcher$AppClassLoader@2a139a55     |
    |                  |   `-sun.misc.Launcher$ExtClassLoader@5fb20bfd |
    +------------------+-----------------------------------------------+
    |           fields | modifier : private                            |
    |                  |     type : java.lang.String                   |
    |                  |     name : username                           |
    |                  |                                               |
    |                  | modifier : private                            |
    |                  |     type : int                                |
    |                  |     name : addressId                          |
    |                  |                                               |
    |                  | modifier : private                            |
    |                  |     type : java.lang.String                   |
    |                  |     name : addressName                        |
    |                  |                                               |
    +------------------+-----------------------------------------------+
    Affect(row-cnt:1) cost in 9 ms.
    ga?>
    

sm命令

“Search-Method”的簡寫,這個命令能搜尋出所有已經載入了Class資訊的方法資訊。

  • 引數說明

    引數名稱 引數說明
    class-pattern 類名錶達式匹配
    method-pattern 方法名錶達式匹配
    [d] 展示每個方法的詳細資訊
    [E] 支援正規表示式匹配
  • 使用參考

    ga?>sm -d *alibaba.Address *
    +-----------------------------+-------------------------------------------------+
    | DECLARED-CLASS              | VISIBLE-METHOD                                  |
    +-----------------------------+-------------------------------------------------+
    | com.alibaba.Address         | declaring-class : class com.alibaba.Address     |
    |                             |     method-name : getAddressId                  |
    |                             |        modifier : public                        |
    |                             |      annotation :                               |
    |                             |      parameters :                               |
    |                             |          return : int                           |
    |                             |      exceptions :                               |
    +-----------------------------+-------------------------------------------------+
    | com.alibaba.Address         | declaring-class : class com.alibaba.Address     |
    |                             |     method-name : getAddressName                |
    |                             |        modifier : public                        |
    |                             |      annotation :                               |
    |                             |      parameters :                               |
    |                             |          return : java.lang.String              |
    |                             |      exceptions :                               |
    +-----------------------------+-------------------------------------------------+
    | com.alibaba.Address         | declaring-class : class com.alibaba.CountObject |
    |   `-com.alibaba.CountObject |     method-name : finalize                      |
    |                             |        modifier : protected                     |
    |                             |      annotation :                               |
    |                             |      parameters :                               |
    |                             |          return : void                          |
    |                             |      exceptions : java.lang.Throwable           |
    +-----------------------------+-------------------------------------------------+
    | com.alibaba.Address         | declaring-class : class com.alibaba.CountObject |
    |   `-com.alibaba.CountObject |     method-name : count                         |
    |                             |        modifier : public                        |
    |                             |      annotation :                               |
    |                             |      parameters :                               |
    |                             |          return : int                           |
    |                             |      exceptions :                               |
    +-----------------------------+-------------------------------------------------+
    Affect(row-cnt:4) cost in 9 ms.
    ga?>
    

monitor命令

對匹配class-patternmethod-pattern的類.方法的呼叫進行監控。

monitor命令是介紹到的第一個非實時返回命令,實時返回命令是輸入之後立即返回,而非實時返回的命令,則是不斷的等待目標Java程式返回資訊,直到使用者輸入Ctrl+D為止。服務端是以任務的形式在後臺跑任務,植入的程式碼隨著任務的中止而被不會被執行,所以任務關閉後,不會對原有效能產生太大影響,而且原則上,任何Greys的命令也不會引起任何原有業務邏輯的改變。

  • 監控的維度說明

    監控項 說明
    timestamp 時間戳
    class Java類
    method 方法(構造方法、普通方法)
    total 呼叫次數
    success 成功次數
    fail 失敗次數
    rt 平均RT
    fail-rate 失敗率
  • 引數說明

    方法擁有一個命名引數[c:],意思是統計週期(cycle of output),擁有一個整形的引數值

    引數名稱 引數說明
    [c:] 統計週期,預設值為120秒
  • 使用參考

    ga?>monitor -c 5 *alibaba*Test printAddress
    Press Ctrl+D to abort.
    Affect(class-cnt:1 , method-cnt:1) cost in 22 ms.
    +-----------+-------+--------+-------+---------+------+-----------+------------+------------+------------+
    | TIMESTAMP | CLASS | METHOD | TOTAL | SUCCESS | FAIL | FAIL-RATE | AVG-RT(ms) | MIN-RT(ms) | MAX-RT(ms) |
    +-----------+-------+--------+-------+---------+------+-----------+------------+------------+------------+
    
    +---------------------+-----------------------+--------------+-------+---------+------+-----------+------------+------------+------------+
    | TIMESTAMP           | CLASS                 | METHOD       | TOTAL | SUCCESS | FAIL | FAIL-RATE | AVG-RT(ms) | MIN-RT(ms) | MAX-RT(ms) |
    +---------------------+-----------------------+--------------+-------+---------+------+-----------+------------+------------+------------+
    | 2015-12-06 16:34:56 | com.alibaba.AgentTest | printAddress | 5     | 3       | 2    | 40.00%    | 0.20       | 0          | 1          |
    +---------------------+-----------------------+--------------+-------+---------+------+-----------+------------+------------+------------+
    
    +---------------------+-----------------------+--------------+-------+---------+------+-----------+------------+------------+------------+
    | TIMESTAMP           | CLASS                 | METHOD       | TOTAL | SUCCESS | FAIL | FAIL-RATE | AVG-RT(ms) | MIN-RT(ms) | MAX-RT(ms) |
    +---------------------+-----------------------+--------------+-------+---------+------+-----------+------------+------------+------------+
    | 2015-12-06 16:35:01 | com.alibaba.AgentTest | printAddress | 5     | 3       | 2    | 40.00%    | 0.00       | 0          | 0          |
    +---------------------+-----------------------+--------------+-------+---------+------+-----------+------------+------------+------------+
    
    ga?>
    
    

trace命令

命令能主動搜尋class-patternmethod-pattern所渲染的方法呼叫路徑,渲染和統計整個呼叫鏈路上的所有效能開銷和追蹤呼叫鏈路。

  • 引數說明

    引數名稱 引數說明
    class-pattern 類名錶達式匹配
    method-pattern 方法名錶達式匹配
    condition-express 條件表示式
    [n:] 命令執行次數
    [E] 支援正規表示式匹配
  • 注意事項

    trace能方便的幫助你定位和發現因RT高而導致的效能問題缺陷,但其每次只能跟蹤一級方法的呼叫鏈路,目前暫時沒有精力去解決往下幾個層級的呼叫。如果真有需求可以Issues我。

  • 使用參考

    ga?>trace com.alibaba.manager.DefaultAddressManager toStringPass2
    Press Ctrl+D to abort.
    Affect(class-cnt:1 , method-cnt:1) cost in 19 ms.
    `---+Tracing for : thread_name="agent-test-address-printer" thread_id=0xb;is_daemon=false;priority=5;
    `---+[0,0ms]com.alibaba.manager.DefaultAddressManager:toStringPass2()
        +---[0,0ms]com.alibaba.Address:getAddressId()
        +---[0,0ms]com.alibaba.Address:getAddressId()
        +---[0,0ms]java.lang.Integer:valueOf()
        +---[0,0ms]com.alibaba.Address:getAddressName()
        +---[0,0ms]com.alibaba.Address:count()
        +---[0,0ms]java.lang.Integer:valueOf()
        `---[0,0ms]java.lang.String:format()
    

    是不是很眼熟,沒錯,在JProfiler等收費軟體中你曾經見識類似的功能,這裡你將可以通過命令就能列印出指定呼叫路徑。

    [10,1ms]的含義,10所代表的含義是:當前節點的整體耗時;1的含義是:當前節點在當前步驟的耗時;兩者之間用逗號分割,單位為毫秒。

ptrace命令

  • 命令解釋

    命令為trace命令的強化版,通過指定渲染路徑來完成對方法執行路徑的渲染過程

    命令能主動搜尋tracing-path-pattern所渲染的路徑,渲染和統計整個呼叫鏈路上的所有效能開銷和追蹤呼叫鏈路。

  • 引數說明

    引數名稱 引數說明
    class-pattern 類名錶達式匹配
    method-pattern 方法名錶達式匹配
    condition-express 條件表示式
    [t] 記錄下渲染路徑上所有方法的入參與返回值,記錄下的返回值可以與tt命令聯動
    [n:] 命令執行次數
    [E] 支援正規表示式匹配
    [path:] 渲染路徑表示式匹配,該引數可多次使用
    [Epath:] 正規表示式渲染路徑表示式匹配,該引數可多次使用
  • 使用例子

    ga?>ptrace -t *alibaba*Test printAddress --path=*alibaba*
    Press Ctrl+D to abort.
    Affect(class-cnt:10 , method-cnt:36) cost in 148 ms.
    `---+pTracing for : thread_name="agent-test-address-printer" thread_id=0xb;is_daemon=false;priority=5;process=1004;
    `---+[2,2ms]com.alibaba.AgentTest:printAddress(); index=1021;
        +---+[1,1ms]com.alibaba.manager.DefaultAddressManager:newAddress(); index=1014;
        |   +---[1,1ms]com.alibaba.CountObject:<init>(); index=1012;
        |   `---[1,0ms]com.alibaba.Address:<init>(); index=1013;
        +---+[2,1ms]com.alibaba.manager.DefaultAddressManager:toString(); index=1020;
        |   +---+[2,1ms]com.alibaba.manager.DefaultAddressManager:toStringPass1(); index=1019;
        |   |   +---+[2,1ms]com.alibaba.manager.DefaultAddressManager:toStringPass2(); index=1017;
        |   |   |   +---[1,0ms]com.alibaba.Address:getAddressId(); index=1015;
        |   |   |   +---+[1,0ms]com.alibaba.manager.DefaultAddressManager:throwRuntimeException(); index=1016;
        |   |   |   |   `---[1,0ms]throw:java.lang.RuntimeException
        |   |   |   `---[1,0ms]throw:java.lang.RuntimeException
        |   |   +---[2,0ms]com.alibaba.AddressException:<init>(); index=1018;
        |   |   `---[2,0ms]throw:com.alibaba.AddressException
        |   `---[2,0ms]throw:com.alibaba.AddressException
        `---[2,0ms]throw:com.alibaba.AddressException
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |    INDEX | PROCESS-ID |            TIMESTAMP |   COST(ms) |   IS-RET |   IS-EXP |          OBJECT |                          CLASS |                         METHOD |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1012 |       1004 |  2015-12-06 16:46:49 |          0 |     true |    false |        0x943cff |                    CountObject |                         <init> |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1013 |       1004 |  2015-12-06 16:46:49 |          0 |     true |    false |        0x943cff |                        Address |                         <init> |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1014 |       1004 |  2015-12-06 16:46:49 |          1 |     true |    false |      0x6833b8a5 |          DefaultAddressManager |                     newAddress |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1015 |       1004 |  2015-12-06 16:46:49 |          0 |     true |    false |        0x943cff |                        Address |                   getAddressId |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1016 |       1004 |  2015-12-06 16:46:49 |          0 |    false |     true |      0x6833b8a5 |          DefaultAddressManager |          throwRuntimeException |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1017 |       1004 |  2015-12-06 16:46:49 |          0 |    false |     true |      0x6833b8a5 |          DefaultAddressManager |                  toStringPass2 |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1018 |       1004 |  2015-12-06 16:46:49 |          0 |     true |    false |      0x67e7a923 |               AddressException |                         <init> |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1019 |       1004 |  2015-12-06 16:46:49 |          1 |    false |     true |      0x6833b8a5 |          DefaultAddressManager |                  toStringPass1 |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1020 |       1004 |  2015-12-06 16:46:49 |          1 |    false |     true |      0x6833b8a5 |          DefaultAddressManager |                       toString |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1021 |       1004 |  2015-12-06 16:46:49 |          2 |    false |     true |       0x2062a3d |                      AgentTest |                   printAddress |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    

watch命令

能方便的讓你觀察到指定方法的呼叫情況。能觀察到的範圍為:返回值丟擲異常入參,通過編寫OGNL表示式進行對應變數的檢視。

  • 引數說明

    watch的引數比較多,主要是因為它能在4個不同的場景觀察物件

    引數名稱 引數說明
    class-pattern 類名錶達式匹配
    method-pattern 方法名錶達式匹配
    condition-express 條件表示式
    express 觀察表示式
    [b] 在**方法呼叫之前**觀察
    [e] 在**方法異常之後**觀察
    [s] 在**方法返回之後**觀察
    [f] 在**方法結束之後**(正常返回和異常返回)觀察

    這裡重點要說明的是觀察表示式,觀察表示式的構成主要由OGNL表示式組成,所以你可以這樣寫params[0]+"$"+target,只要是一個合法的OGNL表示式,都能被正常支援。

    觀察的維度也比較多,主要體現在引數advice的資料結構上。Advice引數最主要是封裝了通知節點的所有資訊。參考表示式核心變數中關於該節點的描述。

  • 使用參考

    ga?>watch -b *Test printAddress `"params[0]="+params[0]`
    Press Ctrl+D to abort.
    Affect(class-cnt:1 , method-cnt:1) cost in 32 ms.
    params[0]=3163
    params[0]=3164
    params[0]=3165
    params[0]=3166
    

    這裡需要說明的一個引數x,這個引數決定了輸出的結果的層級遍歷輸出物件,當加上這個引數之後,greys會將輸出的物件按照指定層級進行剝開。-x 1表明展開第1層級。

    ga?>watch -s com.alibaba.manager.DefaultAddressManager newAddress returnObj -x 1
    Press Ctrl+D to abort.
    Affect(class-cnt:1 , method-cnt:1) cost in 34 ms.
    @Address[
    username=@String[dukun],
    addressId=@Integer[3244],
    addressName=@String[ADDRESS],
    ]
    

tt命令

時間隧道命令是我在使用watch命令進行問題排查的時候衍生出來的想法。watch雖然很方便和靈活,但需要提前想清楚觀察表示式的拼寫,這對排查問題而言要求太高,因為很多時候我們並不清楚問題出自於何方,只能靠蛛絲馬跡進行猜測。

這個時候如果能記錄下當時方法呼叫的所有入參和返回值、丟擲的異常會對整個問題的思考與判斷非常有幫助。

於是乎,TimeTunnel命令就誕生了。

  • 記錄方法的呼叫

    • 基本用法

      對於一個最基本的使用來說,就是記錄下當前方法的每次呼叫環境現場。

      ga?>tt -t -n 3 *Test printAddress
      Press Ctrl+D to abort.
      Affect(class-cnt:1 , method-cnt:1) cost in 33 ms.
      +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
      |    INDEX | PROCESS-ID |            TIMESTAMP |   COST(ms) |   IS-RET |   IS-EXP |          OBJECT |                          CLASS |                         METHOD |
      +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
      |     1036 |       1009 |  2015-12-06 16:57:06 |          1 |    false |     true |       0x2062a3d |                      AgentTest |                   printAddress |
      +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
      |     1037 |       1010 |  2015-12-06 16:57:07 |          0 |    false |     true |       0x2062a3d |                      AgentTest |                   printAddress |
      +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
      |     1038 |       1011 |  2015-12-06 16:57:08 |          0 |     true |    false |       0x2062a3d |                      AgentTest |                   printAddress |
      +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
      ga?>
      
    • 命令引數解析

      • -t

        tt命令有很多個主引數,-t就是其中之一。這個引數的表明希望記錄下類*Testprint方法的每次執行情況。

      • -n 3

        當你執行一個呼叫量不高的方法時可能你還能有足夠的時間用CTRL+D中斷tt命令記錄的過程,但如果遇到呼叫量非常大的方法,瞬間就能將你的JVM記憶體撐爆。

        此時你可以通過-n引數指定你需要記錄的次數,當達到記錄次數時greys會主動中斷tt命令的記錄過程,避免人工操作無法停止的情況。

  • 表格欄位說明

    表格欄位 欄位解釋
    INDEX 時間片段記錄編號,每一個編號代表著一次呼叫,後續tt還有很多命令都是基於此編號指定記錄操作,非常重要。
    PROCESS-ID 過程編號,我們認為同一個執行緒的一次同步呼叫為一個過程
    TIMESTAMP 方法執行的本機時間,記錄了這個時間片段所發生的本機時間
    COST(ms) 方法執行的耗時
    IS-RET 方法是否以正常返回的形式結束
    IS-EXP 方法是否以拋異常的形式結束
    OBJECT 執行物件的hashCode(),注意,曾經有人誤認為是物件在JVM中的無力記憶體地址,但很遺憾他不是。但他能幫助你簡單的標記當前執行方法的類實體
    CLASS 執行的類名
    METHOD 執行的方法名
  • 條件表示式

    不知道大家是否有在使用過程中遇到以下困惑

    1. 似乎很難區分出過載的方法
    2. 我只需要觀察特定引數,但是tt卻全部都給我記錄了下來

    1.6.0.6版本之後,應廣大婦女群眾的要求,增加了條件表示式,這樣你可以通過簡單的條件表示式解決上邊的困惑。

    條件表示式也是用OGNL來編寫,核心的判斷物件依然是Advice物件。

    除了tt命令之外,watchtracestack命令也都支援條件表示式

    • 解決方法過載

      tt -t *Test print params[0].length==1

      通過制定引數個數的形式解決不同的方法簽名,如果引數個數一樣,你還可以這樣寫

      tt -t *Test print `params[1].class == Integer.class`

    • 解決指定引數

      tt -t *Test print params[0].mobile=="13989838402"

  • 檢索呼叫記錄

    當你用tt記錄了一大片的時間片段之後,你希望能從中篩選出自己需要的時間片段,這個時候你就需要對現有記錄進行檢索。

    假設我們有這些記錄

    ga?>tt -l
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |    INDEX | PROCESS-ID |            TIMESTAMP |   COST(ms) |   IS-RET |   IS-EXP |          OBJECT |                          CLASS |                         METHOD |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1047 |       1020 |  2015-12-06 17:03:00 |          1 |     true |    false |       0x2062a3d |                      AgentTest |                      printUser |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1048 |       1021 |  2015-12-06 17:03:01 |          0 |     true |    false |       0x2062a3d |                      AgentTest |                      printUser |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1049 |       1022 |  2015-12-06 17:03:01 |          1 |     true |    false |       0x2062a3d |                      AgentTest |                   printAddress |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1050 |       1023 |  2015-12-06 17:03:01 |          0 |     true |    false |       0x2062a3d |                      AgentTest |                      printUser |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1051 |       1024 |  2015-12-06 17:03:02 |          1 |     true |    false |       0x2062a3d |                      AgentTest |                      printUser |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1052 |       1025 |  2015-12-06 17:03:02 |          1 |    false |     true |       0x2062a3d |                      AgentTest |                   printAddress |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1053 |       1026 |  2015-12-06 17:03:02 |          0 |     true |    false |       0x2062a3d |                      AgentTest |                      printUser |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1054 |       1027 |  2015-12-06 17:03:03 |          0 |     true |    false |       0x2062a3d |                      AgentTest |                      printUser |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1055 |       1028 |  2015-12-06 17:03:03 |          0 |    false |     true |       0x2062a3d |                      AgentTest |                   printAddress |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1056 |       1029 |  2015-12-06 17:03:03 |          0 |     true |    false |       0x2062a3d |                      AgentTest |                      printUser |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    Affect(row-cnt:10) cost in 3 ms.
    ga?>
    

    我需要篩選出printAddress方法的呼叫資訊

    ga?>tt -s method.name=="printAddress"
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |    INDEX | PROCESS-ID |            TIMESTAMP |   COST(ms) |   IS-RET |   IS-EXP |          OBJECT |                          CLASS |                         METHOD |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1049 |       1022 |  2015-12-06 17:03:01 |          1 |     true |    false |       0x2062a3d |                      AgentTest |                   printAddress |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1052 |       1025 |  2015-12-06 17:03:02 |          1 |    false |     true |       0x2062a3d |                      AgentTest |                   printAddress |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    |     1055 |       1028 |  2015-12-06 17:03:03 |          0 |    false |     true |       0x2062a3d |                      AgentTest |                   printAddress |
    +----------+------------+----------------------+------------+----------+----------+-----------------+--------------------------------+--------------------------------+
    Affect(row-cnt:3) cost in 8 ms.
    ga?>
    

    你需要一個-s引數。同樣的,搜尋表示式的核心物件依舊是Advice物件。

  • 檢視呼叫資訊

    對於具體一個時間片的資訊而言,你可以通過-i引數後邊跟著對應的INDEX編號檢視到他的詳細資訊。

    ga?>tt -i 1055
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |           INDEX | 1055                                                                                                                                                   |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |      PROCESS-ID | 1028                                                                                                                                                   |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |      GMT-CREATE | 2015-12-06 17:03:03                                                                                                                                    |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |        COST(ms) | 0                                                                                                                                                      |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |          OBJECT | 0x2062a3d                                                                                                                                              |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |           CLASS | com.alibaba.AgentTest                                                                                                                                  |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |          METHOD | printAddress                                                                                                                                           |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |       IS-RETURN | false                                                                                                                                                  |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |    IS-EXCEPTION | true                                                                                                                                                   |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |   PARAMETERS[0] | 3789                                                                                                                                                   |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    | THROW-EXCEPTION | com.alibaba.AddressException: java.lang.RuntimeException: test                                                                                         |
    |                 |     at com.alibaba.manager.DefaultAddressManager.toStringPass1(DefaultAddressManager.java:22)                                                          |
    |                 |     at com.alibaba.manager.DefaultAddressManager.toString(DefaultAddressManager.java:15)                                                               |
    |                 |     at com.alibaba.AgentTest.printAddress(AgentTest.java:80)                                                                                           |
    |                 |     at com.alibaba.AgentTest.access$300(AgentTest.java:7)                                                                                              |
    |                 |     at com.alibaba.AgentTest$3.null(Unknown Source)                                                                                                    |
    |                 | Caused by: java.lang.RuntimeException: test                                                                                                            |
    |                 |     at com.alibaba.manager.DefaultAddressManager.throwRuntimeException(DefaultAddressManager.java:39)                                                  |
    |                 |     at com.alibaba.manager.DefaultAddressManager.toStringPass2(DefaultAddressManager.java:29)                                                          |
    |                 |     at com.alibaba.manager.DefaultAddressManager.toStringPass1(DefaultAddressManager.java:20)                                                          |
    |                 |     ... 4 more                                                                                                                                         |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |           STACK | thread_name="agent-test-address-printer" thread_id=0xb;is_daemon=false;priority=5;                                                                     |
    |                 |     @com.alibaba.AgentTest.access$300()                                                                                                                |
    |                 |         at com.alibaba.AgentTest$3.null(null:-1)                                                                                                       |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    Affect(row-cnt:1) cost in 7 ms.
    ga?>
    
  • 重做一次呼叫

    當你少少做了一些調整之後,你可能需要前端系統重新觸發一次你的呼叫,此時得求爺爺告奶奶的需要前端配合聯調的同學再次發起一次呼叫。而有些場景下,這個呼叫不是這麼好觸發的。

    tt命令由於儲存了當時呼叫的所有現場資訊,所以我們可以自己主動對一個INDEX編號的時間片自主發起一次呼叫,從而解放你的溝通成本。此時你需要-p引數。

    ga?>tt -i 1055 -p
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |           INDEX | 1055                                                                                                                                                   |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |      PROCESS-ID | 1028                                                                                                                                                   |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |      GMT-CREATE | 2015-12-06 17:03:03                                                                                                                                    |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |        COST(ms) | 1                                                                                                                                                      |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |          OBJECT | 0x2062a3d                                                                                                                                              |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |           CLASS | com.alibaba.AgentTest                                                                                                                                  |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |          METHOD | printAddress                                                                                                                                           |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |       IS-RETURN | true                                                                                                                                                   |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |    IS-EXCEPTION | false                                                                                                                                                  |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |   PARAMETERS[0] | 3789                                                                                                                                                   |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |      RETURN-OBJ | 1                                                                                                                                                      |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    |           STACK | thread_name="ga-command-execute-daemon" thread_id=0x22;is_daemon=true;priority=9;                                                                      |
    |                 |     @com.github.ompc.greys.core.command.TimeTunnelCommand$6.action()                                                                                   |
    |                 |         at com.github.ompc.greys.core.server.DefaultCommandHandler.execute(DefaultCommandHandler.java:210)                                             |
    |                 |         at com.github.ompc.greys.core.server.DefaultCommandHandler.executeCommand(DefaultCommandHandler.java:87)                                       |
    |                 |         at com.github.ompc.greys.core.server.GaServer$4.run(GaServer.java:332)                                                                         |
    |                 |         at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)                                                             |
    |                 |         at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)                                                             |
    |                 |         at java.lang.Thread.run(Thread.java:745)                                                                                                       |
    +-----------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+
    Time fragment[1055] successfully replayed.
    Affect(row-cnt:1) cost in 2 ms.
    ga?>
    

    你會發現結果雖然一樣,但呼叫的路徑發生了變化,有原來的程式發起變成了greys自己的內部執行緒發起的呼叫了。

    得益於Greys的ClassLoader隔離策略,Greys在內部自己發起執行緒請求呼叫的時候,依然採用的是目標類所歸屬的ClassLoader,所以在OSGI、Tomcat容器等場景下,Greys依然能正確的重做此次呼叫。

  • 需要強調的點

  1. ThreadLocal資訊丟失

    很多框架偷偷的將一些環境變數資訊塞到了發起呼叫執行緒的ThreadLocal中,由於呼叫執行緒發生了變化,這些ThreadLocal執行緒資訊無法通過greys儲存,所以這些資訊將會丟失。

    一些常見的CASE比如:阿里鷹眼系統的TraceId、阿里全鏈路平臺的壓測流量標記位。

  2. 引用的物件

    需要強調的是,tt命令是將當前環境的物件引用儲存起來,但僅僅也只能儲存一個引用而已。如果方法內部對入參進行了變更,或者返回的物件經過了後續的處理,那麼在tt檢視的時候將無法看到當時最準確的值。這也是為什麼watch命令存在的意義。

stack命令

很多時候我們都知道一個方法被執行,但這個方法被執行的路徑非常多。或者你根本就不知道這個方法是從那裡被執行了,正在鬱悶,正在彷徨。此時你需要的是stack命令。

  • 引數說明

    引數名稱 引數說明
    class-pattern 類名錶達式匹配
    method-pattern 方法名錶達式匹配
    condition-express 條件表示式
    [n:] 命令執行次數
    [E] 支援正規表示式匹配
  • 使用例子

    ga?>stack com.alibaba.manager.DefaultAddressManager newAddress
    Press Ctrl+D to abort.
    Affect(class-cnt:1 , method-cnt:1) cost in 36 ms.
    thread_name="agent-test-address-printer" thread_id=0xb;is_daemon=false;priority=5;
    @java.lang.reflect.Method.invoke()
        at com.alibaba.manager.DefaultAddressManager.newAddress(DefaultAddressManager.java:-1)
        at com.alibaba.AgentTest.printAddress(AgentTest.java:73)
        at com.alibaba.AgentTest.access$300(AgentTest.java:7)
        at com.alibaba.AgentTest$3.null(null:-1)
    

version命令

這估計是最好理解的一個命令了,輸出當前Greys的版本號,這裡輸出的版本號不是Client的版本,而是當前載入到目標Java程式中的Greys版本。

quit命令

這裡說明下與shutdown命令的區別,quit命令僅僅是將客戶端關閉,而不會將目標Java程式中的與Greys的Server關閉。所以如果僅僅是希望簡單的推出Greys控制檯,則使用quit命令足矣。

一旦Greys控制檯退出,控制檯所繫結的Session將會被關閉,Session上所有存活的事務也都會被中止並釋放。

shutdown命令

命令執行後將會完成兩件事:

  • 關閉Greys在目標Java所載入的Socket服務,所佔用的埠將會被釋放,同時,本地的Greys控制檯也因為遠端Socket關閉而主動退出。

  • 重置所有被Greys所增強的類。同reset命令。

reset命令

重置指定被Greys所增強的類。

session命令

會話命令是在1.6版本之後新增,整個命令的定位是維護好會話級的引數。目前可修改的就一個字符集。

  • 引數說明

    引數名稱 引數說明
    [c:] 指定會話字符集
  • 使用例子

    • 直接檢視會話資訊

      ga?>session 
      +------------+------------------+
      |   JAVA_PID | 8609             |
      +------------+------------------+
      | SESSION_ID | 2                |
      +------------+------------------+
      |   DURATION | 300000           |
      +------------+------------------+
      |    CHARSET | UTF-8            |
      +------------+------------------+
      |     PROMPT | ga?>             |
      +------------+------------------+
      |       FROM | /127.0.0.1:58186 |
      +------------+------------------+
      |         TO | /127.0.0.1:3658  |
      +------------+------------------+
      Affect(row-cnt:1) cost in 0 ms.
      ga?>
      
    • 修改字符集

      ga?>session -c GBK
      change charset before[UTF-8] -> new[GBK]
      Affect(row-cnt:1) cost in 26 ms.
      

jvm命令

檢視當前JVM的資訊,無引數。

ga?>jvm
+--------------------+-----------------------------------------------------------------------------------------------------+
|           CATEGORY | INFO                                                                                                |
+--------------------+-----------------------------------------------------------------------------------------------------+
|            RUNTIME |              MACHINE-NAME : 25428@vlinux-air.local                                                  |
|                    |            JVM-START-TIME : 2015-06-16 22:12:20                                                     |
|                    |   MANAGEMENT-SPEC-VERSION : 1.2                                                                     |
|                    |                 SPEC-NAME : Java Virtual Machine Specification                                      |
|                    |               SPEC-VENDOR : Oracle Corporation                                                      |
|                    |              SPEC-VERSION : 1.8                                                                     |
|                    |                   VM-NAME : Java HotSpot(TM) 64-Bit Server VM                                       |
|                    |                 VM-VENDOR : Oracle Corporation                                                      |
|                    |                VM-VERSION : 25.25-b02                                                               |
|                    |           INPUT-ARGUMENTS : []                                                                      |
|                    |                CLASS-PATH : .                                                                       |
|                    |           BOOT-CLASS-PATH : /Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/jre/li  |
|                    |                             b/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Conte |
|                    |                             nts/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_25.j |
|                    |                             dk/Contents/Home/jre/lib/sunrsasign.jar:/Library/Java/JavaVirtualMachin |
|                    |                             es/jdk1.8.0_25.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVir |
|                    |                             tualMachines/jdk1.8.0_25.jdk/Contents/Home/jre/lib/jce.jar:/Library/Jav |
|                    |                             a/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/jre/lib/charsets.ja |
|                    |                             r:/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/jre/l |
|                    |                             ib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/H |
|                    |                             ome/jre/classes                                                         |
|                    |              LIBRARY-PATH : /Users/vlinux/Library/Java/Extensions:/Library/Java/Extensions:/Networ  |
|                    |                             k/Library/Java/Extensions:/System/Library/Java/Extensions:/usr/lib/java |
|                    |                             :.                                                                      |
+--------------------+-----------------------------------------------------------------------------------------------------+
|      CLASS-LOADING |        LOADED-CLASS-COUNT : 3045                                                                    |
|                    |  TOTAL-LOADED-CLASS-COUNT : 3045                                                                    |
|                    |      UNLOADED-CLASS-COUNT : 0                                                                       |
|                    |                IS-VERBOSE : false                                                                   |
+--------------------+-----------------------------------------------------------------------------------------------------+
|        COMPILATION |                      NAME : HotSpot 64-Bit Tiered Compilers                                         |
|                    |        TOTAL-COMPILE-TIME : 8903(ms)                                                                |
+--------------------+-----------------------------------------------------------------------------------------------------+
| GARBAGE-COLLECTORS |               PS Scavenge : 4/40(ms)                                                                |
|                    |              [count/time]                                                                           |
|                    |              PS MarkSweep : 0/0(ms)                                                                 |
|                    |              [count/time]                                                                           |
+--------------------+-----------------------------------------------------------------------------------------------------+
|    MEMORY-MANAGERS |          CodeCacheManager : Code Cache                                                              |
|                    |         Metaspace Manager : Metaspace                                                               |
|                    |                             Compressed Class Space                                                  |
|                    |               PS Scavenge : PS Eden Space                                                           |
|                    |                             PS Survivor Space                                                       |
|                    |              PS MarkSweep : PS Eden Space                                                           |
|                    |                             PS Survivor Space                                                       |
|                    |                             PS Old Gen                                                              |
+--------------------+-----------------------------------------------------------------------------------------------------+
|             MEMORY |         HEAP-MEMORY-USAGE : 163053568/134217728/1908932608/54796080                                 |
|                    | [committed/init/max/used]                                                                           |
|                    |      NO-HEAP-MEMORY-USAGE : 32571392/2555904/-1/31757608                                            |
|                    | [committed/init/max/used]                                                                           |
|                    |    PENDING-FINALIZE-COUNT : 0                                                                       |
+--------------------+-----------------------------------------------------------------------------------------------------+
|   OPERATING-SYSTEM |                        OS : Mac OS X                                                                |
|                    |                      ARCH : x86_64                                                                  |
|                    |          PROCESSORS-COUNT : 4                                                                       |
|                    |              LOAD-AVERAGE : 2.50634765625                                                           |
|                    |                   VERSION : 10.10.3                                                                 |
+--------------------+-----------------------------------------------------------------------------------------------------+
|             THREAD |                     COUNT : 8                                                                       |
|                    |              DAEMON-COUNT : 7                                                                       |
|                    |                LIVE-COUNT : 9                                                                       |
|                    |             STARTED-COUNT : 19                                                                      |
+--------------------+-----------------------------------------------------------------------------------------------------+
Affect cost in 22 ms.

常見問題答疑

安裝問題

  1. 安裝失敗

    $:>curl -sLk http://ompc.oss.aliyuncs.com/greys/install.sh|ksh
    downloading... greys.zip.43678
    download file failed!
    
  • 問:

    為什麼我在安裝Greys的時候失敗了?

  • 答:

    Greys的安裝指令碼首先先從阿里雲上下載greys.zip,然後進行解壓、安裝。所以必須要求執行安裝指令碼的使用者必須有對當前目錄的寫許可權,一般出現這個問題,可以檢查網路磁碟空間以及當前目錄是否有寫許可權

  1. 安裝目錄
  • 問:

    如果我在/tmp/目錄下執行安裝指令碼,請問Greys會怎麼安裝?

  • 答:

    安裝在/tmp/greys/目錄下,該目錄下一共有三個重要檔案

    • /tmp/greys/greys-agent.jar
    • /tmp/greys/greys-core.jar
    • /tmp/greys/greys.sh

      其中greys-core.jar為greys的程式主體,啟動類、載入類都在這個jar包當中;greys-agent.jar則為目標JVM的載入載入程式;greys.sh為一個可執行指令碼,為Greys的啟動指令碼。

  1. 如何刪除
  • 問:

    安裝的Greys如何進行刪除?

  • 答:

    Greys是一個綠色環保軟體,不會修改你的登錄檔,但會在$HOME目錄下建立隱藏目錄$HOME/.greys,用於儲存不同的版本和Jline的歷史命令,你可以直接刪除。

啟動問題

  1. 啟動報Operation not permitted錯誤
  • 問:

    我在啟動Greys的時候報這樣的錯誤

     $:> ./greys.sh 11064
     start greys failed, because : Operation not permitted
    
  • 答:

    Greys要求執行執行啟動命令的使用者必須擁有和目標程式ID同樣的許可權(在這個Case中,目標程式ID為11064),否則JVM將無法掛載Greys對應的jar包

  1. 啟動報No such process錯誤
  • 問:

    我在啟動Greys的時候報這樣的錯誤

     $:> ./greys.sh 11063
     start greys failed, because : No such process
    
  • 答:

    目標程式ID不存在。報這樣的錯誤,請核對你的目標程式是否存在。

  1. 啟動報Unable to open socket file: target process not responding or HotSpot VM not loaded
  - **問:**

 我在啟動Greys的時候報這樣的錯誤

 ```shell
 $:> sudo -u admin ./greys.sh 12592
 密碼:
 start greys failed, because : Unable to open socket file: target process not responding or HotSpot VM not loaded
 ```
  • 答:

    目標程式ID不是JVM程式,或目標JVM程式不支援載入操作,比如低於JDK6的版本等。
    一般遇到這種問題一定要非常小心謹慎的執行,如果對方程式程式設計不嚴謹,很可能會讓對方程式CORE掉。上次我就弄死了個nginx的worker -_-!!

  1. 啟動沒有響應
  • 問:

    啟動之後就什麼反應也沒有,也沒有出現預期的ga?>提示符

  • 答:

    很有可能你的3658埠已經被別的程式佔據,請核對你當前機器所開的埠

      netstat -anp|grep LIST
    

    解決方案也很簡單,換個埠

      ./greys.sh 4567@127.0.0.1:6666
    
  1. sudo -u方式啟動報許可權不足

    請給你的sudo命令加上-H引數

      sudo -u admin -H ./greys.sh 4567
    

使用問題

  1. 哪些命令會導致效能問題

    Greys的大部分命令效能開銷都非常低廉,當然前提是一次性操作的類不要太多。

  2. 是否能增強由BootstrapClassLoader所載入的類

    當然是可以的,但預設我*封印*了這個能力。主要是Greys自己也使用了大量BootstrapClassLoader所載入的類,如果處理不好極其容易造成故障。

    你可以通過隱藏命令options啟用這個功能

    ga?>options unsafe true
    +--------+--------------+-------------+
    | NAME   | BEFORE-VALUE | AFTER-VALUE |
    +--------+--------------+-------------+
    | unsafe | false        | true        |
    +--------+--------------+-------------+
    Affect(row-cnt:1) cost in 2 ms.
    

    接下來你可以嘗試增強系統類了

    ga?>monitor -c 5 java.lang.String substring
    Press Ctrl+D or Ctrl+X to abort.
    Affect(class-cnt:1 , method-cnt:2) cost in 35 ms.
    +---------------------+------------------+-----------+-------+---------+------+------+-----------+
    | timestamp           | class            | method    | total | success | fail | rt   | fail-rate |
    +---------------------+------------------+-----------+-------+---------+------+------+-----------+
    | 2015-06-16 23:44:54 | java.lang.String | substring | 30    | 30      | 0    | 0.23 | 0.00%     |
    +---------------------+------------------+-----------+-------+---------+------+------+-----------+
    

    但我話就放在這裡,隨意增強系統類。後果自負!

其他問題

  1. Greys支援將資訊輸出到檔案中麼?

    支援,不過要做一些小命令。

     ./greys.sh 4567|tee -a ./greys.log
    
  2. Greys能使用在Sun JDK5的版本麼?

    很遺憾抱歉的說,不行。

    Greys的原理和Btrace一樣,依賴了JDK6+提供的Instumentation特性,所以必須要求目標的JDK環境是**JDK6及其以上的版本**。

    理論上Greys應該能在各種實現了SUN標準的各種JVM實現中執行,比如JRockitZing等,但我自己沒有機會嘗試,若有朋友能提供環境能進行測試並反饋測,我將不勝感激。

    JRE中由於缺少tools.jar,所以無法直接執行Greys,需要稍作一些修改。同樣的也沒有需求要在JRE中執行Greys,我這裡也偷個懶,留給其他有需要的人來實現吧。

  3. 程式中是否有彩蛋?

    有,我當初做這個軟體的唯一目的就是希望能快速定位問題,然後好陪她逛街。為了不忘記這個初心,我將這位她的英文名作為命令整合到了Greys中,可以嘗試找找!

維護資訊

當前版本

1.7.3.0

專案維護者

版本管理

  • 多版本管理

    1.7.x.x版本開始,greys.sh指令碼支援自動更新,在網路允許的情況下會自動監測遠端伺服器上是否存在可升級的最新版本。

    若網路不可達(網路隔離的環境)則需要進行本地安裝。本地安裝也一樣會納入到多版本管理識別範圍。

  • 大版本相容性問題

    大版本之間不做任何相容性保障,比如1.7.x.x版本的客戶端不保證能訪問1.6.x.x啟動的服務端。

版本號說明

主版本.大版本.小版本.漏洞修復

  • 主版本

    這個版本更新說明程式架構體系進行了重大升級,比如之前的0.1版升級到1.0版本,整個軟體的架構從單機版升級到了SOCKET多機版。並將Greys的性質進行的確定:Java版的HouseMD,但要比前輩們更強。

  • 大版本

    程式的架構設計進行重大改造,但不影響使用者對這款軟體的定位。

  • 小版本

    增加新的命令和功能

  • 漏洞修復

    對現有版本進行漏洞修復和增強

版本升級說明

  • 主版本大版本之間不做任何向下相容的承諾。即0.1版本的Client不保證一定能正常訪問1.0版本的Server。
  • 小版本不相容的功能會在版本升級中指出
  • 漏洞修復保證所有功能向下相容

寫在後邊

心路感悟

我編寫和維護這款軟體已經5年了,5年中Greys也從0.1版本一直重構到現在的1.7。在這個過程中我得到了許多人的幫助與建議,並在年底我計劃釋出2.0版本,將開放Greys的底層通訊協議,支援websocket訪問。

多年的問題排查經驗我沒有過多的分享,一個Java程式設計師箇中的苦悶也無從分享,一切我都融入到了這款軟體的命令中,希望這些沉澱能幫助到可能需要到的你少走一些彎路,同時我也非常期待你們對她的反饋,這樣我將感到非常開心和有成就感。

幫助我們

Greys的成長需要大家的幫助。

  • 分享你使用Greys的經驗

    我非常希望能得到大家的使用反饋和經驗分享,如果你有,請將分享文章敏感資訊脫敏之後郵件給我:oldmanpushcart@gmail.com,我將會分享給更多的同行。

  • 幫助我完善程式碼或文件

    一款軟體再好,也需要詳細的幫助文件;一款軟體再完善,也有很多坑要埋。今天我的精力非常有限,希望能得到大家共同的幫助。

  • 如果你喜歡這款軟體,歡迎打賞一杯咖啡

    嗯,說實話,我是指望用這招來買輛瑪莎拉蒂…當然是個玩笑~你們的鼓勵將會是我的動力,錢不在乎多少,重要的是我將能從中得到大家善意的反饋,這將會是我繼續前進的動力。

    alipay

聯絡我們

有問題阿里同事可以通過旺旺找到我,阿里外的同事可以通過我的微博聯絡到我。今晚的杭州大雪紛飛,明天西湖應該非常的美麗,大家晚安。

菜鳥-杜琨(dukun@alibaba-inc.com


相關文章