Hello World
Spiga

使用IronPython检测ASP.NET程序状况(下)

2009-03-25 09:03 by 老赵, 26526 visits

上一篇文章中,我们在一个请求中执行了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程序状况(上)

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

21 条回复

  1. xuefly
    *.*.*.*
    链接

    xuefly 2009-03-25 00:10:00

    又抢到沙发啦
    ^_^

  2. 老赵
    admin
    链接

    老赵 2009-03-25 00:18:00

    @xuefly
    好吧,谢谢支持

  3. NormRen
    *.*.*.*
    链接

    NormRen 2009-03-25 08:31:00

    强大~

  4. Eri[未注册用户]
    *.*.*.*
    链接

    Eri[未注册用户] 2009-03-25 09:34:00

    我要是系统管理员,就用性能监视器+日志分析。

  5. 老赵
    admin
    链接

    老赵 2009-03-25 09:39:00

    @Eri
    任何一个成熟的维护团队都会有一套自定义的辅助工具,这样可以大大提高诊断效率。

  6. Indigo Dai
    *.*.*.*
    链接

    Indigo Dai 2009-03-25 09:46:00

    哇……这就是技术态度……

  7. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2009-03-25 10:59:00

    script.Execute(scope);

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

    这个次序你为什么不写成:
    TextWriter writer = new StringWriter();
    scope.SetVariable("logger", writer);
    script.Execute(scope);

    实际效果可能相同,但是看起来感觉有点怪.

  8. 老赵
    admin
    链接

    老赵 2009-03-25 11:05:00

    @木野狐(Neil Chen)
    其实这是看需求吧,我是先执行代码,再设一个logger变量,你得做法是先设再执行,这对目前来说没有区别,因为执行的代码里没有用到logger。
    我这么写没啥理由,就是个顺手,因为前面是先处理完很多Script相关逻辑,然后再放一个logger,呵呵。

  9. 第一控制.NET
    *.*.*.*
    链接

    第一控制.NET 2009-03-25 11:22:00

    难得看到老赵也有待求证的东西。我还以为你什么都会呢。。。。

  10. 老赵
    admin
    链接

    老赵 2009-03-25 11:29:00

    --引用--------------------------------------------------
    第一控制.NET: 难得看到老赵也有待求证的东西。我还以为你什么都会呢。。。。
    --------------------------------------------------------
    您是在讽刺我么……

  11. 木野狐(Neil Chen)
    *.*.*.*
    链接

    木野狐(Neil Chen) 2009-03-25 11:40:00

    @Jeffrey Zhao
    恩,主要是你的全局代码里没用到 logger.

  12. 第一控制.NET
    *.*.*.*
    链接

    第一控制.NET 2009-03-25 12:08:00

    @Jeffrey Zhao
    没有吧。只是好奇而已。

  13. 老赵
    admin
    链接

    老赵 2009-03-25 12:27:00

    --引用--------------------------------------------------
    第一控制.NET: @Jeffrey Zhao
    没有吧。只是好奇而已。
    --------------------------------------------------------
    hmmm……俺也是随便说说,呵呵。

  14. 韦恩卑鄙
    *.*.*.*
    链接

    韦恩卑鄙 2009-03-25 12:41:00

    我来吐槽的 老赵很希望有人讽刺 不然有点无聊

  15. 老赵
    admin
    链接

    老赵 2009-03-25 14:06:00

    --引用--------------------------------------------------
    韦恩卑鄙: 我来吐槽的 老赵很希望有人讽刺 不然有点无聊
    --------------------------------------------------------
    有道理有道理

  16. Eri[未注册用户]
    *.*.*.*
    链接

    Eri[未注册用户] 2009-03-25 16:01:00

    --引用--------------------------------------------------
    Jeffrey Zhao: @Eri
    任何一个成熟的维护团队都会有一套自定义的辅助工具,这样可以大大提高诊断效率。
    --------------------------------------------------------

    成熟的维护团队更应该知道性能计数器+日志分析的好处。
    我指的不是人工观察。

  17. 老赵
    admin
    链接

    老赵 2009-03-25 16:34:00

    @Eri
    兄弟我无话可说了,我从一开始就说我的这个做法是一个“补充”,从来没有说它是个“替代品”,不知道您怎么得出这样的感觉的……

  18. jilin[未注册用户]
    *.*.*.*
    链接

    jilin[未注册用户] 2009-03-26 20:57:00

    请教老赵:
    近一段时间看到一些有关函数式编程的字样,搜罗了一些资料看了一下,还是觉得有些云山雾绕,结合你的上下文来看,我觉得我还是不太清楚它的本质。因为据我看到的那些资料来看,好像动态语言就是函数式编程(比如闭包,匿名函数)。。

    老赵用自己的话解释一下吧。。

  19. 老赵
    admin
    链接

    老赵 2009-03-26 22:28:00

    @jilin
    与“函数式编程”对应的是“命令式编程”。
    关于这部分,建议您看一下这个:http://www.infoq.com/cn/minibooks/javascript-practise

  20. nulldust
    *.*.*.*
    链接

    nulldust 2009-03-27 13:45:00

    是否可以考虑 使用CodeDom直接动态执行C#代码来完成同样的事情?
    我自己写了一个Winform的例子。在Winform上用个Textbox来编写C#Code,
    然后,直接动态编译执行。
    Microsoft.CSharp.CSharpCodeProvider provider = new CSharpCodeProvider();
    CompilerResults result = provider.CompileAssemblyFromSource() 。。。
    可以把当前form对象传给界面的动态代码。在界面上可以对form进行仍何的操作修改,当然对于私有的数据成员,可以用反射来操作。获取系统信息也是很方便。


  21. 老赵
    admin
    链接

    老赵 2009-03-27 13:48:00

    @nulldust
    上一篇文章中提到过这个方法,选择IronPython算是尝试一下新的东西。

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我