mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-19 17:25:19 +03:00
fix(nesdoctor): the first Windows submission recorded a virtual display adapter
Our first response, and the GPU field is wrong: gpu=Parsec%20Virtual%20Display%20Adapter&gpus=2 Parsec installs an indirect display driver, it enumerated first out of `Win32_VideoController`, and the primary was taken as the first entry -- so the real card on that machine is gone. `gpus=2` is the only reason we can tell anything was lost, and it cannot tell us what. This is not an edge case for this audience. Parsec, Sunshine, Moonlight, TeamViewer and Splashtop all install one, and a cloud-gaming community is precisely the population that has one already. A recorded gpu_model is a hard requirement for a host; a virtual display driver satisfies it in name only. Three changes. Adapters are now sorted so real hardware is first, by two keys: whether the name matches a known software adapter, then whether a vendor could be identified at all. Order is the only signal the rest of the program has for which GPU is "the" GPU. The vendor comes from `AdapterCompatibility` rather than from pattern-matching the marketing name. An "AMD Radeon" string is easy; an OEM-rebadged one is not. And every adapter name is now sent, not only the count. `gpus=2` told us something had been lost and not what, which is the kind of field that wastes a response we cannot ask again. The known-software-adapter list has unit tests, on all platforms -- it is a list of strings and it will need extending, so it should fail loudly rather than quietly stop matching. Version to 0.1.2. For the record, what that submission got right, because none of it needed asking: 50% of 165 Steam launch records fall in five hours of twenty-four (21:00-01:59) against five records across the whole of 07:00-13:59. That is 0017's evening peak, measured, from one person's own files. 140 of the 165 records are titles no longer installed -- restricting the histogram to installed titles, as review suggested, would have left 25 samples and lost the shape entirely.
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -2462,7 +2462,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nesdoctor"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nesdoctor"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
@@ -423,6 +423,17 @@ pub fn submit_url(base: &str, f_: &Full) -> String {
|
||||
}
|
||||
if sys.gpus.len() > 1 {
|
||||
put("gpus", sys.gpus.len().to_string());
|
||||
// Every adapter, not just the count. The first Windows submission
|
||||
// reported `gpus=2` with a virtual adapter as the primary, which told
|
||||
// us something had been lost but not what.
|
||||
put(
|
||||
"gpulist",
|
||||
sys.gpus
|
||||
.iter()
|
||||
.map(|g| g.name.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join("~"),
|
||||
);
|
||||
}
|
||||
put("cpu", sys.cpu_threads.to_string());
|
||||
if let Some(m) = &sys.cpu_model {
|
||||
|
||||
@@ -164,29 +164,86 @@ fn vendor_name(id: &str) -> Option<&'static str> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adapters that are software, not hardware.
|
||||
///
|
||||
/// The first Windows submission we ever received reported
|
||||
/// `gpu=Parsec Virtual Display Adapter` with `gpus=2`: Parsec installs an
|
||||
/// indirect display driver, it enumerated first, and the real card was lost.
|
||||
/// Every remote-play tool does this -- Parsec, Sunshine, Moonlight, TeamViewer,
|
||||
/// Splashtop -- and a cloud-gaming audience is exactly the population that has
|
||||
/// one installed. A recorded `gpu_model` is a hard requirement per our host
|
||||
/// rules, and recording a virtual display driver satisfies it in name only.
|
||||
/// Only consulted on Windows -- Linux adapters are found through DRM render
|
||||
/// nodes, which a virtual display driver does not have -- but kept
|
||||
/// unconditional so the list is compiled and unit-tested on every platform.
|
||||
#[cfg_attr(not(windows), allow(dead_code))]
|
||||
fn is_virtual_adapter(name: &str) -> bool {
|
||||
let n = name.to_lowercase();
|
||||
[
|
||||
"virtual",
|
||||
"basic display",
|
||||
"basic render",
|
||||
"remote display",
|
||||
"indirect display",
|
||||
"idd",
|
||||
"parsec",
|
||||
"sunshine",
|
||||
"teamviewer",
|
||||
"splashtop",
|
||||
"nomachine",
|
||||
"citrix",
|
||||
"vmware",
|
||||
"virtualbox",
|
||||
"hyper-v",
|
||||
"qxl",
|
||||
"meta virtual",
|
||||
]
|
||||
.iter()
|
||||
.any(|p| n.contains(p))
|
||||
}
|
||||
|
||||
fn gpus() -> Vec<Gpu> {
|
||||
#[cfg(target_os = "linux")]
|
||||
return linux_gpus();
|
||||
#[cfg(windows)]
|
||||
return ps("Get-CimInstance Win32_VideoController | ForEach-Object { $_.Name }")
|
||||
.map(|s| {
|
||||
s.lines()
|
||||
{
|
||||
// AdapterCompatibility carries the vendor, which is more reliable than
|
||||
// pattern-matching the marketing name -- an "AMD Radeon" string is easy,
|
||||
// an OEM-rebadged one is not.
|
||||
let raw = ps(
|
||||
r#"Get-CimInstance Win32_VideoController | ForEach-Object { "$($_.Name)|$($_.AdapterCompatibility)" }"#,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut out: Vec<Gpu> = raw
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.filter(|l| !l.is_empty())
|
||||
.map(|l| {
|
||||
let up = l.to_uppercase();
|
||||
let (name, compat) = l.split_once('|').unwrap_or((l, ""));
|
||||
let hay = format!("{name} {compat}").to_uppercase();
|
||||
Gpu {
|
||||
name: l.to_string(),
|
||||
vendor: ["AMD", "NVIDIA", "INTEL"]
|
||||
name: name.trim().to_string(),
|
||||
vendor: [
|
||||
("AMD", "AMD"),
|
||||
("NVIDIA", "NVIDIA"),
|
||||
("INTEL", "Intel"),
|
||||
("ATI", "AMD"),
|
||||
]
|
||||
.into_iter()
|
||||
.find(|v| up.contains(v))
|
||||
.map(str::to_string),
|
||||
.find(|(needle, _)| hay.contains(needle))
|
||||
.map(|(_, v)| v.to_string()),
|
||||
render_node: None,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_default();
|
||||
.collect();
|
||||
|
||||
// Real hardware first, so the primary is never a virtual adapter that
|
||||
// merely happened to enumerate earlier. Order is the only signal the
|
||||
// rest of the program has.
|
||||
out.sort_by_key(|g| (is_virtual_adapter(&g.name), g.vendor.is_none()));
|
||||
return out;
|
||||
}
|
||||
#[cfg(not(any(target_os = "linux", windows)))]
|
||||
return Vec::new();
|
||||
}
|
||||
@@ -543,3 +600,41 @@ fn kv_line(txt: &str, key: &str) -> Option<String> {
|
||||
.and_then(|l| l.split_once('='))
|
||||
.map(|(_, v)| v.trim().trim_matches('"').to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::is_virtual_adapter;
|
||||
|
||||
/// The first Windows submission we received reported
|
||||
/// `Parsec Virtual Display Adapter` as the primary GPU on a machine that
|
||||
/// had a real one. This list is the fix, so it gets a test.
|
||||
#[test]
|
||||
fn virtual_adapters_are_recognised() {
|
||||
for name in [
|
||||
"Parsec Virtual Display Adapter",
|
||||
"Microsoft Basic Display Adapter",
|
||||
"Microsoft Remote Display Adapter",
|
||||
"Microsoft Hyper-V Video",
|
||||
"IddSampleDriver Device",
|
||||
"VMware SVGA 3D",
|
||||
"VirtualBox Graphics Adapter",
|
||||
"Citrix Indirect Display Adapter",
|
||||
"Splashtop Virtual Display",
|
||||
] {
|
||||
assert!(is_virtual_adapter(name), "{name} should be virtual");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn real_cards_are_not_recognised_as_virtual() {
|
||||
for name in [
|
||||
"AMD Radeon RX 9060 XT",
|
||||
"NVIDIA GeForce RTX 4070",
|
||||
"Intel(R) Arc(TM) A310 Graphics",
|
||||
"AMD Barcelo",
|
||||
"Radeon RX 7900 XTX",
|
||||
] {
|
||||
assert!(!is_virtual_adapter(name), "{name} should be real");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user