使用IronPython檢測ASP.NET程序狀況(下)

作者: Jeffrey Zhao  來源: 博客園  發布時間: 2009-03-25 08:31  閱讀: 959 次  推薦: 0   原文鏈接   [收藏]  

  在上一篇文章中,我們在一個請求中執行了IronPython代碼,通過這個方法我們可以輕松地的檢查系統運行的狀態,或對系統進行一些簡單修改。但是這種做法只能檢查系統在當前時刻的狀態,在很多情況下,我們需要對系統的請求進行一段時間的采樣。對于簡單的數據(例如每秒執行的請求數量,請求時間),我們可以通過查看Performance Monitor中相關的計數器來獲得一些概要的數據。但是,如果我們需要獲取一些系統的詳細狀態,甚至是需要根據需要進行動態改變的自定義需求,則勢必要深入到系統內部進行數據采集。因此,我們可能需要讓代碼執行“一段時間”,并將直接結果進行匯總輸出。

  讓代碼執行一段時間不難,簡單地使用Thread.Sleep便可,也不會造成什么性能或吞吐量上的損失。關鍵就在于,在代碼停留的這“一段時間”內,我們使用什么樣的做法來采集數據。這時候,老趙腦海中立即浮現出的便是HttpModule般監聽請求管道(Pipeline)中的各式事件。于是立馬寫出以下的代碼(在前文的示例基礎上進行修改):

protected void btnExecute_Click(object sender, EventArgs e)
{
    ScriptEngine engine = Python.CreateEngine();
    var scope = engine.CreateScope();
    var script = engine.CreateScriptSourceFromString(
        this.txtCode.Text, SourceCodeKind.Statements);
    script.Execute(scope);

    TextWriter writer = new StringWriter();
    scope.SetVariable("logger", writer);

    Action<HttpContext> traceRequest;
    if (scope.TryGetVariable<Action<HttpContext>>("traceRequest", out traceRequest))
    {
        Action endTraceRequests;
        scope.TryGetVariable<Action>("endTraceRequests", out endTraceRequests);

        int waitTime;
        if (!scope.TryGetVariable<int>("waitTime", out waitTime))
        {
            waitTime = 10000;
        }

        this.TraceRequests(traceRequest, waitTime, endTraceRequests);
    }

    this.txtOutput.Text = writer.ToString();
}

private void TraceRequests(Action<HttpContext> traceRequest, int waitTime, Action endTraceRequests)
{
    EventHandler handler = (sender, e) =>
    {
        try
        {
            traceRequest((sender as HttpApplication).Context);
        }
        catch { }
    };

    this.Context.ApplicationInstance.BeginRequest += handler;
    Thread.Sleep(waitTime);
    this.Context.ApplicationInstance.BeginRequest -= handler;

    if (endTraceRequests != null) endTraceRequests();
}

  再編譯了IronPython代碼之后,我們會設法獲取其中的traceRequest和endTraceRequests函數,前者用于“記錄每個請求”,而后者用于采樣最后的“聚合”。此外,還會設法從代碼中獲取等待時間waitTime。然后,使用TraceRequest方法開始對當前請求進行采樣。具體做法為監聽當前Application的BeginRequest事件,并在每次獲得請求時調用traceRequest委托進行“記錄”。在等待時間過后,自然將委托從BeginRequest事件中剝離。最后,再通過endTraceRequests函數進行聚合輸出。

  代碼邏輯很清晰,但可惜的是,上面這段代碼不能生效。具體原因不明,可能是ASP.NET對這方面進行了限制,使得我們無法在HttpModule之外為請求管道動態添加事件處理函數(存疑,求證)。對此我們只能進行讓步。不過,既然ASP.NET允許HttpModule監聽管道事件,那么我們不如事先準備一個HttpModule監聽各種事件,并且在合適的時候把這一事件轉發給IronPython函數。我們這里還是以BeginRequest事件為例:

public class IronPythonTraceModule : IHttpModule
{
    private class TraceRequestEventArgs : EventArgs
    {
        public TraceRequestEventArgs(HttpContext context)
        {
            this.HttpContext = context;
        }

        public HttpContext HttpContext { get; private set; }
    }

    public void Dispose() { }

    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(OnBeginRequest);
    }

    static void OnBeginRequest(object sender, EventArgs e)
    {
        var traceRequest = IronPythonTraceModule.TraceRequest;
        if (traceRequest != null)
        {
            var context = (sender as HttpApplication).Context;
            traceRequest(null, new TraceRequestEventArgs(context));
        }
    }

    private static event EventHandler<TraceRequestEventArgs> TraceRequest;

    public static void TraceRequests(
        Action<HttpContext> traceRequest,
        Action endTraceRequests,
        int milliseconds)
    {
        EventHandler<TraceRequestEventArgs> handler = (sender, e) =>
        {
            try
            {
                traceRequest(e.HttpContext);
            }
            catch { }
        };

        IronPythonTraceModule.TraceRequest += handler;
        Thread.Sleep(milliseconds);
        IronPythonTraceModule.TraceRequest -= handler;

        if (endTraceRequests != null) endTraceRequests();
    }
}

  IronPythonTraceModule需要放入應用程序中,它時刻對請求管道的BeginRequest進行監聽,只是在合適的時候才會發起TraceRequest靜態事件(顯然這并不會對系統性能造成什么影響)。Module包函一個靜態的TraceRequests方法,這便是給外部調用的接口。可以發現這段代碼和之前的TraceRequests方法非常接近,唯一不同的只是動態添加/刪除處理函數的事件是IronPythonTraceModule.TraceRequest,而不是HttpApplication.BeginRequest。于是,原來的代碼也需要做一定修改:

protected void btnExecute_Click(object sender, EventArgs e)
{
    ...

    Action<HttpContext> traceRequest;
    if (scope.TryGetVariable<Action<HttpContext>>("traceRequest", out traceRequest))
    {
        Action endTraceRequests;
        scope.TryGetVariable<Action>("endTraceRequests", out endTraceRequests);

        int waitTime;
        if (!scope.TryGetVariable<int>("waitTime", out waitTime))
        {
            waitTime = 10000;
        }

        // 以下代碼有所修改
        IronPythonTraceModule.TraceRequests(traceRequest, endTraceRequests, waitTime);
    }

    this.txtOutput.Text = writer.ToString();
}

  現在我們進行一番測試,簡單地檢測一下5秒鐘內收到 了多少請求:

  使用自己的Module進行處理還有其他一些好處,比如可以提供更好的控制。事實上,目前文章里的解決方案有一些缺陷,并且肯定無法完全滿足真實需求。不過我們完全可以對目前的做法進行改進,例如:

  • 保證代碼執行的線程安全,包括事件添加時和traceRequest函數執行時。
  • 提供“采樣特性”,而不是對每個請求都使用traceRequest函數進行處理。
  • 將Logger放入公用的空間,并在程序代碼中植入檢查功能,這樣便可以得知當前系統中每個功能的執行時間(這就是Profiler的功能,不是嗎?)。

  不過,除了應對前一篇文章中所提到的負載均衡環境下的問題之外,這個解決方案還有另一個較為重要的情況需要特殊對待。如果使用目前的做法,每次采樣都是通過一個請求進行的,所以它并不會在在請求隊列阻塞時立即執行,但是采樣的常見場景便是在隊列阻塞時間檢查狀況,這顯然形成了一個矛盾。不過要解決這個問題并非難事,只要采樣不要通過IIS即可。例如您使用普通Socket,或“偷偷懶”使用WCF的TcpBinding進行采樣便可——只要和被檢查的應用程序在同一進程(即w3wp.exe,還有人用IIS5 嗎?)中,便可以使用任何方式進行通信。

  改變、探索的過程,其實都是在追求一種編程的美感。老趙喜歡“不拘一格”,只要是有價值的都會去設法嘗試一番,探索的過程會遇到大量問題,解決問題后又能帶來成就感以及新的感受。因為美感和成就感,所以產生樂趣,因為樂趣才有源源不斷地動力。因為有這樣的切身體會,所以在這里也建議大家,不妨放開思路,海納百川。我們可以獲得和想到的東西,遠比預料中來的豐富。

  相關文章:使用IronPython檢測ASP.NET程序狀況(上)

 

0
0
 
標簽:IronPython
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()