Pinpoint插件開發(fā)手冊

你可以編寫Pinpoint的Profiler 插件去擴展Profiling目標的覆蓋。在進入插件開發(fā)之前,最好去查看下Pinpoint插件的跟蹤數(shù)據(jù)記錄。

1. 追蹤數(shù)據(jù)

在Pinpoint中,一個事務(Transaction)包含一組Span。每一個Span代表事務(Transaction)經(jīng)過的單個邏輯點的跟蹤。
為了更形象的表達,假設下面有這樣一個系統(tǒng)。前端(FrontEnd)服務器接收到用戶的請求,然后發(fā)送給后端(BackEnd)查詢DB數(shù)據(jù)庫,在這些節(jié)點中,假設前端(FrontEnd)后端(BackEnd)服務器有配置Pinpoint Agent

鏈路調(diào)用

當一個請求到達后端(BackEnd)時,Pinpoint Agent會生成一個新的事務(Transaction)以及創(chuàng)建一個Span。為了處理請求,前端(FontEnd)會調(diào)用后端(BackEnd)服務器。這時候,Pinpoint Agent向調(diào)用消息注入事務ID(Transaction ID)(附加一些其他用于傳播的數(shù)據(jù))。當后端(BackEnd)接收到消息,它將會提取事務ID(Transaction ID)(及其他附加信息)去創(chuàng)建一個Span。其結(jié)果就是,所有的Span將會在一個事務(Transaction)中共享其事務ID(Transaction ID)。
一個Span記錄著重要的方法調(diào)用及相關的數(shù)據(jù)(參數(shù),返回值,及其他),然后將它們封裝為SpanEvents,類似于調(diào)用棧的表示方式。Span本身以及每個SpanEvents表示一個方法調(diào)用。
SpanSpanEvent有很多字段,但是大多數(shù)字段都是內(nèi)部處理的通過Pinpoint Agent及大部分插件開發(fā)者不需要關心它們。但是對于這些字段,開發(fā)者必須處理的信息如下:

2. Pinpoint 插件結(jié)構(gòu)

Pinpoint PluginTraceMetadataProviderProfilerPlugin組成實現(xiàn)。

  • TraceMetadataProvider :實現(xiàn)提供ServiceTypeAnnotationKeyPinpoint Agent,Web采集器(Collector)。
  • ProfilerPlugin:實現(xiàn)通過Pinpoint Agent轉(zhuǎn)化成目標類進行記錄跟蹤數(shù)據(jù)。

對于的文件如下:

  • META-INF/services/com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin
  • META-INF/services/com.navercorp.pinpoint.common.trace.TraceMetadataProvider

類似如下:


文件目錄樹

2.1 TraceMetadataProvider

TraceMetadataProvider實現(xiàn)規(guī)定ServiceTypesAnnotationKeys

2.1.1 ServiceType

每一個SpanSpanEvent都包含ServiceType。ServiceType表示跟蹤的方法屬于哪個類庫,以及如何處理SpanSpanEvent。
下面顯示ServiceType的屬性。

屬性 描述
name ServiceType的名稱,必須是唯一的
code short類型的值,必須是唯一的
desc 描述信息
properties 屬性

ServiceType值必須使用適當?shù)姆秶鷼w類它,下面這張表展示了其分類和對應的value范圍:

分類 范圍
內(nèi)部使用 0~999
服務 1000~1999
數(shù)據(jù)庫客戶端 2000~2999
緩存客戶端 8000~8999
RPC客戶端 9000~9999
其他 5000~7999

ServiceType碼必須唯一,所以,當你想寫一個插件并將它公布出去,你必須聯(lián)系Pinpoint開發(fā)團隊去分配一個ServiceType碼定義。如果你的插件僅是私自的使用,你可以隨意使用下邊表的數(shù)值

種類 范圍
Server 1900~1999
DB Client 2900~2999
Cache Client 8900~8999
RPC Client 9900~9999
其他 7500~7999

ServiceType可以具有以下屬性:

屬性 描述
TERMINAL Span或者SpanEvent調(diào)用遠程節(jié)點,但是該遠程節(jié)點無法用Pinpoint跟蹤
INCLUDE_DESTINATION_ID Span或者SpanEvent記錄目的端的iddestination id但遠程服務不是可追蹤類型
RECORD_STATISTICS Pinpoint Collector應該收集這個SpanSpanEvent的執(zhí)行時間統(tǒng)計信息

2.1.2 AnnotationKey

你可以給Spans或者SpanEvents標記更多的信息。一個Annotation是一個鍵值對,其中鍵是一個AnnotationKey類型,值是一個原始類型,字符串或字節(jié)數(shù)組。對于常用的Annotation類型,有預先定義了AnnotationKey,但是如果覺得不夠,可以在TraceMetadataProvider中定義你自己的鍵。

屬性 描述
name AnnotationKey的名稱
code AnnotationKey的唯一整型編號
properties 屬性

如果你的公共插件要添加一個新的AnnotationKey,你必須聯(lián)系Pinpoint 開發(fā)團隊來分配一個AnnotationKey代碼。如果你的插件是私人使用的,你可以選擇一個介于900到999之間的值作為AnnotationKey代碼。
下面的表展示了AnnotationKey屬性:

屬性 描述
VIEW_IN_RECORD_SET 展示注解在call tree上
ERROR_API_METADATA 該屬性不是插件的

你也可以通過ServiceType來傳遞AnnotationKeyMatcher (TraceMetadata.addServiceType(ServiceType, AnnotationKeyMatcher) 。如果你通過這種方式傳遞一個AnnotationKeyMatcher,那么當ServiceTypeSpanSpanEvent在事務調(diào)用樹中顯示時,匹配的Annotation將顯示為典型的Annotation。

2.2 ProfilerPlugin

ProfilerPlugin修改目標庫類來完成采集追蹤數(shù)據(jù)。
ProfilerPlugin按照如下順序進行:

  1. 在JVM啟動時,Pinpoint Agent啟動
  2. Pinpoint Agent將會加載plugin目錄下的全部插件
  3. Pinpoint Agent將會在每個插件調(diào)用ProfilerPlugin.setup(ProfilerPluginSetupContext)。
  4. setup()方法中,插件定義了需要轉(zhuǎn)換的類,并注冊了一個TransformerCallback。
  5. 目標應用啟動。
  6. 每當裝載一個類時,Pinpoint Agent就會查找為該類注冊的TransformerCallback
  7. 如果注冊了TransformerCallback,代理將調(diào)用它的doInTransform()方法。
  8. TransformerCallback修改目標類的字節(jié)代碼。(例如,添加攔截器、添加字段等)
  9. 修改后的字節(jié)代碼返回給JVM,并用返回的字節(jié)代碼加載類。
  10. 應用繼續(xù)運行。
  11. 當調(diào)用修改的方法時,將調(diào)用注入的攔截器的beforeafter方法。
  12. 攔截器記錄追蹤的數(shù)據(jù)。

最重要的要考慮的點可以歸納為:

  • 確定哪些方法足夠必須,需要去跟蹤。
  • 注入攔截器去追蹤這些方法。

這些攔截器用來在發(fā)送到Collector之前的數(shù)據(jù)提取,存儲和傳遞跟蹤數(shù)據(jù)。攔截器甚至可以互相協(xié)作,共享上下文。插件還可以向目標類添加getter甚至自定義字段幫助去幫助跟蹤,以便攔截器可以在執(zhí)行期間訪問它們。Pinpoint插件示例展示了TransformerCallback如何修改類,以及注入的攔截器如何跟蹤方法
現(xiàn)在我們將描述攔截器必須做什么來跟蹤不同類型的方法。

2.2.1 Plain method

純方法指的不是節(jié)點的頂級方法,或者與遠程或異步調(diào)用無關的任何方法。示例2展示了如何跟蹤這些普通方法。

2.2.2 Top level method of a node

節(jié)點的頂級方法是其攔截器在節(jié)點中開始新跟蹤的方法。這些方法通常是rpc的接受器,跟蹤記錄為一個Span, ServiceType分類為服務器。
如何記錄Span取決于事務是否已經(jīng)在之前的任何節(jié)點上開始。

2.2.2.1 New transaction

如果當前節(jié)點是記錄事務的第一個節(jié)點,則必須發(fā)出一個新的事務id并記錄它。newtraceobject()將自動處理此任務,因此只需調(diào)用它。

2.2.2.2 Continue Transaction

如果請求來自Pinpoint 代理的另一個節(jié)點,那么事務將已經(jīng)發(fā)出一個事務id;您必須將下面的數(shù)據(jù)記錄到Span中。(這些數(shù)據(jù)中的大多數(shù)是從前面的節(jié)點發(fā)送的,通常打包在請求消息中)

名稱 描述
transactionId Transaction ID
parentSpanId 父節(jié)點的Span ID
parentApplicationName 父節(jié)點的應用名
parentApplicationType 父節(jié)點的應用類型
rpc 程序名(可選)
endPoint 服務器當前節(jié)點地址
remoteAddr 調(diào)用者的地址
acceptorHost 客戶端使用的服務器地址

Pinpoint使用acceptorHost查找節(jié)點之間的調(diào)用者-被調(diào)用者關系。在大多數(shù)情況下,acceptorHostendPoint是相同的。然而,客戶機發(fā)送請求到的地址有時可能與服務器接收請求(代理)的地址不同。要處理這種情況,您必須記錄實際地址的客戶端發(fā)送請求作為acceptorHost。通常,客戶端插件會將這個地址添加到請求消息中,并與事務數(shù)據(jù)一起添加。
此外,還必須使用上一個節(jié)點發(fā)出和發(fā)送的span id。
有時,前一個節(jié)點標記不跟蹤的事務。在這種情況下,你不能跟蹤事務。
正如你所看到的,客戶端插件必須向服務器插件傳遞許多數(shù)據(jù)。如何做到這一點取決于協(xié)議。
你可以在這里找到一個頂級方法服務器攔截器的示例

2.2.3 Methods invoking a remote node

調(diào)用遠程節(jié)點的方法的攔截器必須記錄以下數(shù)據(jù):

名稱 描述
endPoint 目的的服務端地址
destinationId 目標的邏輯名稱
rpc 調(diào)用的程序名(可選)
nextSpanId 下個節(jié)點的Span Id(如果需要跟蹤通過Pinpoint)

2.2.3.1 If the next node is traceable

如果下一個節(jié)點是可跟蹤的,那么攔截器必須將以下數(shù)據(jù)傳播到下一個節(jié)點。如何傳遞它們?nèi)Q于協(xié)議,在最壞的情況下可能根本不可能傳遞它們。

名稱 描述
transactionId Transaction ID
parentSpanId 當前節(jié)點的Span ID
parentApplicationName 當前節(jié)點的應用名
parentApplicationType 當前節(jié)點的應用類型

通過匹配客戶端跟蹤的destinationId和服務器跟蹤的acceptorHost,查明調(diào)用者和被調(diào)用者之間的關系。因此客戶端插件必須記錄destinationId,而服務器插件必須記錄相同值的acceptorHost。如果服務器自己無法獲取該值,客戶端插件必須將其傳遞給服務器。
攔截器記錄的ServiceType必須來自RPC client類別。
可以在這里找到這些攔截器的示例。

2.2.3.2 If the next node is not traceable

如果下一個節(jié)點是不可跟蹤的,那么ServiceType必須具有TERMINAL屬性。
如果你想記錄destinationId,它還必須有INCLUDE_DESTINATION_ID屬性。如果你記錄destinationId,服務器映射將顯示每個destinationId節(jié)點,即使它們有相同的端點。
另外,ServiceType必須是一個DB客戶端或緩存客戶端類別。注意,你不需要擔心術語“DB”或“緩存”,因為任何插件跟蹤一個客戶端庫與不可跟蹤的目標服務器可能會使用它們?!癉B”和“Cache”之間的唯一區(qū)別是響應時間直方圖的時間范圍(“Cache”在直方圖中間隔更小)。

2.3.4 Asynchronous task

跟蹤對象被綁定到第一次通過ThreadLocal創(chuàng)建它們的線程,每當執(zhí)行跨越線程邊界時,跟蹤對象就會丟失到新線程。因此,為了跨線程邊界跟蹤任務,必須負責將當前跟蹤上下文傳遞給新線程。這是通過將AsyncContext注入到由調(diào)用線程和執(zhí)行線程共享的對象中來實現(xiàn)的。
調(diào)用線程從當前跟蹤創(chuàng)建一個AsyncContext,并將其注入一個將被傳遞給執(zhí)行線程的對象中。然后,執(zhí)行線程從對象中檢索AsyncContext,從中創(chuàng)建一個新的跟蹤,并將其綁定到自己的ThreadLocal
因此,必須為兩個方法創(chuàng)建攔截器:

  • 一個用于初始化任務(調(diào)用線程)。
  • 另一個用于實際處理任務(執(zhí)行線程)。
    初始化方法的攔截器必須發(fā)出一個AsyncContext并將其傳遞給處理方法。如何傳遞這個值取決于目標庫。在最壞的情況下,可能根本無法通過它。
    然后,處理方法的攔截器必須使用傳播的AsyncContext繼續(xù)跟蹤,并將其綁定到自己的線程。但是,強烈建議簡單地擴展AsyncContextSpanEventSimpleAroundInterceptor,這樣不必手動處理了。
    請記住,因為共享對象必須能夠?qū)?code>AsyncContext注入其中,所以必須在類轉(zhuǎn)換期間使用AsyncContextAccessor添加字段??梢栽谶@里找到一個跟蹤異步任務的示例。

2.3.5 Case Study: HTTP

HTTP客戶機是調(diào)用遠程節(jié)點(客戶機)的方法的示例,而HTTP服務器是節(jié)點(服務器)的頂級方法的示例。如前所述,客戶端插件必須有將事務數(shù)據(jù)傳遞給服務器插件的方法來繼續(xù)跟蹤。注意,這個實現(xiàn)是依賴于協(xié)議的,HttpClient3插件的HttpMethodBaseExecuteMethodInterceptor和Tomcat插件的StandardHostValveInvokeInterceptor顯示了一個HTTP的工作示例:

  1. 將事務數(shù)據(jù)作為HTTP頭傳遞。您可以在這里找到標題名稱
  2. 客戶端插件記錄服務器的IP:PortdestinationId
  3. 客戶端插件將destinationId值作為頭傳遞給服務器作為Header.HTTP_HOST頭。
  4. 服務器插件Header.HTTP_HOST頭值作為acceptorHost
    必須記住的另一件事是,使用相同協(xié)議的所有客戶機和服務器必須以相同的方式傳遞事務數(shù)據(jù),以確保兼容性。因此,如果你正在編寫其他HTTP客戶端或服務器的插件,你的插件必須如上所述記錄和傳遞交易數(shù)據(jù)。
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容