From 9e1b825a7db3610b1a12524b1a5b4d3a37a6a237 Mon Sep 17 00:00:00 2001 From: alxndrv <> Date: Wed, 12 Feb 2025 13:17:28 +0200 Subject: [PATCH 1/9] `lscpu`: Add support for nested fields in output --- src/uu/lscpu/src/lscpu.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs index 5dd313b..0bde18f 100644 --- a/src/uu/lscpu/src/lscpu.rs +++ b/src/uu/lscpu/src/lscpu.rs @@ -27,6 +27,8 @@ struct CpuInfos { struct CpuInfo { field: String, data: String, + #[serde(skip_serializing_if = "Vec::is_empty")] + children: Vec<CpuInfo>, } impl CpuInfos { @@ -36,10 +38,11 @@ impl CpuInfos { } } - fn push(&mut self, field: &str, data: &str) { + fn push(&mut self, field: &str, data: &str, children: Option<Vec<CpuInfo>>) { let cpu_info = CpuInfo { field: String::from_str(field).unwrap(), data: String::from_str(data).unwrap(), + children: children.unwrap_or_default(), }; self.lscpu.push(cpu_info); } @@ -59,8 +62,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let json = matches.get_flag(options::JSON); let mut cpu_infos = CpuInfos::new(); - cpu_infos.push("Architecture", &get_architecture()); - cpu_infos.push("CPU(s)", &format!("{}", system.cpus().len())); + cpu_infos.push("Architecture", &get_architecture(), None); + cpu_infos.push("CPU(s)", &format!("{}", system.cpus().len()), None); // Add more CPU information here... if let Ok(contents) = fs::read_to_string("/proc/cpuinfo") { @@ -70,7 +73,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { .unwrap(); // Assuming all CPUs have the same model name if let Some(cap) = re.captures_iter(&contents).next() { - cpu_infos.push("Model name", &cap[1]); + cpu_infos.push("Model name", &cap[1], None); }; } From edb3b83ec91362271b07122672cdaafeb011eef9 Mon Sep 17 00:00:00 2001 From: alxndrv <> Date: Wed, 12 Feb 2025 17:05:37 +0200 Subject: [PATCH 2/9] `lscpu`: Use common function to read fields from `/proc/cpuinfo` --- src/uu/lscpu/src/lscpu.rs | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs index 0bde18f..2fc84cf 100644 --- a/src/uu/lscpu/src/lscpu.rs +++ b/src/uu/lscpu/src/lscpu.rs @@ -67,14 +67,21 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { // Add more CPU information here... if let Ok(contents) = fs::read_to_string("/proc/cpuinfo") { - let re = RegexBuilder::new(r"^model name\s+:\s+(.*)$") - .multi_line(true) - .build() - .unwrap(); - // Assuming all CPUs have the same model name - if let Some(cap) = re.captures_iter(&contents).next() { - cpu_infos.push("Model name", &cap[1], None); - }; + if let Some(cpu_model) = find_cpuinfo_value(&contents, "model name") { + if let Some(addr_sizes) = find_cpuinfo_value(&contents, "address sizes") { + cpu_infos.push( + "Model name", + cpu_model.as_str(), + Some(vec![CpuInfo { + field: "Address sizes".to_string(), + data: addr_sizes, + children: vec![], + }]), + ); + } else { + cpu_infos.push("Model name", cpu_model.as_str(), None); + } + } } if json { @@ -87,6 +94,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Ok(()) } +fn find_cpuinfo_value(contents: &str, key: &str) -> Option<String> { + let pattern = format!(r"^{}\s+:\s+(.*)$", key); + let re = RegexBuilder::new(pattern.as_str()) + .multi_line(true) + .build() + .unwrap(); + + if let Some(cap) = re.captures_iter(contents).next() { + return Some(cap[1].to_string()); + }; + + None +} + fn get_architecture() -> String { if cfg!(target_arch = "x86") { "x86".to_string() From 1e456daff503be61d99531a7e33ae0d987924bdd Mon Sep 17 00:00:00 2001 From: alxndrv <> Date: Wed, 12 Feb 2025 18:36:33 +0200 Subject: [PATCH 3/9] `lscpu`: Use recursive printing logic to output nested fields --- src/uu/lscpu/src/lscpu.rs | 116 ++++++++++++++++++++++++++++---------- 1 file changed, 86 insertions(+), 30 deletions(-) diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs index 2fc84cf..21cb5c6 100644 --- a/src/uu/lscpu/src/lscpu.rs +++ b/src/uu/lscpu/src/lscpu.rs @@ -52,46 +52,101 @@ impl CpuInfos { } } +struct OutputOptions { + json: bool, + _hex: bool, +} + #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches: clap::ArgMatches = uu_app().try_get_matches_from(args)?; let system = System::new_all(); - let _hex = matches.get_flag(options::HEX); - let json = matches.get_flag(options::JSON); + let output_opts = OutputOptions { + _hex: matches.get_flag(options::HEX), + json: matches.get_flag(options::JSON), + }; let mut cpu_infos = CpuInfos::new(); - cpu_infos.push("Architecture", &get_architecture(), None); cpu_infos.push("CPU(s)", &format!("{}", system.cpus().len()), None); // Add more CPU information here... - if let Ok(contents) = fs::read_to_string("/proc/cpuinfo") { - if let Some(cpu_model) = find_cpuinfo_value(&contents, "model name") { - if let Some(addr_sizes) = find_cpuinfo_value(&contents, "address sizes") { - cpu_infos.push( - "Model name", - cpu_model.as_str(), - Some(vec![CpuInfo { - field: "Address sizes".to_string(), - data: addr_sizes, - children: vec![], - }]), - ); - } else { - cpu_infos.push("Model name", cpu_model.as_str(), None); + let contents = match fs::read_to_string("/proc/cpuinfo") { + // Early return if we can't read /proc/cpuinfo for whatever reason + // TODO: Should this return an non-zero exit code to user, or do we just ignore it and display whatever CPU info we could find? + Err(_) => return Ok(()), + Ok(contents) => contents, + }; + + let mut arch_children: Vec<CpuInfo> = vec![]; + + if let Some(addr_sizes) = find_cpuinfo_value(&contents, "address sizes") { + arch_children.push(CpuInfo { + field: "Address sizes".to_string(), + data: addr_sizes, + children: vec![], + }) + } + + cpu_infos.push("Architecture", &get_architecture(), Some(arch_children)); + + // TODO: This is currently quite verbose and doesn't strictly respect the hierarchy of `/proc/cpuinfo` contents + // ie. the file might contain multiple sections, each with their own vendor_id/model name etc. but right now + // we're just taking whatever our regex matches first and using that + if let Some(vendor) = find_cpuinfo_value(&contents, "vendor_id") { + let mut vendor_children: Vec<CpuInfo> = vec![]; + + if let Some(model_name) = find_cpuinfo_value(&contents, "model name") { + let mut model_children: Vec<CpuInfo> = vec![]; + + if let Some(family) = find_cpuinfo_value(&contents, "cpu family") { + model_children.push(CpuInfo { + field: "CPU Family".to_string(), + data: family, + children: vec![], + }); } + + if let Some(model) = find_cpuinfo_value(&contents, "model") { + model_children.push(CpuInfo { + field: "Model".to_string(), + data: model, + children: vec![], + }); + } + + vendor_children.push(CpuInfo { + field: "Model name".to_string(), + data: model_name, + children: model_children, + }); + } + cpu_infos.push("Vendor ID", vendor.as_str(), Some(vendor_children)); + } + + print_output(cpu_infos, output_opts); + + Ok(()) +} + +fn print_output(infos: CpuInfos, out_opts: OutputOptions) { + if out_opts.json { + println!("{}", infos.to_json()); + return; + } + + // Recursive function to print nested CpuInfo entries + fn print_entries(entries: Vec<CpuInfo>, depth: usize, _out_opts: &OutputOptions) { + let indent = " ".repeat(depth); + for entry in entries { + // TODO: Align `data` values to form a column + println!("{}{}: {}", indent, entry.field, entry.data); + print_entries(entry.children, depth + 1, _out_opts); } } - if json { - println!("{}", cpu_infos.to_json()); - } else { - for elt in cpu_infos.lscpu { - println!("{}: {}", elt.field, elt.data); - } - } - Ok(()) + print_entries(infos.lscpu, 0, &out_opts); } fn find_cpuinfo_value(contents: &str, key: &str) -> Option<String> { @@ -101,11 +156,11 @@ fn find_cpuinfo_value(contents: &str, key: &str) -> Option<String> { .build() .unwrap(); - if let Some(cap) = re.captures_iter(contents).next() { - return Some(cap[1].to_string()); - }; - - None + let value = re + .captures_iter(contents) + .next() + .map(|cap| cap[1].to_string()); + value } fn get_architecture() -> String { @@ -137,6 +192,7 @@ pub fn uu_app() -> Command { ) .arg( Arg::new(options::JSON) + .short('J') .long("json") .help( "Use JSON output format for the default summary or extended output \ From 3953552e3aa145a2d0770af1afabc31e810a4a82 Mon Sep 17 00:00:00 2001 From: alxndrv <> Date: Wed, 12 Feb 2025 18:56:34 +0200 Subject: [PATCH 4/9] `lscpu`: Clean up creation of new entries --- src/uu/lscpu/src/lscpu.rs | 66 ++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs index 21cb5c6..ad1acc1 100644 --- a/src/uu/lscpu/src/lscpu.rs +++ b/src/uu/lscpu/src/lscpu.rs @@ -6,7 +6,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use regex::RegexBuilder; use serde::Serialize; -use std::{fs, str::FromStr}; +use std::fs; use sysinfo::System; use uucore::{error::UResult, format_usage, help_about, help_usage}; @@ -31,6 +31,20 @@ struct CpuInfo { children: Vec<CpuInfo>, } +impl CpuInfo { + fn new(field: &str, data: &str, children: Option<Vec<CpuInfo>>) -> Self { + Self { + field: field.to_string(), + data: data.to_string(), + children: children.unwrap_or_default(), + } + } + + fn add_child(&mut self, child: Self) { + self.children.push(child); + } +} + impl CpuInfos { fn new() -> CpuInfos { CpuInfos { @@ -38,12 +52,7 @@ impl CpuInfos { } } - fn push(&mut self, field: &str, data: &str, children: Option<Vec<CpuInfo>>) { - let cpu_info = CpuInfo { - field: String::from_str(field).unwrap(), - data: String::from_str(data).unwrap(), - children: children.unwrap_or_default(), - }; + fn push(&mut self, cpu_info: CpuInfo) { self.lscpu.push(cpu_info); } @@ -69,8 +78,13 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let mut cpu_infos = CpuInfos::new(); - cpu_infos.push("CPU(s)", &format!("{}", system.cpus().len()), None); - // Add more CPU information here... + cpu_infos.push(CpuInfo::new( + "CPU(s)", + &format!("{}", system.cpus().len()), + None, + )); + + let mut arch_info = CpuInfo::new("Architecture", &get_architecture(), None); let contents = match fs::read_to_string("/proc/cpuinfo") { // Early return if we can't read /proc/cpuinfo for whatever reason @@ -79,50 +93,32 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { Ok(contents) => contents, }; - let mut arch_children: Vec<CpuInfo> = vec![]; - if let Some(addr_sizes) = find_cpuinfo_value(&contents, "address sizes") { - arch_children.push(CpuInfo { - field: "Address sizes".to_string(), - data: addr_sizes, - children: vec![], - }) + arch_info.add_child(CpuInfo::new("Address sizes", &addr_sizes, None)) } - cpu_infos.push("Architecture", &get_architecture(), Some(arch_children)); + cpu_infos.push(arch_info); // TODO: This is currently quite verbose and doesn't strictly respect the hierarchy of `/proc/cpuinfo` contents // ie. the file might contain multiple sections, each with their own vendor_id/model name etc. but right now // we're just taking whatever our regex matches first and using that if let Some(vendor) = find_cpuinfo_value(&contents, "vendor_id") { - let mut vendor_children: Vec<CpuInfo> = vec![]; + let mut vendor_info = CpuInfo::new("Vendor ID", &vendor, None); if let Some(model_name) = find_cpuinfo_value(&contents, "model name") { - let mut model_children: Vec<CpuInfo> = vec![]; + let mut model_name_info = CpuInfo::new("Model name", &model_name, None); if let Some(family) = find_cpuinfo_value(&contents, "cpu family") { - model_children.push(CpuInfo { - field: "CPU Family".to_string(), - data: family, - children: vec![], - }); + model_name_info.add_child(CpuInfo::new("CPU Family", &family, None)); } if let Some(model) = find_cpuinfo_value(&contents, "model") { - model_children.push(CpuInfo { - field: "Model".to_string(), - data: model, - children: vec![], - }); + model_name_info.add_child(CpuInfo::new("Model", &model, None)); } - vendor_children.push(CpuInfo { - field: "Model name".to_string(), - data: model_name, - children: model_children, - }); + vendor_info.add_child(model_name_info); } - cpu_infos.push("Vendor ID", vendor.as_str(), Some(vendor_children)); + cpu_infos.push(vendor_info); } print_output(cpu_infos, output_opts); From 5e98c668823a24cd97916a7f5745dc013787a5bb Mon Sep 17 00:00:00 2001 From: alxndrv <> Date: Wed, 12 Feb 2025 19:15:57 +0200 Subject: [PATCH 5/9] `lscpu`: Don't early-return on failure to read `/proc/cpuinfo` --- src/uu/lscpu/src/lscpu.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs index ad1acc1..bdeb776 100644 --- a/src/uu/lscpu/src/lscpu.rs +++ b/src/uu/lscpu/src/lscpu.rs @@ -86,12 +86,9 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let mut arch_info = CpuInfo::new("Architecture", &get_architecture(), None); - let contents = match fs::read_to_string("/proc/cpuinfo") { - // Early return if we can't read /proc/cpuinfo for whatever reason - // TODO: Should this return an non-zero exit code to user, or do we just ignore it and display whatever CPU info we could find? - Err(_) => return Ok(()), - Ok(contents) => contents, - }; + // TODO: We just silently ignore failures to read `/proc/cpuinfo` currently and treat it as empty + // Perhaps a better solution should be put in place, but what? + let contents = fs::read_to_string("/proc/cpuinfo").unwrap_or_default(); if let Some(addr_sizes) = find_cpuinfo_value(&contents, "address sizes") { arch_info.add_child(CpuInfo::new("Address sizes", &addr_sizes, None)) From e0a6ec7eb5e1cfe1c2430ed4f3b6cf56a0ac0fdb Mon Sep 17 00:00:00 2001 From: alxndrv <> Date: Wed, 12 Feb 2025 19:40:34 +0200 Subject: [PATCH 6/9] `lscpu`: Add support for CPU byte order --- src/uu/lscpu/src/lscpu.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs index bdeb776..7b12734 100644 --- a/src/uu/lscpu/src/lscpu.rs +++ b/src/uu/lscpu/src/lscpu.rs @@ -94,6 +94,15 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { arch_info.add_child(CpuInfo::new("Address sizes", &addr_sizes, None)) } + if let Ok(byte_order) = fs::read_to_string("/sys/kernel/cpu_byteorder") { + match byte_order.trim() { + "big" => arch_info.add_child(CpuInfo::new("Byte Order", "Big Endian", None)), + "little" => arch_info.add_child(CpuInfo::new("Byte Order", "Little Endian", None)), + _ => eprintln!("Unrecognised Byte Order: {}", byte_order) + + } + } + cpu_infos.push(arch_info); // TODO: This is currently quite verbose and doesn't strictly respect the hierarchy of `/proc/cpuinfo` contents From 0281da00dd6d752416728a70de85282f99aab9fb Mon Sep 17 00:00:00 2001 From: alxndrv <> Date: Wed, 12 Feb 2025 20:22:05 +0200 Subject: [PATCH 7/9] `lscpu`: Align output values to the same column --- src/uu/lscpu/src/lscpu.rs | 55 ++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs index 7b12734..0676a1d 100644 --- a/src/uu/lscpu/src/lscpu.rs +++ b/src/uu/lscpu/src/lscpu.rs @@ -6,7 +6,7 @@ use clap::{crate_version, Arg, ArgAction, Command}; use regex::RegexBuilder; use serde::Serialize; -use std::fs; +use std::{cmp, fs}; use sysinfo::System; use uucore::{error::UResult, format_usage, help_about, help_usage}; @@ -98,8 +98,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { match byte_order.trim() { "big" => arch_info.add_child(CpuInfo::new("Byte Order", "Big Endian", None)), "little" => arch_info.add_child(CpuInfo::new("Byte Order", "Little Endian", None)), - _ => eprintln!("Unrecognised Byte Order: {}", byte_order) - + _ => eprintln!("Unrecognised Byte Order: {}", byte_order), } } @@ -138,17 +137,53 @@ fn print_output(infos: CpuInfos, out_opts: OutputOptions) { return; } - // Recursive function to print nested CpuInfo entries - fn print_entries(entries: Vec<CpuInfo>, depth: usize, _out_opts: &OutputOptions) { - let indent = " ".repeat(depth); + fn indentation(depth: usize) -> usize { + // Indentation is 2 spaces per level, used in a few places, hence its own helper function + depth * 2 + } + + // Recurses down the tree of entries and find the one with the "widest" field name (taking into account tree depth) + fn get_max_field_width(info: &CpuInfo, depth: usize) -> usize { + let max_child_width = info + .children + .iter() + .map(|entry| get_max_field_width(entry, depth + 1)) + .max() + .unwrap_or_default(); + + let own_width = indentation(depth) + info.field.len(); + cmp::max(own_width, max_child_width) + } + + // Used later to align all values to the same column + let max_field_width = infos + .lscpu + .iter() + .map(|info| get_max_field_width(info, 0)) + .max() + .unwrap(); + + fn print_entries( + entries: &Vec<CpuInfo>, + depth: usize, + max_field_width: usize, + _out_opts: &OutputOptions, + ) { for entry in entries { - // TODO: Align `data` values to form a column - println!("{}{}: {}", indent, entry.field, entry.data); - print_entries(entry.children, depth + 1, _out_opts); + let margin = indentation(depth); + let padding = cmp::max(max_field_width - margin - entry.field.len(), 0); + println!( + "{}{}:{} {}", + " ".repeat(margin), + entry.field, + " ".repeat(padding), + entry.data + ); + print_entries(&entry.children, depth + 1, max_field_width, _out_opts); } } - print_entries(infos.lscpu, 0, &out_opts); + print_entries(&infos.lscpu, 0, max_field_width, &out_opts); } fn find_cpuinfo_value(contents: &str, key: &str) -> Option<String> { From 21706eb103ab8585d8c15769bcd8d60ad1534a54 Mon Sep 17 00:00:00 2001 From: alxndrv <> Date: Thu, 13 Feb 2025 12:28:00 +0200 Subject: [PATCH 8/9] `lscpu`: Move code around as per the review comments --- src/uu/lscpu/src/lscpu.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/uu/lscpu/src/lscpu.rs b/src/uu/lscpu/src/lscpu.rs index 0676a1d..3150e53 100644 --- a/src/uu/lscpu/src/lscpu.rs +++ b/src/uu/lscpu/src/lscpu.rs @@ -78,11 +78,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { }; let mut cpu_infos = CpuInfos::new(); - cpu_infos.push(CpuInfo::new( - "CPU(s)", - &format!("{}", system.cpus().len()), - None, - )); let mut arch_info = CpuInfo::new("Architecture", &get_architecture(), None); @@ -103,6 +98,11 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { } cpu_infos.push(arch_info); + cpu_infos.push(CpuInfo::new( + "CPU(s)", + &format!("{}", system.cpus().len()), + None, + )); // TODO: This is currently quite verbose and doesn't strictly respect the hierarchy of `/proc/cpuinfo` contents // ie. the file might contain multiple sections, each with their own vendor_id/model name etc. but right now @@ -155,14 +155,6 @@ fn print_output(infos: CpuInfos, out_opts: OutputOptions) { cmp::max(own_width, max_child_width) } - // Used later to align all values to the same column - let max_field_width = infos - .lscpu - .iter() - .map(|info| get_max_field_width(info, 0)) - .max() - .unwrap(); - fn print_entries( entries: &Vec<CpuInfo>, depth: usize, @@ -183,6 +175,14 @@ fn print_output(infos: CpuInfos, out_opts: OutputOptions) { } } + // Used to align all values to the same column + let max_field_width = infos + .lscpu + .iter() + .map(|info| get_max_field_width(info, 0)) + .max() + .unwrap(); + print_entries(&infos.lscpu, 0, max_field_width, &out_opts); } From 95d57eb23064b15bb1787e61f83b4c78b1183b79 Mon Sep 17 00:00:00 2001 From: alxndrv <> Date: Thu, 13 Feb 2025 12:48:30 +0200 Subject: [PATCH 9/9] `lscpu`: Improve tests a tiny bit --- tests/by-util/test_lscpu.rs | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/tests/by-util/test_lscpu.rs b/tests/by-util/test_lscpu.rs index ca4c92e..474001d 100644 --- a/tests/by-util/test_lscpu.rs +++ b/tests/by-util/test_lscpu.rs @@ -17,12 +17,33 @@ fn test_hex() { } #[test] +#[cfg(target_os = "linux")] fn test_json() { - new_ucmd!() - .arg("--json") - .succeeds() - // ensure some fields are there, non-exhausting - .stdout_contains("\"lscpu\": [") + let res = new_ucmd!().arg("--json").succeeds(); + + let stdout = res.no_stderr().stdout_str(); + assert!(stdout.starts_with("{")); + assert!(stdout.ends_with("}\n")); + + res.stdout_contains("\"lscpu\": [") .stdout_contains("\"field\": \"Architecture\"") - .stdout_contains("\"field\": \"CPU(s)\""); + .stdout_contains("\"field\": \"CPU(s)\"") + .stdout_contains("\"children\": ["); +} + +#[test] +#[cfg(target_os = "linux")] +fn test_output() { + let res = new_ucmd!().succeeds(); + let stdout = res.no_stderr().stdout_str(); + + // Non-exhaustive list of fields we expect + // This also checks that fields which should be indented, are indeed indented as excepted + assert!(stdout.contains("Architecture:")); + assert!(stdout.contains("\n Address sizes:")); + assert!(stdout.contains("\n Byte Order:")); + assert!(stdout.contains("\nCPU(s):")); + assert!(stdout.contains("\nVendor ID:")); + assert!(stdout.contains("\n Model name:")); + assert!(stdout.contains("\n CPU Family:")); }