pinpoint 使用和相關原始碼解析
架構
對原生的pinpoint進行了一些二次開發,大致流程如下
agent 收集資料
pinpoint預設資料collector是用的netty方式,agent是netty client端,進行抽樣採集,傳送資料到netty server端collector,collector在進行處理寫入到hbase,包括agent本身的一些資訊,記憶體,執行緒資訊還有trace資訊,但是這樣效能就會損失很多,如果100%採集的話,會對應用和伺服器都會造成很大負荷是,所有需要進行改造
我們將agent的netty通訊方式改成了打日誌的形式,在pinpoint spi獲取對應例項的時候寫死資料傳送方式, 比如ApiMetaDataServiceProvider這個povider,判斷config是storm的方式,直接使用了自定義的dataSender
@Override public ApiMetaDataService get() { final EnhancedDataSender enhancedDataSender; if (PinpointConstants.AGENT_SENDER_TYPE_LOGGING.equals(profilerConfig.getAgentSenderType())) { enhancedDataSender = new StormEnhanceLoggingDataSender(); } else { enhancedDataSender = this.enhancedDataSenderProvider.get(); } return new DefaultApiMetaDataService(agentId, agentStartTime, enhancedDataSender); }
agent載入初始化
核心類入口是PinpointBootStrap的premain方法,這個是agent的標準
每個應用會配置pinpoint的一些啟動引數,例如agentId和日誌位置,之後在啟動過程中會載入bootstrap-*.jar的這些jar包,還有通過日誌目錄進行擷取,獲取到外掛的對應路徑,載入外掛的所有jar,並新增到bootstrap的classloader中,並通過spi的方式載入外掛META-INF中配置的對應的類,載入其中的setup方法,將一些常量放到上下文中去,資料都載入到了TraceMetadataLoader中的annotationKeys和serviceTypeInfos這兩個list集合中
java-Dpinpoint.log=/Users/walis/work/dwb/dapoint/logs -Dpinpoint.agentId=rider-fund-service -Dpinpoint.applicationName=rider-fund-service -javaagent:/Users/walis/work/dwb/dapoint/agent/target/pinpoint-agent-1.7.2-1.0.0-SNAPSHOT/pinpoint-bootstrap-1.7.2-1.0.0-SNAPSHOT.jar -jar rider-service.jar
物件例項化
pinpoint這貨使用了很多spi和ioc方式,guice,spring ioc,類似serviceLoader的方式,有點多,很多重要的類都在spi中進行初始化了, 不去細看的話,很多類什麼時候初始化還真不好找。
-
guice
pinpoint-profile模組中的 ApplicationContextModule 這個類中載入大多的核心的provider
``` configure(){
bindTraceComponent() bindDataTransferComponent() bindServiceComponent() bindAgentInformation() bindAgentStatComponent()
... } ```
- spring ioc
主要是collector模組中的applicationContext-collector.xml, 這邊是collector接收到agent傳送的資料進行處理一些核心類,包括hbase的儲存 例如
核心類DefaultAgent啟動
agent的start方法呼叫了DeafultApplicationContext,這個context類載入所有guice module生成,然後獲取重要的核心類,比如AgentInfoSender和AgentStatMonitor,然後呼叫這兩個類的start方法,啟動agent資料打點
* AgentInfoSender
AgentInfoSender內部會建立一個schedule,定時傳送agent的資訊,agent的資訊主要是每個應用的id,ip,名稱,埠,程序號,vm版本,agent版本, jvm資訊的話都是從RuntimeMxBean獲取的
* AgentStatsMonitor
agentStatusMonitor收集各種agent相關的資訊,比如cpu,jvm記憶體,trace響應時間等資料,AgentStatMetricCollector介面對應很多實現類,一部分呼叫的是metric包下面的包,像記憶體,cpu這些資料都是從RuntimeMBean中去拿的資料,
trace 過程
trace的過程主要涉及到traceId,span和spanEvent,接著就是spansender
traceId預設生成方式是agentId + agent啟動時間 + 自增的id
一次呼叫是由很多span組成,最終形成一個id它們公用一個traceId
SpanRecorder 新增跟span相關聯的spanEvent,包括服務的ip,引數資訊,型別,返回值或者異常等等
trace的大致過程類似如下:
createTrace -> currentTraceObject -> traceBlockBegin -> traceBlockEnd -> close
close結束後,就會呼叫對應sender將span資訊進行打點或者傳送給對應的collector
位元組碼織入
plugin結構
-
ProfilerPlugin
每個外掛需要實現ProfilerPlugin, TransformTemplateAware介面,提供設定servicetype,annotationKey
覆蓋setup方法,設定需要處理的class,對特定攔截物件比如方法,新增攔截器,進行位元組碼植入,這個呼叫了ASMMethod的addScopedInterceptor方法,有興趣的可以去看下,底層還是到asm的insertBefore方法
例如
``` for (InstrumentMethod m : target.getDeclaredMethods(MethodFilters.name("fromJson"))) {
m.addScopedInterceptor("com.navercorp.pinpoint.plugin.gson.interceptor.FromJsonInterceptor", GSON_SCOPE);
}
```
-
TraceMetadataProvider 每個外掛需要實現TraceMetadataProvider,覆蓋setup方法,設定serviceTye和annotationKey
-
Config 因為外掛不一定開啟,還要附件的其他一些自定義的配置項,這些都是放到pinpoint.config中去配置的
-
Intercepter
實現AroundInterceptor1介面,在before方法和after後植入程式碼,進行trace操作
獲取位元組碼處理引擎
InstrumentEngineProvider, 預設使用的是asm, 生成ASMEngine類,getClass方法返回
ASMClass,這個類中的所有方法是對asm的封裝,便於更方便使用的,每個ProfilerPlugin實現類都有會實現TransformCallback介面,最終呼叫的的都是ASMClass的方法
ProfilePlugin載入
PluginContextLoadResultProvider在建立DefaultPluginContextLoadResult例項的時候,會找到所有plugin的jar,載入實現ProfilerPlugin介面,呼叫其setUp方法和設定
jdk載入註冊
按照jdk標準載入agent裡面的程式碼的話需要實現ClassFileTransformer介面,實現transform方法,然後在jvm應用啟動,然後在類的位元組碼載入jvm前會呼叫,這個方法,從而實現修改原來方法的功能
pinpoint實現了是通過ClassFileTransformerDispatcher這個最上層的類,TransformTemplate的transform方法會將抽象出來的模板類TransformCallback,ClassFileTransformerLoader會將將這個callback包裝為MatchableClassFileTransformerGuardDelegate(同樣實現了jdk標準的ClassFileTransformer介面)然後,新增到自己的一個內部集合中去,之後ClassFileTransformerDispatcher會獲取這個集合做後續處理。
資料傳送
核心資料結構
pinpoint涉及到的資料結構體都在thrift module下面,主要是Pinpoint.thift和Trace.thrift這兩個檔案
主要有TJvmInfo,TAgentInfo,TJvmGc,TJvmGcDetailed,TCpuLoad,TTransaction,TActiveTraceHistogram,TActiveTrace,TResponseTime,TAgentStat,TAgentStatBatch,TSpan,TSpanChunk,TApiMetaData等
傳送實現
資料離不開agent和span,所有的sender都圍繞這幾個核心
資料傳送呼叫provider產生的這些dataSender,以下幾個3個核心的sender類的provider,由guice進行例項化
- SpanDataSenderProvider (span採集打點)
- StatDataSenderProvider (agent狀態資訊採集打點)
- AgentInfoSenderProvider (agent的資訊採集打點)
資料聚合
所有sender列印的日誌都寫在pinpoint.log檔案下,具體檔案的位置是jar包啟動的環境變數, 然後,運維會通過firebeats將日誌進行實時蒐集,傳送到kafka, 由jstorm的topology消費,在將資料寫入到hbase,自己基於pinpoint的tcp的receiver進行改造,資料寫入到hbase
資料儲存
資料儲存包含agent的和span的資料,直接寫入到hbase的,沒有使用pheonix,使用的是pinpoint自己封裝的工具進行操作,原生的hbase的增刪改查可讀性不是很好的,沒有標準的sql語法來的自然清晰。
為了能通過一些查詢條件和服務名稱和呼叫的方法查詢,在stom處理的時候,將這些資訊寫入到es,只保留近7天的資料,像這種多條件查詢的話還是es效能好點.
原有介面進行二次開發,加上這些條件,然後通過agentId,spanId,transactionId去查詢鏈路資訊, transactionId可以拿到所有相關的span,然後對所有的span根據關係串成一個屬性結構的CallTreeNode
public class CallTreeNode { private CallTreeNode parent; private CallTreeNode child; private CallTreeNode sibling; private SpanAlign value; public CallTreeNode(final CallTreeNode parent, SpanAlign value) { this.parent = parent; this.value = value; }