一:背景

1. 讲故事

上篇聊到了如何对AOT程序进行轻量级的APM监控,有朋友问我如何获取AOT程序的CPU利用率,本来我觉得这是一个挺简单的问题,但一研究不是这么一回事,这篇我们简单的聊一聊。

二:如何获取CPU利用率

1. 认识cpuUtilization字段

熟悉.NET底层的朋友应该知道,.NET线程池中有一个cpuUtilization字段就记录了当前机器的CPU利用率,所以接下来的思路就是如何把这个字段给挖出来,在挖这个字段之前也要知道 .NET6 为界限出现过两个线程池。

1)win32threadpool.cpp

这是 .NET6 之前一直使用的 .NET线程池,它是由 clr 的 1)win32threadpool.cpp 实现的,参考代码如下:


SVAL_IMPL(LONG,ThreadpoolMgr,cpuUtilization);
  1. PortableThreadPool.cs

为了更好的跨平台以及高层统一, .NET团队用C#对原来的线程池进行了重构,所以这个字段自然也落到了C#中,参考如下:


internal sealed class PortableThreadPool
{
private int _cpuUtilization;
}
  1. WindowsThreadPool.cs

我原以为线程池已经被这两种实现平分天下,看来我还是年轻了,不知道什么时候又塞入了一种线程池实现 WindowsThreadPool.cs,无语了,它是简单的 WindowsThreadPool 的 C#封装,舍去了很多原来的方法实现,比如:


internal static class WindowsThreadPool
{
public static bool SetMinThreads(int workerThreads, int completionPortThreads)
{
return false;
}
public static bool SetMaxThreads(int workerThreads, int completionPortThreads)
{
return false;
} internal static void NotifyThreadUnblocked()
{
} internal unsafe static void RequestWorkerThread()
{
//todo...
//提交到 windows线程池
Interop.Kernel32.SubmitThreadpoolWork(s_work);
}
}

而这个也是 Windows 版的AOT默认实现,因为 Windows线程池是由操作系统实现,没有源码公开,观察了reactos的开源实现,也未找到类似的cpuUtilization字段,这就比较尴尬了,常见的应对措施如下:

  1. 因为dump或者program中没有现成字段,只能在程序中使用代码获取。
  2. 修改windows上的 aot 默认线程池。

2. 如果修改AOT的默认线程池

在微软的官方文档:https://learn.microsoft.com/zh-cn/dotnet/core/runtime-config/threading 上就记录了Windows线程池的一些概况以及如何切换线程池的方法,截图如下:

这里选择 MSBuild 的方式来配置。


<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PublishAot>true</PublishAot>
<UseWindowsThreadPool>false</UseWindowsThreadPool>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>
</Project>

接下来写一段简单的C#代码,故意让一个线程死循环。


internal class Program
{
static void Main(string[] args)
{
Task.Run(() =>
{
Test();
}).Wait();
} static void Test()
{
var flag = true;
while (true)
{
flag = !flag;
}
}
}

这里要注意的一点是发布成AOT的程序不能以普通的带有元数据的C#程序来套。毕竟前者没有元数据了,那怎么办呢?这就考验你对AOT依赖树的理解,熟悉AOT的朋友都知道,依赖树的构建最终是以有向图的方式存储在 _dependencyGraph 字段中,每个节点由基类 NodeFactory 承载,参考代码如下:


public abstract class Compilation : ICompilation
{
protected readonly DependencyAnalyzerBase<NodeFactory> _dependencyGraph;
} public abstract partial class NodeFactory
{
public virtual void AttachToDependencyGraph(DependencyAnalyzerBase<NodeFactory> graph)
{
ReadyToRunHeader = new ReadyToRunHeaderNode(); graph.AddRoot(ReadyToRunHeader, "ReadyToRunHeader is always generated");
graph.AddRoot(new ModulesSectionNode(), "ModulesSection is always generated"); graph.AddRoot(GCStaticsRegion, "GC StaticsRegion is always generated");
graph.AddRoot(ThreadStaticsRegion, "ThreadStaticsRegion is always generated");
graph.AddRoot(EagerCctorTable, "EagerCctorTable is always generated");
graph.AddRoot(TypeManagerIndirection, "TypeManagerIndirection is always generated");
graph.AddRoot(FrozenSegmentRegion, "FrozenSegmentRegion is always generated");
graph.AddRoot(InterfaceDispatchCellSection, "Interface dispatch cell section is always generated");
graph.AddRoot(ModuleInitializerList, "Module initializer list is always generated"); if (_inlinedThreadStatics.IsComputed())
{
graph.AddRoot(_inlinedThreadStatiscNode, "Inlined threadstatics are used if present");
graph.AddRoot(TlsRoot, "Inlined threadstatics are used if present");
} ReadyToRunHeader.Add(ReadyToRunSectionType.GCStaticRegion, GCStaticsRegion);
ReadyToRunHeader.Add(ReadyToRunSectionType.ThreadStaticRegion, ThreadStaticsRegion);
ReadyToRunHeader.Add(ReadyToRunSectionType.EagerCctor, EagerCctorTable);
ReadyToRunHeader.Add(ReadyToRunSectionType.TypeManagerIndirection, TypeManagerIndirection);
ReadyToRunHeader.Add(ReadyToRunSectionType.FrozenObjectRegion, FrozenSegmentRegion);
ReadyToRunHeader.Add(ReadyToRunSectionType.ModuleInitializerList, ModuleInitializerList); var commonFixupsTableNode = new ExternalReferencesTableNode("CommonFixupsTable", this);
InteropStubManager.AddToReadyToRunHeader(ReadyToRunHeader, this, commonFixupsTableNode);
MetadataManager.AddToReadyToRunHeader(ReadyToRunHeader, this, commonFixupsTableNode);
MetadataManager.AttachToDependencyGraph(graph);
ReadyToRunHeader.Add(MetadataManager.BlobIdToReadyToRunSection(ReflectionMapBlob.CommonFixupsTable), commonFixupsTableNode);
}
}

结合上面的代码,我们的 PortableThreadPool 静态类会记录到根区域的 GCStaticsRegion 中,有了这些知识,接下来就是开挖了。

3. 使用 windbg 开挖

用 windbg 启动生成好的 aot程序,接下来用 Example_21_8!S_P_CoreLib_System_Threading_PortableThreadPool::__GCSTATICS 找到类中的静态字段。


0:007> dp Example_21_8!S_P_CoreLib_System_Threading_PortableThreadPool::__GCSTATICS L1
00007ff6`e4b7c5d0 000002a5`a4000468
0:007> dp 000002a5`a4000468+0x8 L1
000002a5`a4000470 000002a5`a6809ca0
0:007> dd 000002a5`a6809ca0+0x50 L1
000002a5`a6809cf0 0000000a
0:007> ? a
Evaluate expression: 10 = 00000000`0000000a

从上面的卦中可以清晰的看到,当前的CPU=16%。这里稍微解释下 000002a5a4000468+0x8 是用来跳过vtable从而取到类实例,后面的 000002a5a6809ca0+0x50 是用来获取 PortableThreadPool._cpuUtilization 字段的,布局参考如下:


0:012> !dumpobj /d 27bc100b288
Name: System.Threading.PortableThreadPool
MethodTable: 00007ffc6c1aa6f8
EEClass: 00007ffc6c186b38
Tracked Type: false
Size: 512(0x200) bytes
File: C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.8\System.Private.CoreLib.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007ffc6c031188 4000d42 50 System.Int32 1 instance 10 _cpuUtilization
00007ffc6c0548b0 4000d43 5c System.Int16 1 instance 12 _minThreads
00007ffc6c0548b0 4000d44 5e System.Int16 1 instance 32767 _maxThreads

三:总结

总的来说如果你的AOT使用默认的 WindowsThreadPool,那想获取 cpu利用率基本上是无力回天,当然有达人知道的话可以告知下,如果切到默认的.NET线程池还是有的一拼,即使没有 pdb 符号也可以根据_minThreads和_maxThreads的内容反向搜索。

AOT漫谈专题(第三篇): 如何获取C#程序的CPU利用率的更多相关文章

  1. [置顶] android利用jni调用第三方库——第三篇——编写库android程序整合第三方库libhello.so到自己的库libhelloword.so

    0:前言: 在第二篇中,我们主要介绍了丙方android公司利用乙方C++公司给的动态库,直接调用库中的方法,但是这样方式受限于: 乙方C++公司开发的动态库是否符合jni的规范,如果不规范,则不能直 ...

  2. asp.net signalR 专题—— 第三篇 如何从外部线程访问 PersistentConnection

    在前面的两篇文章中,我们讲到的都是如何将消息从server推向client,又或者是client再推向server,貌似这样的逻辑没什么异常,但是放在真实 的环境中,你会很快发现有一个新需求,如何根据 ...

  3. PerfView专题 (第三篇):如何寻找 C# 中的 VirtualAlloc 内存泄漏

    一:背景 上一篇我们聊到了如何用 PerfView 去侦察 NTHeap 的内存泄漏,这种内存泄漏往往是用 C 的 malloc 或者 C++ 的 new 分配而不释放所造成的,这一篇我们来聊一下由 ...

  4. 第三篇、微信小程序-网络请求API

    wx.request(OBJECT)发起的是https请求.一个微信小程序,同时只能有5个网络请求连接. OBJECT参数说明: 效果图: net.js Page({ data:{ result:{} ...

  5. 第三篇python用户登录程序实现

    需求: 1.通过注册输入用户名和密码 2.能够验证用户名和密码是否正确 3.限制输入一定错误次数后退出程序 4.利用格式化输出方式输出信息 分析: 使用username=input()和passwor ...

  6. Spring事务专题(三)事务的基本概念,Mysql事务处理原理

    前言 本专题大纲: 我重新整理了大纲,思考了很久,决定单独将MySQL的事务实现原理跟Spring中的事务示例分为两篇文章,因为二者毕竟没有什么实际关系,实际上如果你对MySQL的事务原理不感兴趣也可 ...

  7. 无线安全专题_攻击篇--MAC泛洪攻击

    上一篇讲解了无线安全专题_攻击篇--干扰通信,没在首页待多长时间就被拿下了,看来之后不能只是讲解攻击实战,还要进行技术原理和防御方法的讲解.本篇讲解的是局域网内的MAC泛洪攻击,这种攻击方式主要目的是 ...

  8. 【第三篇】ASP.NET MVC快速入门之安全策略(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

  9. 我的屌丝giser成长记-研三篇

    进入研三以来,基本都是自己的自由时间了,从导师的项目抽离出来,慢慢的都交给师弟他们来负责.研三的核心任务就是找工作以及写毕业论文,因为有导师科研基金项目成果作为支撑,所以自己的论文没什么可担心,一切都 ...

  10. 第三篇 :微信公众平台开发实战Java版之请求消息,响应消息以及事件消息类的封装

    微信服务器和第三方服务器之间究竟是通过什么方式进行对话的? 下面,我们先看下图: 其实我们可以简单的理解: (1)首先,用户向微信服务器发送消息: (2)微信服务器接收到用户的消息处理之后,通过开发者 ...

随机推荐

  1. 【JavaScript】文件上传下载问题

    问题原因 一般文件上传前端甚至可以不涉及JS来实现 input标签套在form标签,由form标签直接发送请求就可以实现上传功能 但是现在很多项目都使用前后端分离,AJAX一刀切所有. input标签 ...

  2. 【SpringBoot】15 数据访问P3 整合Mybatis

    重新建立一个SpringBoot工程 选择依赖组件 然后删除不需要的Maven&Git文件 还是先查看我们的POM文件 整合Mybatis的组件多了这一个,默认的版本是3.5.4 然后再看看整 ...

  3. 计算机论文中的SD SE是什么 ?

    Standard Deviation (SD) and Standard Error (SE): 标准差和标准误差:在统计学和数据分析中常用来描述数据的分布和估计值的精确性.SD 表示标准差,SE 表 ...

  4. 【转载】 docker挂载volume的用户权限问题,理解docker容器的uid

    =================================================================== 在刚开始使用docker volume挂载数据卷的时候,经常出现 ...

  5. AC自动机 提高篇

    文本生成器 首先考虑一个容斥,算出不包含任何一个单词的文章的数量. 我们设 \(dp_{i,j}\) 表示当前文章长度为 \(i\),最后一个字符在 \(AC\) 自动机上的 \(j\) 号点的方案数 ...

  6. Java Web中的request,response,重定位与转发的详解

    request与response响应 Web服务器接收到客户端的http请求,其会对每一次的http请求分别创建应该代表请求的request对象,和一个代表响应的response对象. request ...

  7. Win32 sdk 下树形控件响应鼠标单击与双击,获得选中项的名称

    //窗口过程函数INT_PTR CALLBACK myWin::myWinDlgProc(HWND dlgHwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) ...

  8. .NET Core3.1 跨域 Cors 找不到 “Access-Control-Allow-Origin”

    今天在做项目的时候遇到了调用WebAPI跨域的问题 No 'Access-Control-Allow-Origin' header is present on the requested resour ...

  9. Prometheus 告警恢复时,怎么获取恢复时的值?

    Prometheus 告警事件中的 $value 表示当前告警触发时的值,但是在告警恢复时,Resolved 事件中的 $value 仍然是最新告警时的值,并非是恢复时的值,这是什么原因和原理?是否有 ...

  10. NET Core 多身份校验与策略模式

    背景需求:   系统需要对接到XXX官方的API,但因此官方对接以及管理都十分严格.而本人部门的系统中包含诸多子系统,系统间为了稳定,程序间多数固定Token+特殊验证进行调用,且后期还要提供给其他兄 ...