mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-05 18:20:34 +00:00
Revert "Revert "metrics: Add metrics crate.""
This reverts commit 55a5c8d8f9
.
In order to accommodate the removal of the
UnixSeqPacket+CloseNotifier change, I updated the controller.rs
to reference platform-specific internal implementations.
On Windows, CloseNotifier is used to detect closed Tubes.
On Linux, we rely on PollContext returning because the socket fd
is hung up.
Some minor adjustmets to the code were made just to allow as
litte duplication as possible.
In the end, very little logic has changed from the original CL,
it's just moved around.
TL;DR:
This fixes the downstream regression by removing its dependency
on the breaking changes to base.
BUG=b:232316549
FIXED=b:232316549
TEST=Crosvm tests
Change-Id: I946d5096f7a312538c3c694950697fab1be7f0ca
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3661257
Commit-Queue: Michael Hoyle <mikehoyle@google.com>
Reviewed-by: Daniel Verkamp <dverkamp@chromium.org>
Tested-by: kokoro <noreply+kokoro@google.com>
This commit is contained in:
parent
a19d6ce89c
commit
c29271e168
21 changed files with 1532 additions and 0 deletions
|
@ -171,6 +171,7 @@ libc = "0.2.93"
|
|||
libcras = "*"
|
||||
# Compile out trace statements in release builds
|
||||
log = { version = "0", features = ["release_max_level_debug"]}
|
||||
metrics = { path = "metrics" }
|
||||
minijail = "*" # provided by ebuild
|
||||
net_util = { path = "net_util" }
|
||||
p9 = "*"
|
||||
|
|
28
metrics/Cargo.toml
Normal file
28
metrics/Cargo.toml
Normal file
|
@ -0,0 +1,28 @@
|
|||
[package]
|
||||
name = "metrics"
|
||||
version = "0.1.0"
|
||||
authors = ["The Chromium OS Authors"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
kiwi = ["lazy_static", "libc", "serde_json", "sync"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "*"
|
||||
base = { path = "../base" }
|
||||
cfg-if = "*"
|
||||
lazy_static = { version = "*", optional = true }
|
||||
libc = { version = "*", optional = true }
|
||||
protobuf = { version = "2.24", features = [ "with-serde" ] }
|
||||
serde = { version = "1", features = [ "derive" ] }
|
||||
serde_json = { version = "*", optional = true }
|
||||
sync = { path = "../common/sync", optional = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
chrono = { version = "*" }
|
||||
winapi = { version = "*" }
|
||||
wmi = { version = "^0.9" }
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
protoc-rust = "2.24"
|
52
metrics/build.rs
Normal file
52
metrics/build.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use protoc_rust::{Codegen, Customize};
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use std::{env, fs};
|
||||
|
||||
fn main() {
|
||||
build_protos();
|
||||
}
|
||||
|
||||
// TODO(mikehoyle): Unify all proto-building logic across crates into a common build dependency.
|
||||
fn build_protos() {
|
||||
let proto_files = vec!["protos/event_details.proto"];
|
||||
let out_dir = format!(
|
||||
"{}/metrics_protos",
|
||||
env::var("OUT_DIR").expect("OUT_DIR env does not exist.")
|
||||
);
|
||||
fs::create_dir_all(&out_dir).unwrap();
|
||||
|
||||
Codegen::new()
|
||||
.out_dir(&out_dir)
|
||||
.inputs(&proto_files)
|
||||
.customize(Customize {
|
||||
serde_derive: Some(true),
|
||||
..Default::default()
|
||||
})
|
||||
.run()
|
||||
.unwrap();
|
||||
create_gen_file(proto_files, &out_dir);
|
||||
}
|
||||
|
||||
fn create_gen_file(proto_files: Vec<&str>, out_dir: &str) {
|
||||
let generated = PathBuf::from(&out_dir).join("generated.rs");
|
||||
let out = File::create(generated).expect("Failed to create generated file.");
|
||||
|
||||
for proto in proto_files {
|
||||
let file_stem = PathBuf::from(proto)
|
||||
.file_stem()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
let out_dir = out_dir.replace("\\", "/");
|
||||
writeln!(&out, "#[path = \"{}/{}.rs\"]", out_dir, file_stem)
|
||||
.expect("failed to write to generated.");
|
||||
writeln!(&out, "pub mod {}_proto;", file_stem).expect("failed to write to generated.");
|
||||
}
|
||||
}
|
98
metrics/protos/event_details.proto
Normal file
98
metrics/protos/event_details.proto
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Provides data structures for additional details for metrics events.
|
||||
syntax = "proto2";
|
||||
|
||||
message RecordDetails {
|
||||
reserved 1 to 11, 14 to 16;
|
||||
// Additional details about an unexpected exit of a child process within
|
||||
// the emulator.
|
||||
optional EmulatorChildProcessExitDetails emulator_child_process_exit_details =
|
||||
12;
|
||||
// Additional details about wave formats from the Window's host system.
|
||||
optional WaveFormatDetails wave_format_details = 13;
|
||||
}
|
||||
|
||||
message WaveFormatDetails {
|
||||
// Format requested by WASAPI `GetMixFormat` system call.
|
||||
optional WaveFormat requested = 1;
|
||||
// Originally the requested wave format that's modified by the emulator. Only
|
||||
// populated if the emulator decides the requested wave format should not be
|
||||
// used.
|
||||
optional WaveFormat modified = 2;
|
||||
// Format that is valid and closest matching to the modified format, if the
|
||||
// modified was rejected. Should only be populated if modified is also
|
||||
// non-null and was rejected by WASAPI `IsFormatSupported` system call.
|
||||
optional WaveFormat closest_matched = 3;
|
||||
}
|
||||
|
||||
// Defines the format of waveformat audio data. This information is used by
|
||||
// WASAPI to determine how to process the audio playback data coming from the
|
||||
// emulator.
|
||||
//
|
||||
// The fields in the structure come from WAVEFORMATEXTENSIBLE of win32 api.
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/mmreg/ns-mmreg-waveformatextensible
|
||||
message WaveFormat {
|
||||
// Ex. 65534 (Maps to WAVE_FORMAT_EXTENSIBLE)
|
||||
optional int32 format_tag = 1;
|
||||
// Number of channels.
|
||||
optional int32 channels = 2;
|
||||
// Sample rate in Hz. Ex: 48000
|
||||
optional int32 samples_per_sec = 3;
|
||||
// Required average data-transfer rate for the format tag. Usually this will
|
||||
// be samples_per_sec * block_align, since the format tag is usually
|
||||
// WAVE_FORMAT_IEEE_FLOAT or it's extensible and SubFormat is
|
||||
// KSDATAFORMAT_SUBTYPE_IEEE_FLOAT.
|
||||
optional int32 avg_bytes_per_sec = 4;
|
||||
// Minimum atomic unit of data based on the format_tag. Usually this will
|
||||
// just be bits_per_samples * channels.
|
||||
optional int32 block_align = 5;
|
||||
// Bits used per sample. Must be a multiple of 8.
|
||||
optional int32 bits_per_sample = 6;
|
||||
// Size in bytes of extra information appended to WAVEFORMATEX struct.
|
||||
optional int32 size_bytes = 7;
|
||||
|
||||
// The next fields are part of the WAVEFORMATEXTENSIBLE struct. They will only
|
||||
// be non-null if format_tag is WAVE_FORMAT_EXTENSIBLE.
|
||||
|
||||
// Bit depth. Can be any value. Ex. bits_per_sample is 24,
|
||||
// but samples is 20. Note: This value is a union, so it could mean something
|
||||
// slightly different, but most likely won't. Refer to doc for more info.
|
||||
optional int32 samples = 8;
|
||||
// Bitmask mapping channels in stream to speaker positions.
|
||||
// Ex. 3 ( SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT )
|
||||
optional int64 channel_mask = 9;
|
||||
// Similar to format_tag, but for WAVEFORMATEXTENSIBLE structs.
|
||||
optional WaveFormatSubFormat sub_format = 10;
|
||||
|
||||
// Subformat GUID mapping:
|
||||
// https://github.com/retep998/winapi-rs/blob/2f76bdea3a79817ccfab496fbd1786d5a697387b/src/shared/ksmedia.rs
|
||||
enum WaveFormatSubFormat {
|
||||
KSDATAFORMAT_SUBTYPE_INVALID = 0;
|
||||
KSDATAFORMAT_SUBTYPE_ANALOG = 1;
|
||||
KSDATAFORMAT_SUBTYPE_PCM = 2;
|
||||
KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = 3;
|
||||
KSDATAFORMAT_SUBTYPE_DRM = 4;
|
||||
KSDATAFORMAT_SUBTYPE_ALAW = 5;
|
||||
KSDATAFORMAT_SUBTYPE_MULAW = 6;
|
||||
KSDATAFORMAT_SUBTYPE_ADPCM = 7;
|
||||
KSDATAFORMAT_SUBTYPE_MPEG = 8;
|
||||
}
|
||||
}
|
||||
|
||||
enum EmulatorProcessType {
|
||||
PROCESS_TYPE_UNKNOWN = 0;
|
||||
PROCESS_TYPE_MAIN = 1;
|
||||
PROCESS_TYPE_BLOCK = 2;
|
||||
PROCESS_TYPE_METRICS = 3;
|
||||
PROCESS_TYPE_NET = 4;
|
||||
PROCESS_TYPE_SLIRP = 5;
|
||||
PROCESS_TYPE_GPU = 6;
|
||||
PROCESS_TYPE_SOUND = 7;
|
||||
}
|
||||
|
||||
message EmulatorChildProcessExitDetails {
|
||||
// The Windows exit code of the child process
|
||||
optional uint32 exit_code = 1;
|
||||
// The process identifier, as defined by the ProcessType enum in the
|
||||
// emulator code.
|
||||
optional EmulatorProcessType process_type = 2;
|
||||
}
|
77
metrics/src/controller.rs
Normal file
77
metrics/src/controller.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! Encapsulate the main runtime loop of a metrics process.
|
||||
|
||||
use crate::metrics_requests::MetricsRequest;
|
||||
use crate::RequestHandler;
|
||||
use anyhow::Result;
|
||||
use base::{info, warn, PollToken, Tube};
|
||||
|
||||
/// Handles incoming requests to log metrics
|
||||
pub(crate) trait MetricsRequestHandler {
|
||||
fn new() -> Self;
|
||||
fn handle_request(&self, request: MetricsRequest);
|
||||
fn shutdown(&self);
|
||||
}
|
||||
|
||||
/// Runs the metrics controller.
|
||||
pub struct MetricsController {
|
||||
pub(crate) agents: Vec<Tube>,
|
||||
handler: RequestHandler,
|
||||
pub(crate) closed_tubes: usize,
|
||||
}
|
||||
|
||||
#[derive(PollToken)]
|
||||
pub(crate) enum MetricsControllerToken {
|
||||
/// Triggered when the agent's pipe is readable (e.g. read_notifier).
|
||||
Agent(usize),
|
||||
/// Triggered when the agent's pipe closes (e.g. close_notifier).
|
||||
#[cfg(windows)]
|
||||
AgentExited(usize),
|
||||
}
|
||||
|
||||
impl MetricsController {
|
||||
pub fn new(agents: Vec<Tube>) -> Self {
|
||||
Self {
|
||||
agents,
|
||||
handler: RequestHandler::new(),
|
||||
closed_tubes: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the metrics controller until all clients exit & close their Tubes.
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
self.run_internal()?;
|
||||
self.handler.shutdown();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handles a tube that has indicated it has data ready to read.
|
||||
pub(crate) fn on_tube_readable(&self, client: &Tube) {
|
||||
match client.recv::<MetricsRequest>() {
|
||||
Ok(req) => self.handler.handle_request(req),
|
||||
Err(e) => {
|
||||
warn!("unexpected error receiving agent metrics request: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles a closed connection, and returns a bool indicating
|
||||
/// whether the run loop itself should close.
|
||||
pub(crate) fn on_connection_closed(&mut self) -> bool {
|
||||
self.closed_tubes += 1;
|
||||
info!(
|
||||
"metrics tube closed: {} out of {} closed",
|
||||
self.closed_tubes,
|
||||
self.agents.len(),
|
||||
);
|
||||
if self.closed_tubes == self.agents.len() {
|
||||
info!("metrics run loop exiting: all tubes closed");
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
93
metrics/src/event_types.rs
Normal file
93
metrics/src/event_types.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use anyhow::Error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::From;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
// TODO(mikehoyle): Create a way to generate these directly from the
|
||||
// proto for a single source-of-truth.
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum MetricEventType {
|
||||
CpuUsage,
|
||||
MemoryUsage,
|
||||
Fps,
|
||||
JankyFps,
|
||||
NetworkTxRate,
|
||||
NetworkRxRate,
|
||||
Interrupts,
|
||||
FrameTime,
|
||||
EmulatorGraphicsFreeze,
|
||||
EmulatorGraphicsUnfreeze,
|
||||
EmulatorGfxstreamVkAbortReason,
|
||||
ChildProcessExit,
|
||||
ReadIo,
|
||||
WriteIo,
|
||||
AudioFormatRequestOk,
|
||||
AudioFormatModifiedOk,
|
||||
AudioFormatFailed,
|
||||
TscCoresOutOfSync,
|
||||
NetworkTxRateSummarized,
|
||||
NetworkRxRateSummarized,
|
||||
Other(i64),
|
||||
}
|
||||
|
||||
impl From<MetricEventType> for i64 {
|
||||
fn from(event_code: MetricEventType) -> Self {
|
||||
match event_code {
|
||||
MetricEventType::CpuUsage => 10001,
|
||||
MetricEventType::MemoryUsage => 10002,
|
||||
MetricEventType::Fps => 10003,
|
||||
MetricEventType::JankyFps => 10004,
|
||||
MetricEventType::NetworkTxRate => 10005,
|
||||
MetricEventType::NetworkRxRate => 10006,
|
||||
MetricEventType::Interrupts => 10007,
|
||||
MetricEventType::FrameTime => 10008,
|
||||
MetricEventType::EmulatorGraphicsFreeze => 10009,
|
||||
MetricEventType::EmulatorGraphicsUnfreeze => 10010,
|
||||
MetricEventType::EmulatorGfxstreamVkAbortReason => 10011,
|
||||
MetricEventType::ChildProcessExit => 10012,
|
||||
MetricEventType::ReadIo => 10013,
|
||||
MetricEventType::WriteIo => 10014,
|
||||
MetricEventType::AudioFormatRequestOk => 10015,
|
||||
MetricEventType::AudioFormatModifiedOk => 10016,
|
||||
MetricEventType::AudioFormatFailed => 10017,
|
||||
MetricEventType::TscCoresOutOfSync => 10018,
|
||||
MetricEventType::NetworkTxRateSummarized => 10019,
|
||||
MetricEventType::NetworkRxRateSummarized => 10020,
|
||||
MetricEventType::Other(code) => code,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<i64> for MetricEventType {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(event_code: i64) -> Result<Self, Self::Error> {
|
||||
match event_code {
|
||||
10001 => Ok(MetricEventType::CpuUsage),
|
||||
10002 => Ok(MetricEventType::MemoryUsage),
|
||||
10003 => Ok(MetricEventType::Fps),
|
||||
10004 => Ok(MetricEventType::JankyFps),
|
||||
10005 => Ok(MetricEventType::NetworkTxRate),
|
||||
10006 => Ok(MetricEventType::NetworkRxRate),
|
||||
10007 => Ok(MetricEventType::Interrupts),
|
||||
10008 => Ok(MetricEventType::FrameTime),
|
||||
10009 => Ok(MetricEventType::EmulatorGraphicsFreeze),
|
||||
10010 => Ok(MetricEventType::EmulatorGraphicsUnfreeze),
|
||||
10011 => Ok(MetricEventType::EmulatorGfxstreamVkAbortReason),
|
||||
10012 => Ok(MetricEventType::ChildProcessExit),
|
||||
10013 => Ok(MetricEventType::ReadIo),
|
||||
10014 => Ok(MetricEventType::WriteIo),
|
||||
10015 => Ok(MetricEventType::AudioFormatRequestOk),
|
||||
10016 => Ok(MetricEventType::AudioFormatModifiedOk),
|
||||
10017 => Ok(MetricEventType::AudioFormatFailed),
|
||||
10018 => Ok(MetricEventType::TscCoresOutOfSync),
|
||||
10019 => Ok(MetricEventType::NetworkTxRateSummarized),
|
||||
10020 => Ok(MetricEventType::NetworkRxRateSummarized),
|
||||
_ => Ok(MetricEventType::Other(event_code)),
|
||||
}
|
||||
}
|
||||
}
|
30
metrics/src/lib.rs
Normal file
30
metrics/src/lib.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! This crate serves to provide metrics bindings to be used throughout the codebase.
|
||||
//! For binaries that wish to use metrics, the intention is that an independent metrics
|
||||
//! process will run (main loop in the controller mod), and receive requests via a tube from
|
||||
//! another process.
|
||||
//!
|
||||
//! At head, metrics requests are ignored. However, a branching codebase can choose to implement
|
||||
//! their own handler which processes and uploads metrics requests as it sees fit, by setting the
|
||||
//! appropriate RequestHandler.
|
||||
|
||||
mod controller;
|
||||
mod event_types;
|
||||
mod metrics_cleanup;
|
||||
mod metrics_requests;
|
||||
mod noop;
|
||||
mod sys;
|
||||
// Exports a <name>_proto module for each proto file
|
||||
include!(concat!(env!("OUT_DIR"), "/metrics_protos/generated.rs"));
|
||||
|
||||
pub use controller::MetricsController;
|
||||
pub use event_types::MetricEventType;
|
||||
pub use metrics_cleanup::MetricsClientDestructor;
|
||||
pub use noop::*;
|
||||
#[allow(unused_imports)]
|
||||
pub use sys::*;
|
||||
|
||||
pub type RequestHandler = NoopMetricsRequestHandler;
|
22
metrics/src/metrics_cleanup.rs
Normal file
22
metrics/src/metrics_cleanup.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! Provides an tool for metrics client cleanup which may hold global state.
|
||||
|
||||
/// Ensures any cleanup necessary is performed on drop. Can be used to ensure cleanup is done
|
||||
/// regardless of how the caller exits. Should be idempotent.
|
||||
pub struct MetricsClientDestructor(Box<dyn FnMut()>);
|
||||
impl MetricsClientDestructor {
|
||||
pub fn new<T: 'static + FnMut()>(cleanup: T) -> Self {
|
||||
MetricsClientDestructor(Box::new(cleanup))
|
||||
}
|
||||
/// A convenience method for immediately dropping self and invoking drop logic on the contained
|
||||
/// object.
|
||||
pub fn cleanup(self) {}
|
||||
}
|
||||
impl Drop for MetricsClientDestructor {
|
||||
fn drop(&mut self) {
|
||||
self.0();
|
||||
}
|
||||
}
|
47
metrics/src/metrics_requests.rs
Normal file
47
metrics/src/metrics_requests.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! Structs used to transport log requests between client processes and the logging controller
|
||||
|
||||
use crate::MetricEventType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LogMetric {
|
||||
pub event_code: MetricEventType,
|
||||
pub value: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LogDescriptor {
|
||||
pub event_code: MetricEventType,
|
||||
pub descriptor: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct LogHighFrequencyDescriptorMetric {
|
||||
pub event_code: MetricEventType,
|
||||
pub descriptor: i64,
|
||||
pub step: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct EventWithSerializedDetails {
|
||||
pub event_code: MetricEventType,
|
||||
pub serialized_details: Box<[u8]>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub enum MetricsRequest {
|
||||
LogDescriptor(LogDescriptor),
|
||||
LogEvent(MetricEventType),
|
||||
LogMetric(LogMetric),
|
||||
LogHistogram(LogMetric),
|
||||
SetAuthToken(String),
|
||||
SetGraphicsApi(String),
|
||||
SetPackageName(String),
|
||||
MergeSessionInvariants(Vec<u8>),
|
||||
LogHighFrequencyDescriptorMetric(LogHighFrequencyDescriptorMetric),
|
||||
LogEventWithSerializedDetails(EventWithSerializedDetails),
|
||||
}
|
25
metrics/src/noop/client.rs
Normal file
25
metrics/src/noop/client.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::{event_details_proto::RecordDetails, MetricEventType, MetricsClientDestructor};
|
||||
use base::Tube;
|
||||
|
||||
/// This interface exists to be used and re-implemented by downstream forks. Updates shouldn't be
|
||||
/// done without ensuring they won't cause breakages in dependent codebases.
|
||||
pub fn initialize(_: Tube) {}
|
||||
#[cfg(test)]
|
||||
pub fn force_initialize(_: Tube) {}
|
||||
pub fn get_destructor() -> MetricsClientDestructor {
|
||||
MetricsClientDestructor::new(|| {})
|
||||
}
|
||||
pub fn set_auth_token(_: &str) {}
|
||||
pub fn set_graphics_api(_: &str) {}
|
||||
pub fn set_package_name(_: &str) {}
|
||||
pub fn merge_session_invariants(_: &[u8]) {}
|
||||
pub fn log_descriptor(_: MetricEventType, _: i64) {}
|
||||
pub fn log_event(_: MetricEventType) {}
|
||||
pub fn log_metric(_: MetricEventType, _: i64) {}
|
||||
pub fn log_histogram_metric(_: MetricEventType, _: i64) {}
|
||||
pub fn log_high_frequency_descriptor_event(_: MetricEventType, _: i64, _: i64) {}
|
||||
pub fn log_event_with_details(_: MetricEventType, _: &RecordDetails) {}
|
14
metrics/src/noop/mod.rs
Normal file
14
metrics/src/noop/mod.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! Provides noop implementations of metrics interfaces, to be used by builds which don't wish
|
||||
//! to log metrics.
|
||||
|
||||
mod client;
|
||||
mod periodic_logger;
|
||||
mod request_handler;
|
||||
|
||||
pub use client::*;
|
||||
pub use periodic_logger::PeriodicLogger;
|
||||
pub use request_handler::NoopMetricsRequestHandler;
|
22
metrics/src/noop/periodic_logger.rs
Normal file
22
metrics/src/noop/periodic_logger.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::MetricEventType;
|
||||
use std::result::Result;
|
||||
use std::time::Duration;
|
||||
|
||||
/// A logging struct meant for use in tracking and periodically
|
||||
/// logging a single metric. The metric is aggregated over the
|
||||
/// designated time period. Intended for use with high-frequency metrics.
|
||||
pub struct PeriodicLogger;
|
||||
|
||||
impl PeriodicLogger {
|
||||
pub fn new(_event: MetricEventType, _period: Duration) -> Result<PeriodicLogger, String> {
|
||||
Ok(PeriodicLogger)
|
||||
}
|
||||
|
||||
/// Indicate the event has occurred with the given
|
||||
/// value to be aggregated over the given time period.
|
||||
pub fn log(&self, _value: i64) {}
|
||||
}
|
15
metrics/src/noop/request_handler.rs
Normal file
15
metrics/src/noop/request_handler.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::controller::MetricsRequestHandler;
|
||||
use crate::metrics_requests::MetricsRequest;
|
||||
|
||||
pub struct NoopMetricsRequestHandler;
|
||||
impl MetricsRequestHandler for NoopMetricsRequestHandler {
|
||||
fn new() -> Self {
|
||||
NoopMetricsRequestHandler
|
||||
}
|
||||
fn handle_request(&self, _req: MetricsRequest) {}
|
||||
fn shutdown(&self) {}
|
||||
}
|
12
metrics/src/sys.rs
Normal file
12
metrics/src/sys.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(windows)] {
|
||||
pub(crate) mod windows;
|
||||
pub use windows::*;
|
||||
} else if #[cfg(unix)] {
|
||||
pub(crate) mod unix;
|
||||
}
|
||||
}
|
1
metrics/src/sys/unix.rs
Normal file
1
metrics/src/sys/unix.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub(crate) mod controller;
|
37
metrics/src/sys/unix/controller.rs
Normal file
37
metrics/src/sys/unix/controller.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::controller::{MetricsController, MetricsControllerToken};
|
||||
use anyhow::Result;
|
||||
use base::{ReadNotifier, WaitContext};
|
||||
|
||||
impl MetricsController {
|
||||
pub(crate) fn run_internal(&mut self) -> Result<()> {
|
||||
let wait_ctx: WaitContext<MetricsControllerToken> = WaitContext::new()?;
|
||||
self.closed_tubes = 0;
|
||||
|
||||
for (agent_index, agent) in self.agents.iter().enumerate() {
|
||||
wait_ctx.add(
|
||||
agent.get_read_notifier(),
|
||||
MetricsControllerToken::Agent(agent_index),
|
||||
)?;
|
||||
}
|
||||
|
||||
'listen: loop {
|
||||
let events = wait_ctx.wait()?;
|
||||
for event in events.iter().filter(|e| e.is_readable) {
|
||||
let MetricsControllerToken::Agent(client_index) = event.token;
|
||||
self.on_tube_readable(&self.agents[client_index]);
|
||||
}
|
||||
for event in events.iter().filter(|e| e.is_hungup) {
|
||||
let MetricsControllerToken::Agent(client_index) = event.token;
|
||||
wait_ctx.delete(self.agents[client_index].get_read_notifier())?;
|
||||
if self.on_connection_closed() {
|
||||
break 'listen;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
24
metrics/src/sys/windows.rs
Normal file
24
metrics/src/sys/windows.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
pub(crate) mod controller;
|
||||
pub mod gpu_metrics;
|
||||
pub(crate) mod system_metrics;
|
||||
pub mod wmi;
|
||||
|
||||
pub use gpu_metrics::*;
|
||||
|
||||
pub const METRIC_UPLOAD_INTERVAL_SECONDS: i64 = 60;
|
||||
pub const API_GUEST_ANGLE_VK_ENUM_NAME: &str = "API_GUEST_ANGLE_VK";
|
||||
pub const API_HOST_ANGLE_D3D_ENUM_NAME: &str = "API_HOST_ANGLE_D3D";
|
||||
pub const API_UNKNOWN_ENUM_NAME: &str = "API_UNKNOWN";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
CannotCloneEvent,
|
||||
CannotInstantiateEvent,
|
||||
InstanceAlreadyExists,
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
45
metrics/src/sys/windows/controller.rs
Normal file
45
metrics/src/sys/windows/controller.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::controller::{MetricsController, MetricsControllerToken};
|
||||
use anyhow::Result;
|
||||
use base::{CloseNotifier, ReadNotifier, WaitContext};
|
||||
|
||||
impl MetricsController {
|
||||
pub(crate) fn run_internal(&mut self) -> Result<()> {
|
||||
let wait_ctx: WaitContext<MetricsControllerToken> = WaitContext::new()?;
|
||||
self.closed_tubes = 0;
|
||||
|
||||
for (agent_index, agent) in self.agents.iter().enumerate() {
|
||||
wait_ctx.add(
|
||||
agent.get_read_notifier(),
|
||||
MetricsControllerToken::Agent(agent_index),
|
||||
)?;
|
||||
wait_ctx.add(
|
||||
agent.get_close_notifier(),
|
||||
MetricsControllerToken::AgentExited(agent_index),
|
||||
)?;
|
||||
}
|
||||
|
||||
'listen: loop {
|
||||
let events = wait_ctx.wait()?;
|
||||
for event in events.iter().filter(|e| e.is_readable) {
|
||||
match event.token {
|
||||
MetricsControllerToken::Agent(client_index) => {
|
||||
self.on_tube_readable(&self.agents[client_index]);
|
||||
}
|
||||
MetricsControllerToken::AgentExited(client_index) => {
|
||||
let client = &self.agents[client_index];
|
||||
wait_ctx.delete(client.get_read_notifier())?;
|
||||
wait_ctx.delete(client.get_close_notifier())?;
|
||||
if self.on_connection_closed() {
|
||||
break 'listen;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
52
metrics/src/sys/windows/gpu_metrics.rs
Normal file
52
metrics/src/sys/windows/gpu_metrics.rs
Normal file
|
@ -0,0 +1,52 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::windows::system_metrics::CoreWinMetrics;
|
||||
use crate::windows::{Error, Result};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
static INSTANCE_EXISTS: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Used by gpu_display to show metrics in the CrosVm performance overlay.
|
||||
pub struct Metrics {
|
||||
metrics: Vec<Box<dyn ToString + Send + Sync>>,
|
||||
// more_metrics is for metrics which have multiple owners (e.g., device dependent).
|
||||
more_metrics: Vec<Arc<dyn ToString + Send + Sync>>,
|
||||
}
|
||||
|
||||
impl Metrics {
|
||||
pub fn new() -> Result<Self> {
|
||||
if INSTANCE_EXISTS
|
||||
.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
|
||||
.is_err()
|
||||
{
|
||||
return Err(Error::InstanceAlreadyExists);
|
||||
}
|
||||
Ok(Metrics {
|
||||
metrics: vec![
|
||||
#[cfg(windows)]
|
||||
Box::new(CoreWinMetrics::new()?),
|
||||
],
|
||||
more_metrics: vec![],
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_gpu_metrics(&mut self, t: Arc<dyn ToString + Send + Sync>) {
|
||||
self.more_metrics.push(t);
|
||||
}
|
||||
|
||||
pub fn get_metric_string(&self) -> String {
|
||||
let mut buf = String::new();
|
||||
for collector in self.metrics.iter() {
|
||||
buf.push_str(&collector.to_string());
|
||||
buf.push('\n');
|
||||
}
|
||||
for collector in self.more_metrics.iter() {
|
||||
buf.push_str(&collector.to_string());
|
||||
buf.push('\n');
|
||||
}
|
||||
buf
|
||||
}
|
||||
}
|
596
metrics/src/sys/windows/system_metrics.rs
Normal file
596
metrics/src/sys/windows/system_metrics.rs
Normal file
|
@ -0,0 +1,596 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::windows::{Error, Result, METRIC_UPLOAD_INTERVAL_SECONDS};
|
||||
use crate::{log_metric, MetricEventType};
|
||||
|
||||
use base::{
|
||||
error, AsRawDescriptor, Error as SysError, Event, FromRawDescriptor, PollToken, SafeDescriptor,
|
||||
WaitContext,
|
||||
};
|
||||
|
||||
use chrono::{DateTime, Local};
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::thread;
|
||||
use std::thread::JoinHandle;
|
||||
use std::time::Duration;
|
||||
|
||||
use winapi::shared::minwindef::{DWORD, FILETIME};
|
||||
use winapi::um::processthreadsapi::{GetProcessTimes, GetSystemTimes, OpenProcess};
|
||||
use winapi::um::psapi::{
|
||||
GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS, PROCESS_MEMORY_COUNTERS_EX,
|
||||
};
|
||||
use winapi::um::winbase::GetProcessIoCounters;
|
||||
use winapi::um::winnt::{
|
||||
IO_COUNTERS, LARGE_INTEGER, LONGLONG, PROCESS_QUERY_LIMITED_INFORMATION, PROCESS_VM_READ,
|
||||
SYNCHRONIZE,
|
||||
};
|
||||
|
||||
const BYTES_PER_MB: usize = 1024 * 1024;
|
||||
const WORKER_REPORT_INTERVAL: Duration = Duration::from_secs(1);
|
||||
|
||||
type SysResult<T> = std::result::Result<T, SysError>;
|
||||
|
||||
/// A worker job which periodically logs system metrics.
|
||||
struct Worker {
|
||||
exit_evt: Event,
|
||||
io: Arc<Mutex<Option<ProcessIoRecord>>>,
|
||||
measurements: Arc<Mutex<Option<Measurements>>>,
|
||||
memory: Arc<Mutex<ProcessMemory>>,
|
||||
memory_acc: Arc<Mutex<Option<ProcessMemoryAccumulated>>>,
|
||||
metrics_string: Arc<Mutex<String>>,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
fn run(&mut self) {
|
||||
#[derive(PollToken)]
|
||||
enum Token {
|
||||
Exit,
|
||||
}
|
||||
let event_ctx: WaitContext<Token> =
|
||||
match WaitContext::build_with(&[(&self.exit_evt, Token::Exit)]) {
|
||||
Ok(event_ctx) => event_ctx,
|
||||
Err(e) => {
|
||||
error!("failed creating WaitContext: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut last_metric_upload_time = Local::now();
|
||||
'poll: loop {
|
||||
let events = match event_ctx.wait_timeout(WORKER_REPORT_INTERVAL) {
|
||||
Ok(events) => events,
|
||||
Err(e) => {
|
||||
error!("failed polling for events: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
if events.is_empty() {
|
||||
self.collect_metrics();
|
||||
// Time budget for UI thread is very limited.
|
||||
// We make the metric string for displaying in UI in the
|
||||
// worker thread for best performance.
|
||||
self.make_metrics_string();
|
||||
|
||||
self.upload_metrics(&mut last_metric_upload_time);
|
||||
}
|
||||
|
||||
if events.into_iter().any(|e| e.is_readable) {
|
||||
break 'poll;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_metrics_string(&mut self) {
|
||||
let mut metrics_string = self.metrics_string.lock().unwrap();
|
||||
*metrics_string = format!(
|
||||
"{}\n{}",
|
||||
self.cpu_metrics_string(),
|
||||
self.mem_metrics_string()
|
||||
);
|
||||
}
|
||||
|
||||
fn upload_metrics(&self, last_metric_upload_time: &mut DateTime<Local>) {
|
||||
let time_elapsed = (Local::now() - *last_metric_upload_time).num_seconds();
|
||||
if time_elapsed >= METRIC_UPLOAD_INTERVAL_SECONDS {
|
||||
let mut memory_acc = self.memory_acc.lock().unwrap();
|
||||
if let Some(acc) = &*memory_acc {
|
||||
let mem = acc.accumulated.physical / acc.accumulated_count / BYTES_PER_MB;
|
||||
// The i64 cast will not cause overflow because the mem is at most 10TB for
|
||||
// Windows 10.
|
||||
log_metric(MetricEventType::MemoryUsage, mem as i64);
|
||||
}
|
||||
*memory_acc = None;
|
||||
|
||||
let mut cpu_measurements = self.measurements.lock().unwrap();
|
||||
if let Some(measurements) = &*cpu_measurements {
|
||||
let sys_time = measurements.current.sys_time;
|
||||
let process_time = measurements.current.process_time;
|
||||
let prev_sys_time = measurements.last_upload.sys_time;
|
||||
let prev_process_time = measurements.last_upload.process_time;
|
||||
|
||||
let diff_systime_kernel =
|
||||
compute_filetime_subtraction(sys_time.kernel, prev_sys_time.kernel);
|
||||
let diff_systime_user =
|
||||
compute_filetime_subtraction(sys_time.user, prev_sys_time.user);
|
||||
|
||||
let diff_processtime_kernel =
|
||||
compute_filetime_subtraction(process_time.kernel, prev_process_time.kernel);
|
||||
let diff_processtime_user =
|
||||
compute_filetime_subtraction(process_time.user, prev_process_time.user);
|
||||
|
||||
let total_systime = diff_systime_kernel + diff_systime_user;
|
||||
let total_processtime = diff_processtime_kernel + diff_processtime_user;
|
||||
|
||||
if total_systime > 0 {
|
||||
let cpu_usage = 100 * total_processtime / total_systime;
|
||||
// The i64 cast will not cause overflow because the usage is at most 100.
|
||||
log_metric(MetricEventType::CpuUsage, cpu_usage as i64);
|
||||
}
|
||||
}
|
||||
*cpu_measurements = None;
|
||||
|
||||
let mut io = self.io.lock().unwrap();
|
||||
if let Some(io_record) = &*io {
|
||||
let new_io_read_bytes =
|
||||
io_record.current.read_bytes - io_record.last_upload.read_bytes;
|
||||
let new_io_write_bytes =
|
||||
io_record.current.write_bytes - io_record.last_upload.write_bytes;
|
||||
|
||||
let ms_elapsed =
|
||||
(io_record.current_time - io_record.last_upload_time).num_milliseconds();
|
||||
if ms_elapsed > 0 {
|
||||
let io_read_bytes_per_sec =
|
||||
(new_io_read_bytes as f32) / (ms_elapsed as f32) * 1000.0;
|
||||
let io_write_bytes_per_sec =
|
||||
(new_io_write_bytes as f32) / (ms_elapsed as f32) * 1000.0;
|
||||
log_metric(MetricEventType::ReadIo, io_read_bytes_per_sec as i64);
|
||||
log_metric(MetricEventType::WriteIo, io_write_bytes_per_sec as i64);
|
||||
}
|
||||
}
|
||||
*io = None;
|
||||
*last_metric_upload_time = Local::now();
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_metrics(&mut self) {
|
||||
match self.get_cpu_metrics() {
|
||||
Ok(new_measurement) => {
|
||||
let mut measurements = self.measurements.lock().unwrap();
|
||||
let next_measurements = match *measurements {
|
||||
Some(Measurements {
|
||||
current,
|
||||
last_upload,
|
||||
..
|
||||
}) => Measurements {
|
||||
current: new_measurement,
|
||||
previous: current,
|
||||
last_upload,
|
||||
},
|
||||
None => Measurements {
|
||||
current: new_measurement,
|
||||
previous: new_measurement,
|
||||
last_upload: new_measurement,
|
||||
},
|
||||
};
|
||||
*measurements = Some(next_measurements);
|
||||
}
|
||||
Err(e) => {
|
||||
// Do not panic because of cpu query related failures.
|
||||
error!("Get cpu measurement failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
match self.get_mem_metrics() {
|
||||
Ok(mem) => {
|
||||
// Keep running sum and count to calculate averages.
|
||||
let mut memory_acc = self.memory_acc.lock().unwrap();
|
||||
let updated_memory_acc = match *memory_acc {
|
||||
Some(acc) => accumulate_process_memory(acc, mem),
|
||||
None => ProcessMemoryAccumulated {
|
||||
accumulated: mem,
|
||||
accumulated_count: 1,
|
||||
},
|
||||
};
|
||||
*memory_acc = Some(updated_memory_acc);
|
||||
*self.memory.lock().unwrap() = mem
|
||||
}
|
||||
Err(e) => {
|
||||
// Do not panic because of memory query failures.
|
||||
error!("Get cpu measurement failed: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
match self.get_io_metrics() {
|
||||
Ok(new_io) => {
|
||||
let mut io_record = self.io.lock().unwrap();
|
||||
let updated_io = match *io_record {
|
||||
Some(io) => ProcessIoRecord {
|
||||
current: new_io,
|
||||
current_time: Local::now(),
|
||||
last_upload: io.last_upload,
|
||||
last_upload_time: io.last_upload_time,
|
||||
},
|
||||
None => ProcessIoRecord {
|
||||
current: new_io,
|
||||
current_time: Local::now(),
|
||||
last_upload: new_io,
|
||||
last_upload_time: Local::now(),
|
||||
},
|
||||
};
|
||||
*io_record = Some(updated_io);
|
||||
}
|
||||
Err(e) => {
|
||||
// Do not panic because of io query failures.
|
||||
error!("Get io measurement failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mem_metrics(&self) -> SysResult<ProcessMemory> {
|
||||
let process_handle = CoreWinMetrics::get_process_handle()?;
|
||||
|
||||
let mut counters = PROCESS_MEMORY_COUNTERS_EX::default();
|
||||
|
||||
// Safe because we own the process handle and all memory was allocated.
|
||||
let result = unsafe {
|
||||
GetProcessMemoryInfo(
|
||||
process_handle.as_raw_descriptor(),
|
||||
// Converting is necessary because the `winapi`' GetProcessMemoryInfo
|
||||
// does NOT support `PROCESS_MEMORY_COUNTERS_EX`, but only
|
||||
// 'PROCESS_MEMORY_COUNTERS'. The casting is safe because the underlining
|
||||
// Windows api does `PROCESS_MEMORY_COUNTERS_EX`.
|
||||
&mut counters as *mut PROCESS_MEMORY_COUNTERS_EX as *mut PROCESS_MEMORY_COUNTERS,
|
||||
mem::size_of::<PROCESS_MEMORY_COUNTERS_EX>() as DWORD,
|
||||
)
|
||||
};
|
||||
if result == 0 {
|
||||
return Err(SysError::last());
|
||||
}
|
||||
|
||||
Ok(ProcessMemory {
|
||||
page_fault_count: counters.PageFaultCount,
|
||||
working_set_size: counters.WorkingSetSize,
|
||||
working_set_peak: counters.PeakWorkingSetSize,
|
||||
page_file_usage: counters.PagefileUsage,
|
||||
page_file_peak: counters.PeakPagefileUsage,
|
||||
physical: counters.PrivateUsage,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_cpu_metrics(&self) -> SysResult<Measurement> {
|
||||
let mut sys_time: SystemCpuTime = Default::default();
|
||||
let mut process_time: ProcessCpuTime = Default::default();
|
||||
let sys_time_success: i32;
|
||||
|
||||
// Safe because memory is allocated for sys_time before the windows call.
|
||||
// And the value were initilized to 0s.
|
||||
unsafe {
|
||||
// First get kernel cpu time.
|
||||
sys_time_success =
|
||||
GetSystemTimes(&mut sys_time.idle, &mut sys_time.kernel, &mut sys_time.user);
|
||||
}
|
||||
if sys_time_success == 0 {
|
||||
error!("Systime collection failed.\n");
|
||||
return Err(SysError::last());
|
||||
} else {
|
||||
// Query current process cpu time.
|
||||
let process_handle = CoreWinMetrics::get_process_handle()?;
|
||||
let process_time_success: i32;
|
||||
// Safe because memory is allocated for process_time before the windows call.
|
||||
// And the value were initilized to 0s.
|
||||
unsafe {
|
||||
process_time_success = GetProcessTimes(
|
||||
process_handle.as_raw_descriptor(),
|
||||
&mut process_time.create,
|
||||
&mut process_time.exit,
|
||||
&mut process_time.kernel,
|
||||
&mut process_time.user,
|
||||
);
|
||||
}
|
||||
if process_time_success == 0 {
|
||||
error!("Systime collection failed.\n");
|
||||
return Err(SysError::last());
|
||||
}
|
||||
}
|
||||
Ok(Measurement {
|
||||
sys_time: SystemCpuTime {
|
||||
idle: sys_time.idle,
|
||||
kernel: sys_time.kernel,
|
||||
user: sys_time.user,
|
||||
},
|
||||
process_time: ProcessCpuTime {
|
||||
create: process_time.create,
|
||||
exit: process_time.exit,
|
||||
kernel: process_time.kernel,
|
||||
user: process_time.user,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn get_io_metrics(&self) -> SysResult<ProcessIo> {
|
||||
let process_handle = CoreWinMetrics::get_process_handle()?;
|
||||
let mut io_counters = IO_COUNTERS::default();
|
||||
// Safe because we own the process handle and all memory was allocated.
|
||||
let result = unsafe {
|
||||
GetProcessIoCounters(
|
||||
process_handle.as_raw_descriptor(),
|
||||
&mut io_counters as *mut IO_COUNTERS,
|
||||
)
|
||||
};
|
||||
if result == 0 {
|
||||
return Err(SysError::last());
|
||||
}
|
||||
Ok(ProcessIo {
|
||||
read_bytes: io_counters.ReadTransferCount,
|
||||
write_bytes: io_counters.WriteTransferCount,
|
||||
})
|
||||
}
|
||||
|
||||
fn mem_metrics_string(&self) -> String {
|
||||
let mut buf: String;
|
||||
let guard = self.memory.lock().unwrap();
|
||||
let memory: ProcessMemory = *guard;
|
||||
buf = format!(
|
||||
"Physical memory used: {} mb.\n",
|
||||
memory.physical / BYTES_PER_MB
|
||||
);
|
||||
buf.push_str(&format!(
|
||||
"Total working memory: {} mb.\n",
|
||||
memory.working_set_size / BYTES_PER_MB
|
||||
));
|
||||
buf.push_str(&format!(
|
||||
"Peak working memory: {} mb.\n",
|
||||
memory.working_set_peak / BYTES_PER_MB
|
||||
));
|
||||
buf.push_str(&format!("Page fault count: {}.\n", memory.page_fault_count));
|
||||
buf.push_str(&format!(
|
||||
"Page file used: {} mb.\n",
|
||||
memory.page_file_usage / BYTES_PER_MB
|
||||
));
|
||||
buf.push_str(&format!(
|
||||
"Peak page file used: {} mb.\n",
|
||||
memory.page_file_peak / BYTES_PER_MB
|
||||
));
|
||||
buf
|
||||
}
|
||||
|
||||
fn cpu_metrics_string(&self) -> String {
|
||||
let guard = self.measurements.lock().unwrap();
|
||||
let mut buf = String::new();
|
||||
|
||||
// Now we use current and last cpu measurment data to calculate cpu usage
|
||||
// as a percentage.
|
||||
if let Some(measurements) = &*guard {
|
||||
let sys_time = measurements.current.sys_time;
|
||||
let process_time = measurements.current.process_time;
|
||||
let prev_sys_time = measurements.previous.sys_time;
|
||||
let prev_process_time = measurements.previous.process_time;
|
||||
|
||||
let diff_systime_kernel =
|
||||
compute_filetime_subtraction(sys_time.kernel, prev_sys_time.kernel);
|
||||
let diff_systime_user = compute_filetime_subtraction(sys_time.user, prev_sys_time.user);
|
||||
|
||||
let diff_processtime_kernel =
|
||||
compute_filetime_subtraction(process_time.kernel, prev_process_time.kernel);
|
||||
let diff_processtime_user =
|
||||
compute_filetime_subtraction(process_time.user, prev_process_time.user);
|
||||
|
||||
let total_systime = diff_systime_kernel + diff_systime_user;
|
||||
let total_processtime = diff_processtime_kernel + diff_processtime_user;
|
||||
|
||||
let mut process_cpu = String::from("still calculating...");
|
||||
if total_systime > 0 {
|
||||
process_cpu = format!("{}%", (100 * total_processtime / total_systime));
|
||||
}
|
||||
buf.push_str(&format!("Process cpu usage is: {}\n", process_cpu));
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
// Show data supporting our cpu usage calculation.
|
||||
// Output system cpu time.
|
||||
buf.push_str(&format!(
|
||||
"Systime Idle: low {} / high {}\n",
|
||||
sys_time.idle.dwLowDateTime, sys_time.idle.dwHighDateTime
|
||||
));
|
||||
buf.push_str(&format!(
|
||||
"Systime User: low {} / high {}\n",
|
||||
sys_time.user.dwLowDateTime, sys_time.user.dwHighDateTime
|
||||
));
|
||||
buf.push_str(&format!(
|
||||
"Systime kernel: low {} / high {}\n",
|
||||
sys_time.kernel.dwLowDateTime, sys_time.kernel.dwHighDateTime
|
||||
));
|
||||
// Output process cpu time.
|
||||
buf.push_str(&format!(
|
||||
"Process Create: low {} / high {}\n",
|
||||
process_time.create.dwLowDateTime, process_time.create.dwHighDateTime
|
||||
));
|
||||
buf.push_str(&format!(
|
||||
"Process Exit: low {} / high {}\n",
|
||||
process_time.exit.dwLowDateTime, process_time.exit.dwHighDateTime
|
||||
));
|
||||
buf.push_str(&format!(
|
||||
"Process kernel: low {} / high {}\n",
|
||||
process_time.kernel.dwLowDateTime, process_time.kernel.dwHighDateTime
|
||||
));
|
||||
buf.push_str(&format!(
|
||||
"Process user: low {} / high {}\n",
|
||||
process_time.user.dwLowDateTime, process_time.user.dwHighDateTime
|
||||
));
|
||||
}
|
||||
} else {
|
||||
buf.push_str("Calculating cpu usage...");
|
||||
}
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_filetime_subtraction(fta: FILETIME, ftb: FILETIME) -> LONGLONG {
|
||||
// safe because we are initializing the struct to 0s.
|
||||
unsafe {
|
||||
let mut a: LARGE_INTEGER = mem::zeroed::<LARGE_INTEGER>();
|
||||
a.u_mut().LowPart = fta.dwLowDateTime;
|
||||
a.u_mut().HighPart = fta.dwHighDateTime as i32;
|
||||
let mut b: LARGE_INTEGER = mem::zeroed::<LARGE_INTEGER>();
|
||||
b.u_mut().LowPart = ftb.dwLowDateTime;
|
||||
b.u_mut().HighPart = ftb.dwHighDateTime as i32;
|
||||
a.QuadPart() - b.QuadPart()
|
||||
}
|
||||
}
|
||||
|
||||
// Adds to a running total of memory metrics over the course of a collection period.
|
||||
// Can divide these sums to calculate averages.
|
||||
fn accumulate_process_memory(
|
||||
acc: ProcessMemoryAccumulated,
|
||||
mem: ProcessMemory,
|
||||
) -> ProcessMemoryAccumulated {
|
||||
ProcessMemoryAccumulated {
|
||||
accumulated: ProcessMemory {
|
||||
page_fault_count: mem.page_fault_count,
|
||||
working_set_size: acc.accumulated.working_set_size + mem.working_set_size,
|
||||
working_set_peak: mem.working_set_peak,
|
||||
page_file_usage: acc.accumulated.page_file_usage + mem.page_file_usage,
|
||||
page_file_peak: mem.page_file_peak,
|
||||
physical: acc.accumulated.physical + mem.physical,
|
||||
},
|
||||
accumulated_count: acc.accumulated_count + 1,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct SystemCpuTime {
|
||||
idle: FILETIME,
|
||||
kernel: FILETIME,
|
||||
user: FILETIME,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct ProcessCpuTime {
|
||||
create: FILETIME,
|
||||
exit: FILETIME,
|
||||
kernel: FILETIME,
|
||||
user: FILETIME,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct ProcessMemory {
|
||||
page_fault_count: u32,
|
||||
working_set_size: usize,
|
||||
working_set_peak: usize,
|
||||
page_file_usage: usize,
|
||||
page_file_peak: usize,
|
||||
physical: usize,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct ProcessMemoryAccumulated {
|
||||
accumulated: ProcessMemory,
|
||||
accumulated_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
struct ProcessIo {
|
||||
read_bytes: u64,
|
||||
write_bytes: u64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct ProcessIoRecord {
|
||||
current: ProcessIo,
|
||||
current_time: DateTime<Local>,
|
||||
last_upload: ProcessIo,
|
||||
last_upload_time: DateTime<Local>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct Measurement {
|
||||
sys_time: SystemCpuTime,
|
||||
process_time: ProcessCpuTime,
|
||||
}
|
||||
|
||||
struct Measurements {
|
||||
current: Measurement,
|
||||
previous: Measurement,
|
||||
last_upload: Measurement,
|
||||
}
|
||||
|
||||
/// A managing struct for a job which defines regular logging of core Windows system metrics.
|
||||
pub(crate) struct CoreWinMetrics {
|
||||
metrics_string: Weak<Mutex<String>>,
|
||||
exit_evt: Event,
|
||||
worker_thread: Option<JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl CoreWinMetrics {
|
||||
pub fn new() -> Result<Self> {
|
||||
let exit_evt = match Event::new() {
|
||||
Ok(evt) => evt,
|
||||
Err(_e) => return Err(Error::CannotInstantiateEvent),
|
||||
};
|
||||
|
||||
let metrics_string = String::new();
|
||||
let arc_metrics_memory = Arc::new(Mutex::new(metrics_string));
|
||||
let weak_metrics_memory = Arc::downgrade(&arc_metrics_memory);
|
||||
|
||||
let mut me = Self {
|
||||
metrics_string: weak_metrics_memory,
|
||||
exit_evt,
|
||||
worker_thread: None,
|
||||
};
|
||||
let exit_evt_clone = match me.exit_evt.try_clone() {
|
||||
Ok(evt) => evt,
|
||||
Err(_) => return Err(Error::CannotCloneEvent),
|
||||
};
|
||||
me.worker_thread.replace(thread::spawn(|| {
|
||||
Worker {
|
||||
exit_evt: exit_evt_clone,
|
||||
io: Arc::new(Mutex::new(None)),
|
||||
measurements: Arc::new(Mutex::new(None)),
|
||||
memory: Arc::new(Mutex::new(Default::default())),
|
||||
memory_acc: Arc::new(Mutex::new(None)),
|
||||
metrics_string: arc_metrics_memory,
|
||||
}
|
||||
.run();
|
||||
}));
|
||||
Ok(me)
|
||||
}
|
||||
|
||||
fn get_process_handle() -> SysResult<SafeDescriptor> {
|
||||
// Safe because we own the current process.
|
||||
let process_handle = unsafe {
|
||||
OpenProcess(
|
||||
PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ | SYNCHRONIZE,
|
||||
0,
|
||||
std::process::id(),
|
||||
)
|
||||
};
|
||||
if process_handle.is_null() {
|
||||
return Err(SysError::last());
|
||||
}
|
||||
// Safe as the SafeDescriptor is the only thing with access to the handle after this.
|
||||
Ok(unsafe { SafeDescriptor::from_raw_descriptor(process_handle) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CoreWinMetrics {
|
||||
fn drop(&mut self) {
|
||||
if let Some(join_handle) = self.worker_thread.take() {
|
||||
let _ = self.exit_evt.write(1);
|
||||
join_handle
|
||||
.join()
|
||||
.expect("fail to join the worker thread of a win core metrics collector.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CoreWinMetrics {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.metrics_string.upgrade() {
|
||||
Some(metrics_string) => write!(f, "{}", *metrics_string.lock().unwrap()),
|
||||
None => write!(f, ""),
|
||||
}
|
||||
}
|
||||
}
|
241
metrics/src/sys/windows/wmi.rs
Normal file
241
metrics/src/sys/windows/wmi.rs
Normal file
|
@ -0,0 +1,241 @@
|
|||
// Copyright 2022 The ChromiumOS Authors.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
/*
|
||||
The struct must be named in non_camel and non_snake because we want to query the windows wmi
|
||||
interface and conform to the windows naming convension.
|
||||
*/
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use serde::{de::DeserializeOwned, Deserialize};
|
||||
use {
|
||||
base::warn,
|
||||
std::{collections::HashMap, error::Error, rc::Rc},
|
||||
wmi::{query::FilterValue, COMLibrary, WMIConnection},
|
||||
};
|
||||
|
||||
const VIDEO_CONTROLLER_AVAILABILITY_ENABLED: i64 = 3;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Win32_Processor {
|
||||
pub Manufacturer: String,
|
||||
pub Name: String,
|
||||
pub NumberOfCores: u32,
|
||||
pub NumberOfLogicalProcessors: u32,
|
||||
pub ThreadCount: u32,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Win32_VideoController {
|
||||
pub Name: String,
|
||||
// TODO(b/191406729): re-enable.
|
||||
// pub AdapterRAM: u64,
|
||||
pub DriverVersion: String,
|
||||
pub Availability: u16,
|
||||
pub Description: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct MSFT_Partition {
|
||||
DriveLetter: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct MSFT_Disk {
|
||||
__Path: String,
|
||||
UniqueId: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct MSFT_DiskToPartition {}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct MSFT_PhysicalDisk {
|
||||
FriendlyName: String,
|
||||
MediaType: u16,
|
||||
BusType: u16,
|
||||
Size: u64,
|
||||
UniqueId: String,
|
||||
}
|
||||
|
||||
// Keep the formatting so that the debug output string matches the proto field
|
||||
// values.
|
||||
#[derive(Debug)]
|
||||
pub enum MediaType {
|
||||
UNKNOWN,
|
||||
HDD,
|
||||
SSD,
|
||||
SCM,
|
||||
}
|
||||
|
||||
impl From<u16> for MediaType {
|
||||
fn from(value: u16) -> Self {
|
||||
match value {
|
||||
3 => MediaType::HDD,
|
||||
4 => MediaType::SSD,
|
||||
5 => MediaType::SCM,
|
||||
_ => MediaType::UNKNOWN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the formatting so that the debug output string matches the proto field
|
||||
// values.
|
||||
#[derive(Debug)]
|
||||
pub enum BusType {
|
||||
UNKNOWN,
|
||||
SCSI,
|
||||
ATAPI,
|
||||
ATA,
|
||||
TYPE_1394,
|
||||
SSA,
|
||||
FIBRE_CHANNEL,
|
||||
USB,
|
||||
RAID,
|
||||
ISCSI,
|
||||
SAS,
|
||||
SATA,
|
||||
SD,
|
||||
MMC,
|
||||
FILE_BACKED_VIRTUAL,
|
||||
STORAGE_SPACES,
|
||||
NVME,
|
||||
}
|
||||
|
||||
impl From<u16> for BusType {
|
||||
fn from(value: u16) -> Self {
|
||||
match value {
|
||||
1 => BusType::SCSI,
|
||||
2 => BusType::ATAPI,
|
||||
3 => BusType::ATA,
|
||||
4 => BusType::TYPE_1394,
|
||||
5 => BusType::SSA,
|
||||
6 => BusType::FIBRE_CHANNEL,
|
||||
7 => BusType::USB,
|
||||
8 => BusType::RAID,
|
||||
9 => BusType::ISCSI,
|
||||
10 => BusType::SAS,
|
||||
11 => BusType::SATA,
|
||||
12 => BusType::SD,
|
||||
13 => BusType::MMC,
|
||||
15 => BusType::FILE_BACKED_VIRTUAL,
|
||||
16 => BusType::STORAGE_SPACES,
|
||||
17 => BusType::NVME,
|
||||
_ => BusType::UNKNOWN,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Friendly format for MSFT_PhysicalDisk.
|
||||
// Also includes the cross-referenced partitions within the disk.
|
||||
#[derive(Debug)]
|
||||
pub struct PhysicalDisk {
|
||||
pub Name: String,
|
||||
pub MediaType: MediaType,
|
||||
pub BusType: BusType,
|
||||
pub Size: u64,
|
||||
pub DriveLetters: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct Win32_PhysicalMemory {
|
||||
pub Capacity: u64,
|
||||
pub ConfiguredClockSpeed: u32,
|
||||
pub PartNumber: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Debug)]
|
||||
pub struct Win32_BaseBoard {
|
||||
pub Manufacturer: String,
|
||||
pub Product: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WmiMetrics {
|
||||
pub cpus: Vec<Win32_Processor>,
|
||||
pub gpus: Vec<Win32_VideoController>,
|
||||
pub disks: Vec<PhysicalDisk>,
|
||||
pub mems: Vec<Win32_PhysicalMemory>,
|
||||
pub motherboard: Option<Win32_BaseBoard>,
|
||||
}
|
||||
|
||||
pub fn get_wmi_metrics() -> Result<WmiMetrics, Box<dyn Error>> {
|
||||
let com_con = Rc::new(COMLibrary::new()?);
|
||||
let wmi_con = WMIConnection::new(Rc::clone(&com_con))?;
|
||||
|
||||
// Fetch WMI data, including all entries.
|
||||
let cpus: Vec<Win32_Processor> = run_wmi_query(&wmi_con);
|
||||
let disks = get_disks(Rc::clone(&com_con))?;
|
||||
let mems: Vec<Win32_PhysicalMemory> = run_wmi_query(&wmi_con);
|
||||
let motherboard: Option<Win32_BaseBoard> = run_wmi_query(&wmi_con).into_iter().next();
|
||||
let gpus = get_gpus(&wmi_con);
|
||||
|
||||
let wmi_metrics = WmiMetrics {
|
||||
cpus,
|
||||
gpus,
|
||||
disks,
|
||||
mems,
|
||||
motherboard,
|
||||
};
|
||||
|
||||
Ok(wmi_metrics)
|
||||
}
|
||||
|
||||
fn get_disks(com_con: Rc<COMLibrary>) -> Result<Vec<PhysicalDisk>, Box<dyn Error>> {
|
||||
// For MSFT_PhysicalDisk, we need to connect with storage namespace.
|
||||
let wmi_con = WMIConnection::with_namespace_path("Root\\Microsoft\\Windows\\Storage", com_con)?;
|
||||
// First we get all instances of following classes:
|
||||
// MSFT_Disk, MSFT_PhysicalDisk
|
||||
// We use the WMI associator query to find mapping for each:
|
||||
// MSFT_Disk -> MSFT_Partition (1:N)
|
||||
// Then, we find the mapping from each:
|
||||
// MSFT_Disk -> MSFT_PhysicalDisk (1:1)
|
||||
// Finally, we construct each PhysicalDisk structure by combining the
|
||||
// matched MSFT_PhysicalDisk and MSFT_Parition instances.
|
||||
let msft_disks: Vec<MSFT_Disk> = run_wmi_query(&wmi_con);
|
||||
let physical_disks: Vec<MSFT_PhysicalDisk> = run_wmi_query(&wmi_con);
|
||||
|
||||
let mut disks = Vec::with_capacity(physical_disks.len());
|
||||
for msft_disk in msft_disks {
|
||||
let partitions =
|
||||
wmi_con.associators::<MSFT_Partition, MSFT_DiskToPartition>(&msft_disk.__Path)?;
|
||||
let physical_disk = physical_disks
|
||||
.iter()
|
||||
.find(|d| d.UniqueId == msft_disk.UniqueId)
|
||||
.ok_or("Could not find a matching MSFT_PhysicalDisk!")?;
|
||||
disks.push(PhysicalDisk {
|
||||
Name: physical_disk.FriendlyName.clone(),
|
||||
MediaType: physical_disk.MediaType.into(),
|
||||
BusType: physical_disk.BusType.into(),
|
||||
Size: physical_disk.Size,
|
||||
DriveLetters: partitions.into_iter().map(|p| p.DriveLetter).collect(),
|
||||
});
|
||||
}
|
||||
Ok(disks)
|
||||
}
|
||||
|
||||
fn get_gpus(wmi_con: &WMIConnection) -> Vec<Win32_VideoController> {
|
||||
// TODO(b/191406729): Fix the query once the AdapterRAM can be correctly
|
||||
// queried.
|
||||
let mut filters = HashMap::new();
|
||||
filters.insert(
|
||||
"Availability".to_string(),
|
||||
FilterValue::Number(VIDEO_CONTROLLER_AVAILABILITY_ENABLED),
|
||||
);
|
||||
wmi_con
|
||||
.filtered_query(&filters)
|
||||
.map_err(|e| warn!("wmi query failed: {}", e))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn run_wmi_query<T>(wmi_con: &WMIConnection) -> Vec<T>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
wmi_con
|
||||
.query()
|
||||
.map_err(|e| warn!("wmi query failed: {}", e))
|
||||
.unwrap_or_default()
|
||||
}
|
Loading…
Reference in a new issue