metrics_process/implementation/
windows.rs

1use std::mem::{size_of, MaybeUninit};
2use windows::{Win32::Foundation::*, Win32::System::ProcessStatus::*, Win32::System::Threading::*};
3
4use super::Metrics;
5
6/// Collect metrics.
7///
8/// Refer: / https://github.com/prometheus/client_golang/blob/c7aa2a5b843527449adb99ad113fe14ed15e4eb0/prometheus/process_collector_windows.go#L81-L116
9///
10// Copyright 2019 The Prometheus Authors
11// Licensed under the Apache License, Version 2.0 (the "License");
12// you may not use this file except in compliance with the License.
13// You may obtain a copy of the License at
14//
15// http://www.apache.org/licenses/LICENSE-2.0
16//
17// Unless required by applicable law or agreed to in writing, software
18// distributed under the License is distributed on an "AS IS" BASIS,
19// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20// See the License for the specific language governing permissions and
21// limitations under the License.
22pub fn collect() -> Metrics {
23    let mut metrics = Metrics::default();
24    unsafe {
25        let h = GetCurrentProcess();
26        let (start_time_seconds, cpu_seconds_total) = {
27            let mut creationtime = MaybeUninit::uninit();
28            let mut _exittime = MaybeUninit::uninit();
29            let mut kerneltime = MaybeUninit::uninit();
30            let mut usertime = MaybeUninit::uninit();
31            let ret = GetProcessTimes(
32                h,
33                creationtime.as_mut_ptr(),
34                _exittime.as_mut_ptr(),
35                kerneltime.as_mut_ptr(),
36                usertime.as_mut_ptr(),
37            );
38            if ret.is_ok() {
39                // `creationtime` and `_exittime` are points in time expressed as the amount of time that
40                // has elapsed since midnight on January 1, 1601 in 100 nanosecond time units.
41                let start_time_seconds =
42                    filetime_to_unix_epoch_in_seconds(creationtime.assume_init());
43                // `kerneltime` and `usertime` are amounts of time in 100 nanosecond time units.
44                let cpu_seconds_total = {
45                    let stime = filetime_to_seconds(kerneltime.assume_init());
46                    let utime = filetime_to_seconds(usertime.assume_init());
47                    stime + utime
48                };
49                (Some(start_time_seconds as u64), Some(cpu_seconds_total))
50            } else {
51                (None, None)
52            }
53        };
54        metrics.start_time_seconds = start_time_seconds;
55        metrics.cpu_seconds_total = cpu_seconds_total;
56
57        let (virtual_memory_bytes, resident_memory_bytes) = {
58            // We need to use PROCESS_MEMORY_COUNTERS_EX but GetProcessMemoryInfoEx is not provided
59            // thus we need to cast PROCESS_MEMORY_COUNTERS_EX into PROCESS_MEMORY_COUNTERS to use
60            // it with GetProcessMemoryInfo.
61            let memcounters = {
62                let m = &PROCESS_MEMORY_COUNTERS_EX::default();
63                m as *const _ as *mut PROCESS_MEMORY_COUNTERS
64            };
65            let cb = size_of::<PROCESS_MEMORY_COUNTERS_EX>();
66            let ret = GetProcessMemoryInfo(h, memcounters, cb as u32);
67            if ret.is_ok() {
68                let memcounters = memcounters as *const _ as *const PROCESS_MEMORY_COUNTERS_EX;
69                let &memcounters = &*memcounters;
70                (
71                    Some(memcounters.PrivateUsage as u64),
72                    Some(memcounters.WorkingSetSize as u64),
73                )
74            } else {
75                (None, None)
76            }
77        };
78        metrics.virtual_memory_bytes = virtual_memory_bytes;
79        metrics.resident_memory_bytes = resident_memory_bytes;
80
81        let open_fds = {
82            let mut handlecount = 0;
83            let ret = GetProcessHandleCount(h, &mut handlecount);
84            if ret.is_ok() {
85                Some(handlecount as u64)
86            } else {
87                None
88            }
89        };
90        metrics.open_fds = open_fds;
91        metrics.max_fds = Some(16 * 1024 * 1024); // Windows has a hard-coded max limit, not per-process.
92    }
93    metrics
94}
95
96/// Convert FILETIME to seconds.
97///
98/// FILETIME contains a 64-bit value representing the number of 100-nanosecond intervals.
99///
100/// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
101fn filetime_to_seconds(ft: FILETIME) -> f64 {
102    // 100-nanosecond intervals since January 1, 1601
103    let low = ft.dwLowDateTime as u64;
104    let high = ft.dwHighDateTime as u64;
105    let nsec = high.checked_shl(32).unwrap_or(0) + low;
106    // convert into nanoseconds
107    let nsec = nsec * 100;
108    // convert into seconds
109    nsec as f64 / 1e9
110}
111
112/// Convert FILETIME to Unix Epoch in seconds.
113///
114/// FILETIME contains a 64-bit value representing the number of 100-nanosecond intervals
115/// since January 1, 1601 (UTC).
116///
117/// https://docs.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
118/// https://cs.opensource.google/go/x/sys/+/2296e014:windows/types_windows.go;l=792-800
119///
120// Copyright (c) 2009 The Go Authors. All rights reserved.
121//
122// Redistribution and use in source and binary forms, with or without
123// modification, are permitted provided that the following conditions are
124// met:
125//
126//    * Redistributions of source code must retain the above copyright
127// notice, this list of conditions and the following disclaimer.
128//    * Redistributions in binary form must reproduce the above
129// copyright notice, this list of conditions and the following disclaimer
130// in the documentation and/or other materials provided with the
131// distribution.
132//    * Neither the name of Google Inc. nor the names of its
133// contributors may be used to endorse or promote products derived from
134// this software without specific prior written permission.
135//
136// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
137// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
138// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
139// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
140// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
141// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
142// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
143// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
144// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
145// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
146// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
147//
148fn filetime_to_unix_epoch_in_seconds(ft: FILETIME) -> f64 {
149    let low = ft.dwLowDateTime as u64;
150    let high = ft.dwHighDateTime as u64;
151    let nsec = high.checked_shl(32).unwrap_or(0) + low;
152    // change starting time to the epoch (00:00:00 UTC, January 1, 1970)
153    let nsec = nsec - 116444736000000000;
154    // convert into nanoseconds
155    let nsec = nsec * 100;
156    // convert into seconds
157    nsec as f64 / 1e9
158}