mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2024-11-28 01:16:50 +00:00
Revert "metrics: Add metrics crate."
This reverts commit4c2017c757
. Reason for revert: dependency crrev.com/c/3622798 caused regression Original change's description: > metrics: Add metrics crate. > > This crate is effectively a collection of stubs in this repo, > but will allow a downstream repo to implement those stubs. > > BUG=b:213152497 > FIXED=b:213152497 > TEST=build + kokoro > > Change-Id: I3c544644ec3d7917f9ebb2e8621042c35b556db8 > Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3622799 > Reviewed-by: Noah Gold <nkgold@google.com> > Commit-Queue: Michael Hoyle <mikehoyle@google.com> > Tested-by: kokoro <noreply+kokoro@google.com> BUG=b:213152497,b:232316549 TEST=CtsAccessibilityServiceTestCases Change-Id: Idd636d7d34e68977fffc1f6c0a89924eb7be5d54 Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3641218 Auto-Submit: Shao-Chuan Lee <shaochuan@chromium.org> Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com> Reviewed-by: Michael Hoyle <mikehoyle@google.com> Commit-Queue: Daniel Verkamp <dverkamp@chromium.org> Tested-by: kokoro <noreply+kokoro@google.com> Reviewed-by: Daniel Verkamp <dverkamp@chromium.org> (cherry picked from commit55a5c8d8f9
) Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3650067 Tested-by: Shao-Chuan Lee <shaochuan@chromium.org> Commit-Queue: Shao-Chuan Lee <shaochuan@chromium.org> Reviewed-by: Junichi Uekawa <uekawa@chromium.org>
This commit is contained in:
parent
c0b4fbdce7
commit
4521376a0a
18 changed files with 0 additions and 1453 deletions
|
@ -171,7 +171,6 @@ 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 = "*"
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
[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"
|
|
@ -1,52 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// 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.");
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
// 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;
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// 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, CloseNotifier, PollToken, ReadNotifier, Tube, WaitContext};
|
||||
|
||||
/// 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 {
|
||||
agents: Vec<Tube>,
|
||||
handler: RequestHandler,
|
||||
}
|
||||
|
||||
#[derive(PollToken)]
|
||||
enum Token {
|
||||
/// 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).
|
||||
AgentExited(usize),
|
||||
}
|
||||
|
||||
impl MetricsController {
|
||||
pub fn new(agents: Vec<Tube>) -> Self {
|
||||
Self {
|
||||
agents,
|
||||
handler: RequestHandler::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the metrics controller until all clients exit & close their Tubes.
|
||||
pub fn run(&mut self) -> Result<()> {
|
||||
let wait_ctx: WaitContext<Token> = WaitContext::new()?;
|
||||
let mut closed_tubes = 0;
|
||||
|
||||
for (agent_index, agent) in self.agents.iter().enumerate() {
|
||||
wait_ctx.add(agent.get_read_notifier(), Token::Agent(agent_index))?;
|
||||
wait_ctx.add(agent.get_close_notifier(), Token::AgentExited(agent_index))?;
|
||||
}
|
||||
|
||||
'listen: loop {
|
||||
let events = wait_ctx.wait()?;
|
||||
for event in events.iter().filter(|e| e.is_readable) {
|
||||
match event.token {
|
||||
Token::Agent(client_index) => {
|
||||
let client = &self.agents[client_index];
|
||||
match client.recv::<MetricsRequest>() {
|
||||
Ok(req) => self.handler.handle_request(req),
|
||||
Err(e) => {
|
||||
warn!("unexpected error receiving agent metrics request: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
Token::AgentExited(client_index) => {
|
||||
let client = &self.agents[client_index];
|
||||
wait_ctx.delete(client.get_read_notifier())?;
|
||||
wait_ctx.delete(client.get_close_notifier())?;
|
||||
closed_tubes += 1;
|
||||
info!(
|
||||
"metrics tube closed: {} out of {} closed",
|
||||
closed_tubes,
|
||||
self.agents.len(),
|
||||
);
|
||||
if closed_tubes == self.agents.len() {
|
||||
info!("metrics run loop exiting: all tubes closed");
|
||||
break 'listen;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.handler.shutdown();
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// 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)),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// 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::*;
|
||||
pub use sys::*;
|
||||
|
||||
pub type RequestHandler = NoopMetricsRequestHandler;
|
|
@ -1,22 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// 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();
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// 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),
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// 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) {}
|
|
@ -1,14 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// 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;
|
|
@ -1,22 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// 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) {}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// 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) {}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// 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::*;
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
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>;
|
|
@ -1,52 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// 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
|
||||
}
|
||||
}
|
|
@ -1,596 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// 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, ""),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,241 +0,0 @@
|
|||
// Copyright 2022 The Chromium OS Authors. All rights reserved.
|
||||
// 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