程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> CLR全面透徹解析: CLR 4中的生產診斷改進

CLR全面透徹解析: CLR 4中的生產診斷改進

編輯:關於.NET

在公共語言運行庫 (CLR) 團隊,我們有個小組專門提供 API 和服務,供其他人構建托管代碼的診斷 工具。我們所擁有(就專用的工程資源而言)的兩個最大組件是托管調試和分析 API(分別是 ICorDebug* 和 ICorProfiler*)。

與其他 CLR 和框架團隊類似,我們也只能通過努力開發應用程序實現我們的價值。例如,Visual Studio 團隊會將這些調試和分析 API 用於他們的托管調試器和性能分析工具,還有大量第三方開發人員 在使用分析 API 構建工具。

在過去 10 年中,這個領域主要關注(無論是對於 CLR 還是 Visual Studio)在開發人員的桌面系統 上實現功能:在調試器中逐步運行源代碼以找到代碼中的錯誤;在性能探查器下啟動應用程序來找出速度 低的代碼路徑;使用 “編輯並繼續”功能減少“編輯-構建-調試”周期所花時間等等。這些工具有助於 在應用程序已經安裝到用戶的計算機或部署到服務器後(這兩種情況以下將統稱生產)查找其中的錯誤, 我們有許多第三方供應商會以我們的工作為基礎構建世界一流的生產診斷工具。

不過,客戶和這些供應商也一直在向我們提供反饋,強調進一步簡化應用程序整個生命周期的錯誤查 找過程的重要性。畢竟,在應用程序生命周期中發現軟件錯誤的時間越晚,修復代價就越高昂,這一點已 成共識。

針對這類反饋意見,我們付出了大量努力,並開始將我們的診斷 API 支持的范疇向這一頻譜的生產端 加以擴展,CLR 4(支撐 Microsoft .NET Framework 4 的運行庫)正是我們的工作成果的第一次發布。

在本文中,我將討論我們目前認為特別棘手的一些情況、我們正在采取的解決方式以及它們提供的工 具類型。具體而言,我將闡述我們如何發展了調試 API,從而對應用程序崩潰以及掛起情況進行轉儲調試 ;我還要闡述我們如何簡化了對多線程問題導致的掛起進行檢測的過程。

我還將說明,對已經運行的應用程序附加分析工具這項功能可如何進一步簡化對這類情況進行故障排 除的過程,並大大減少內存消耗過量所引發問題所需的診斷時間。

最後,我將簡要說明我們如何通過消除對注冊表的依賴而簡化了對分析工具的部署過程。整篇文章重 點都在介紹作為我們工作成果的新工具類型,不過,為了幫助您了解可如何利用我們通過 Visual Studio 發布的工作成果,我也根據情況討論了其他一些資源。

轉儲調試

托管轉儲調試是我們將隨 Visual Studio 2010 推出的一項常用功能。流程轉儲(一般簡稱為轉儲) 通常用於對本機代碼和托管代碼進行生產調試。從本質上講,轉儲就是特定時間點進程狀態的快照。具體 來說,它就是轉儲到文件中的進程虛擬內存(或其中的某些子集)的內容。

在 Visual Studio 2010 之前,為了調試轉儲中的托管代碼,需要使用專門的 Windows 調試器擴展程 序 sos.dll,對轉儲內容進行分析,而無法使用 Visual Studio 等更熟悉的工具(在產品開發過程中, 您可能會在其中編寫和調試您的代碼)。當您在 Visual Studio 中使用轉儲診斷問題時,我們希望您能 獲得類似於停止狀態實時調試的高層次體驗,也就是說,類似於您對代碼進行調試,並在斷點處停止。

通常會在應用程序中發生未處理異常(崩潰)時收集轉儲。您使用轉儲找出崩潰發生的原因,一般情 況下需要先著手查看出錯線程的調用堆棧。發生應用程序掛起和內存使用問題時也會用到轉儲。

例如,如果您的網站已停止處理請求,您可以附加調試器、收集轉儲,然後重新啟動應用程序。例如 ,對轉儲進行離線分析可能表明,您所有處理請求的線程都在等待連接到數據庫;或者,您可能會在代碼 中發現死鎖。從最終用戶的角度來看,內存使用問題可有多種表現形式:因過量收集垃圾而導致應用程序 速度變慢;因應用程序耗盡虛擬內存而導致服務中斷,需要重新啟動,等等。

調試 API 通過 CLR 2 僅支持調試正在運行的進程,這使得工具很難定位我們剛剛講述的情景。從本 質上講,API 在設計時就沒有考慮轉儲調試這類情況。API 使用運行於目標進程中的幫助器線程處理調試 器請求,這一事實突出說明了這一點。

例如,在 CLR 2 中,如果托管調試器要遍歷某線程的堆棧,它會向所調試進程中的幫助器線程發送請 求。此進程中的 CLR 會處理請求,並將結果返回調試器。由於轉儲只是一個文件,這種情況下沒有幫助 器線程來處理請求。

為了解決調試轉儲文件中的托管代碼的問題,我們需要構建不需要在目標中運行代碼即可檢查托管代 碼狀態的 API。然而,為提供實時調試,調試器編寫器(主要是 Visual Studio)已經在 CLR 調試 API 方面進行了大量投入,我們不希望強制使用兩種不同的 API。

我們在 CLR 4 中重新實現了許多調試器 API(主要是代碼和數據檢查所需的 API),以避免再使用幫 助器線程。其結果就是,現有 API 不再需要考慮目標是轉儲文件還是有效進程。此外,調試器編寫器能 夠使用同一 API 定位實時調試和轉儲調試兩類情況。當執行控制明確是在進行實時調試時(設置斷點, 逐步執行代碼),調試 API 仍會使用幫助器線程。從長遠來說,我們還是傾向於消除這類情形下的依賴 性。Rick Byers(以前的一名調試服務 API 開發人員)有一篇很有用的博客帖子,較為詳細地介紹了這 方面的工作,帖子地址是 blogs.msdn.com/rmbyers/archive/2008/10/27/icordebug-re-architecture- in-clr-4-0.aspx。

您現在可以使用 ICorDebug 檢查轉儲文件中的托管代碼和數據:遍歷堆棧、枚舉局部變量、獲取異常 類型等。對於崩潰和掛起,往往能從線程堆棧和輔助數據中找到足夠的上下文,以便查找問題的原因。

盡管我們知道內存診斷和其他方案也很重要,只是在 CLR 4 時間表中,我們根本沒有足夠的時間來構 建新的 API,讓調試器有能力以這一情形所要求的方式檢查托管堆。展望未來,隨著我們不斷擴大我們所 支持的生產診斷范疇,我希望我們能增加此類內容。在本文後面的部分,我將討論我們所進行的其他有助 於解決此類問題的工作。

我還想明確指出,這項工作同時支持 32 位和 64 位目標,同時支持僅托管調試和混合模式(本機和 托管)調試。Visual Studio 2010 為包含托管代碼的轉儲提供了混合模式。

監控器鎖檢查

多線程編程可能會遇到很多困難。無論您是顯式編寫多線程代碼,還是利用框架或庫為您完成這項工 作,診斷異步和並行代碼中的問題都相當具有挑戰性。如果您的邏輯工作單元是在單線程上執行,理解因 果關系要簡單得多,通常只要查看線程的調用堆棧即可確定。但是,當這一工作被劃分到多個線程中,跟 蹤流向就要困難得多:為什麼這項工作沒有完成?是它的某一部分在某個地方被阻止了?

隨著多核技術的應用越來越普遍,開發人員越來越多地依靠並行編程提高性能,而不再完全依靠芯片 速度的進步。Microsoft 開發人員也不例外,因此,在過去的幾年裡,我們一直在集中精力,幫助開發人 員在這一領域取得成功。從診斷的角度出發,我們增加了幾個簡單卻很實用的 API,可以讓工具幫助開發 人員更好地應對復雜的多線程代碼。 

對於 CLR 調試器 API,我們添加了對監測鎖的檢查 API。簡單地說,監控器提供了一種方式,使程序 可以實現多個線程訪問共享資源(.NET 代碼中的某個對象)的同步。因此,當一個線程已將資源鎖定時 ,另一個線程會等待解鎖。當擁有鎖的線程釋放它時,第一個處於等待狀態的線程現在可以獲取此資源。

在 .NET Framework 中,監控器是通過 System.Threading.Monitor 命名空間直接公開的;而在 C# 和 Visual Basic 中,更常見的是分別通過鎖和 SyncLock 關鍵字公開。它們還用於實現同步方法、任務 並行庫(Task Parallel Library,TPL)和其他異步編程模型。新的調試器 API 可讓您更好地了解有哪 個對象(如果有的話)阻止了給定線程,以及哪個線程擁有給定對象的鎖(如果有的話)。利用這些 API ,調試器可以幫助開發人員找出死鎖,並了解何時多線程競爭資源(鎖保護)可能會影響應用程序的性能 。

Visual Studio 2010 中的並行調試功能可作為這項工作所推出的工具類型的一個示例。Daniel Moth 和 Stephen Toub 在 2009 年 9 月這期的 MSDN 雜志 (msdn.microsoft.com/magazine/ee410778) 中對 這些功能有精彩的概述。

就轉儲調試工作而言,最令我們興奮的事情之一是,構建調試目標的抽象視圖就意味著增加“監控-鎖 定-檢查”功能等新的檢測功能,這項功能為實時調試和轉儲調試兩種情況都提供了價值。我認為“監控- 鎖定-檢查”功能在開發人員最初開發應用程序時對他們非常有價值,對轉儲調試的支持更是使得它成為 CLR 4 生產診斷功能中增加的一項引人注目的功能。

Microsoft 支持工程師 Tess Ferrandez 在一段 Channel 9 視頻 (channel9.msdn.com/posts/Glucose/Hanselminutes-on-9-Debugging-Crash-Dumps-with-Tess- Ferrandez-and-VS2010/) 中模擬了她在對客戶應用程序進行故障排除時經常遇到的鎖保護情形。然後, 她逐步引導用戶使用 Visual Studio 2010 來診斷此問題。這一示例很好地說明了這些新功能所實現的應 用類型。

超越轉儲

雖然我們相信這些功能所實現的工具有助於減少開發人員解決生產問題所需的時間,我們不認為(或 期望)轉儲調試會是診斷生產問題的唯一方式。

在診斷過量使用內存問題時,我們通常首先會按類型對對象實例進行分組,列出各個實例及其計數、 聚合規模和進展,從而理解對象的引用鏈。在生產用計算機、開發用計算機、操作人員、技術支持工程師 和開發人員之間來回傳遞包含這一信息的轉儲文件,這類過程可能非常耗時、費力。隨著應用程序的規模 增長(對於 64 位應用程序尤其普遍),轉儲文件的規模也會增長,傳遞和處理都需要更長的時間。正是 考慮到這些高層次應用,我們承擔了在以下部分中描述的分析功能的開發。

分析工具

在 CLR 分析 API 的基礎上構建了多種不同類型的工具。涉及分析 API 的情況一般都集中關注三個功 能類別:性能、內存和工具化。

性能探查器(類似於某些 Visual Studio 版本中的探查器)集中關注代碼如何占用時間。內存探查器 詳細說明應用程序的內存使用情況。工具化探查器負責其余所有部分。

讓我們簡單說明一下最後一句。分析 API 提供的功能之一是在運行時將中間語言 (IL) 插入托管代碼 。我們稱之為代碼工具化。客戶可使用此功能來構建工具,針對基於 .NET Framework 的應用程序實現從 代碼覆蓋到錯誤注入到企業級生產監控等一系列應用場景。

相對於調試 API,分析 API 的優勢之一是,它有相當輕量級的設計。兩者都是事件驅動的 API,例如 ,在這兩個 API 中都有程序集加載、線程創建、異常引發等事件;但就分析 API 而言,您可以只注冊自 己關心的事件。此外,分析 DLL 會加載到目標進程中,以確保快速訪問運行時狀態。

與此相反,調試器 API 會將每一個事件報告給進程外調試器(如有附加的話),並在發生每一個事件 時都掛起運行庫。在針對生產環境構建在線診斷工具方面,分析 API 是一個很有吸引力的選擇,而其原 因還有很多,這些僅僅是其中的幾個。

探查器附加和分離

盡管一些供應商正在通過 IL 工具化構建永不間斷的“生產-應用-監控”工具,我們目前沒有很多的 工具可利用分析 API 中的性能和內存監控功能,以支持被動診斷。一直以來,這種情況下的主要障礙都 是無法將基於 CLR 分析 API 的工具附加到已經運行的進程。

在 CLR 4 之前的版本中,CLR 都會在啟動過程中檢查是否有探查器注冊。如果發現有注冊的探查器, CLR 會進行加載並根據請求提供回調。DLL 永遠不會卸載。如果該工具的目的是從端到端的角度了解應用 程序的行為,這種方式的效果通常還不錯;不過,對於應用程序啟動時已經存在但您沒有覺察的問題,它 是無效的。

也許內存使用診斷是這方面最令人棘手的一個例子。今天,在這種情況下,我們常常會看到以下診斷 模式:收集多個轉儲,查看相互之間所分配類型之間的差異,構建增長時間線,然後在應用程序代碼中找 到引用可疑類型的地方。也許問題出在緩存方案實施效果不良,或某一類型的事件處理程序可能持有了對 另一類型的引用,否則將超出范圍。客戶和支持工程師會花很多時間診斷這類問題。

首先,我在上面簡要地說明過,規模較大的進程的轉儲本身也較大,將它們拿到專家處進行診斷,可 能需要延誤很久才能解決。此外,主要歸因於只能通過 Windows 調試器擴展程序公開數據,您將不得不 求助於專家解決此類問題。沒有公共 API 實現工具對數據的使用,或在此基礎上構建直觀的視圖,或將 其與其他工具相集成,為分析提供幫助。

為有助於解決這種情況下(和一些其他情況下)的問題,我們增加了新的 API,允許探查器附加到正 在運行的進程,並充分利用現有分析 API 的子集。附加到進程後,可以實現的功能有采樣(請參閱“VS 2010:將探查器附加到托管應用程序”,網址為 blogs.msdn.com/profiler/archive/2009/12/07/vs2010-attaching-the-profiler-to-a-managed- application.aspx)和內存診斷:遍歷堆棧;將函數地址映射到符號名稱;大多數的垃圾回收器 (GC) 回 調;對象檢查。

在我剛才所描述的情形中,工具可以利用此功能,讓客戶附加到一個響應時間變長或內存增長過量的 應用程序,了解當前正在執行哪些功能,托管堆上有哪些有效類型,以及什麼原因使它們保持有效狀態。 收集此信息後,您可以分離工具,而 CLR 將卸載探查器 DLL。盡管工作流的其余部分與此類似(即查找 代碼中引用或創建這些類型的位置),我們還是認為這種性質的工具能對這些問題的平均解決時間產生相 當大的影響。作為 CLR 分析 API 開發人員的 Dave Broman 在他的博客 (blogs.msdn.com/davbr) 上更 為深入地討論了這個問題和其他分析功能。

不過,我希望在這篇文章中明確指出兩項限制。首先,附加式內存診斷僅限於非並發式(或攔截式) GC 模式:執行某個 GC 時,GC 會掛起對所有托管代碼的執行。雖然默認情況下會采用並發 GC,ASP.NET 使用的卻是非並發模式的服務器模式 GC。在這一環境中,我們看到了這些問題中的大部分問題。我們欣 賞“附加-分析”診斷對客戶端應用程序的作用,希望在未來版本中推出這一功能。我們只是優先解決了 CLR 4 的更常見問題。

其次,附加後您無法使用分析 API 來實現 IL 的工具化。如果我們的企業監控工具供應商希望根據運 行時狀況動態改變工具化功能,此功能尤其重要。我們通常將此功能稱為 re-JIT。目前,當方法的 IL 正文第一次進行 JIT 編譯時,您有機會對其加以改變。我們將推出 re-JIT 看作是一項有重大意義的任 務,正積極調查這項功能所能產生的客戶價值以及對技術的影響力,從而考慮在未來版本中推出這項工作 成果。

探查器的免注冊表激活

探查器附加以及附加後對啟用具體分析 API 的支持工作是我們在 CLR 4 的 CLR 分析 API 中完成的 最大一項工作。不過,看到另一項非常小(就工程成本而言)的功能能對客戶構建生產級工具產生令人驚 訝的巨大影響,我們也很高興。

在 CLR 4 之前,工具為獲取在托管應用程序中加載的探查器 DLL,必須創建兩方面的關鍵配置信息: 首先是一對環境變量,指示 CLR 啟動分析功能以及在 CLR 啟動時加載何種探查器實現方式(它的 CLSID 或 ProgID)。鑒於探查器 DLL 是作為進程內 COM 服務器實現的(CLR 是客戶端),配置數據的第二部 分是存儲在注冊表中的相應 COM 注冊信息。這主要是通過 COM 告知運行庫在磁盤的何處可以找到 DLL。

在 CLR 4 規劃過程中,我們盡力了解我們可如何簡化供應商構建生產級工具的過程,在此期間,我們 同一些支持工程師和工具供應商進行了幾次交談,內容很有趣。我們收到的反饋是,當在生產中面臨應用 程序失敗時,客戶通常較少關心對應用程序(它已經失敗了;他們只想獲得診斷信息,使操作繼續進行) 的影響,更多地是關心對機器狀態的影響。許多客戶會不遺余力地記錄並檢查他們的機器配置,而對該配 置加以更改可能引入某些風險。

因此,就解決方案的 CLR 部分而言,我們希望可以啟用通過 XCopy 進行部署的診斷工具,如此既最 大限度地減少了狀態變化的風險,同時又縮短了工具在機器上啟動並運行所需的時間。

我們對 CLR 4 的設置是,不需要再在注冊表中注冊 COM 探查器 DLL。如果您要在探查器下啟動您的 應用程序,我們仍然需要環境變量;但對於上文詳述的附加情況沒有配置要求。工具只是調用新的附加 API,將路徑傳遞給 DLL,CLR 即會加載探查器。因為某些情況下客戶仍會發現環境變量解決方案很棘手 ,未來我們要研究如何進一步簡化探查器的配置。

將我剛才討論的兩項分析功能相結合,可以推出一類低開銷工具。在突發故障時,客戶可以將這類工 具迅速部署到生產機器,以收集性能或內存診斷信息,幫助查明問題的原因。然後用戶就可以盡快使機器 恢復到原始狀態。

總結

為 CLR 開發診斷功能的工作苦樂參半。我目睹過多次客戶如何努力解決某些問題,深感欣慰的是:我 們總能完成夠酷的工作,可以簡化開發過程,讓客戶更輕松愉快。如果我們客戶感到棘手的問題列表沒有 改變的話(也就是說,我們沒有從中刪除項目),那麼,我們就不算成功。

我認為我們在 CLR 4 中所構建的功能不僅為解決客戶所面臨的一些最緊迫的問題提供了立竿見影的價 值,而且還為我們未來繼續推出其他功能奠定了基礎。展望未來,我們將繼續在 API 和服務上加大投入 ,以便在應用程序生命周期的各個階段提供有意義的診斷信息,讓您可以集中精力為您的客戶構建新功能 ,而減少調試現有應用程序所花的時間。

如果您有興趣對我們要在下一個版本考慮的診斷工作提供反饋,請參與我們在 surveymonkey.com/s/developer-productivity-survey 發布的一項調查。由於我們現在要推出 CLR 4, 要花費大量時間規劃即將發布的新版本,調查所得數據可以幫助我們區分工作的輕重緩急。如果您能花幾 分鐘寫下您的建議,我們不勝感激!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved