前言

大家可能对诊断工具并不陌生,从大名鼎鼎的 dotTrace,到 .NET CLI 推出的一系列的高效诊断组件(dotnet trace,dotnet sos,dotnet dump)等, 这些工具提升了对程序Debug的能力和效率,可以让开发人员从更高层次的维度来发现程序中的问题。

今天我们针对于.NET Core, 尝试动手实现一个简单的诊断工具,在保证对程序无侵入(不修改代码和配置)的前提下,我们尝试获取程序的运行信息,包括内存,线程,垃圾回收,异常等。

这里可能会有小伙伴说,我可以用C++编写然后利用Profiling API实现,类似于OneAPM,Datadog 自动探针的形式来收集数据,当然也可以,不过今天我们主要用到了 Microsoft.Diagnostics.NETCore.Client,运行时团队给开发人员提供了更简单和友好的组件。

初始化项目

首先,我们需要创建两个.NET Core 的项目,一个是C#的控制台项目,名字叫ConsoleApp,这是我们的诊断程序,另一个是普通的WebAPI,我们需要对这个API项目进行诊断分析。

然后在控制台项目上通过Nuget引入诊断组件,分别是 Microsoft.Diagnostics.NETCore.Client,Microsoft.Diagnostics.Tracing.TraceEvent

1.获取正在运行的程序列表

在无侵入的情况下,我们首先需要获取到运行的dotnet程序,包括进程的名字和PID,在多个dotnet项目中,我们后边都会通过PID来对特定的程序进行诊断。 修改ConsoleApp的Program.cs如下,这里主要用到了 GetPublishedProcesses 方法。

class Program
{
static void Main(string[] args)
{
if (args.Any())
{
switch (args[0])
{
case "ps": PrintProcessStatus(); break;
}
}
} public static void PrintProcessStatus()
{
var processes = DiagnosticsClient.GetPublishedProcesses()
.Select(Process.GetProcessById)
.Where(process => process != null); foreach (var process in processes)
{
Console.WriteLine($"ProcessId: {process.Id}");
Console.WriteLine($"ProcessName: {process.ProcessName}");
Console.WriteLine($"StartTime: {process.StartTime}");
Console.WriteLine($"Threads: {process.Threads.Count}"); Console.WriteLine();
Console.WriteLine();
} }
}

修改完成后,我们用命令行启动项目,WebAPI 项目运行dotnet run命令 , 启动之后,ConsoleApp 再运行 dotnet run ps命令,ps 是我们传入的参数,我们可以在控制台上看到正在运行的进程信息,我们主要会用到pid。

2.获取 GC 信息

我们创建了一个 DiagnosticsClient的实例,在构造函数中传入了processId进程ID,然后开启了一个有关GC信息的会话,最后订阅了CLR相关的事件回调,输出了事件名称EventName到控制台。

static void Main(string[] args)
{
if (args.Any())
{
switch (args[0])
{
case "ps": PrintProcessStatus(); break;
case "runtime": PrintRuntime(int.Parse(args[1])); break;
}
}
} public static void PrintRuntime(int processId)
{
var providers = new List<EventPipeProvider>()
{
new ("Microsoft-Windows-DotNETRuntime",EventLevel.Informational, (long)ClrTraceEventParser.Keywords.GC) }; var client = new DiagnosticsClient(processId);
using (var session = client.StartEventPipeSession(providers, false))
{
var source = new EventPipeEventSource(session.EventStream); source.Clr.All += (TraceEvent obj) =>
{
Console.WriteLine(obj.EventName);
}; try
{
source.Process();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
}

接下来,我们修改一下WebAPI的代码,在控制器中的方法中创建了一个集合,并且添加了很多数据。

[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
List<string> list = new (); for (int i = 0; i < 1000000; i++)
{
list.Add(i.ToString());
} var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray();
}

同样,我们首先通过 dotnet run 命令启动WebAPI项目,然后 dotnet run ps 启动ConsoleApp项目,控制台会输出 webapi 项目的进程信息,我这里的pid是3832

然后在控制台项目中运行 dotnet run runtime 3832, runtime 和 3832 都是我们传入的参数, 然后开启一个新的命令行窗口,通过curl访问几次webapi的接口,当然你也可以在浏览器中访问,我们发现,在右边的控制台项目输出了GC的相关信息, 这里我们只输出了事件名,实际上我们可以拿到更多的数据信息。

3.获取异常信息

同样的,我们先修改WebApi项目,手动抛出一个异常。

[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
throw new Exception("error"); var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray();
}

在控制台项目中,我们只需要改动一个Keywords 枚举,就是把 ClrTraceEventParser.Keywords.GC 改成 ClrTraceEventParser.Keywords.Exception,当然这里支持了其他更多的类型。

修改完成后,我们先启动 WebApi 项目,然后在ConsoleApp中先运行 dotnet run ps,查看webapi的进程id,然后再运行 dotnet run runtime 13600, 最后我们通过 curl 命令或者浏览器访问webapi的接口,同样,在右边的ConsoleApp中,输出了异常的相关事件信息。

在上面的代码中,我手动抛出一个异常,我们的诊断工具ConsoleApp是可以获取到相关的异常信息,那我用try,catch 把异常吃掉呢?它还能捕获到异常吗?

[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
try
{
Convert.ToInt32("sss");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
} var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray();
}

修改代码后,我们重新运行webapi和诊断工具ConsoleApp,访问api接口时,你会发现,就算我们用try,catch 吃掉了异常,它仍然会输出异常信息。

4. 生成Dump文件

通过 Microsoft.Diagnostics.NETCore.Client 组件,我们可以很方便的为程序生生成Dump文件,然后可以用 windbg 工具来进行分析。

修改控制台项目ConsoleApp的Program.cs如下:

 static void Main(string[] args)
{
if (args.Any())
{
switch (args[0])
{
case "ps": PrintProcessStatus(); break;
case "runtime": PrintRuntime(int.Parse(args[1])); break;
case "dump": Dump(int.Parse(args[1])); break;
}
}
} public static void Dump(int processId)
{
var client = new DiagnosticsClient(processId);
client.WriteDump(DumpType.Normal, @"mydump.dmp", false);
}

修改完成后,启动webapi项目和控制台项目,在控制台项目中运行 dotnet run dump 13288 命令,它会在webapi的目录下,生成程序的dump文件

5.生成 Trace 文件

同样,我们可以很方便的生成 Trace 文件,它可以分析到CPU的函数执行耗时情况,它的格式是.nettrace, 你可以直接用VS 2017及以上或者 PerfView 工具打开。

修改控制台项目ConsoleApp的Program.cs如下:

static void Main(string[] args)
{
if (args.Any())
{
switch (args[0])
{
case "ps": PrintProcessStatus(); break;
case "runtime": PrintRuntime(int.Parse(args[1])); break;
case "dump": Dump(int.Parse(args[1])); break;
case "trace": Trace(int.Parse(args[1])); break;
}
}
} public static void Trace(int processId)
{
var cpuProviders = new List<EventPipeProvider>()
{
new EventPipeProvider("Microsoft-Windows-DotNETRuntime", EventLevel.Informational, (long)ClrTraceEventParser.Keywords.Default),
new EventPipeProvider("Microsoft-DotNETCore-SampleProfiler", EventLevel.Informational, (long)ClrTraceEventParser.Keywords.None)
};
var client = new DiagnosticsClient(processId);
using (var traceSession = client.StartEventPipeSession(cpuProviders))
{
Task.Run(async () =>
{
using (FileStream fs = new FileStream(@"mytrace.nettrace", FileMode.Create, FileAccess.Write))
{
await traceSession.EventStream.CopyToAsync(fs);
} }).Wait(10 * 1000); traceSession.Stop();
}
}

修改完成后,启动webapi项目和控制台项目,在控制台项目中运行 dotnet run trace 13288命令,trace和13288都是参数,它会在控制台项目的目录下,生成 mytrace.nettrace文件

我们可以使用VS或者 PerfView 打开它

总结

其实在.NET Core CLI 中,已经提供了高度可用的一系列诊断工具,dotnet-trace,dotnet-dump 等等,Microsoft.Diagnostics.NETCore.Client 提供了非常友好和高层次的API,不仅仅是文中这些, 我们可以用C#代码,来完成对CLR层面的一些操作,来帮助我们发掘对程序诊断的更多可能性。

示例代码都已经上传到 https://github.com/SpringLeee/DiagnosticDemo,觉得不错的就给我点个赞吧!

最后欢迎扫码关注我们的公众号 【全球技术精选】,专注国外优秀博客的翻译和开源项目分享。

动手实现一个适用于.NET Core 的诊断工具的更多相关文章

  1. .NET Core 服务诊断工具

    前言: 前一篇文中介绍了.NET Core-全局性能诊断工具 的使用方法,那么接下来自己实现一个简单.NET Core的诊断工具. 该工具主要包括:.NET Core 程序进程信息查看.性能计数器结果 ...

  2. .NET Core-全局性能诊断工具

    前言: 现在.NET Core 上线后,不可避免的会出现各种问题,如内存泄漏.CPU占用高.接口处理耗时较长等问题.这个时候就需要快速准确的定位问题,并解决. 这时候就可以使用.NET Core 为开 ...

  3. 如何一秒钟从头构建一个 ASP.NET Core 中间件

    前言 其实地上本没有路,走的人多了,也便成了路. -- 鲁迅 就像上面鲁迅说的那样,其实在我们开发中间件的过程中,微软并没有制定一些策略或者文档来约束你如何编写一个中间件程序, 但是其中却存在者一些最 ...

  4. Linux 下的一个全新的性能测量和调式诊断工具 Systemtap, 第 3 部分: Systemtap

    Systemtap的原理,Systemtap与DTrace比较,以及安装要求和安装步骤本系列文章详细地介绍了一个Linux下的全新的调式.诊断和性能测量工具Systemtap和它所依赖的基础kprob ...

  5. 中小研发团队架构实践之生产环境诊断工具WinDbg 三分钟学会.NET微服务之Polly 使用.Net Core+IView+Vue集成上传图片功能 Fiddler原理~知多少? ABP框架(asp.net core 2.X+Vue)模板项目学习之路(一) C#程序中设置全局代理(Global Proxy) WCF 4.0 使用说明 如何在IIS上发布,并能正常访问

    中小研发团队架构实践之生产环境诊断工具WinDbg 生产环境偶尔会出现一些异常问题,WinDbg或GDB是解决此类问题的利器.调试工具WinDbg如同医生的听诊器,是系统生病时做问题诊断的逆向分析工具 ...

  6. Swift:一个基于.NET Core的分布式批处理框架

    Swift是什么 从文章的标题可知:此Swift非Apple那个Swift,只是考虑这个词的含义比较适合. Swift是一个基于.NET Core的分布式批处理框架,支持将作业分割后分发到多台服务器并 ...

  7. 动手写一个简单版的谷歌TPU-矩阵乘法和卷积

    谷歌TPU是一个设计良好的矩阵计算加速单元,可以很好的加速神经网络的计算.本系列文章将利用公开的TPU V1相关资料,对其进行一定的简化.推测和修改,来实际编写一个简单版本的谷歌TPU.计划实现到行为 ...

  8. 动手写一个简单版的谷歌TPU-指令集

    系列目录 谷歌TPU概述和简化 基本单元-矩阵乘法阵列 基本单元-归一化和池化(待发布) TPU中的指令集 SimpleTPU实例: (计划中) 拓展 TPU的边界(规划中) 重新审视深度神经网络中的 ...

  9. 死磕 java线程系列之自己动手写一个线程池

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. (手机横屏看源码更方便) 问题 (1)自己动手写一个线程池需要考虑哪些因素? (2)自己动手写 ...

随机推荐

  1. 初探JavaScript原型链污染

    18年p师傅在知识星球出了一些代码审计题目,其中就有一道难度为hard的js题目(Thejs)为原型链污染攻击,而当时我因为太忙了(其实是太菜了,流下了没技术的泪水)并没有认真看过,后续在p师傅写出w ...

  2. WPF 基础 - 在模板中找元素

    1. 在 ControlTemplate 中寻找元素 <Window.Resources> <ControlTemplate x:Key="cTmp"> & ...

  3. 目标检测入门论文YOLOV1精读以及pytorch源码复现(yolov1)

    结果展示 其中绿线是我绘制的图像划分网格. 这里的loss是我训练的 0.77 ,由于损失函数是我自己写的,所以可能跟大家的不太一样,这个不重要,重要的是学习思路. 重点提示 yolov1是一个目标检 ...

  4. 攻防世界 reverse Windows_Reverse1

    Windows_Reverse1  2019_DDCTF 查壳 脱壳 脱壳后运行闪退,(或许需要修复下IAT??),先IDA 静态分析一下 int __cdecl main(int argc, con ...

  5. matplotlib常规使用方法

    1,指定图片大小和像素 Python绘图问题:Matplotlib中指定图片大小和像素 2,绘图命令的基本架构及其属性设置 绘图与可视化 3,python基础语法(二)--- plt的一些函数使用 p ...

  6. Android Studio 如何在TextView中设置图标并按需调整图标大小

    •任务 相信大家对这张图片都不陌生,没错,就是 QQ动态 向我们展示的界面. 如何实现呢? •添加文字并放入图标 新建一个 Activity,取名为 QQ,Android Studio 自动为我们生成 ...

  7. Android Studio之圆形按钮设计

    •效果展示图 •实现方法 点击 app/src/main/res 找到 drawable 文件夹,右击->New->Drawable Resource File. 创建一个 $drawab ...

  8. MRCTF My secret

    My secret 知识点:wireshark基本操作,shadowsocks3.0源码利用,拼图(os脚本编写能力), 根据这里的信息可以知道,tcp所传输的源数据是在target address后 ...

  9. 201871030135-姚辉 实验二 个人项目—《D{0-1} KP》项目报告

    项目 内容 课程班级博客链接 课程班级博客链接 这个作业要求链接 这个作业要求链接 我的课程学习目标 (1)掌握软件项目个人开发流程.(2)掌握Github发布软件项目的操作方法. 这个作业在哪些方面 ...

  10. Java8中的默认方法

    作者:汤圆 个人博客:javalover.cc 前言 大家好啊,我是汤圆,今天给大家带来的是<Java8中的默认方法>,希望对大家有帮助,谢谢 文章纯属原创,个人总结难免有差错,如果有,麻 ...