【Rust调用Windows API】读取进程启动时间、退出时间、CPU利用率

前言

上一篇文章 讲了读取系统的CPU利用率,本篇讲如何获取进程的CPU利用率,以及进程启动和退出的时间。

依赖

需要用到processthreadsapi.h的函数,在 Cargo.toml 添加相应的feature

winapi = { version = "0.3.9", features = ["processthreadsapi"] }

实现

接口函数定义

读取进程CPU利用率和读取系统的一样,需要两次获取并根据间隔时间进行计算,读取进程的数据需要调用 GetProcessTimes 函数,下面是此函数在Rust中的定义:

pub fn GetProcessTimes(hProcess: HANDLE,lpCreationTime: LPFILETIME,lpExitTime: LPFILETIME,lpKernelTime: LPFILETIME,lpUserTime: LPFILETIME,
) -> BOOL;

函数的几个参数就是输入和输出,可以理解为输入一个进程句柄,输出这个进程的若干信息,参数释义:

  • hProcess:线程的句柄,可通过 OpenProcess获得,此句柄至少需要 PROCESS_QUERY_LIMITED_INFORMATION权限,为了更好的兼容性,最好选择 ** PROCESS_QUERY_INFORMATION**权限
  • lpCreationTime:指向 FILETIME 的指针,用于接收进程创建的时间
  • lpExitTime:指向 FILETIME 的指针,用于接收进程退出的时间,如果进程未退出,这个指针指向的地址不会填充数据
  • lpKernelTime:指向 FILETIME 的指针,用于接收该进程在内核模式下的执行时间
  • lpUserTime:指向 FILETIME 的指针,用于接收该进程在用户模式下的执行时间

这里需要注意一下lpCreationTimelpExitTime 所对应的 FILETIME 表示的是从 UTC+0时区下1601年1月1日零点到对应行为点的间隔百纳秒数。

你可以把它理解成“时间戳”,只是这个“时间戳”和我们平时所用的时间戳有两点不同:

  1. 它是从 1601年1月1日零点开始计算,而我们平时用的以Linux Epoch为基准的时间戳是从 1970年1月1日零点开始计算。
  2. 它的单位既不是秒也不是毫秒,而是百纳秒,顾名思义就是 100纳秒,100纳秒 = 1百纳秒

lpKernelTimelpUserTime就与上一篇讲的 读取系统CPU利用率 里读取到的参数一样了,它描述的是间隔的时间量,单位也是百纳秒。

接口调用

下面是接口调用的示例代码(Result和WinError是自定义的,详情见上一篇文章:系统错误代码转为可读文本信息)

use winapi::shared::minwindef::FILETIME;
use winapi::um::processthreadsapi::GetProcessTimes;
use winapi::um::errhandlingapi::GetLastError;pub struct ProcessTimes {creation: FILETIME,exit: FILETIME,kernel: FILETIME,user: FILETIME,
}pub fn get_process_times(process_handle: HANDLE) -> error::Result<ProcessTimes> {let mut creation: FILETIME = unsafe { std::mem::zeroed() };let mut exit: FILETIME = unsafe { std::mem::zeroed() };let mut kernel: FILETIME = unsafe { std::mem::zeroed() };let mut user: FILETIME = unsafe { std::mem::zeroed() };unsafe {//  https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-getprocesstimeslet res = GetProcessTimes(process_handle,&mut creation as *mut FILETIME,&mut exit as *mut FILETIME,&mut kernel as *mut FILETIME,&mut user as *mut FILETIME,);if res == 0 {return Err(WinError::with_code(GetLastError()));}};Ok(ProcessTimes::new(creation, exit, kernel, user))
}

这个函数得到的是我们定义好的结构体 ProcessTimes,接下来把解析 FILETIME 的相关代码放在这个结构体的实现中

解析时间

上一篇介绍的几个工具函数这里再贴一下,这里也会用到

use std::time::Duration;
use winapi::shared::minwindef::FILETIME;/// 将C中的 FILETIME 转为64位数字,单位为百纳秒
pub fn parse_filetime(filetime: FILETIME) -> u64 {u64::wrapping_shl(filetime.dwHighDateTime as u64, 32) | filetime.dwLowDateTime as u64
}/// 解析 FILETIME 为 Duration,精度为纳秒
pub fn parse_filetime_duration(filetime: FILETIME) -> Duration {Duration::from_nanos(parse_filetime(filetime) * 100)
}/// 计算两个 FILETIME 时间间隔,单位百纳秒
pub fn calculate_time_gap(start: FILETIME, end: FILETIME) -> i64 {let a = parse_filetime(start);let b = parse_filetime(end);(b - a) as i64
}

时间转换的逻辑这里简单讲一下思路,剩下的就看代码吧~ 在计算时先把百纳秒转为纳秒(这个百纳秒实在干扰我们思路,先干掉它),然后把这个所谓的“时间戳”转换为真正的Linux Epoch时间戳,1601年到1970年相差 369 年,只需要减掉这369年的纳秒数就可以了。

在windows开发环境下,你会发现 rust-std 标准库里的 SystemTime 也是这么做的,有兴趣的可以看看源码实现

use std::time::{Duration, SystemTime, UNIX_EPOCH};
use winapi::shared::minwindef::FILETIME;/// 1601年到1970年相差369年,此值为时间戳相差纳秒数描述
const INTERVALS_TO_UNIX_EPOCH_NANOS: u64 = 11644473600_000_000_000;pub struct ProcessTimes {creation: FILETIME,exit: FILETIME,kernel: FILETIME,user: FILETIME,
}impl ProcessTimes {pub fn new(creation: FILETIME, exit: FILETIME, kernel: FILETIME, user: FILETIME) -> Self {Self {creation,exit,kernel,user,}}/// 获取进程创建时间,返回windows内部的FILETIME结构,以 1601年1月1日0点以来在英国格林威治经过的时间量,单位:百纳秒pub fn get_creation(&self) -> FILETIME {self.creation}/// 获取进程创建到当前的时间间隔,格式化为 Duration////// windows api获取到的值是以 **1601年1月1日零点** 为起始时间的时间戳,并且时间戳的单位为:100纳秒////// 现在常用的是以UNIX系统Epoch时间 **1970年1月1日零点** 为起始时间的时间戳////// 帮助理解百纳秒单位:////// - 1秒 = 1_000_000_000 纳秒/// - 1秒 = 10_000_000 百纳秒/// - 1秒 = 1000 毫秒/// - 1毫秒 = 10_000 百纳秒/// - 1百纳秒 = 100 纳秒pub fn get_creation_duration(&self) -> Duration {let creation = parse_filetime(self.creation) * 100 - INTERVALS_TO_UNIX_EPOCH_NANOS;let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64;Duration::from_nanos(now - creation)}/// 获取进程退出时间。////// 如果进程已退出,返回windows内部的FILETIME结构,以 1601年1月1日0点以来在英国格林威治经过的时间量,单位:百纳秒////// 如果进程未退出,返回 Nonepub fn get_exit(&self) -> Option<FILETIME> {if self.exit.dwHighDateTime == 0 && self.exit.dwLowDateTime == 0 {return None;}Some(self.exit)}/// 获取进程退出到当前的时间间隔,格式化为 Durationpub fn get_exit_duration(&self) -> Option<Duration> {self.get_exit().map(|filetime| {let exit = parse_filetime(filetime) * 100 - INTERVALS_TO_UNIX_EPOCH_NANOS;let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos() as u64;Duration::from_nanos(now - exit)})}/// 获取kernel用时,返回windows内部的FILETIME结构pub fn get_kernel(&self) -> FILETIME {self.kernel}/// 获取内核态用时,格式化为 Durationpub fn get_kernel_duration(&self) -> Duration {parse_filetime_duration(self.kernel)}/// 获取用户态用时,返回windows内部的FILETIME结构pub fn get_user(&self) -> FILETIME {self.user}/// 获取用户态用时,格式化为 Durationpub fn get_user_duration(&self) -> Duration {parse_filetime_duration(self.user)}
}

计算CPU利用率

和获取系统CPU利用率一样需要两次获取一个时间值,然后再计算求这期间的变化值,思路比较简单,这里不单独写代码了,为了一起展示进程启动和结束时间,见后面的测试代码~

测试

获取某个进程的句柄方法在本系列的 杀掉指定进程 一文中有写,这里就直接贴一下代码

/// 获取进程句柄
///
/// - pid: 进程描述符,进程ID
/// - access_privilege: 访问权限,见`winapi::um::winnt` 常量,或参阅 [官方文档](https://learn.microsoft.com/zh-cn/windows/win32/procthread/process-security-and-access-rights)
pub fn open_process(pid: DWORD, access_privilege: DWORD) -> error::Result<HANDLE> {unsafe {// https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocesslet handle = OpenProcess(access_privilege, 0, pid);if handle.is_null() {//  https://learn.microsoft.com/zh-cn/windows/win32/debug/system-error-codes--0-499-return Err(WinError::new(format!("Could not open process {}", pid), GetLastError()));}Ok(handle)}
}///	杀掉指定进程
pub fn kill_process(process_handle: HANDLE) -> error::Result<()> {unsafe {//  https://learn.microsoft.com/zh-cn/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminateprocesslet success = TerminateProcess(process_handle, 0);if success == 0 {return Err(WinError::new("Could not terminate process", GetLastError()));}Ok(())}
}

获取句柄时需要有 PROCESS_QUERY_INFORMATION 权限,这里为了演示退出时间需要杀掉进程,所以还需要 PROCESS_TERMINATE 权限。

测试代码思路:

  1. 用户输入一个PID,尝试获取该进程的句柄
  2. 读取该进程的启动时间
  3. 如果该进程还在运行,循环10次每次间隔1秒打印进程的CPU使用率
  4. sleep 3秒后杀掉该进程
  5. sleep 3秒后再读取进程的启动时间和退出时间
pub fn test_process_times() {print!("Please enter the process you want to monitor: ");stdout().flush().unwrap();let mut input = String::new();stdin().read_line(&mut input).unwrap();let pid = input.trim().parse::<u32>().unwrap();let handle = open_process(pid, PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE).unwrap();fn print_cpu_usage(handle: HANDLE) {let mut count = 0;let mut last_get_time = Instant::now();let mut last_times = get_process_times(handle).unwrap();while count < 10 {sleep(Duration::from_secs(1));let get_time = Instant::now();let cur_times = get_process_times(handle).unwrap();let creation = cur_times.get_creation_duration();println!("进程已启动:{}", format_duration_to_text(creation, false));match cur_times.get_exit_duration() {None => {}Some(exit) => {println!("进程已退出:{}", format_duration_to_text(exit, false));return}};let total = get_time.duration_since(last_get_time).as_nanos() as f64 / 100.0;let kernel = calculate_time_gap(last_times.get_kernel(), cur_times.get_kernel()) as f64;let user = calculate_time_gap(last_times.get_user(), cur_times.get_user()) as f64;let idle_percent = (total - kernel - user) * 100.0 / total;let cpu_percent = (kernel + user) * 100.0 / total;let kernel_percent = kernel * 100.0 / total;let user_percent = user * 100.0 / total;println!("CPU利用率:{:.2}% (Kernel: {:.2}%\tUser:{:.2}%)\tCPU空闲率:{:.2}%", cpu_percent, kernel_percent, user_percent, idle_percent);last_get_time = get_time;last_times = cur_times;count += 1;}println!()}print_cpu_usage(handle);println!("Sleep for 3 seconds and then kill the process...");sleep(Duration::from_secs(3));kill_process(handle).unwrap();print!("Process Killed");sleep(Duration::from_secs(3));print_cpu_usage(handle);
}/// 将 duration 格式化为 `天:时:分:秒` 的格式
pub fn format_duration_to_text(duration: Duration, ignore_zero_prefix: bool) -> String {let mut s = String::new();let mut secs = duration.as_secs();let day = secs / SECONDS_PER_DAY;if day != 0 || !ignore_zero_prefix {s.push_str(&format!("{}:", day));}secs = secs % SECONDS_PER_DAY;let hour = secs / SECONDS_PER_HOUR;if hour != 0 || !ignore_zero_prefix {s.push_str(&format!("{}:", hour));}secs = secs % SECONDS_PER_HOUR;let minute = secs / 60;if minute != 0 || !ignore_zero_prefix {s.push_str(&format!("{}:", minute));}secs = secs % 60;s.push_str(&format!("{}", secs));s
}

结果

Please enter the process you want to monitor: 5328
进程已启动:0:7:26:10
CPU利用率:0.00% (Kernel: 0.00% User:0.00%)     CPU空闲率:100.00%
进程已启动:0:7:26:11
CPU利用率:0.00% (Kernel: 0.00% User:0.00%)     CPU空闲率:100.00%
进程已启动:0:7:26:12
CPU利用率:0.00% (Kernel: 0.00% User:0.00%)     CPU空闲率:100.00%
进程已启动:0:7:26:13
CPU利用率:0.00% (Kernel: 0.00% User:0.00%)     CPU空闲率:100.00%
进程已启动:0:7:26:14
CPU利用率:0.00% (Kernel: 0.00% User:0.00%)     CPU空闲率:100.00%
进程已启动:0:7:26:15
CPU利用率:1.56% (Kernel: 0.00% User:1.56%)     CPU空闲率:98.44%
进程已启动:0:7:26:16
CPU利用率:0.00% (Kernel: 0.00% User:0.00%)     CPU空闲率:100.00%
进程已启动:0:7:26:17
CPU利用率:4.64% (Kernel: 0.00% User:4.64%)     CPU空闲率:95.36%
进程已启动:0:7:26:18
CPU利用率:3.12% (Kernel: 0.00% User:3.12%)     CPU空闲率:96.88%
进程已启动:0:7:26:19
CPU利用率:0.00% (Kernel: 0.00% User:0.00%)     CPU空闲率:100.00%Sleep for 3 seconds and then kill the process...
Process Killed进程已启动:0:7:26:26
进程已退出:0:0:0:4

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/19149.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

如何制作代购系统的客服支持模块

在代购系统中&#xff0c;客服支持模块是维护用户满意度和忠诚度的关键环节。一个有效的客服支持模块不仅可以解决用户的疑问和问题&#xff0c;还可以收集用户反馈&#xff0c;用于改进服务和产品。本文将详细介绍如何制作一个代购系统的客服支持模块&#xff0c;包括前端界面…

【unity小技巧】一些unity3D灯光的使用与渲染及性能优化方案

文章目录 天空盒反射配置太阳耀斑眩光烘培光照烘培光照时弹出错误&#xff0c;记得勾选模型下面的选择阴影项目配置光源模型模型shader的问题 全局光照混合光照模式混合照明模式减性照明模式Shadowmask照明模式间接烘焙照明模式 环境光遮罩灯光探针反射探针技术关闭反射探针可以…

Spring Boot汽车资讯:科技与汽车的对话

5系统详细实现 5.1 管理员模块的实现 5.1.1 用户信息管理 汽车资讯网站的系统管理员可以管理用户&#xff0c;可以对用户信息修改删除审核以及查询操作。具体界面的展示如图5.1所示。 图5.1 用户信息管理界面 5.1.2 汽车品牌管理 系统管理员可以汽车品牌信息进行添加&#xf…

go 学习网站,go例子 go demo go学习视频

1. 代码例子&#xff1a; Go by Example 2. b站 视频&#xff1a; 尚硅谷视频&#xff1a; 004_尚硅谷_程序的基本概念_哔哩哔哩_bilibili 3. go技术文档&#xff1a; fmt Go语言中文文档

记录下,用油猴Tampermonkey监听所有请求,绕过seesion

油猴Tampermonkey监听所有请求&#xff0c;绕过seesion 前因后果脚本编写 前因后果 原因是要白嫖一个网站的接口&#xff0c;这个接口的页面入口被隐藏掉了&#xff0c;不能通过页面调用&#xff0c;幸好之前有想过逆向破解通过账号密码模拟登录后拿到token&#xff0c;请求该…

网络安全:我们的安全防线

在数字化时代&#xff0c;网络安全已成为国家安全、经济发展和社会稳定的重要组成部分。网络安全不仅仅是技术问题&#xff0c;更是一个涉及政治、经济、文化、社会等多个层面的综合性问题。从宏观到微观&#xff0c;网络安全的重要性不言而喻。 宏观层面&#xff1a;国家安全与…

多账号登录管理器(淘宝、京东、拼多多等)

目录 下载安装与运行 解决什么问题 功能说明 目前支持的平台 功能演示 登录后能保持多久 下载安装与运行 下载、安装与运行 语雀 解决什么问题 多个账号的快捷登录与切换 功能说明 支持多个电商平台支持多个账号的登录保持支持快捷切换支持导入导出支持批量删除支持…

浅谈网络 | 二层到三层

目录 物理层到MAC层第一层&#xff08;物理层&#xff09;第二层&#xff08;数据链路层&#xff09;局域网 交换机与VLAN生成树协议VLAN ICMP与pingICMP 协议的格式 网关静态路由是什么&#xff1f; 路由协议如何配置策略路由&#xff1f;动态路由算法动态路由协议 物理层到MA…

c++ 后端

基础知识 1. 指针、引用2. 数组3. 缺省参数4. 函数重载5. 内联函数6. 宏7. auto8. const9. 类和对象10. 类的6个默认成员函数11. 初始化列表12. this指针13. C/C的区别14. C 三大特性15. 结构体内存对齐规则16. explicit17. static18. 友元类、友元函数19. 内部类20. 内存管理&…

汽车资讯新趋势:Spring Boot技术解读

5系统详细实现 5.1 管理员模块的实现 5.1.1 用户信息管理 汽车资讯网站的系统管理员可以管理用户&#xff0c;可以对用户信息修改删除审核以及查询操作。具体界面的展示如图5.1所示。 图5.1 用户信息管理界面 5.1.2 汽车品牌管理 系统管理员可以汽车品牌信息进行添加&#xf…

【C++】vector

一、vector的介绍及使用 1.1 vector的介绍 vector的底层与string相似都是顺序表形式管理数组&#xff0c;本质上来说string就可以归入到vector里面&#xff0c;但是在实际使用中&#xff0c;字符有很多自身独有的接口设计需要&#xff0c;因此string被单独拿出来设计。在前面s…

uniapp Uview上传图片组件Upload会自动刷新

背景 最近在做跑团小程序&#xff0c;马上接近尾声了&#xff0c;今天新增一个团长增加活动页面&#xff1a; 然后一切准备就绪&#xff0c;发现了一个问题&#xff0c;当选择上传图片后&#xff0c;页面会自动刷新&#xff0c;把之前填写的信息全部重置了。奇怪了&#xff0c…

软件测试之缺陷管理

一、软件缺陷的基本概念 1、软件缺陷的基本概念主要分为&#xff1a;缺陷、故障、失效这三种。 &#xff08;1&#xff09;缺陷&#xff08;defect&#xff09;&#xff1a;存在于软件之中的偏差&#xff0c;可被激活&#xff0c;以静态的形式存在于软件内部&#xff0c;相当…

数字资产与大健康领域的知识宝藏:高效知识库搭建策略

在数字化时代&#xff0c;大健康领域的企业积累了丰富的数字资产&#xff0c;这些资产如同一座待挖掘的金矿&#xff0c;蕴含着巨大的价值。高效搭建知识库&#xff0c;能够将这些数字资产转化为企业竞争力。 数字资产与大健康领域知识宝藏 数字资产在大健康领域包括患者数据…

使用WebRTC实现点对点实时音视频通信的技术详解

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用WebRTC实现点对点实时音视频通信的技术详解 使用WebRTC实现点对点实时音视频通信的技术详解 使用WebRTC实现点对点实时音视频…

Leetcode打卡:最少翻转次数使二进制矩阵回文II

执行结果&#xff1a;通过 题目&#xff1a;3240 最少翻转次数使二进制矩阵回文II 给你一个 m x n 的二进制矩阵 grid 。 如果矩阵中一行或者一列从前往后与从后往前读是一样的&#xff0c;那么我们称这一行或者这一列是 回文 的。 你可以将 grid 中任意格子的值 翻转 &…

VTK知识学习(9)-空间变换

1、前言 在三维空间里定义的三维模型&#xff0c;最后显示时都是投影到二维平面&#xff0c;比如在屏幕上显示。 三维到二维的投影包括透视投影&#xff08;Perspective Projection&#xff09;和正交投影&#xff08;Orthogonale Projection&#xff09;。正交投影也叫平行投…

英伟达 Isaac Sim仿真平台安装体验

硬件配置、系统 RTX 3080RAM: 32Gi7-12700Fubuntu20.04 使用Omniverse launcher安装加载isaac sim 这种方法我并没有成功&#xff0c;因为启动的时候报错Failed to create any GPU devices, including an attempt with compatibility mode. 。后面我选择使用 isaac sim dock…

笔记02----重新思考轻量化视觉Transformer中的局部感知CloFormer(即插即用)

1. 基本信息 论文标题: 《Rethinking Local Perception in Lightweight Vision Transformer》中文标题: 《重新思考轻量化视觉Transformer中的局部感知》作者单位: 清华大学发表时间: 2023论文地址: https://arxiv.org/abs/2303.17803代码地址: https://github.com/qhfan/CloF…

LVGL-从入门到熟练使用

LVGL简介 LVGL&#xff08; Light and Versatile Graphics Library &#xff09;是一个轻量、多功能的开源图形库。 1、丰富且强大的模块化图形组件&#xff1a;按钮 、图表 、列表、滑动条、图片等 2、高级的图形引擎&#xff1a;动画、抗锯齿、透明度、平滑滚动、图层混合等…