news 2026/9/7 19:34:51

PowerToys 设置界面遥测:Settingsv2 遥测事件体系与 ETW 上报实现解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PowerToys 设置界面遥测:Settingsv2 遥测事件体系与 ETW 上报实现解析

PowerToys 设置界面遥测:Settingsv2 遥测事件体系与 ETW 上报实现解析

【免费下载链接】PowerToysMicrosoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows项目地址: https://gitcode.com/GitHub_Trending/po/PowerToys

Microsoft PowerToys 的设置界面(内部称 Settingsv2)会针对启动耗时、模块开关等关键行为产生遥测事件。本文基于仓库中的开发文档 telemetry.md 与对应源码,梳理 Settingsv2 遥测事件的继承体系(IEvent/EventBase)、两类核心事件(SettingsBootEventSettingsEnabledEvent)的字段与触发时机,以及事件经由 ETW EventSource 上报、由注册表开关控制的完整链路,帮助读者理解 PowerToys 设置界面遥测的设计与实现。

1. Settingsv2 遥测的定位与代码分布

Settingsv2 的遥测事件集中定义在 Settings.UI.Library/Telemetry 目录下。这些事件类都派生自 EventBase 并实现 IEvent 接口,两者位于公共遥测库 ManagedTelemetry 中,被 PowerToys 各模块共享复用:

  • IEvent提供最底层的抽象,要求每个遥测事件都携带隐私标签(privacy tags)等必备属性;
  • EventBase提供更高层的抽象,承载所有 PowerToys 遥测事件的公共属性。

这种"接口 + 基类"的双层设计把"隐私合规属性"与"通用遥测属性"分离,使得新增一个遥测事件时,开发者只需要关注事件特有的业务字段。

2. 双层抽象的源码剖析

2.1 IEvent:最低层抽象,暴露隐私标签

IEvent.cs 的完整定义只有一个成员:

public interface IEvent { PartA_PrivTags PartA_PrivTags { get; } }

PartA_PrivTags是隐私分级标签。在 PowerToys 的遥测体系中,每个事件都必须声明自己的隐私类别,这决定了该事件数据在采集、上报层面的合规处理方式。把它放在接口而非基类中,意味着它是遥测事件的"硬性契约"。

2.2 EventBase:所有 PowerToys 遥测事件的公共属性

EventBase.cs 标记了[EventData]特性(ETW EventSource 用于序列化事件负载),并提供三个公共属性:

属性类型说明
UTCReplace_AppSessionGuidbool恒为true,由遥测基础设施做应用会话 GUID 的 UTC 替换
EventNamestring事件名,上报时作为 ETW 事件名传入
Versionstring懒加载,从当前程序集读取版本号,格式为v{Major}.{Minor}.{Build}

其中Version属性的实现值得注意:首次访问时才调用GetVersionFromAssembly()从程序集元数据取版本号并缓存,且代码注释明确要求版本格式与 C++ 侧common.cpp中的版本号格式保持一致——从源码结构看,这是为了 .NET 遥测与原生遥测在数据口径上对齐。

3. Settingsv2 生成的两类核心事件

根据 telemetry.md,Settingsv2 生成的事件为SettingsBootEventSettingsEnabledEvent。两者都额外标记了[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]特性,配合PowerToysTelemetry.WriteEvent<T>上的泛型约束,保证在 NativeAOT 裁剪场景下事件的公共属性不会被误删。

3.1 SettingsBootEvent:MainWindow 初始化耗时

SettingsBootEvent.cs 捕获 Settingsv2 初始化MainWindowUI 控件所花费的时间:

[EventData] [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] public class SettingsBootEvent : EventBase, IEvent { public double BootTimeMs { get; set; } public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServicePerformance; }

字段与隐私标签说明:

  • BootTimeMsdouble,单位毫秒):MainWindow 从构造开始到完成初始化的耗时;
  • PartA_PrivTags:取值为ProductAndServicePerformance,即"性能类"事件,与SettingsEnabledEvent的"使用类"标签形成区分。

上报触发点位于 MainWindow.xaml.cs:构造函数末尾在InitializeComponent()SetTitleBar()完成、IPC 消息回调注册完毕之后停止秒表并写入事件:

bootTime.Stop(); PowerToysTelemetry.Log.WriteEvent(new SettingsBootEvent() { BootTimeMs = bootTime.ElapsedMilliseconds });

可以推断,该事件用于持续观测设置界面 UI 启动性能,例如 XAML 加载、设置加载、模块初始化等步骤引入的回归都能通过BootTimeMs的分布变化被发现。

3.2 SettingsEnabledEvent:模块启用/禁用行为

SettingsEnabledEvent.cs 在用户启用或禁用某个模块时生成:

public class SettingsEnabledEvent : EventBase, IEvent { public string Name { get; set; } // 模块名(实为承载该开关的属性名) public bool Value { get; set; } // true 表示启用,false 表示禁用 public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage; }

其上报封装在 EnabledModules.cs 的私有静态方法中:

private static void LogTelemetryEvent(bool value, [CallerMemberName] string moduleName = null) { var dataEvent = new SettingsEnabledEvent() { Value = value, Name = moduleName, }; PowerToysTelemetry.Log.WriteEvent(dataEvent); }

两个实现细节值得注意:

  1. [CallerMemberName]自动填参:每个模块开关属性的 setter 在值变化时调用LogTelemetryEvent(value)(不带第二个参数),编译器会把调用方成员名(即属性名,如GrabAndMovePowerDisplay)自动填入Name字段。新增一个模块开关时,开发者无需手写模块名字符串,天然避免拼写漂移;
  2. 仅在值真正变化时上报:setter 中先比较if (grabAndMove != value)再触发LogTelemetryEvent+NotifyChange(),因此重复设置相同值不会产生冗余事件。

从源码结构看,该事件并非只覆盖模块总开关:PowerPreviewProperties.cs 中也复用了同一个SettingsEnabledEvent类型(同样借助[CallerMemberName]取属性名),用于追踪 Power Preview 具体配置项的启用/禁用,可见这类"配置项开关"遥测是共用同一事件模型的。

4. 上报通道:ETW EventSource 与诊断数据开关

两类事件最终都通过 PowerToysTelemetry 单例写入:

public class PowerToysTelemetry : TelemetryBase { private const string EventSourceName = "Microsoft.PowerToys"; public static PowerToysTelemetry Log { get; } = new PowerToysTelemetry(); public void WriteEvent<T>(T telemetryEvent) where T : EventBase, IEvent { if (DataDiagnosticsSettings.GetEnabledValue()) { this.Write<T>( telemetryEvent.EventName, new EventSourceOptions() { Keywords = ProjectKeywordMeasure, Tags = ProjectTelemetryTagProductAndServicePerformance, }, telemetryEvent); } } }

可以确认的机制:

  • 传输协议:PowerToys 的托管侧遥测基于 .NET 的EventSource(ETW),事件源名为Microsoft.PowerToysWriteEvent<T>的泛型约束where T : EventBase, IEvent与事件类上的[EventData][DynamicallyAccessedMembers]特性共同保证事件负载可被 ETW 正确序列化且 AOT 裁剪安全;
  • 写入前置门禁:每次写入前都会调用DataDiagnosticsSettings.GetEnabledValue(),未开启诊断数据时事件直接丢弃,不做任何序列化开销;
  • 事件选项:统一使用Keywords = ProjectKeywordMeasureTags = ProjectTelemetryTagProductAndServicePerformance这两个项目级常量,便于采集端按关键字/标签过滤。

4.1 诊断数据开关的存储位置

DataDiagnosticsSettings.cs 表明遥测开关并非保存在 PowerToys 自己的设置 JSON 中,而是写入注册表,供所有进程统一读取:

注册表项说明
键路径HKEY_CURRENT_USER\Software\Classes\PowerToys\
AllowDataDiagnostics主开关,1允许 /0或不存在则不允许(默认0,即默认关闭)
DataDiagnosticsUserAction记录用户是否主动操作过该开关
DataDiagnosticsViewEnabled是否允许查看已采集数据

所有读取路径都包裹在 try/catch 中且默认返回false——从源码结构看,这意味着即使注册表访问失败(权限等问题),遥测也倾向于"静默不上报",把隐私安全作为默认行为。对使用者而言,这一开关对应 PowerToys 设置界面"常规"页中的诊断数据选项。

5. 事件全集与扩展方式

虽然 telemetry.md 只列出了 Settingsv2 的两类代表性事件,Settings.UI.Library/Telemetry/Events 目录中实际上还定义了更多事件类,例如ModuleLaunchedFromSettingsEventTrayFlyoutActivatedEventShortcutConflictDetectedEvent/ShortcutConflictResolvedEvent,以及一组Oobe*EventOobeStartedEventOobeModuleRunEventOobeSettingsEvent等,用于首次运行体验引导)和CmdNotFoundInstallEvent/CmdNotFoundUninstallEvent。它们的结构都与上文一致:继承EventBase、实现IEvent、标注[EventData][DynamicallyAccessedMembers]

若要为 Settingsv2 新增一个遥测事件,从现有实现可以归纳出固定套路:

  1. 在 Settings.UI.Library/Telemetry/Events 下新建事件类,继承EventBase并实现IEvent,声明事件特有属性与PartA_PrivTags
  2. 按语义选择隐私标签:性能数据用ProductAndServicePerformance,行为数据用ProductAndServiceUsage
  3. 在业务代码的触发点调用PowerToysTelemetry.Log.WriteEvent(new XxxEvent { ... }),无需关心上报通道与诊断开关——门禁逻辑已内聚在WriteEvent中。

6. 小结与延伸阅读

Settingsv2 的遥测体系可以用一条链路概括:事件类(业务字段 + 隐私标签)→PowerToysTelemetry.WriteEvent(诊断开关门禁)→ ETW EventSource "Microsoft.PowerToys"(注册表AllowDataDiagnostics控制采集)IEvent/EventBase的双层抽象让隐私契约与通用属性各司其职,而SettingsBootEvent(启动耗时)与SettingsEnabledEvent(模块开关,借助[CallerMemberName]自动记录模块名)分别覆盖了性能与使用行为两条观测维度。

如需进一步理解设置界面整体架构与模块间通信,可参考同目录下的 project-overview.md、settings-implementation.md 与 ui-architecture.md;遥测基础设施的公共文档见 Telemetry/readme.md。

【免费下载链接】PowerToysMicrosoft PowerToys is a collection of utilities that supercharge productivity and customization on Windows项目地址: https://gitcode.com/GitHub_Trending/po/PowerToys

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 19:32:26

Buzz:本地离线音频转录工具,一次导出 3 种字幕格式

Buzz&#xff1a;本地离线音频转录工具&#xff0c;一次导出 3 种字幕格式 【免费下载链接】buzz Buzz transcribes and translates audio offline on your personal computer. Powered by OpenAIs Whisper. 项目地址: https://gitcode.com/GitHub_Trending/buz/buzz 会…

作者头像 李华
网站建设 2026/9/7 19:32:10

一个注解搞定接口限速:自定义注解+Spring拦截器+Redis实践

1. 先聊清楚&#xff1a;这个“限速注解”到底解决了什么问题做后端接口开发的时候&#xff0c;限速是个绕不开的话题。尤其是面向公网的业务接口&#xff0c;一旦遇到突发流量、爬虫脚本、或者某个调用方写了个有问题的重试循环&#xff0c;服务端的压力瞬间就能被打满。轻则接…

作者头像 李华
网站建设 2026/9/7 19:31:18

头歌实践教学平台:Java入门-循环结构基础(一~二)

第1关&#xff1a;Java循环结构之while循环任务描述 本小节需要你完成一个做俯卧撑的小程序&#xff0c;效果如下&#xff1a;相关知识 Java中常用的循环有三种&#xff1a;while&#xff0c;do...while&#xff0c;for。 本小节我们先来看看while循环。while循环的特点 循环顾…

作者头像 李华
网站建设 2026/9/7 19:28:06

解决鱼香肉丝一键安装docker+ros中VC插件中显示不到容器的问题

一、Docker及镜像环境配置 1.1 使用小鱼的一键安装指令 source <(wget -qO- http://fishros.com/install)选择11&#xff0c;跟着步骤来就行了。 1.2 配置镜像加速器 可能遇到的问题&#xff1a;docker在拉取镜像的时候因为网络的问题可能出现失败的情况&#xff0c;导致后面…

作者头像 李华