pcm-pcie 解析
简介
pcm 全称为 Performance Counter Monitor,该项目是针对 intel 平台处理器的资源利用率进行监控的工具。在现代 Intel 处理器已经提供了监视处理器内部性能事件的功能,pcm 通过读取性能监视单元(PMU),从而获得的动态数据。
pcm-pcie.x 运行
pcm-pcie.x 为 pcm 软件中监控 pcie 带宽工具。软件运行时需要 root 权限,在当前平台(Cascade Lake)可以输出包括如下指标内容。
| 监控指标 | 缩写 | 指标介绍 |
|---|---|---|
| PCIRdCur_miss/PCIRdCur_hit | PCIRdCur | PCIe read current transfer (full cache line)(PCIe 设备从系统内存读取) |
| RFO_miss/RFO_hit | RFO | Demand Data RFO(PCIe 设备写入系统内存部分 cache line) |
| CRd_miss/CRd_hit | CRd | Demand Code Read(与PCIRdCur不同的PCIe读取流) |
| DRd_miss/DRd_hit | DRd | Demand Data Read(与PCIRdCur不同的PCIe读取流) |
| ItoM_miss/ItoM_hit | ItoM | PCIe write full cache line(PCIe 设备写入系统内存完整 cache line) |
| PRd_miss/PRd_hit | PRd | MMIO Read [Haswell Server only] (Partial Cache Line)(CPU 通过MMIO模式从设备内存读取) |
| WiL_miss/WiL_hit | WiL | MMIO Write (Full/Partial)(CPU 通过MMIO模式写入设备内存) |
除此之外,pcm-pcie.x 还支持使用参数 -B 输出 PCIe 总读写带宽
$ ./pcm-pcie.x -B
...
Detected Intel(R) Xeon(R) Gold 6230 CPU @ 2.10GHz "Intel(r) microarchitecture codename Cascade Lake-SP" stepping 6 microcode level 0x4000017
Skt | PCIRdCur | RFO | CRd | DRd | ItoM | PRd | WiL | PCIe Rd (B) | PCIe Wr (B)
0 18 K 2478 0 0 826 2688 1092 1313 K 211 K
1 0 0 0 0 0 2282 560 0 0
--------------------------------------------------------------------------------------------------
* 18 K 2478 0 0 826 4970 1652 1313 K 211 K
pcm-pcie 代码
下面将对 Cascade Lake 平台上 pcm-pcie.x 运行过程进行介绍。
初始化
在 purley 平台,通过以下代码生成对应平台 platform 指针
unique_ptr<IPlatform> platform(IPlatform::getPlatform(
m, csv, print_bandwidth, print_additional_info,
(uint)delay)); // FIXME: do we support only integer delay?
这句话声明了一个 unique_ptr<IPlatform> 类型指针,初始化使用 IPlatform 的静态函数 IPlatform::getPlatform 执行。在 purley 平台,IPlatform::getPlatform 会返回 PurleyPlatform 类型对象,因此需要对类的初始化进一步查看。
PurleyPlatform 类型继承自 LegacyPlatform,因此其初始化过程主要使用后者的初始化函数作为初始化列表。
PurleyPlatform(PCM *m, bool csv, bool bandwidth, bool verbose, uint32 delay) :
LegacyPlatform( {"PCIRdCur", "RFO", "CRd", "DRd","ItoM", "PRd", "WiL"},
{
{0x00043c33}, //PCIRdCur_miss
{0x00043c37}, //PCIRdCur_hit
{0x00040033}, //RFO_miss
{0x00040037}, //RFO_hit
{0x00040233}, //CRd_miss
{0x00040237}, //CRd_hit
{0x00040433}, //DRd_miss
{0x00040437}, //DRd_hit
{0x00049033}, //ItoM_miss
{0x00049037}, //ItoM_hit
{0x40040e33}, //PRd_miss
{0x40040e37}, //PRd_hit
{0x40041e33}, //WiL_miss
{0x40041e37}, //WiL_hit
},
m, csv, bandwidth, verbose, delay)
{
};
在 LegacyPlatform 输入参数中,除了 PurleyPlatform 输入参数外,还多了两个常量数组,分别赋值给 eventNames 和 eventGroups 属性。其中传入的 eventNames 代表监控事件的简称,而 eventGroups 对应了监控事件的编码。注意,此编码既不是监控事件对应的 event 或 umask 编码,也不是 filter 的操作码,而是三个编码的集合。后面将在函数 PCM::programPCIeEventGroup 内通过对此编码进行操作,最终得到对应监控事件的所需的所有信息。
指标监控
监控过程中,调用 platform->getEvents() 方法获取所有监控指标的信息。检查 LegacyPlatform::getEvents() 方法可以发现,运行过程中对 eventGroups 数组中每个监控指标调用函数 getEventGroup 采集,最终获取监控指标后保存。
函数 LegacyPlatform::getEventGroup 具体内容为:
void LegacyPlatform::getEventGroup(eventGroup_t &eventGroup)
{
m_pcm->programPCIeEventGroup(eventGroup);
uint offset = eventGroupOffset(eventGroup);
for (auto &run : eventCount) {
for(uint skt =0; skt < m_socketCount; ++skt)
for (uint ctr = 0; ctr < eventGroup.size(); ++ctr)
run[skt][ctr + offset] = m_pcm->getPCIeCounterData(skt, ctr);
MySleepMs(m_delay);
}
for(uint skt = 0; skt < m_socketCount; ++skt)
for (uint idx = offset; idx < offset + eventGroup.size(); ++idx)
eventSample[skt][idx] += getEventCount(skt, idx);
}
其中第一个循环为监控循环,第二个获取监控数据。eventCount 为长度为 2 的数组 std::array ,分别储存一段时间间隔前后监控信息。所以,整个监控过程其实按照(1)监控指标循环(2)监控步(3)socket(4)监控指标个数(1个)进行循环。
监控事件指定
在 LegacyPlatform::getEventGroup 函数每次监控前,首先需要调用 programPCIeEventGroup(eventGroup) 指定对应监控事件。
在 PCM::programPCIeEventGroup 函数内,针对 CascadeLake 平台执行主要指令如下:
case PCM::SKX:
// JKT through СLX generations allow programming only one required event
// at a time.
if (eventGroup[0] & SKX_CHA_MSR_PMON_BOX_FILTER1_NC(1))
umask[0] |= (uint64)(SKX_CHA_TOR_INSERTS_UMASK_IRQ(1));
else
umask[0] |= (uint64)(SKX_CHA_TOR_INSERTS_UMASK_PRQ(1));
if (eventGroup[0] & SKX_CHA_MSR_PMON_BOX_FILTER1_RSV(1))
umask[0] |= (uint64)(SKX_CHA_TOR_INSERTS_UMASK_HIT(1));
else
umask[0] |= (uint64)(SKX_CHA_TOR_INSERTS_UMASK_MISS(1));
events[0] +=
CBO_MSR_PMON_CTL_EVENT(0x35) + CBO_MSR_PMON_CTL_UMASK(umask[0]);
programCbo(events, SKX_CHA_MSR_PMON_BOX_GET_OPC0(eventGroup[0]),
SKX_CHA_MSR_PMON_BOX_GET_NC(eventGroup[0]));
break;
可以看出,对于每个输入的 eventGroup 数组,由于其长度仅为 1,因此在使用时直接将其赋值在 events[0] 上,随后调用 programCbo 函数对控制寄存器进行编程。
在 programCbo 函数中,需要对 filter 和 cbo 控制寄存器进行分别赋值。为了后面描述方便,先展示详细代码如下所示:
for (size_t i = 0; (i < cboPMUs.size()) && MSR.size(); ++i)
{
uint32 refCore = socketRefCore[i];
// std::cout << "cboPMUs info: \n"
// << "\tcboPMUs index: " << i << "\n"
// << "\tref core ind: " << refCore << std::endl;
TemporalThreadAffinity tempThreadAffinity(
refCore); // speedup trick for Linux
for (uint32 cbo = 0; cbo < getMaxNumOfCBoxes(); ++cbo)
{
cboPMUs[i][cbo].freeze(UNC_PMON_UNIT_CTL_FRZ_EN);
programCboOpcodeFilter(opCode, cboPMUs[i][cbo], nc_, 0, loc, rem);
if ((HASWELLX == cpu_model || BDX_DE == cpu_model || BDX == cpu_model ||
SKX == cpu_model) &&
llc_lookup_tid_filter != 0)
*cboPMUs[i][cbo].filter[0] = llc_lookup_tid_filter;
for (int c = 0; c < 4; ++c)
{
*cboPMUs[i][cbo].counterControl[c] = CBO_MSR_PMON_CTL_EN;
*cboPMUs[i][cbo].counterControl[c] = CBO_MSR_PMON_CTL_EN + events[c];
}
cboPMUs[i][cbo].resetUnfreeze(UNC_PMON_UNIT_CTL_FRZ_EN);
for (int c = 0; c < 4; ++c)
{
*cboPMUs[i][cbo].counterValue[c] = 0;
}
}
}
在上面代码中,有两个主要循环,分别是对 cboPMUs 对象和 cbo 循环。cboPMUs 对象个数和节点内 socket 个数相同,对于每个 socket 会生成一个对应 cboPMUs 进行操作。在 uncore 中 CBox(cbo)个数则与每个 CPU 包含的物理核心数完全相同,并且在 SkyLake 和 CascadeLake 平台,还需要判断在 CPU 内 28 个可用 CHA 哪些是开启的。
通过输入的 filter 操作码和对应的 cbo 对象,programCboOpcodeFilter 函数将监控的操作码写入 filter 控制寄存器中。随后,在通过 cboPMUs[i][cbo].counterControl[c] 对象将事件号写入控制寄存器。需要指出的是,尽管下面代码对 4 个控制集群器都进行了编码,但是其实仅有 enents[0] 对应的编号不为0。
for (int c = 0; c < 4; ++c)
{
*cboPMUs[i][cbo].counterControl[c] = CBO_MSR_PMON_CTL_EN;
*cboPMUs[i][cbo].counterControl[c] = CBO_MSR_PMON_CTL_EN + events[c];
}
监控事件对应指标
通过在代码中加入相应输出内容,我们可以得到不同事件的监控对应的事件、掩码和filter操作码。在intel官方手册 Skylake Uncore Performance Monitoring 中,对不同的 filter 操作码进行了介绍,如下表所示。
| name | event | umask | opc0 | description |
|---|---|---|---|---|
| PCIRdCur_miss | 35 | 24 | 21e | Read current • Read Current requests from IIO. Used to read data without changing state. |
| PCIRdCur_hit | 35 | 14 | 21e | \ |
| RFO_miss | 35 | 24 | 200 | Demand Data RFO • Read for Ownership requests from core for lines to be cached in E. |
| RFO_hit | 35 | 14 | 200 | \ |
| CRd_miss | 35 | 24 | 201 | Demand Code Read • Full cache-line read requests from core for lines to be cached in S, typically for code. |
| CRd_hit | 35 | 14 | 201 | \ |
| DRd_miss | 35 | 24 | 202 | Demand Data Read • Full cache-line read requests from core for lines to be cached in S or E, typically for data. |
| DRd_hit | 35 | 14 | 202 | \ |
| ItoM_miss | 35 | 24 | 248 | Request Invalidate Line • Request Exclusive Ownership of cache line. |
| ItoM_hit | 35 | 14 | 248 | \ |
| PRd_miss | 35 | 21 | 207 | Partial Reads (UC) • Partial read requests of 0-32B (IIO can be up to 64B). Uncacheable. |
| PRd_hit | 35 | 11 | 207 | \ |
| WiL_miss | 35 | 21 | 20f | Write Invalidate Line - Partial |
| WiL_hit | 35 | 11 | 20f | \ |

在 PCIE 读取数据时,包含上图中描述的两种数据流动。
- 第一种是 CPU 直接对 PCIe 数据进行读写操作,而监控指标 PRd 和 WiL 分别监控 CPU 以 MMIO 模式读写过程;
- 第二种则为 PCIe 设备对内存数据读写操作,PCIeRdCur 为 PCIe 设备对内存数据读操作,而 ItoM 和 RFO 则为对内存数据的写操作。
总结
在本文中,对 pcm-pcie.x 程序运行主要过程进行了解析,以上过程对于建立 Intel 平台 uncore 部分监控,特别是 PCIe 带宽监控具有一定的参考价值。
pcm-pcie 解析的更多相关文章
- PCI-E配置MSI中断流程解析
在传统的pci中断体系中,每一个pci总线上的设备被分配一个特定的中断号,然后当设备需要中断cpu时,设备直接发出int信号,然后在cpu的inta引脚拉低的时候将自己的中断号放在数据总线上,一切都要 ...
- PCIE协议解析 synopsys IP loopback 读书笔记(1)
1 Overview Core支持单个Pcie内核的Loopback功能,该功能主要为了做芯片验证,以及在没有远程接收器件的情况下完成自己的回环.同时,Core也支持有远程接收器件的loop ...
- 深入解析SQL Server并行执行原理及实践(上)
在成熟领先的企业级数据库系统中,并行查询可以说是一大利器,在某些场景下他可以显著的提升查询的相应时间,提升用户体验.如SQL Server, Oracle等, Mysql目前还未实现,而Postgre ...
- 音频文件解析(一):WAV格式文件头部解析
WAV为微软公司(Microsoft)开发的一种声音文件格式,它符合RIFF(Resource Interchange File Format)文件规范,用于保存Windows平台的音频信息资源. 文 ...
- KVM 介绍(4):I/O 设备直接分配和 SR-IOV [KVM PCI/PCIe Pass-Through SR-IOV]
学习 KVM 的系列文章: (1)介绍和安装 (2)CPU 和 内存虚拟化 (3)I/O QEMU 全虚拟化和准虚拟化(Para-virtulizaiton) (4)I/O PCI/PCIe设备直接分 ...
- H.264码流结构解析
from:http://wenku.baidu.com/link?url=hYQHJcAWUIS-8C7nSBbf-8lGagYGXKb5msVwQKWyXFAcPLU5gR4BKOVLrFOw4bX ...
- USB Type-C工作原理解析
自从苹果发布了新MacBook,USB Type-C接口就成为了热议对象.我来从硬件角度解析下这个USB Type-C,以便大家更好的了解USB Type-C的工作原理. 特色 尺寸小,支持正反插,速 ...
- FFmpeg的HEVC解码器源码简单分析:解析器(Parser)部分
===================================================== HEVC源码分析文章列表: [解码 -libavcodec HEVC 解码器] FFmpeg ...
- OMX Codec详细解析
概述 OMX Codec是stagefrightplayer中负责解码的模块. 由于遵循openmax接口规范,因此结构稍微有点负责,这里就依照awesomeplayer中的调用顺序来介绍. 主要分如 ...
随机推荐
- 如果你还不知道Apache Zookeeper?你凭什么拿大厂Offer!!
很多同学或多或少都用到了Zookeeper,并知道它能实现两个功能 配置中心,实现表分片规则的统一配置管理 注册中心,实现sharding-proxy节点的服务地址注册 那么Zookeeper到底是什 ...
- 单片机stm32零基础入门之--初识STM32 标准库
CMSIS 标准及库层次关系 因为基于Cortex 系列芯片采用的内核都是相同的,区别主要为核外的片上外设的差异,这些差异却导致软件在同内核,不同外设的芯片上移植困难.为了解决不同的芯片厂商生产的Co ...
- 21.7.1 test
\(NOI\) 模拟赛 呜呜呜 \(\cdots\cdots\) \(T1\) 类似哈夫曼编码,虽然没学过但是我依然画出了二叉树,然后尝试树形dp,并且最后还抓住了一个优化!让我兴奋地以为自己能赛时A ...
- Java并发:重入锁 ReentrantLock(一)
ReentrantLock 是一种可重入的互斥锁,它不像 synchronized关键字一样支持隐式的重进入,但能够使一个线程(不同的方法)重复对资源的重复加锁而不受阻塞. ReentrantLock ...
- Sending and Trapping Signals
http://mywiki.wooledge.org/SignalTrap Signals are a basic tool for asynchronous interprocess communi ...
- 第一个只出现一次字符的位置 牛客网 剑指Offer
第一个只出现一次字符的位置 牛客网 剑指Offer 题目描述 在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 ...
- atcoder ABC233
B 题意 给一个字符串, 可以把第一个字母移到最后, 也可以把最后一个字母放第一个, 问字典序最大最小的字符串. 题解 把第一个放最后一个, 相当于把最后一个放第一个执行n-1次, 那么我们不妨只进行 ...
- cf12E Start of the season(构造,,,)
题意: 给一个偶数N. 构造出一个矩阵. 满足:主对角线上全为0.每一行是0~N-1的一个全排列.矩阵关于主对角线对称. 思路: 觉得是智商题,,,,看完题解后觉得不难,但是我就是没想出来.只想到了前 ...
- 为什么IDEA不推荐你使用@Autowired ?
@Autowired注解相信每个Spring开发者都不陌生了!在DD的Spring Boot基础教程和Spring Cloud基础教程中也都经常会出现. 但是当我们使用IDEA写代码的时候,经常会发现 ...
- PTA甲级1094 The Largest Generation (25分)
PTA甲级1094 The Largest Generation (25分) A family hierarchy is usually presented by a pedigree tree wh ...