App效能監控-newRelicSDK使用及攔截上報方案
隨著應用的發展越來越大,產品的開發和測試逐漸開始關注app的線上執行效能。最近一直在關注這個方向,說一下自己的心得和體會。
所謂的效能監控,並不是做出一堆花哨的圖表讓人賞心悅目。其實目的只有兩個,一個是能夠實時發現線上的問題,通過報警機制通知到相關負責人;另外一個就是能夠看到歷史資料,供測試和開發進行效能優化。那麼監控的關鍵是確定監控指標,在監控指標的指導下確定需要客戶端打點收集的日誌。
APP效能監控指標
最近調研了一下這個方向,行業內把這個方向稱為APM(application performance management)。國內外都有不少公司在做這方面的產品,一些大公司為了防止應用監控資料的外洩,也會自行開發效能監控管理平臺。國內做得比較好的如基調、博睿、雲測、聽雲等,國外有New Relic、Blueware、OneAPM等。 各個平臺的技術方案大同小異(這裡主要說的是IOS平臺),提供一個整合到應用的SDK(static framework 或者 .a 檔案), 然後收集日誌上報到其後臺進行資料處理和緯度分析。這些平臺的商業策略一般都是先免費提供試用,再進行租用收費的方式。
APP的效能指標主要分為兩大類:InterActions(UI互動) 和 Network (網路連線)。在這兩個大分類下,會有如下細分指標:
InterActions:(按時間-版本:+device型別,+os型別)
- APP啟動次數、APP啟動平均時間;
- Controller display次數、平均display時間;
- Controller display過程中各個method(包括UI thread、worker thread中的method)的平均執行時間;
Network方面:(按時間-版本:+域名,+地區,+運營商, +連線網路型別)
- 平均相應時間
- rpm(一分鐘請求次數)
- 總耗時
- 傳輸資料大小
- 再具體到某個API介面:
>
(1) 平均http響應時間 (區分web Application(webview) 和 network);
(2) rpm;
(3) 平均資料傳輸大小(區分send和receive);
在兩大指標上會增加如下方面的維度: 接入網路、運營商、裝置、系統版本、地區、app版本、統計時間;
NewRelic SDK使用說明
我主要關注了國內的聽雲平臺和國外的NewRelic平臺。國內聽雲的統計平臺註冊只能看到非常基礎的統計,一點細分分析維度,就提示你升級套餐,當然你可以申請14天免費試用,然後就是市場給你打電話過來。 其SDK中的framework也只提供一個頭檔案,想要對統計資料的定製基本上沒有。於是果斷切換到國外的newRelic平臺,newRlic平臺需要vpn訪問才能快點,一註冊所有的功能都能夠試用,當然試用期也只有14天。
關注一下SDK的使用,SDK提供了眾多可配置的標頭檔案,讓你能夠更加清楚的瞭解sdk提供的功能;
(1)最基礎的使用,當然就是通過Apptoken註冊使用:(注意一下,newRelic整合在debug時會檢查當前應用中hook的使用,需要將newrelic的註冊使用放到所有hook之後,一般放到appdidfinishLaunch靠後的位置)
#import <NewRelicAgent/NewRelic.h>
[NewRelicAgent startWithApplicationToken:@"token"];
如果是最基礎的用法,我們可以通過charles攔截其資料上報,可以發現request和response的資料都是通過ssl加密、https傳輸,看不到明文資料。如果需要看到明文資料可以使用非加密介面+ (void)startWithApplicationToken:(NSString*)appToken withoutSecurity:(BOOL)disableSSL
(2) SDK集成了各種指標收集,包括Interaction、SwiftInteraction、Crash、NSURLSession、HTTPResponse等。如下程式碼所示:
typedef NS_OPTIONS(unsigned long long, NRMAFeatureFlags){
NRFeatureFlag_InteractionTracing = 1 << 1,
NRFeatureFlag_SwiftInteractionTracing = 1 << 2, //disabled by default
NRFeatureFlag_CrashReporting = 1 << 3,
NRFeatureFlag_NSURLSessionInstrumentation = 1 << 4,
NRFeatureFlag_HttpResponseBodyCapture = 1 << 5,
NRFeatureFlag_ExperimentalNetworkingInstrumentation = 1 << 13, //disabled by default
NRFeatureFlag_AllFeatures = ~0ULL //in 32-bit land the alignment is 4bytes
};
你可以按照自己的需要指定需要收集的指標資料,使用+ (void) enableFeatures:(NRMAFeatureFlags)featureFlags; 或 + (void) disableFeatures:(NRMAFeatureFlags)featureFlags;
.
(3) 關於Interaction的主要方案就是hook controller中的各個生命週期方法,NewRelicSDK中主要攔截瞭如下方法:
UIViewController
- viewDidLoad
- viewWillAppear:
- viewDidAppear:
- viewWillDisappear:
- viewDidDisappear:
- viewWillLayoutSubviews
- viewDidLayoutSubviews
UIImage:
- imageNamed:
- imageWithContentsOfFile:
- imageWithData:
- imageWithData:scale:
- initWithContentsOfFile:
- initWithData:
- initWithData:scale:
NSJSONSerialization:
- JSONObjectWithData:options:error:
- JSONObjectWithStream:options:error:
- dataWithJSONObject:options:error:
- writeJSONObject:toStream:options:error:
NSManagedObjectContext
- executeFetchRequest:error:
- processPendingChanges
(4)在ARC模式下,你也可以使用如下巨集攔截你想統計的應用中關鍵方法:
- (void)myMethod
{
NR_TRACE_METHOD_START(0);
// … existing code
NR_TRACE_METHOD_STOP;
}
newRelic SDK的資料攔截上報
newRelic SDK已經做得非常精緻,但是如果我們不想把資料上報到第三方平臺管理,那應該怎麼辦呢,自己去研發一套,耗時費力,而且還費力不討好。那何不做一個數據攔截,把資料轉發到我們自己的後臺平臺呢?
static framework public的標頭檔案並不包括上傳class的標頭檔案,但是當網路不好或者沒有網的時候,我們可以從控制檯列印上報資料失敗的日誌看到一些端倪,我們可以找到上傳日誌的方法所在的類為“NRMAHarvesterConnection”,如果是ssl上傳,日誌為send方法,如果是非ssl上傳,日誌中顯示為sendData方法;
為了更好的知道類中方法的分佈,我們可以用class-dump-z工具解析出SDK中所有的標頭檔案。 class-dump-z是不能直接dump framework中的標頭檔案,解析出來的標頭檔案中會有一個source 為null的提示。 一開始覺得可能是因為framework做了加密處理,找了一個越獄機器在cydia中安裝了openssh-how-to工具,通過sftp把framework和Clutch上傳到越獄機器中,開始用Clutch進行解密,發現Clutch並不能直接解析,也不能解析debug安裝的除錯app。
最後終於將集成了newRelic SDK的的app 通過archive打包,匯出一個ipa。解壓出app目錄之後,再用class-dump-z工具對app目錄的二進位制檔案進行標頭檔案匯出,這時候我們就能夠看到sdk中的所有標頭檔案了;
我找到“NRMAHarvesterConnection”檔案,如下所示,可以看到其中的sendData方法。
#import <XXUnknownSuperclass.h> // Unknown library
@class NRMAConnectInformation, NSString;
@interface NRMAHarvesterConnection : XXUnknownSuperclass {
BOOL _useSSL;
NRMAConnectInformation* _connectionInformation;
NSString* _collectorHost;
NSString* _applicationToken;
NSString* _crossProcessID;
long long _serverTimestamp;
}
@property(assign) BOOL useSSL;
@property(retain) NSString* crossProcessID;
@property(assign) long long serverTimestamp;
@property(retain) NSString* applicationToken;
@property(retain) NSString* collectorHost;
@property(retain) NRMAConnectInformation* connectionInformation;
-(void).cxx_destruct;
-(id)collectorHostURL:(id)url;
-(id)collectorHostDataURL;
-(id)collectorConnectURL;
-(id)createDataPost:(id)post;
-(id)createConnectPost:(id)post;
-(id)sendData:(id)data;
-(id)sendConnect;
-(id)send:(id)send;
-(id)createPostWithURI:(id)uri message:(id)message;
-(id)init;
@end
為了更好的瞭解方法的輸入和輸出引數,我使用了阿里最近開源的ali-wax的lua除錯工具進行了sendData的方法的輸入輸出瞭解, 我們可以看到輸入引數data的class型別,是一個“NRMAHarvestData”,我們同樣可以找到其標頭檔案,查詢到裡面的JSONObject方法,可以把data轉化成一個json物件。 得到這個物件了我們就可以將資料轉發到我們自己的平臺,並偽造一個http上傳成功或失敗的物件給sendData方法進行記憶體資料刪除處理即可。
waxClass{"NRMAHarvesterConnection"}
--如果需要上報,攔截這個方法即可
function sendData(self,data)
print(data:class())
jsonObject = data:JSONObject()
self:ORIGsendData(data)
end
注:這裡我只提供一種方案,公司產品並沒有採用這種方法,因為我們自己的效能監控雖然資料粒度不夠,已經足夠日常查詢問題使用了。更多是分享一下取巧的思路,以及紀錄一下破解的過程。