Bug 1565033: Add memory testing to crash reporter client. r=afranchuk,fluent-reviewers,supply-chain-reviewers,cmartin,flod,gsvelto
Differential Revision: https://phabricator.services.mozilla.com/D231813
This commit is contained in:
18
Cargo.lock
generated
18
Cargo.lock
generated
@@ -1107,6 +1107,7 @@ dependencies = [
|
||||
"libloading",
|
||||
"lmdb-rkv-sys",
|
||||
"log",
|
||||
"memtest",
|
||||
"minidump-analyzer",
|
||||
"mozbuild",
|
||||
"mozilla-central-workspace-hack",
|
||||
@@ -1114,6 +1115,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
@@ -3776,6 +3778,22 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memtest"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f099da8d7ab73f5fbbaf7374ce3ec2fb220e7c77fe56f193269b5d3294a9c73c"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"libc",
|
||||
"num_cpus",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "metal"
|
||||
version = "0.31.0"
|
||||
|
||||
@@ -1326,6 +1326,18 @@
|
||||
value: 32
|
||||
mirror: always
|
||||
|
||||
# Whether memorytesting is enabled when crash reporter client is launched
|
||||
- name: browser.crashReporter.memtest
|
||||
type: RelaxedAtomicBool
|
||||
value: false
|
||||
mirror: always
|
||||
|
||||
# Space-separated list of prioritized memory test kinds
|
||||
- name: browser.crashReporter.memtestKinds
|
||||
type: DataMutexString
|
||||
value: ""
|
||||
mirror: always
|
||||
|
||||
# Min font device pixel size at which to turn on high quality.
|
||||
- name: browser.display.auto_quality_min_font_size
|
||||
type: RelaxedAtomicUint32
|
||||
|
||||
@@ -3141,6 +3141,12 @@ who = "Gabriele Svelto <gsvelto@mozilla.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
delta = "0.8.0 -> 0.9.0"
|
||||
|
||||
[[audits.memtest]]
|
||||
who = "Brian Tsoi <brian.s.tsoi@gmail.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
version = "0.1.3"
|
||||
notes = "This crate is written and maintained by Mozilla employees."
|
||||
|
||||
[[audits.metal]]
|
||||
who = "Jim Blandy <jimb@red-bean.com>"
|
||||
criteria = "safe-to-deploy"
|
||||
|
||||
1
third_party/rust/memtest/.cargo-checksum.json
vendored
Normal file
1
third_party/rust/memtest/.cargo-checksum.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"files":{"Cargo.lock":"875ecd06833109491b2b2d22207d851ff9e945e57d876615bf0adf89ad630b97","Cargo.toml":"1e0dbfce20696aa03dd89c1b7ef7178531901c3e9be57afa1884c6b62afc0ef1","LICENSE":"1f256ecad192880510e84ad60474eab7589218784b9a50bc7ceee34c2b91f1d5","README.md":"9317903e5896852844bd769652ac9818c9fec6d5b57424f230e30ff7f82e2e49","examples/usage.rs":"12d610e161b69de31543ad668c47ba5e3486ef45f7574c38dc4b547d60deff95","src/lib.rs":"18f3cdef993b06315073808a51d785688005c30ca73044b2fdea9649ae5d617a","src/memtest.rs":"2613311965bab589be36506d57efa112cd7209dd28c3f60dd08451a03a0fd3a2","src/prelude.rs":"73581af5d71192875b4c9b525c5f2e150b505e3f6df8ec1c837632a5f08b50fd"},"package":"f099da8d7ab73f5fbbaf7374ce3ec2fb220e7c77fe56f193269b5d3294a9c73c"}
|
||||
498
third_party/rust/memtest/Cargo.lock
generated
vendored
Normal file
498
third_party/rust/memtest/Cargo.lock
generated
vendored
Normal file
@@ -0,0 +1,498 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.169"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "memtest"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"libc",
|
||||
"num_cpus",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||
dependencies = [
|
||||
"overload",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.93"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.138"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.98"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
|
||||
dependencies = [
|
||||
"nu-ansi-term",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.58.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
73
third_party/rust/memtest/Cargo.toml
vendored
Normal file
73
third_party/rust/memtest/Cargo.toml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "memtest"
|
||||
version = "0.1.3"
|
||||
authors = ["Brian Tsoi <brian.s.tsoi@gmail.com>"]
|
||||
build = false
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "A library for detecting faulty memory"
|
||||
homepage = "https://github.com/mozilla/memtest"
|
||||
readme = "README.md"
|
||||
keywords = [
|
||||
"memory",
|
||||
"stability",
|
||||
]
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/mozilla/memtest"
|
||||
|
||||
[lib]
|
||||
name = "memtest"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[example]]
|
||||
name = "usage"
|
||||
path = "examples/usage.rs"
|
||||
|
||||
[dependencies.anyhow]
|
||||
version = "1.0.69"
|
||||
|
||||
[dependencies.num_cpus]
|
||||
version = "1.16.0"
|
||||
|
||||
[dependencies.rand]
|
||||
version = "0.8.5"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "1.0.214"
|
||||
features = ["derive"]
|
||||
|
||||
[dependencies.serde_json]
|
||||
version = "1.0.116"
|
||||
|
||||
[dependencies.tracing]
|
||||
version = "0.1.37"
|
||||
|
||||
[dev-dependencies.tracing-subscriber]
|
||||
version = "0.3.18"
|
||||
|
||||
[target."cfg(unix)".dependencies.libc]
|
||||
version = "0.2.158"
|
||||
|
||||
[target."cfg(windows)".dependencies.windows]
|
||||
version = "0.58.0"
|
||||
features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_Memory",
|
||||
"Win32_System_SystemInformation",
|
||||
"Win32_System_Threading",
|
||||
]
|
||||
373
third_party/rust/memtest/LICENSE
vendored
Normal file
373
third_party/rust/memtest/LICENSE
vendored
Normal file
@@ -0,0 +1,373 @@
|
||||
Mozilla Public License Version 2.0
|
||||
==================================
|
||||
|
||||
1. Definitions
|
||||
--------------
|
||||
|
||||
1.1. "Contributor"
|
||||
means each individual or legal entity that creates, contributes to
|
||||
the creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
means the combination of the Contributions of others (if any) used
|
||||
by a Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
means Source Code Form to which the initial Contributor has attached
|
||||
the notice in Exhibit A, the Executable Form of such Source Code
|
||||
Form, and Modifications of such Source Code Form, in each case
|
||||
including portions thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
(a) that the initial Contributor has attached the notice described
|
||||
in Exhibit B to the Covered Software; or
|
||||
|
||||
(b) that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the
|
||||
terms of a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
means a work that combines Covered Software with other material, in
|
||||
a separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
means having the right to grant, to the maximum extent possible,
|
||||
whether at the time of the initial grant or subsequently, any and
|
||||
all of the rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
means any of the following:
|
||||
|
||||
(a) any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered
|
||||
Software; or
|
||||
|
||||
(b) any new file in Source Code Form that contains any Covered
|
||||
Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the
|
||||
License, by the making, using, selling, offering for sale, having
|
||||
made, import, or transfer of either its Contributions or its
|
||||
Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
means either the GNU General Public License, Version 2.0, the GNU
|
||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
||||
Public License, Version 3.0, or any later versions of those
|
||||
licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that
|
||||
controls, is controlled by, or is under common control with You. For
|
||||
purposes of this definition, "control" means (a) the power, direct
|
||||
or indirect, to cause the direction or management of such entity,
|
||||
whether by contract or otherwise, or (b) ownership of more than
|
||||
fifty percent (50%) of the outstanding shares or beneficial
|
||||
ownership of such entity.
|
||||
|
||||
2. License Grants and Conditions
|
||||
--------------------------------
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
(a) under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
||||
for sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
(a) for any code that a Contributor has removed from Covered Software;
|
||||
or
|
||||
|
||||
(b) for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights
|
||||
to grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
||||
in Section 2.1.
|
||||
|
||||
3. Responsibilities
|
||||
-------------------
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
(a) such Covered Software must also be made available in Source Code
|
||||
Form, as described in Section 3.1, and You must inform recipients of
|
||||
the Executable Form how they can obtain a copy of such Source Code
|
||||
Form by reasonable means in a timely manner, at a charge no more
|
||||
than the cost of distribution to the recipient; and
|
||||
|
||||
(b) You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter
|
||||
the recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty,
|
||||
or limitations of liability) contained within the Source Code Form of
|
||||
the Covered Software, except that You may alter any license notices to
|
||||
the extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
---------------------------------------------------
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this
|
||||
License with respect to some or all of the Covered Software due to
|
||||
statute, judicial order, or regulation then You must: (a) comply with
|
||||
the terms of this License to the maximum extent possible; and (b)
|
||||
describe the limitations and the code they affect. Such description must
|
||||
be placed in a text file included with all distributions of the Covered
|
||||
Software under this License. Except to the extent prohibited by statute
|
||||
or regulation, such description must be sufficiently detailed for a
|
||||
recipient of ordinary skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
--------------
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically
|
||||
if You fail to comply with any of its terms. However, if You become
|
||||
compliant, then the rights granted under this License from a particular
|
||||
Contributor are reinstated (a) provisionally, unless and until such
|
||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
||||
ongoing basis, if such Contributor fails to notify You of the
|
||||
non-compliance by some reasonable means prior to 60 days after You have
|
||||
come back into compliance. Moreover, Your grants from a particular
|
||||
Contributor are reinstated on an ongoing basis if such Contributor
|
||||
notifies You of the non-compliance by some reasonable means, this is the
|
||||
first time You have received notice of non-compliance with this License
|
||||
from such Contributor, and You become compliant prior to 30 days after
|
||||
Your receipt of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
||||
end user license agreements (excluding distributors and resellers) which
|
||||
have been validly granted by You or Your distributors under this License
|
||||
prior to termination shall survive termination.
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 6. Disclaimer of Warranty *
|
||||
* ------------------------- *
|
||||
* *
|
||||
* Covered Software is provided under this License on an "as is" *
|
||||
* basis, without warranty of any kind, either expressed, implied, or *
|
||||
* statutory, including, without limitation, warranties that the *
|
||||
* Covered Software is free of defects, merchantable, fit for a *
|
||||
* particular purpose or non-infringing. The entire risk as to the *
|
||||
* quality and performance of the Covered Software is with You. *
|
||||
* Should any Covered Software prove defective in any respect, You *
|
||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
||||
* essential part of this License. No use of any Covered Software is *
|
||||
* authorized under this License except under this disclaimer. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
************************************************************************
|
||||
* *
|
||||
* 7. Limitation of Liability *
|
||||
* -------------------------- *
|
||||
* *
|
||||
* Under no circumstances and under no legal theory, whether tort *
|
||||
* (including negligence), contract, or otherwise, shall any *
|
||||
* Contributor, or anyone who distributes Covered Software as *
|
||||
* permitted above, be liable to You for any direct, indirect, *
|
||||
* special, incidental, or consequential damages of any character *
|
||||
* including, without limitation, damages for lost profits, loss of *
|
||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
||||
* and all other commercial damages or losses, even if such party *
|
||||
* shall have been informed of the possibility of such damages. This *
|
||||
* limitation of liability shall not apply to liability for death or *
|
||||
* personal injury resulting from such party's negligence to the *
|
||||
* extent applicable law prohibits such limitation. Some *
|
||||
* jurisdictions do not allow the exclusion or limitation of *
|
||||
* incidental or consequential damages, so this exclusion and *
|
||||
* limitation may not apply to You. *
|
||||
* *
|
||||
************************************************************************
|
||||
|
||||
8. Litigation
|
||||
-------------
|
||||
|
||||
Any litigation relating to this License may be brought only in the
|
||||
courts of a jurisdiction where the defendant maintains its principal
|
||||
place of business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions.
|
||||
Nothing in this Section shall prevent a party's ability to bring
|
||||
cross-claims or counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
----------------
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides
|
||||
that the language of a contract shall be construed against the drafter
|
||||
shall not be used to construe this License against a Contributor.
|
||||
|
||||
10. Versions of the License
|
||||
---------------------------
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses
|
||||
|
||||
If You choose to distribute Source Code Form that is Incompatible With
|
||||
Secondary Licenses under the terms of this version of the License, the
|
||||
notice described in Exhibit B of this License must be attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
-------------------------------------------
|
||||
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular
|
||||
file, then You may include the notice in a location (such as a LICENSE
|
||||
file in a relevant directory) where a recipient would be likely to look
|
||||
for such a notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
---------------------------------------------------------
|
||||
|
||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
defined by the Mozilla Public License, v. 2.0.
|
||||
7
third_party/rust/memtest/README.md
vendored
Normal file
7
third_party/rust/memtest/README.md
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# memtest
|
||||
|
||||
[](https://crates.io/crates/memtest)
|
||||
[](https://docs.rs/memtest)
|
||||
[](LICENSE)
|
||||
|
||||
A library for detecting faulty memory.
|
||||
98
third_party/rust/memtest/examples/usage.rs
vendored
Normal file
98
third_party/rust/memtest/examples/usage.rs
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
use {
|
||||
anyhow::Context,
|
||||
memtest::{MemtestKind, MemtestRunner, MemtestRunnerArgs},
|
||||
rand::{seq::SliceRandom, thread_rng},
|
||||
std::{
|
||||
mem::size_of,
|
||||
time::{Duration, Instant},
|
||||
},
|
||||
tracing::info,
|
||||
tracing_subscriber::fmt::format::FmtSpan,
|
||||
};
|
||||
|
||||
// TODO: Command line option for json output
|
||||
fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
|
||||
.with_max_level(tracing::Level::TRACE)
|
||||
.with_writer(std::io::stderr)
|
||||
.with_thread_ids(true)
|
||||
.init();
|
||||
let start_time = Instant::now();
|
||||
let (mem_usize_count, memtest_runner_args, memtest_kinds) = match parse_args() {
|
||||
Ok(parsed_args) => parsed_args,
|
||||
Err(s) => {
|
||||
eprintln!(concat!(
|
||||
"Usage: memtest-runner ",
|
||||
"<memsize in MiB> ",
|
||||
"<timeout in ms> ",
|
||||
"<mem_lock_mode> ",
|
||||
"<allow_working_set_resize as bool> ",
|
||||
"<allow_multithread as bool> ",
|
||||
"<allow_early_temrmination as bool> ",
|
||||
"<memtest_kinds as space separated string>"
|
||||
));
|
||||
anyhow::bail!("Invalid/missing argument '{s}'");
|
||||
}
|
||||
};
|
||||
|
||||
info!("Running memtest-runner with: {memtest_runner_args:#?}");
|
||||
let mut memory = vec![0; mem_usize_count];
|
||||
let report_list = MemtestRunner::from_test_kinds(&memtest_runner_args, memtest_kinds)
|
||||
.run(&mut memory)
|
||||
.context("Failed to run memtest-runner")?;
|
||||
println!("Tester ran for {:?}", start_time.elapsed());
|
||||
println!("Test results: \n{report_list}");
|
||||
|
||||
anyhow::ensure!(
|
||||
report_list.all_pass(),
|
||||
"Found failures or errors among memtest reports"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Parse command line arguments to return a usize for the requested memory vector length and
|
||||
/// other MemtestRunner arguments
|
||||
fn parse_args() -> Result<(usize, MemtestRunnerArgs, Vec<MemtestKind>), &'static str> {
|
||||
const KIB: usize = 1024;
|
||||
const MIB: usize = 1024 * KIB;
|
||||
|
||||
let mut iter = std::env::args().skip(1);
|
||||
|
||||
macro_rules! parse_next(($n: literal) => {
|
||||
iter.next().and_then(|s| s.parse().ok()).ok_or($n)?
|
||||
});
|
||||
|
||||
let memsize: usize = parse_next!("memsize");
|
||||
let mem_usize_count = memsize * MIB / size_of::<usize>();
|
||||
|
||||
let memtest_runner_args = MemtestRunnerArgs {
|
||||
timeout: Duration::from_millis(parse_next!("timeout_ms")),
|
||||
mem_lock_mode: parse_next!("mem_lock_mode"),
|
||||
allow_working_set_resize: parse_next!("allow_working_set_resize"),
|
||||
allow_multithread: parse_next!("allow_multithread"),
|
||||
allow_early_termination: parse_next!("allow_early_termination"),
|
||||
};
|
||||
|
||||
let memtest_kinds_string: String = parse_next!("memtest_kinds");
|
||||
let memtest_kinds = memtest_kinds_from_str(&memtest_kinds_string)?;
|
||||
|
||||
Ok((mem_usize_count, memtest_runner_args, memtest_kinds))
|
||||
}
|
||||
|
||||
/// Returns a vector of MemtestKind that contains all kinds, but prioritizes the given memtests.
|
||||
fn memtest_kinds_from_str(str: &str) -> Result<Vec<MemtestKind>, &'static str> {
|
||||
let specified = str
|
||||
.split_whitespace()
|
||||
.map(|s| s.parse().map_err(|_| "memtest_kinds"))
|
||||
.collect::<Result<Vec<MemtestKind>, &'static str>>()?;
|
||||
|
||||
let mut remaining: Vec<_> = MemtestKind::ALL
|
||||
.iter()
|
||||
.filter(|k| !specified.contains(k))
|
||||
.cloned()
|
||||
.collect();
|
||||
remaining.shuffle(&mut thread_rng());
|
||||
|
||||
Ok([specified, remaining].concat())
|
||||
}
|
||||
696
third_party/rust/memtest/src/lib.rs
vendored
Normal file
696
third_party/rust/memtest/src/lib.rs
vendored
Normal file
@@ -0,0 +1,696 @@
|
||||
#[cfg(unix)]
|
||||
use unix::{memory_lock, memory_resize_and_lock};
|
||||
#[cfg(windows)]
|
||||
use windows::{memory_lock, memory_resize_and_lock, replace_set_size};
|
||||
use {
|
||||
prelude::*,
|
||||
rand::{seq::SliceRandom, thread_rng},
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{
|
||||
error::Error,
|
||||
fmt,
|
||||
mem::size_of_val,
|
||||
time::{Duration, Instant},
|
||||
},
|
||||
};
|
||||
|
||||
mod memtest;
|
||||
mod prelude;
|
||||
|
||||
pub use memtest::{
|
||||
MemtestError, MemtestFailure, MemtestKind, MemtestOutcome, ParseMemtestKindError,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MemtestRunner {
|
||||
test_kinds: Vec<MemtestKind>,
|
||||
timeout: Duration,
|
||||
mem_lock_mode: MemLockMode,
|
||||
#[allow(dead_code)]
|
||||
allow_working_set_resize: bool,
|
||||
allow_multithread: bool,
|
||||
allow_early_termination: bool,
|
||||
}
|
||||
|
||||
// TODO: Replace MemtestRunnerArgs with a Builder struct implementing fluent interface
|
||||
/// A set of arguments that define the behavior of MemtestRunner
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct MemtestRunnerArgs {
|
||||
/// How long should MemtestRunner run the test suite before timing out
|
||||
pub timeout: Duration,
|
||||
/// Whether memory will be locked before testing and whether the requested memory size of
|
||||
/// testing can be reduced to accomodate memory locking
|
||||
/// If memory locking failed but is required, MemtestRunner returns with error
|
||||
pub mem_lock_mode: MemLockMode,
|
||||
/// Whether the process working set can be resized to accomodate memory locking
|
||||
/// This argument is only meaningful for Windows
|
||||
pub allow_working_set_resize: bool,
|
||||
/// Whether mulithreading is enabled
|
||||
pub allow_multithread: bool,
|
||||
/// Whether MemtestRunner returns immediately if a test fails or continues until all tests are run
|
||||
pub allow_early_termination: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MemtestRunnerError {
|
||||
MemLockFailed(anyhow::Error),
|
||||
Other(anyhow::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct MemtestReportList {
|
||||
pub tested_mem_length: usize,
|
||||
pub mlocked: bool,
|
||||
pub reports: Vec<MemtestReport>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct MemtestReport {
|
||||
pub test_kind: MemtestKind,
|
||||
pub outcome: Result<MemtestOutcome, MemtestError>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub enum MemLockMode {
|
||||
Resizable,
|
||||
FixedSize,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ParseMemLockModeError;
|
||||
|
||||
/// The minimum memory length (in usize) for MemtestRunner to run tests on
|
||||
/// On a 64-bit machine, this is the size of a page
|
||||
pub const MIN_MEMORY_LENGTH: usize = 512;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct MemLockGuard {
|
||||
base_ptr: *mut usize,
|
||||
mem_size: usize,
|
||||
}
|
||||
|
||||
/// A struct to ensure the test timeouts in a given duration
|
||||
#[derive(Debug)]
|
||||
struct TimeoutChecker {
|
||||
deadline: Instant,
|
||||
state: Option<TimeoutCheckerState>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TimeoutCheckerState {
|
||||
test_start_time: Instant,
|
||||
expected_iter: u64,
|
||||
completed_iter: u64,
|
||||
checkpoint: u64,
|
||||
}
|
||||
|
||||
impl MemtestRunner {
|
||||
/// Create a MemtestRunner containing all test kinds in random order
|
||||
pub fn all_tests_random_order(args: &MemtestRunnerArgs) -> MemtestRunner {
|
||||
let mut test_kinds = MemtestKind::ALL.to_vec();
|
||||
test_kinds.shuffle(&mut thread_rng());
|
||||
|
||||
Self::from_test_kinds(args, test_kinds)
|
||||
}
|
||||
|
||||
/// Create a MemtestRunner with specified test kinds
|
||||
pub fn from_test_kinds(
|
||||
args: &MemtestRunnerArgs,
|
||||
test_kinds: Vec<MemtestKind>,
|
||||
) -> MemtestRunner {
|
||||
MemtestRunner {
|
||||
test_kinds,
|
||||
timeout: args.timeout,
|
||||
mem_lock_mode: args.mem_lock_mode,
|
||||
allow_working_set_resize: args.allow_working_set_resize,
|
||||
allow_multithread: args.allow_multithread,
|
||||
allow_early_termination: args.allow_early_termination,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the tests, possibly after locking the memory
|
||||
pub fn run(&self, memory: &mut [usize]) -> Result<MemtestReportList, MemtestRunnerError> {
|
||||
if memory.len() < MIN_MEMORY_LENGTH {
|
||||
return Err(anyhow!("Insufficient memory length").into());
|
||||
}
|
||||
|
||||
let deadline = Instant::now() + self.timeout;
|
||||
|
||||
if matches!(self.mem_lock_mode, MemLockMode::Disabled) {
|
||||
return Ok(MemtestReportList {
|
||||
tested_mem_length: memory.len(),
|
||||
mlocked: false,
|
||||
reports: self.run_tests(memory, deadline),
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
// TODO: When it is MemLockMode::Resizable and working set resize failed, consider shrinking
|
||||
// the memory region and try again
|
||||
let _working_set_resize_guard = if self.allow_working_set_resize {
|
||||
Some(
|
||||
replace_set_size(size_of_val(memory))
|
||||
.context("Failed to replace process working set size")?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (memory, _mem_lock_guard) = match self.mem_lock_mode {
|
||||
MemLockMode::FixedSize => memory_lock(memory),
|
||||
MemLockMode::Resizable => memory_resize_and_lock(memory),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
.map_err(MemtestRunnerError::MemLockFailed)?;
|
||||
|
||||
Ok(MemtestReportList {
|
||||
tested_mem_length: memory.len(),
|
||||
mlocked: true,
|
||||
reports: self.run_tests(memory, deadline),
|
||||
})
|
||||
}
|
||||
|
||||
/// Run tests
|
||||
fn run_tests(&self, memory: &mut [usize], deadline: Instant) -> Vec<MemtestReport> {
|
||||
let mut reports = Vec::new();
|
||||
let mut timed_out = false;
|
||||
|
||||
for test_kind in &self.test_kinds {
|
||||
let test = match test_kind {
|
||||
MemtestKind::OwnAddressBasic => memtest::test_own_address_basic,
|
||||
MemtestKind::OwnAddressRepeat => memtest::test_own_address_repeat,
|
||||
MemtestKind::RandomVal => memtest::test_random_val,
|
||||
MemtestKind::Xor => memtest::test_xor,
|
||||
MemtestKind::Sub => memtest::test_sub,
|
||||
MemtestKind::Mul => memtest::test_mul,
|
||||
MemtestKind::Div => memtest::test_div,
|
||||
MemtestKind::Or => memtest::test_or,
|
||||
MemtestKind::And => memtest::test_and,
|
||||
MemtestKind::SeqInc => memtest::test_seq_inc,
|
||||
MemtestKind::SolidBits => memtest::test_solid_bits,
|
||||
MemtestKind::Checkerboard => memtest::test_checkerboard,
|
||||
MemtestKind::BlockSeq => memtest::test_block_seq,
|
||||
};
|
||||
|
||||
let test_result = if timed_out {
|
||||
Err(MemtestError::Timeout)
|
||||
} else if self.allow_multithread {
|
||||
std::thread::scope(|scope| {
|
||||
let num_threads = num_cpus::get();
|
||||
let chunk_size = memory.len() / num_threads;
|
||||
|
||||
let mut handles = vec![];
|
||||
for chunk in memory.chunks_exact_mut(chunk_size) {
|
||||
let handle = scope.spawn(|| test(chunk, TimeoutChecker::new(deadline)));
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
#[allow(clippy::manual_try_fold)]
|
||||
handles
|
||||
.into_iter()
|
||||
.map(|handle| {
|
||||
handle
|
||||
.join()
|
||||
.unwrap_or(Err(MemtestError::Other(anyhow!("Thread panicked"))))
|
||||
})
|
||||
.fold(Ok(MemtestOutcome::Pass), |acc, result| {
|
||||
use {MemtestError::*, MemtestOutcome::*};
|
||||
match (acc, result) {
|
||||
(Err(Other(e)), _) | (_, Err(Other(e))) => Err(Other(e)),
|
||||
(Err(Timeout), _) | (_, Err(Timeout)) => Err(Timeout),
|
||||
(Ok(Fail(f)), _) | (_, Ok(Fail(f))) => Ok(Fail(f)),
|
||||
_ => Ok(Pass),
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
test(memory, TimeoutChecker::new(deadline))
|
||||
};
|
||||
timed_out = matches!(test_result, Err(MemtestError::Timeout));
|
||||
|
||||
if matches!(test_result, Ok(MemtestOutcome::Fail(_))) && self.allow_early_termination {
|
||||
reports.push(MemtestReport::new(*test_kind, test_result));
|
||||
warn!("Memtest failed, terminating early");
|
||||
break;
|
||||
}
|
||||
reports.push(MemtestReport::new(*test_kind, test_result));
|
||||
}
|
||||
|
||||
reports
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MemtestRunnerError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for MemtestRunnerError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
MemtestRunnerError::MemLockFailed(err) | MemtestRunnerError::Other(err) => {
|
||||
Some(err.as_ref())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for MemtestRunnerError {
|
||||
fn from(err: anyhow::Error) -> MemtestRunnerError {
|
||||
MemtestRunnerError::Other(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::str::FromStr for MemLockMode {
|
||||
type Err = ParseMemLockModeError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"resizable" => Ok(Self::Resizable),
|
||||
"fixedsize" => Ok(Self::FixedSize),
|
||||
"disabled" => Ok(Self::Disabled),
|
||||
_ => Err(ParseMemLockModeError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseMemLockModeError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseMemLockModeError {}
|
||||
|
||||
impl fmt::Display for MemtestReportList {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "tested_mem_len = {}", self.tested_mem_length)?;
|
||||
writeln!(f, "mlocked = {}", self.mlocked)?;
|
||||
for report in &self.reports {
|
||||
let outcome = match &report.outcome {
|
||||
Ok(outcome) => format!("{}", outcome),
|
||||
Err(e) => format!("{}", e),
|
||||
};
|
||||
writeln!(
|
||||
f,
|
||||
"{:<30} {}",
|
||||
format!("Ran Test: {:?}", report.test_kind),
|
||||
outcome
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl MemtestReportList {
|
||||
pub fn iter(&self) -> impl Iterator<Item = &MemtestReport> {
|
||||
self.reports.iter()
|
||||
}
|
||||
|
||||
/// Returns true if all tests were run successfully and all tests passed
|
||||
pub fn all_pass(&self) -> bool {
|
||||
self.iter()
|
||||
.all(|report| matches!(report.outcome, Ok(MemtestOutcome::Pass)))
|
||||
}
|
||||
}
|
||||
|
||||
impl MemtestReport {
|
||||
fn new(test_kind: MemtestKind, outcome: Result<MemtestOutcome, MemtestError>) -> MemtestReport {
|
||||
MemtestReport { test_kind, outcome }
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeoutChecker {
|
||||
fn new(deadline: Instant) -> TimeoutChecker {
|
||||
TimeoutChecker {
|
||||
deadline,
|
||||
state: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize TimeoutCheckerState
|
||||
/// This function should be called in the beginning of a memtest.
|
||||
fn init(&mut self, expected_iter: u64) {
|
||||
const FIRST_CHECKPOINT: u64 = 8;
|
||||
|
||||
assert!(
|
||||
self.state.is_none(),
|
||||
"init() should only be called once per test"
|
||||
);
|
||||
|
||||
// The first checkpoint is set to 8 to have a more accurate sample of duration per
|
||||
// iteration for determining new checkpoint
|
||||
self.state = Some(TimeoutCheckerState {
|
||||
test_start_time: Instant::now(),
|
||||
expected_iter,
|
||||
completed_iter: 0,
|
||||
checkpoint: FIRST_CHECKPOINT,
|
||||
});
|
||||
}
|
||||
|
||||
/// Check if the current iteration is a checkpoint. If so, check if timeout occurred
|
||||
///
|
||||
/// This function should be called in every iteration of a memtest.
|
||||
///
|
||||
/// To reduce overhead, the function only checks for timeout at specific checkpoints, and
|
||||
/// early returns otherwise.
|
||||
///
|
||||
// It is important to ensure that the "early return" hot path is inlined. This results in a
|
||||
// 100% improvement in performance.
|
||||
#[inline(always)]
|
||||
fn check(&mut self) -> Result<(), MemtestError> {
|
||||
let state = self
|
||||
.state
|
||||
.as_mut()
|
||||
.expect("init() should be called before check()");
|
||||
|
||||
if state.completed_iter < state.checkpoint {
|
||||
state.completed_iter += 1;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
state.on_checkpoint(self.deadline)
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeoutCheckerState {
|
||||
fn on_checkpoint(&mut self, deadline: Instant) -> Result<(), MemtestError> {
|
||||
let current_time = Instant::now();
|
||||
if current_time >= deadline {
|
||||
return Err(MemtestError::Timeout);
|
||||
}
|
||||
|
||||
self.trace_progress();
|
||||
self.set_next_checkpoint(deadline, current_time);
|
||||
|
||||
self.completed_iter += 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Note: Because `trace_progress()` is only called in `on_checkpoints()`, not every percent
|
||||
// of the test progress is traced. If memtests are running way ahead of the given deadline, the
|
||||
// progress may only be traced once or twice. Although this makes the logs less comprehensive,
|
||||
// it avoids signficiant perforamnce overhead.
|
||||
fn trace_progress(&mut self) {
|
||||
if tracing::enabled!(tracing::Level::TRACE) {
|
||||
trace!(
|
||||
"Progress on checkpoint: {:.2}%",
|
||||
self.completed_iter as f64 / self.expected_iter as f64 * 100.0
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the remaining time before the deadline and schedule the next check at 75% of that
|
||||
/// interval, then estimate the number of iterations to get there and set as next checkpoint
|
||||
fn set_next_checkpoint(&mut self, deadline: Instant, current_time: Instant) {
|
||||
const DEADLINE_CHECK_RATIO: f64 = 0.75;
|
||||
|
||||
let duration_until_next_checkpoint = {
|
||||
let duration_until_deadline = deadline - current_time;
|
||||
duration_until_deadline.mul_f64(DEADLINE_CHECK_RATIO)
|
||||
};
|
||||
|
||||
let avg_iter_duration = {
|
||||
let test_elapsed = current_time - self.test_start_time;
|
||||
test_elapsed.div_f64(self.completed_iter as f64)
|
||||
};
|
||||
|
||||
let iter_until_next_checkpoint = {
|
||||
let x =
|
||||
Self::div_duration_f64(duration_until_next_checkpoint, avg_iter_duration) as u64;
|
||||
u64::max(x, 1)
|
||||
};
|
||||
|
||||
self.checkpoint += iter_until_next_checkpoint;
|
||||
}
|
||||
|
||||
// This is equivalent to `Duration::div_duration_f64`, but that is not stable on Rust 1.76
|
||||
fn div_duration_f64(lhs: Duration, rhs: Duration) -> f64 {
|
||||
const NANOS_PER_SEC: u32 = 1_000_000_000;
|
||||
let lhs_nanos =
|
||||
(lhs.as_secs() as f64) * (NANOS_PER_SEC as f64) + (lhs.subsec_nanos() as f64);
|
||||
let rhs_nanos =
|
||||
(rhs.as_secs() as f64) * (NANOS_PER_SEC as f64) + (rhs.subsec_nanos() as f64);
|
||||
lhs_nanos / rhs_nanos
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows {
|
||||
use {
|
||||
crate::{prelude::*, MemLockGuard},
|
||||
std::mem::{size_of, size_of_val},
|
||||
windows::Win32::{
|
||||
Foundation::ERROR_WORKING_SET_QUOTA,
|
||||
System::{
|
||||
Memory::{VirtualLock, VirtualUnlock},
|
||||
SystemInformation::{
|
||||
GetNativeSystemInfo, GlobalMemoryStatusEx, MEMORYSTATUSEX, SYSTEM_INFO,
|
||||
},
|
||||
Threading::{
|
||||
GetCurrentProcess, GetProcessWorkingSetSize, SetProcessWorkingSetSize,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WorkingSetResizeGuard {
|
||||
min_set_size: usize,
|
||||
max_set_size: usize,
|
||||
}
|
||||
|
||||
// TODO: Consider verifying that the process memory is properly sized by using
|
||||
// `GetProcessMemoryInfo` during memtests to retrieve the number of page faults this process is
|
||||
// causing. If it's suddenly a very high number, it indicates the set size might be too small
|
||||
pub(super) fn replace_set_size(memsize: usize) -> anyhow::Result<WorkingSetResizeGuard> {
|
||||
const ESTIMATED_TEST_MEM_USAGE: usize = 1024 * 1024; // 1MiB
|
||||
let (min_set_size, max_set_size) = get_set_size()?;
|
||||
let new_min_set_size = memsize + ESTIMATED_TEST_MEM_USAGE;
|
||||
let new_max_set_size =
|
||||
get_physical_memory_size().context("Failed to get physical memory size")?;
|
||||
unsafe {
|
||||
SetProcessWorkingSetSize(GetCurrentProcess(), new_min_set_size, new_max_set_size)
|
||||
.context("Failed to set process working set size")?;
|
||||
}
|
||||
Ok(WorkingSetResizeGuard {
|
||||
min_set_size,
|
||||
max_set_size,
|
||||
})
|
||||
}
|
||||
|
||||
impl Drop for WorkingSetResizeGuard {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if let Err(e) = SetProcessWorkingSetSize(
|
||||
GetCurrentProcess(),
|
||||
self.min_set_size,
|
||||
self.max_set_size,
|
||||
) {
|
||||
warn!("Failed to restore process working set: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn memory_lock(
|
||||
memory: &mut [usize],
|
||||
) -> anyhow::Result<(&mut [usize], MemLockGuard)> {
|
||||
let base_ptr = memory.as_mut_ptr();
|
||||
let mem_size = size_of_val(memory);
|
||||
|
||||
unsafe {
|
||||
VirtualLock(base_ptr.cast(), mem_size).context("VirtualLock failed")?;
|
||||
}
|
||||
info!("Successfully locked {}MB", mem_size);
|
||||
Ok((memory, MemLockGuard { base_ptr, mem_size }))
|
||||
}
|
||||
|
||||
pub(super) fn memory_resize_and_lock(
|
||||
mut memory: &mut [usize],
|
||||
) -> anyhow::Result<(&mut [usize], MemLockGuard)> {
|
||||
// Resizing to system limit first is more efficient than only decrementing by page
|
||||
// size and retry locking.
|
||||
let min_set_size_usize = get_set_size()?.0 / size_of::<usize>();
|
||||
if memory.len() > min_set_size_usize {
|
||||
memory = &mut memory[0..min_set_size_usize];
|
||||
warn!(
|
||||
"Resized memory to system limit ({} bytes)",
|
||||
size_of_val(memory)
|
||||
);
|
||||
}
|
||||
|
||||
let usize_per_page = get_page_size()? / std::mem::size_of::<usize>();
|
||||
loop {
|
||||
let base_ptr = memory.as_mut_ptr();
|
||||
let mem_size = size_of_val(memory);
|
||||
|
||||
let res = unsafe { VirtualLock(base_ptr.cast(), mem_size) };
|
||||
let Err(e) = res else {
|
||||
info!("Successfully locked {} bytes", mem_size);
|
||||
return Ok((memory, MemLockGuard { base_ptr, mem_size }));
|
||||
};
|
||||
|
||||
ensure!(
|
||||
e == ERROR_WORKING_SET_QUOTA.into(),
|
||||
anyhow!(e).context("VirtualLock failed")
|
||||
);
|
||||
|
||||
// Locking with the system limit can still fail as the memory to be locked may not be
|
||||
// page aligned. In that case retry locking after decrement memory size by a page.
|
||||
let new_len = memory
|
||||
.len()
|
||||
.checked_sub(usize_per_page)
|
||||
.context("Failed to lock any memory, memory size has been decremented to 0")?;
|
||||
|
||||
memory = &mut memory[0..new_len];
|
||||
warn!(
|
||||
"Decremented memory size to {} bytes, retry memory locking",
|
||||
new_len * usize_per_page
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MemLockGuard {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if let Err(e) = VirtualUnlock(self.base_ptr.cast(), self.mem_size) {
|
||||
warn!("Failed to unlock memory: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_set_size() -> anyhow::Result<(usize, usize)> {
|
||||
let (mut min_set_size, mut max_set_size) = (0, 0);
|
||||
unsafe {
|
||||
GetProcessWorkingSetSize(GetCurrentProcess(), &mut min_set_size, &mut max_set_size)
|
||||
.context("Failed to get process working set")?;
|
||||
}
|
||||
Ok((min_set_size, max_set_size))
|
||||
}
|
||||
|
||||
fn get_page_size() -> anyhow::Result<usize> {
|
||||
Ok((unsafe {
|
||||
let mut sysinfo: SYSTEM_INFO = std::mem::zeroed();
|
||||
GetNativeSystemInfo(&mut sysinfo);
|
||||
sysinfo.dwPageSize
|
||||
})
|
||||
.try_into()
|
||||
.unwrap())
|
||||
}
|
||||
|
||||
fn get_physical_memory_size() -> anyhow::Result<usize> {
|
||||
let mut memory_status = MEMORYSTATUSEX::default();
|
||||
memory_status.dwLength = std::mem::size_of_val(&memory_status).try_into().unwrap();
|
||||
unsafe {
|
||||
GlobalMemoryStatusEx(&mut memory_status)
|
||||
.context("Failed to get global memory status")?
|
||||
};
|
||||
Ok(memory_status.ullTotalPhys.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
mod unix {
|
||||
use {
|
||||
crate::{prelude::*, MemLockGuard},
|
||||
libc::{getrlimit, mlock, munlock, rlimit, sysconf, RLIMIT_MEMLOCK, _SC_PAGESIZE},
|
||||
std::{
|
||||
borrow::BorrowMut,
|
||||
io::{Error, ErrorKind},
|
||||
mem::{size_of, size_of_val},
|
||||
},
|
||||
};
|
||||
|
||||
pub(super) fn memory_lock(
|
||||
memory: &mut [usize],
|
||||
) -> anyhow::Result<(&mut [usize], MemLockGuard)> {
|
||||
let base_ptr = memory.as_mut_ptr();
|
||||
let mem_size = size_of_val(memory);
|
||||
if unsafe { mlock(base_ptr.cast(), mem_size) } == 0 {
|
||||
info!("Successfully locked {} bytes", mem_size);
|
||||
Ok((
|
||||
memory,
|
||||
MemLockGuard {
|
||||
base_ptr: base_ptr.cast(),
|
||||
mem_size,
|
||||
},
|
||||
))
|
||||
} else {
|
||||
Err(anyhow!(Error::last_os_error()).context("mlock failed"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn memory_resize_and_lock(
|
||||
mut memory: &mut [usize],
|
||||
) -> anyhow::Result<(&mut [usize], MemLockGuard)> {
|
||||
// Note: Resizing to system limit first is more efficient than only decrementing by page
|
||||
// size and retry locking, but this may not work as intended when running as a priviledged
|
||||
// process, since priviledged processes do not need to respect the limit.
|
||||
let max_mem_lock_usize = get_max_mem_lock()? / size_of::<usize>();
|
||||
if memory.len() > max_mem_lock_usize {
|
||||
memory = &mut memory[0..max_mem_lock_usize];
|
||||
warn!(
|
||||
"Resized memory to system limit ({} bytes)",
|
||||
size_of_val(memory)
|
||||
);
|
||||
}
|
||||
|
||||
let usize_per_page = get_page_size()? / std::mem::size_of::<usize>();
|
||||
loop {
|
||||
let base_ptr = memory.as_mut_ptr();
|
||||
let mem_size = size_of_val(memory);
|
||||
if unsafe { mlock(base_ptr.cast(), mem_size) } == 0 {
|
||||
info!("Successfully locked {} bytes", mem_size);
|
||||
return Ok((memory, MemLockGuard { base_ptr, mem_size }));
|
||||
}
|
||||
|
||||
let e = Error::last_os_error();
|
||||
ensure!(
|
||||
e.kind() == ErrorKind::OutOfMemory,
|
||||
anyhow!(e).context("mlock failed")
|
||||
);
|
||||
|
||||
// Locking with the system limit can still fail as the memory to be locked may not be
|
||||
// page aligned. In that case retry locking after decrement memory size by a page.
|
||||
let new_len = memory
|
||||
.len()
|
||||
.checked_sub(usize_per_page)
|
||||
.context("Failed to lock any memory, memory size has been decremented to 0")?;
|
||||
memory = &mut memory[0..new_len];
|
||||
warn!(
|
||||
"Decremented memory size to {} bytes, retry memory locking",
|
||||
size_of_val(memory)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MemLockGuard {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
if munlock(self.base_ptr.cast(), self.mem_size) != 0 {
|
||||
warn!("Failed to unlock memory: {}", Error::last_os_error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_max_mem_lock() -> anyhow::Result<usize> {
|
||||
unsafe {
|
||||
let mut rlim: rlimit = std::mem::zeroed();
|
||||
ensure!(
|
||||
getrlimit(RLIMIT_MEMLOCK, rlim.borrow_mut()) == 0,
|
||||
anyhow!(Error::last_os_error()).context("Failed to get RLIMIT_MEMLOCK")
|
||||
);
|
||||
Ok(rlim.rlim_cur.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_page_size() -> anyhow::Result<usize> {
|
||||
(unsafe { sysconf(_SC_PAGESIZE) })
|
||||
.try_into()
|
||||
.context("Failed to get page size")
|
||||
}
|
||||
}
|
||||
547
third_party/rust/memtest/src/memtest.rs
vendored
Normal file
547
third_party/rust/memtest/src/memtest.rs
vendored
Normal file
@@ -0,0 +1,547 @@
|
||||
use {
|
||||
crate::{prelude::*, TimeoutChecker},
|
||||
rand::random,
|
||||
serde::{Deserialize, Deserializer, Serialize, Serializer},
|
||||
std::{error::Error, fmt},
|
||||
};
|
||||
|
||||
// TODO: Intend to convert this module to a standalone `no_std` crate
|
||||
// TODO: TimeoutChecker will be a trait instead
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[must_use]
|
||||
pub enum MemtestOutcome {
|
||||
Pass,
|
||||
Fail(MemtestFailure),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum MemtestFailure {
|
||||
/// Failure due to the actual value read being different from the expected value
|
||||
UnexpectedValue {
|
||||
address: usize,
|
||||
expected: usize,
|
||||
actual: usize,
|
||||
},
|
||||
/// Failure due to the two memory locations being compared returning two different values
|
||||
/// This is used by tests where memory is split in two and values are written in pairs
|
||||
MismatchedValues {
|
||||
address1: usize,
|
||||
value1: usize,
|
||||
address2: usize,
|
||||
value2: usize,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub enum MemtestError {
|
||||
Timeout,
|
||||
#[serde(
|
||||
serialize_with = "serialize_memtest_error_other",
|
||||
deserialize_with = "deserialize_memtest_error_other"
|
||||
)]
|
||||
Other(anyhow::Error),
|
||||
}
|
||||
|
||||
macro_rules! memtest_kinds {{
|
||||
$($variant: ident),* $(,)?
|
||||
} => {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum MemtestKind {
|
||||
$($variant,)*
|
||||
}
|
||||
|
||||
impl MemtestKind {
|
||||
pub const ALL: &'static [Self] = &[
|
||||
$(Self::$variant),*
|
||||
];
|
||||
}
|
||||
|
||||
impl std::str::FromStr for MemtestKind {
|
||||
type Err = ParseMemtestKindError;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
$(
|
||||
stringify!($variant) => Ok(Self::$variant),
|
||||
)*
|
||||
_ => Err(ParseMemtestKindError),
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
memtest_kinds! {
|
||||
OwnAddressBasic,
|
||||
OwnAddressRepeat,
|
||||
RandomVal,
|
||||
Xor,
|
||||
Sub,
|
||||
Mul,
|
||||
Div,
|
||||
Or,
|
||||
And,
|
||||
SeqInc,
|
||||
SolidBits,
|
||||
Checkerboard,
|
||||
BlockSeq,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ParseMemtestKindError;
|
||||
|
||||
/// Write the address of each memory location to itself, then read back the value and check that it
|
||||
/// matches the expected address.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn test_own_address_basic(
|
||||
memory: &mut [usize],
|
||||
mut timeout_checker: TimeoutChecker,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
let expected_iter = u64::try_from(memory.len())
|
||||
.ok()
|
||||
.and_then(|count| count.checked_mul(2))
|
||||
.context("Total number of iterations overflowed")?;
|
||||
timeout_checker.init(expected_iter);
|
||||
|
||||
for mem_ref in memory.iter_mut() {
|
||||
timeout_checker.check()?;
|
||||
write_volatile_safe(mem_ref, address_from_ref(mem_ref));
|
||||
}
|
||||
|
||||
for mem_ref in memory.iter() {
|
||||
timeout_checker.check()?;
|
||||
let address = address_from_ref(mem_ref);
|
||||
let actual = read_volatile_safe(mem_ref);
|
||||
|
||||
if actual != address {
|
||||
info!("Test failed at 0x{address:x}");
|
||||
return Ok(MemtestOutcome::Fail(MemtestFailure::UnexpectedValue {
|
||||
address,
|
||||
expected: address,
|
||||
actual,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(MemtestOutcome::Pass)
|
||||
}
|
||||
|
||||
/// Write the address of each memory location (or its complement) to itself, then read back the
|
||||
/// value and check that it matches the expected address.
|
||||
/// This procedure is repeated 16 times.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn test_own_address_repeat(
|
||||
memory: &mut [usize],
|
||||
mut timeout_checker: TimeoutChecker,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
const NUM_RUNS: u64 = 16;
|
||||
let expected_iter = u64::try_from(memory.len())
|
||||
.ok()
|
||||
.and_then(|count| count.checked_mul(2 * NUM_RUNS))
|
||||
.context("Total number of iterations overflowed")?;
|
||||
timeout_checker.init(expected_iter);
|
||||
|
||||
let val_to_write = |address: usize, i, j| {
|
||||
if (i + j) % 2 == 0 {
|
||||
address
|
||||
} else {
|
||||
!(address)
|
||||
}
|
||||
};
|
||||
|
||||
for i in 0..usize::try_from(NUM_RUNS).unwrap() {
|
||||
for (j, mem_ref) in memory.iter_mut().enumerate() {
|
||||
timeout_checker.check()?;
|
||||
let val = val_to_write(address_from_ref(mem_ref), i, j);
|
||||
write_volatile_safe(mem_ref, val);
|
||||
}
|
||||
|
||||
for (j, mem_ref) in memory.iter().enumerate() {
|
||||
timeout_checker.check()?;
|
||||
let address = address_from_ref(mem_ref);
|
||||
let expected = val_to_write(address, i, j);
|
||||
let actual = read_volatile_safe(mem_ref);
|
||||
|
||||
if actual != expected {
|
||||
info!("Test failed at 0x{address:x}");
|
||||
return Ok(MemtestOutcome::Fail(MemtestFailure::UnexpectedValue {
|
||||
address,
|
||||
expected,
|
||||
actual,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(MemtestOutcome::Pass)
|
||||
}
|
||||
|
||||
/// Split given memory into two halves and iterate through memory locations in pairs. For each
|
||||
/// pair, write a random value. After all locations are written, read and compare the two halves.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn test_random_val(
|
||||
memory: &mut [usize],
|
||||
mut timeout_checker: TimeoutChecker,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
let (first_half, second_half) = split_slice_in_half(memory)?;
|
||||
let expected_iter =
|
||||
u64::try_from(first_half.len() * 2).context("Total number of iterations overflowed")?;
|
||||
timeout_checker.init(expected_iter);
|
||||
|
||||
for (first_ref, second_ref) in first_half.iter_mut().zip(second_half.iter_mut()) {
|
||||
timeout_checker.check()?;
|
||||
let val = random();
|
||||
write_volatile_safe(first_ref, val);
|
||||
write_volatile_safe(second_ref, val);
|
||||
}
|
||||
|
||||
compare_regions(first_half, second_half, &mut timeout_checker)
|
||||
}
|
||||
|
||||
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through
|
||||
/// memory locations in pairs. For each pair, write the XOR result of a random value and the value
|
||||
/// read from the location. After all locations are written, read and compare the two halves.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn test_xor(
|
||||
memory: &mut [usize],
|
||||
timeout_checker: TimeoutChecker,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
test_two_regions(memory, timeout_checker, std::ops::BitXor::bitxor)
|
||||
}
|
||||
|
||||
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through
|
||||
/// memory locations in pairs. For each pair, write the result of subtracting a random value from
|
||||
/// the value read from the location. After all locations are written, read and compare the two
|
||||
/// halves.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn test_sub(
|
||||
memory: &mut [usize],
|
||||
timeout_checker: TimeoutChecker,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
test_two_regions(memory, timeout_checker, usize::wrapping_sub)
|
||||
}
|
||||
|
||||
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through
|
||||
/// memory locations in pairs. For each pair, write the result of multiplying a random value with
|
||||
/// the value read from the location. After all locations are written, read and compare the two
|
||||
/// halves.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn test_mul(
|
||||
memory: &mut [usize],
|
||||
timeout_checker: TimeoutChecker,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
test_two_regions(memory, timeout_checker, usize::wrapping_mul)
|
||||
}
|
||||
|
||||
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through
|
||||
/// memory locations in pairs. For each pair, write the result of dividing the value read from the
|
||||
/// location with a random value. After all locations are written, read and compare the two halves.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn test_div(
|
||||
memory: &mut [usize],
|
||||
timeout_checker: TimeoutChecker,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
test_two_regions(memory, timeout_checker, |n, d| {
|
||||
n.wrapping_div(usize::max(d, 1))
|
||||
})
|
||||
}
|
||||
|
||||
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through
|
||||
/// memory locations in pairs. For each pair, write the OR result of a random value and the value
|
||||
/// read from the location. After all locations are written, read and compare the two halves.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn test_or(
|
||||
memory: &mut [usize],
|
||||
timeout_checker: TimeoutChecker,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
test_two_regions(memory, timeout_checker, std::ops::BitOr::bitor)
|
||||
}
|
||||
|
||||
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through
|
||||
/// memory locations in pairs. For each pair, write the AND result of a random value and the value
|
||||
/// read from the location. After all locations are written, read and compare the two halves.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn test_and(
|
||||
memory: &mut [usize],
|
||||
timeout_checker: TimeoutChecker,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
test_two_regions(memory, timeout_checker, std::ops::BitAnd::bitand)
|
||||
}
|
||||
|
||||
/// Base function for `test_xor`, `test_sub`, `test_mul`, `test_div`, `test_or` and `test_and`
|
||||
///
|
||||
/// Reset all bits in given memory to 1s. Split given memory into two halves and iterate through
|
||||
/// memory locations in pairs. Write to each pair using the given `write_val` function. After all
|
||||
/// locations are written, read and compare the two halves.
|
||||
fn test_two_regions(
|
||||
memory: &mut [usize],
|
||||
mut timeout_checker: TimeoutChecker,
|
||||
transform_fn: fn(usize, usize) -> usize,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
mem_reset(memory);
|
||||
let (first_half, second_half) = split_slice_in_half(memory)?;
|
||||
let expected_iter =
|
||||
u64::try_from(first_half.len() * 2).context("Total number of iterations overflowed")?;
|
||||
timeout_checker.init(expected_iter);
|
||||
|
||||
for (first_ref, second_ref) in first_half.iter_mut().zip(second_half.iter_mut()) {
|
||||
timeout_checker.check()?;
|
||||
|
||||
let mixing_val = random();
|
||||
|
||||
let val = read_volatile_safe(first_ref);
|
||||
let new_val = transform_fn(val, mixing_val);
|
||||
write_volatile_safe(first_ref, new_val);
|
||||
|
||||
let val = read_volatile_safe(second_ref);
|
||||
let new_val = transform_fn(val, mixing_val);
|
||||
write_volatile_safe(second_ref, new_val);
|
||||
}
|
||||
|
||||
compare_regions(first_half, second_half, &mut timeout_checker)
|
||||
}
|
||||
|
||||
/// Split given memory into two halves and iterate through memory locations in pairs. Generate a
|
||||
/// random value at the start. For each pair, write the result of adding the random value and the
|
||||
/// index of iteration. After all locations are written, read and compare the two halves.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn test_seq_inc(
|
||||
memory: &mut [usize],
|
||||
mut timeout_checker: TimeoutChecker,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
let (first_half, second_half) = split_slice_in_half(memory)?;
|
||||
let expected_iter =
|
||||
u64::try_from(first_half.len() * 2).context("Total number of iterations overflowed")?;
|
||||
timeout_checker.init(expected_iter);
|
||||
|
||||
let mut val: usize = random();
|
||||
for (first_ref, second_ref) in first_half.iter_mut().zip(second_half.iter_mut()) {
|
||||
timeout_checker.check()?;
|
||||
val = val.wrapping_add(1);
|
||||
write_volatile_safe(first_ref, val);
|
||||
write_volatile_safe(second_ref, val);
|
||||
}
|
||||
|
||||
compare_regions(first_half, second_half, &mut timeout_checker)
|
||||
}
|
||||
|
||||
/// Split given memory into two halves and iterate through memory locations in pairs. For each
|
||||
/// pair, write to all bits as either 1s or 0s, alternating after each memory location pair.
|
||||
/// After all locations are written, read and compare the two halves.
|
||||
/// This procedure is repeated 64 times.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn test_solid_bits(
|
||||
memory: &mut [usize],
|
||||
mut timeout_checker: TimeoutChecker,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
const NUM_RUNS: u64 = 64;
|
||||
let (first_half, second_half) = split_slice_in_half(memory)?;
|
||||
let expected_iter = u64::try_from(first_half.len() * 2)
|
||||
.ok()
|
||||
.and_then(|count| count.checked_mul(NUM_RUNS))
|
||||
.context("Total number of iterations overflowed")?;
|
||||
timeout_checker.init(expected_iter);
|
||||
|
||||
let mut solid_bits = !0;
|
||||
for _ in 0..NUM_RUNS {
|
||||
solid_bits = !solid_bits;
|
||||
let mut val = solid_bits;
|
||||
|
||||
for (first_ref, second_ref) in first_half.iter_mut().zip(second_half.iter_mut()) {
|
||||
timeout_checker.check()?;
|
||||
val = !val;
|
||||
write_volatile_safe(first_ref, val);
|
||||
write_volatile_safe(second_ref, val);
|
||||
}
|
||||
|
||||
if let MemtestOutcome::Fail(failure) =
|
||||
compare_regions(first_half, second_half, &mut timeout_checker)?
|
||||
{
|
||||
return Ok(MemtestOutcome::Fail(failure));
|
||||
}
|
||||
}
|
||||
Ok(MemtestOutcome::Pass)
|
||||
}
|
||||
|
||||
/// Split given memory into two halves and iterate through memory locations in pairs. For each pair,
|
||||
/// write to a pattern of alternating 1s and 0s (in bytes it is either 0x55 or 0xaa, and alternating
|
||||
/// after each memory location pair). After all locations are written, read and compare the two
|
||||
/// halves.
|
||||
/// This procedure is repeated 64 times.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn test_checkerboard(
|
||||
memory: &mut [usize],
|
||||
mut timeout_checker: TimeoutChecker,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
const NUM_RUNS: u64 = 64;
|
||||
let (first_half, second_half) = split_slice_in_half(memory)?;
|
||||
let expected_iter = u64::try_from(first_half.len() * 2)
|
||||
.ok()
|
||||
.and_then(|count| count.checked_mul(NUM_RUNS))
|
||||
.context("Total number of iterations overflowed")?;
|
||||
timeout_checker.init(expected_iter);
|
||||
|
||||
let mut checker_board = usize_filled_from_byte(0xaa);
|
||||
|
||||
for _ in 0..NUM_RUNS {
|
||||
checker_board = !checker_board;
|
||||
let mut val = checker_board;
|
||||
|
||||
for (first_ref, second_ref) in first_half.iter_mut().zip(second_half.iter_mut()) {
|
||||
timeout_checker.check()?;
|
||||
val = !val;
|
||||
write_volatile_safe(first_ref, val);
|
||||
write_volatile_safe(second_ref, val);
|
||||
}
|
||||
|
||||
if let MemtestOutcome::Fail(failure) =
|
||||
compare_regions(first_half, second_half, &mut timeout_checker)?
|
||||
{
|
||||
return Ok(MemtestOutcome::Fail(failure));
|
||||
}
|
||||
}
|
||||
Ok(MemtestOutcome::Pass)
|
||||
}
|
||||
|
||||
/// Split given memory into two halves and iterate through memory locations in pairs. For each pair,
|
||||
/// write to all bytes with the value i. After all locations are written, read and compare the two
|
||||
/// halves.
|
||||
/// This procedure is repeated 256 times, with i corresponding to the iteration number 0-255.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn test_block_seq(
|
||||
memory: &mut [usize],
|
||||
mut timeout_checker: TimeoutChecker,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
const NUM_RUNS: u64 = 256;
|
||||
let (first_half, second_half) = split_slice_in_half(memory)?;
|
||||
let expected_iter = u64::try_from(first_half.len() * 2)
|
||||
.ok()
|
||||
.and_then(|count| count.checked_mul(NUM_RUNS))
|
||||
.context("Total number of iterations overflowed")?;
|
||||
timeout_checker.init(expected_iter);
|
||||
|
||||
for i in 0..=(u8::try_from(NUM_RUNS - 1).unwrap()) {
|
||||
let val = usize_filled_from_byte(i);
|
||||
|
||||
for (first_ref, second_ref) in first_half.iter_mut().zip(second_half.iter_mut()) {
|
||||
timeout_checker.check()?;
|
||||
write_volatile_safe(first_ref, val);
|
||||
write_volatile_safe(second_ref, val);
|
||||
}
|
||||
|
||||
if let MemtestOutcome::Fail(failure) =
|
||||
compare_regions(first_half, second_half, &mut timeout_checker)?
|
||||
{
|
||||
return Ok(MemtestOutcome::Fail(failure));
|
||||
}
|
||||
}
|
||||
Ok(MemtestOutcome::Pass)
|
||||
}
|
||||
|
||||
fn read_volatile_safe<T: Copy>(src: &T) -> T {
|
||||
unsafe { std::ptr::read_volatile(src) }
|
||||
}
|
||||
|
||||
fn write_volatile_safe<T: Copy>(dst: &mut T, src: T) {
|
||||
unsafe { std::ptr::write_volatile(dst, src) }
|
||||
}
|
||||
|
||||
fn split_slice_in_half(slice: &mut [usize]) -> anyhow::Result<(&mut [usize], &mut [usize])> {
|
||||
let mut it = slice.chunks_exact_mut(slice.len() / 2);
|
||||
let (Some(first), Some(second)) = (it.next(), it.next()) else {
|
||||
bail!("Insufficient memory length for two-regions memtest");
|
||||
};
|
||||
Ok((first, second))
|
||||
}
|
||||
|
||||
fn mem_reset(memory: &mut [usize]) {
|
||||
for mem_ref in memory.iter_mut() {
|
||||
write_volatile_safe(mem_ref, !0);
|
||||
}
|
||||
}
|
||||
|
||||
fn address_from_ref(r: &usize) -> usize {
|
||||
std::ptr::from_ref(r) as usize
|
||||
}
|
||||
|
||||
/// Return a usize where all bytes are set to to value of `byte`
|
||||
fn usize_filled_from_byte(byte: u8) -> usize {
|
||||
let mut val = 0;
|
||||
unsafe { std::ptr::write_bytes(&mut val, byte, 1) }
|
||||
val
|
||||
}
|
||||
|
||||
fn compare_regions(
|
||||
region1: &mut [usize],
|
||||
region2: &mut [usize],
|
||||
timeout_checker: &mut TimeoutChecker,
|
||||
) -> Result<MemtestOutcome, MemtestError> {
|
||||
for (ref1, ref2) in region1.iter().zip(region2.iter()) {
|
||||
timeout_checker.check()?;
|
||||
|
||||
let address1 = address_from_ref(ref1);
|
||||
let address2 = address_from_ref(ref2);
|
||||
let val1 = read_volatile_safe(ref1);
|
||||
let val2 = read_volatile_safe(ref2);
|
||||
|
||||
if val1 != val2 {
|
||||
info!("Test failed at 0x{address1:x} compared to 0x{address2:x}");
|
||||
return Ok(MemtestOutcome::Fail(MemtestFailure::MismatchedValues {
|
||||
address1,
|
||||
value1: val1,
|
||||
address2,
|
||||
value2: val2,
|
||||
}));
|
||||
}
|
||||
}
|
||||
Ok(MemtestOutcome::Pass)
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseMemtestKindError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for ParseMemtestKindError {}
|
||||
|
||||
impl fmt::Display for MemtestOutcome {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Outcome: {:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MemtestError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Error: {:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for MemtestError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
match self {
|
||||
MemtestError::Timeout => None,
|
||||
MemtestError::Other(err) => Some(err.as_ref()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for MemtestError {
|
||||
fn from(err: anyhow::Error) -> MemtestError {
|
||||
MemtestError::Other(err)
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_memtest_error_other<S>(error: &anyhow::Error, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&format!("{:?}", error))
|
||||
}
|
||||
|
||||
fn deserialize_memtest_error_other<'de, D>(deserializer: D) -> Result<anyhow::Error, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let str = String::deserialize(deserializer)?;
|
||||
Ok(anyhow!(str))
|
||||
}
|
||||
5
third_party/rust/memtest/src/prelude.rs
vendored
Normal file
5
third_party/rust/memtest/src/prelude.rs
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
#![allow(unused)]
|
||||
pub use {
|
||||
anyhow::{anyhow, bail, ensure, Context},
|
||||
tracing::{debug, error, info, trace, warn},
|
||||
};
|
||||
@@ -637,6 +637,12 @@ Marionette:
|
||||
Set to 1 when Marionette (WebDriver Classic) is enabled.
|
||||
type: boolean
|
||||
|
||||
MemtestOutput:
|
||||
description: >
|
||||
The output of memory tests done by the crash reporter client after a crash
|
||||
in a JSON format
|
||||
type: string
|
||||
|
||||
LinuxUnderMemoryPressure:
|
||||
description: >
|
||||
Set to true if the memory pressure watcher was under memory pressure when
|
||||
|
||||
@@ -16,11 +16,13 @@ glean = { workspace = true }
|
||||
intl-memoizer = "0.5"
|
||||
libloading = "0.8"
|
||||
log = "0.4.17"
|
||||
memtest = "0.1.3"
|
||||
minidump-analyzer = { path = "../../minidump-analyzer" }
|
||||
mozbuild = "0.1"
|
||||
mozilla-central-workspace-hack = { version = "0.1", features = ["crashreporter"], optional = true }
|
||||
once_cell = "1"
|
||||
phf = "0.11"
|
||||
rand = "0.8.5"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
sha2 = "^0.10.7"
|
||||
|
||||
@@ -32,6 +32,8 @@ pub struct Config {
|
||||
pub dump_all_threads: bool,
|
||||
/// Whether to delete the dump files after submission.
|
||||
pub delete_dump: bool,
|
||||
/// Whether to run memtest while ui is shown
|
||||
pub run_memtest: bool,
|
||||
/// The data directory.
|
||||
pub data_dir: Option<PathBuf>,
|
||||
/// The events directory.
|
||||
@@ -84,6 +86,7 @@ impl Config {
|
||||
self.auto_submit = env_bool(ekey!("AUTO_SUBMIT"));
|
||||
self.dump_all_threads = env_bool(ekey!("DUMP_ALL_THREADS"));
|
||||
self.delete_dump = !env_bool(ekey!("NO_DELETE_DUMP"));
|
||||
self.run_memtest = env_bool(ekey!("RUN_MEMTEST"));
|
||||
self.data_dir = env_path(ekey!("DATA_DIRECTORY"));
|
||||
self.events_dir = env_path(ekey!("EVENTS_DIRECTORY"));
|
||||
self.ping_dir = env_path(ekey!("PING_DIRECTORY"));
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use crate::std::{
|
||||
cell::RefCell,
|
||||
path::PathBuf,
|
||||
process::Command,
|
||||
process::{Child, Command},
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering::Relaxed},
|
||||
Arc, Mutex,
|
||||
@@ -32,6 +32,15 @@ pub struct ReportCrash {
|
||||
settings_file: PathBuf,
|
||||
attempted_to_send: AtomicBool,
|
||||
ui: Option<AsyncTask<ReportCrashUIState>>,
|
||||
memtest: RefCell<Memtest>,
|
||||
}
|
||||
|
||||
/// The memtest child process
|
||||
struct Memtest {
|
||||
feature_enabled: bool,
|
||||
user_setting_enabled: bool,
|
||||
child: Option<Child>,
|
||||
extra_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl ReportCrash {
|
||||
@@ -47,6 +56,13 @@ impl ReportCrash {
|
||||
Err(_) => Default::default(),
|
||||
Ok(f) => Settings::from_reader(f)?,
|
||||
};
|
||||
let memtest = Memtest {
|
||||
feature_enabled: config.run_memtest,
|
||||
user_setting_enabled: settings.test_hardware,
|
||||
child: None,
|
||||
extra_file: config.extra_file(),
|
||||
};
|
||||
|
||||
Ok(ReportCrash {
|
||||
config,
|
||||
extra,
|
||||
@@ -54,6 +70,7 @@ impl ReportCrash {
|
||||
settings: settings.into(),
|
||||
attempted_to_send: Default::default(),
|
||||
ui: None,
|
||||
memtest: memtest.into(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -70,6 +87,9 @@ impl ReportCrash {
|
||||
}
|
||||
self.sanitize_extra();
|
||||
self.check_eol_version()?;
|
||||
|
||||
self.memtest.borrow_mut().init();
|
||||
|
||||
if !self.config.auto_submit {
|
||||
self.run_ui();
|
||||
} else {
|
||||
@@ -426,12 +446,22 @@ impl ReportCrash {
|
||||
// since the above loop will only exit when `logic_send` is dropped at the end of
|
||||
// the scope.
|
||||
self.ui = None;
|
||||
|
||||
// Save settings after UI is closed
|
||||
self.save_settings();
|
||||
});
|
||||
|
||||
barrier.wait();
|
||||
crash_ui.run()
|
||||
});
|
||||
}
|
||||
|
||||
/// Update the user setting stored in memtest according to settings
|
||||
pub fn update_memtest_setting(&self) {
|
||||
self.memtest
|
||||
.borrow_mut()
|
||||
.update_user_setting(self.settings.borrow_mut().test_hardware);
|
||||
}
|
||||
}
|
||||
|
||||
// These methods may interact with `self.ui`.
|
||||
@@ -470,7 +500,6 @@ impl ReportCrash {
|
||||
|
||||
/// Restart the application and send the crash report.
|
||||
pub fn restart(&self) {
|
||||
self.save_settings();
|
||||
// Get the program restarted before sending the report.
|
||||
self.restart_process();
|
||||
let result = self.try_send();
|
||||
@@ -479,7 +508,6 @@ impl ReportCrash {
|
||||
|
||||
/// Quit and send the crash report.
|
||||
pub fn quit(&self) {
|
||||
self.save_settings();
|
||||
let result = self.try_send();
|
||||
self.close_window(result.is_some());
|
||||
}
|
||||
@@ -523,12 +551,21 @@ impl ReportCrash {
|
||||
return None;
|
||||
};
|
||||
|
||||
// Add memtest output to extra data
|
||||
let mut extra = self.current_extra_data();
|
||||
if let Some(output) = self
|
||||
.memtest
|
||||
.borrow_mut()
|
||||
.collect_output_for_submission(&self.ui)
|
||||
{
|
||||
extra["MemtestOutput"] = output.into();
|
||||
}
|
||||
|
||||
if let Some(ui) = &self.ui {
|
||||
ui.push(|r| *r.submit_state.borrow_mut() = SubmitState::InProgress);
|
||||
}
|
||||
|
||||
// Send the report to the server.
|
||||
let extra = self.current_extra_data();
|
||||
let memory_file = self.config.memory_file();
|
||||
let report = net::report::CrashReport {
|
||||
extra: &extra,
|
||||
@@ -624,3 +661,161 @@ impl ReportCrash {
|
||||
self.ui.as_ref().expect("UI remote queue missing")
|
||||
}
|
||||
}
|
||||
|
||||
impl Memtest {
|
||||
/// Spawn the memtest process if memtest is enabled
|
||||
fn init(&mut self) {
|
||||
if self.should_run() {
|
||||
self.spawn()
|
||||
.unwrap_or_else(|e| log::warn!("failed to spawn memtest: {e:#}"));
|
||||
}
|
||||
}
|
||||
|
||||
/// Spawn the memtest process
|
||||
fn spawn(&mut self) -> anyhow::Result<()> {
|
||||
use {
|
||||
crate::std::{env, process::Stdio, time::Duration},
|
||||
memtest::MemtestRunnerArgs,
|
||||
};
|
||||
let memsize_mb = 1024;
|
||||
let memtest_runner_args = MemtestRunnerArgs {
|
||||
timeout: Duration::from_secs(3),
|
||||
mem_lock_mode: memtest::MemLockMode::Resizable,
|
||||
allow_working_set_resize: true,
|
||||
allow_multithread: true,
|
||||
allow_early_termination: true,
|
||||
};
|
||||
|
||||
let curr_exe = env::current_exe().context("failed to get current exe path")?;
|
||||
let child = Command::new(curr_exe)
|
||||
.arg("--memtest")
|
||||
.arg(memsize_mb.to_string())
|
||||
.arg(
|
||||
serde_json::to_string(&memtest_runner_args)
|
||||
.expect("memtest_runner_args conversion to json string should not fail"),
|
||||
)
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.context("failed to spawn memtest process")?;
|
||||
|
||||
self.child = Some(child);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// If memtest is allowed to run, change ui SubmitState and wait and return the memtest output,
|
||||
/// otherwise kill memtest process if it exists and return None
|
||||
fn collect_output_for_submission(
|
||||
&mut self,
|
||||
ui: &Option<AsyncTask<ReportCrashUIState>>,
|
||||
) -> Option<String> {
|
||||
if self.should_run() {
|
||||
if let Some(ui) = ui {
|
||||
ui.push(|r| *r.submit_state.borrow_mut() = SubmitState::WaitingHardwareTests);
|
||||
}
|
||||
|
||||
if self.child.is_none() {
|
||||
self.spawn()
|
||||
.unwrap_or_else(|e| log::warn!("failed to spawn memtest: {e:#}"));
|
||||
}
|
||||
|
||||
match self.wait_with_output() {
|
||||
Ok(output) => return Some(output),
|
||||
Err(e) => log::warn!("failed to wait memtest for submission {e:?}"),
|
||||
}
|
||||
} else if self.child.is_some() {
|
||||
self.kill_and_wait()
|
||||
.unwrap_or_else(|e| log::warn!("failed to kill memtest before submission {e:?}"));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Wait on memtest and return its output
|
||||
fn wait_with_output(&mut self) -> anyhow::Result<String> {
|
||||
let child = self
|
||||
.child
|
||||
.take()
|
||||
.context("attempted to wait on non-existent memtest process")?;
|
||||
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.context("failed to wait on memtest process")?;
|
||||
if output.status.success() {
|
||||
String::from_utf8(output.stdout)
|
||||
.context("failed to get valid string from memtest stdout")
|
||||
} else {
|
||||
String::from_utf8(output.stderr)
|
||||
.context("failed to get valid string from memtest stderr")
|
||||
}
|
||||
}
|
||||
|
||||
/// Kill and wait on memtest
|
||||
fn kill_and_wait(&mut self) -> anyhow::Result<()> {
|
||||
let mut child = self
|
||||
.child
|
||||
.take()
|
||||
.context("attempted to kill non-existent memtest process")?;
|
||||
|
||||
child.kill().context("failed to kill memtest process")?;
|
||||
child
|
||||
.wait()
|
||||
.context("failed to wait memtest process after kill")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_user_setting(&mut self, updated_setting: bool) {
|
||||
self.user_setting_enabled = updated_setting;
|
||||
}
|
||||
|
||||
/// Whether the memtest process is supposed to be run
|
||||
fn should_run(&self) -> bool {
|
||||
self.feature_enabled && self.user_setting_enabled
|
||||
}
|
||||
|
||||
/// Wait or kill memtest process depending on user setting
|
||||
fn shutdown(&mut self) -> anyhow::Result<()> {
|
||||
if self.child.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.user_setting_enabled {
|
||||
let memtest_output = self.wait_with_output().context("failed to wait memtest")?;
|
||||
self.add_to_extra_file("MemtestOutput", &memtest_output)
|
||||
.context("failed to add memtest output to extra file")
|
||||
} else {
|
||||
self.kill_and_wait().context("failed to kill memtest")
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a key value pair to the extra file
|
||||
fn add_to_extra_file(&self, key: &str, value: &str) -> anyhow::Result<()> {
|
||||
use crate::std::{fs, io::Read};
|
||||
|
||||
let extra_file = self
|
||||
.extra_file
|
||||
.as_ref()
|
||||
.context("extra file path does not exist")?;
|
||||
|
||||
let mut extra_file_content = String::new();
|
||||
fs::File::open(extra_file)
|
||||
.context("failed to open extra file")?
|
||||
.read_to_string(&mut extra_file_content)
|
||||
.context("failed to read extra file")?;
|
||||
|
||||
let mut extra: serde_json::Value = serde_json::from_str(&extra_file_content)
|
||||
.context("failed to parse extra file content")?;
|
||||
extra[key] = value.into();
|
||||
|
||||
let extra_file = fs::File::create(extra_file).context("failed to create new extra file")?;
|
||||
serde_json::to_writer(extra_file, &extra).context("failed to write to extra file")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Memtest {
|
||||
fn drop(&mut self) {
|
||||
self.shutdown()
|
||||
.unwrap_or_else(|e| log::warn!("failed to handle memtest before shutdown: {e:#}"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ mod glean;
|
||||
mod lang;
|
||||
mod logging;
|
||||
mod logic;
|
||||
mod memory_test;
|
||||
mod net;
|
||||
mod process;
|
||||
mod settings;
|
||||
@@ -79,14 +80,10 @@ fn main() {
|
||||
// Determine the mode in which to run. This is very simplistic, but need not be more permissive
|
||||
// nor flexible since we control how the program is invoked. We don't use the mocked version
|
||||
// because we want the actual args.
|
||||
if ::std::env::args_os()
|
||||
.nth(1)
|
||||
.map(|s| s == "--analyze")
|
||||
.unwrap_or(false)
|
||||
{
|
||||
analyze::main()
|
||||
} else {
|
||||
report_main()
|
||||
match ::std::env::args_os().nth(1) {
|
||||
Some(s) if s == "--analyze" => analyze::main(),
|
||||
Some(s) if s == "--memtest" => memory_test::main(),
|
||||
_ => report_main(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
84
toolkit/crashreporter/client/app/src/memory_test.rs
Normal file
84
toolkit/crashreporter/client/app/src/memory_test.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! A frontend for memory testing.
|
||||
|
||||
use {
|
||||
crate::std::{env, mem::size_of},
|
||||
anyhow::Context,
|
||||
memtest::{MemtestKind, MemtestRunner, MemtestRunnerArgs},
|
||||
rand::{seq::SliceRandom, thread_rng},
|
||||
serde_json,
|
||||
};
|
||||
|
||||
// runtimeobject is not automatically linked correctly
|
||||
#[cfg(windows)]
|
||||
#[link(name = "runtimeobject")]
|
||||
extern "C" {}
|
||||
|
||||
/// Usage: crashreporter --memtest <memsize_mb> <json formatted memtest_runner_args>
|
||||
/// Set the env var `MOZ_CRASHREPORTER_MEMTEST_KINDS` to specify prioritized memtest kinds
|
||||
pub fn main() {
|
||||
let (mem_usize_count, memtest_runner_args, memtest_kinds) = match parse_args() {
|
||||
Ok(parsed) => parsed,
|
||||
Err(e) => {
|
||||
eprintln!("Error: {e:?}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let mut memory = vec![0; mem_usize_count];
|
||||
|
||||
let memtest_runner_result = MemtestRunner::from_test_kinds(&memtest_runner_args, memtest_kinds)
|
||||
.run(&mut memory)
|
||||
.expect("failed to run memtest-runner");
|
||||
println!(
|
||||
"{}",
|
||||
serde_json::to_string(&memtest_runner_result)
|
||||
.expect("memtest runner results failed to serialize")
|
||||
);
|
||||
}
|
||||
|
||||
/// Parse command line arguments and environment to return the parameters for running memtest,
|
||||
/// including a usize for the requested memory vector length, MemtestRunnerArgs and a vector of
|
||||
/// MemtestKinds to run.
|
||||
fn parse_args() -> anyhow::Result<(usize, MemtestRunnerArgs, Vec<MemtestKind>)> {
|
||||
const KIB: usize = 1024;
|
||||
const MIB: usize = 1024 * KIB;
|
||||
|
||||
let mut iter = env::args_os().skip(2).map_while(|s| s.into_string().ok());
|
||||
|
||||
let memsize_mb: usize = iter
|
||||
.next()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.context("missing/invalid memsize_MB")?;
|
||||
let memtest_runner_args = iter
|
||||
.next()
|
||||
.and_then(|s| serde_json::from_str(&s).ok())
|
||||
.context("missing/invalid memtest_runner_args")?;
|
||||
|
||||
Ok((
|
||||
memsize_mb * MIB / size_of::<usize>(),
|
||||
memtest_runner_args,
|
||||
get_memtest_kinds()?,
|
||||
))
|
||||
}
|
||||
|
||||
/// Returns a vector of MemtestKind that contains all kinds, but prioritizes the given kinds.
|
||||
fn get_memtest_kinds() -> anyhow::Result<Vec<MemtestKind>> {
|
||||
let env_string = env::var(ekey!("MEMTEST_KINDS")).unwrap_or_default();
|
||||
|
||||
let specified = env_string
|
||||
.split_whitespace()
|
||||
.map(|s| s.parse().context("failed to parse memtest kinds"))
|
||||
.collect::<anyhow::Result<Vec<MemtestKind>>>()?;
|
||||
|
||||
let mut remaining: Vec<_> = MemtestKind::ALL
|
||||
.iter()
|
||||
.filter(|k| !specified.contains(k))
|
||||
.cloned()
|
||||
.collect();
|
||||
remaining.shuffle(&mut thread_rng());
|
||||
|
||||
Ok([specified, remaining].concat())
|
||||
}
|
||||
@@ -10,6 +10,10 @@ pub struct Settings {
|
||||
pub submit_report: bool,
|
||||
/// Whether the URL that was open should be included in a sent report.
|
||||
pub include_url: bool,
|
||||
/// Whether hardware tests (such as memory tests) are enabled
|
||||
// This is a new field, so might be missing in previously stored settings
|
||||
#[serde(default = "missing_test_hardware")]
|
||||
pub test_hardware: bool,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
@@ -17,6 +21,7 @@ impl Default for Settings {
|
||||
Settings {
|
||||
submit_report: true,
|
||||
include_url: true,
|
||||
test_hardware: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,3 +42,7 @@ impl Settings {
|
||||
serde_json::to_string_pretty(self).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn missing_test_hardware() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
@@ -360,15 +360,14 @@ impl File {
|
||||
MockFS.get(|files| {
|
||||
let name = path.file_name().expect("invalid path");
|
||||
files.parent_dir(path, move |d| {
|
||||
if !d.contains_key(name) {
|
||||
d.insert(
|
||||
name.to_owned(),
|
||||
MockFSItem {
|
||||
content: MockFSContent::File(Ok(Default::default())),
|
||||
modified: super::time::SystemTime::now().0,
|
||||
},
|
||||
);
|
||||
}
|
||||
d.remove(name);
|
||||
d.insert(
|
||||
name.to_owned(),
|
||||
MockFSItem {
|
||||
content: MockFSContent::File(Ok(Default::default())),
|
||||
modified: super::time::SystemTime::now().0,
|
||||
},
|
||||
);
|
||||
})
|
||||
})?;
|
||||
Self::open(path)
|
||||
|
||||
@@ -152,6 +152,17 @@ impl Child {
|
||||
self.ref_wait_with_output().unwrap()
|
||||
}
|
||||
|
||||
pub fn wait(&mut self) -> std::io::Result<ExitStatus> {
|
||||
self.ref_wait_with_output().unwrap().map(|o| o.status)
|
||||
}
|
||||
|
||||
// This function doesn't actually do anything with the mock Child.
|
||||
// It would be more accurate to affect the ExitStatus of the Child, but that requires a more
|
||||
// complete model of `MockCommand`.
|
||||
pub fn kill(&mut self) -> std::io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ref_wait_with_output(&mut self) -> Option<std::io::Result<Output>> {
|
||||
drop(self.stdin.take());
|
||||
if let Some(stdin) = self.stdin_data.take() {
|
||||
|
||||
@@ -14,7 +14,7 @@ use crate::std::{
|
||||
fs::{MockFS, MockFiles},
|
||||
io::ErrorKind,
|
||||
mock,
|
||||
process::Command,
|
||||
process::{Command, Output},
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering::Relaxed},
|
||||
Arc,
|
||||
@@ -102,6 +102,12 @@ const MOCK_MINIDUMP_EXTRA: &str = r#"{
|
||||
"SomeNestedJson": { "foo": "bar" },
|
||||
"URL": "https://url.example.com"
|
||||
}"#;
|
||||
macro_rules! MOCK_MINIDUMP_EXTRA_COMPACT {
|
||||
() => {{
|
||||
let value: serde_json::Value = serde_json::from_str(MOCK_MINIDUMP_EXTRA).unwrap();
|
||||
serde_json::to_string(&value).unwrap()
|
||||
}};
|
||||
}
|
||||
|
||||
// Actual content doesn't matter, aside from the hash that is generated.
|
||||
const MOCK_MINIDUMP_FILE: &[u8] = &[1, 2, 3, 4];
|
||||
@@ -185,7 +191,7 @@ impl GuiTest {
|
||||
)
|
||||
.add_file_result(
|
||||
"minidump.extra",
|
||||
Ok(MOCK_MINIDUMP_EXTRA.into()),
|
||||
Ok(MOCK_MINIDUMP_EXTRA_COMPACT!().into()),
|
||||
current_system_time(),
|
||||
);
|
||||
|
||||
@@ -341,11 +347,23 @@ impl AssertFiles {
|
||||
pub fn pending(&mut self) -> &mut Self {
|
||||
let dmp = self.data("pending/minidump.dmp");
|
||||
self.inner
|
||||
.check(self.data("pending/minidump.extra"), MOCK_MINIDUMP_EXTRA)
|
||||
.check(
|
||||
self.data("pending/minidump.extra"),
|
||||
MOCK_MINIDUMP_EXTRA_COMPACT!(),
|
||||
)
|
||||
.check_bytes(dmp, MOCK_MINIDUMP_FILE);
|
||||
self
|
||||
}
|
||||
|
||||
/// Assert that a crash is pending according to the filesystem, with updated files.
|
||||
pub fn pending_with_change(&mut self, new_dmp: &[u8], new_extra: &str) -> &mut Self {
|
||||
let dmp = self.data("pending/minidump.dmp");
|
||||
self.inner
|
||||
.check(self.data("pending/minidump.extra"), new_extra)
|
||||
.check_bytes(dmp, new_dmp);
|
||||
self
|
||||
}
|
||||
|
||||
/// Assert that a crash ping was sent according to the filesystem.
|
||||
pub fn ping(&mut self) -> &mut Self {
|
||||
self.inner.check(
|
||||
@@ -609,6 +627,7 @@ fn no_submit() {
|
||||
Settings {
|
||||
submit_report: true,
|
||||
include_url: false,
|
||||
test_hardware: false,
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
@@ -641,6 +660,7 @@ fn no_submit() {
|
||||
.saved_settings(Settings {
|
||||
submit_report: false,
|
||||
include_url: false,
|
||||
test_hardware: false,
|
||||
})
|
||||
.pending();
|
||||
}
|
||||
@@ -910,6 +930,7 @@ fn include_url() {
|
||||
Settings {
|
||||
submit_report: true,
|
||||
include_url: setting,
|
||||
test_hardware: false,
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
@@ -929,6 +950,193 @@ fn include_url() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persistent_settings() {
|
||||
let mut test = GuiTest::new();
|
||||
test.run(|interact| {
|
||||
interact.element("include-url", |_style, c: &model::Checkbox| {
|
||||
c.checked.set(false)
|
||||
});
|
||||
interact.element("send", |_style, c: &model::Checkbox| c.checked.set(false));
|
||||
interact.element("test-hardware", |_style, c: &model::Checkbox| {
|
||||
c.checked.set(false)
|
||||
});
|
||||
interact.element("quit", |_style, b: &model::Button| b.click.fire(&()));
|
||||
});
|
||||
|
||||
test.assert_files()
|
||||
.saved_settings(Settings {
|
||||
submit_report: false,
|
||||
include_url: false,
|
||||
test_hardware: false,
|
||||
})
|
||||
.pending();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn send_memtest_output() {
|
||||
let mut test = GuiTest::new();
|
||||
test.config.run_memtest = true;
|
||||
let invoked = Counter::new();
|
||||
let mock_invoked = invoked.clone();
|
||||
test.mock
|
||||
.set(
|
||||
// memtest is the only expected process spawned using current exe
|
||||
Command::mock("work_dir/crashreporter"),
|
||||
Box::new(|cmd| assert_mock_memtest(cmd)),
|
||||
)
|
||||
.set(
|
||||
net::report::MockReport,
|
||||
Box::new(move |report| {
|
||||
mock_invoked.inc();
|
||||
assert_eq!(
|
||||
report.extra.get("MemtestOutput").and_then(|v| v.as_str()),
|
||||
Some("memtest output")
|
||||
);
|
||||
Ok(Ok(format!("CrashID={MOCK_REMOTE_CRASH_ID}")))
|
||||
}),
|
||||
);
|
||||
test.run(|interact| {
|
||||
interact.element("quit", |_style, b: &model::Button| b.click.fire(&()));
|
||||
});
|
||||
|
||||
invoked.assert_one();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_memtest_output_to_extra() {
|
||||
let mut test = GuiTest::new();
|
||||
test.config.run_memtest = true;
|
||||
test.files.add_dir("data_dir").add_file(
|
||||
"data_dir/crashreporter_settings.json",
|
||||
Settings {
|
||||
submit_report: false,
|
||||
include_url: false,
|
||||
test_hardware: true,
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
test.mock.set(
|
||||
Command::mock("work_dir/crashreporter"),
|
||||
Box::new(|cmd| assert_mock_memtest(cmd)),
|
||||
);
|
||||
test.run(|interact| {
|
||||
interact.element("quit", |_style, b: &model::Button| b.click.fire(&()));
|
||||
});
|
||||
|
||||
let mut value: serde_json::Value = serde_json::from_str(MOCK_MINIDUMP_EXTRA).unwrap();
|
||||
value["MemtestOutput"] = "memtest output".into();
|
||||
let new_extra = serde_json::to_string(&value).unwrap();
|
||||
|
||||
test.assert_files()
|
||||
.saved_settings(Settings {
|
||||
submit_report: false,
|
||||
include_url: false,
|
||||
test_hardware: true,
|
||||
})
|
||||
.pending_with_change(MOCK_MINIDUMP_FILE, &new_extra);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toggle_memtest_spawn() {
|
||||
let mut test = GuiTest::new();
|
||||
test.config.run_memtest = true;
|
||||
test.files.add_dir("data_dir").add_file(
|
||||
"data_dir/crashreporter_settings.json",
|
||||
Settings {
|
||||
submit_report: true,
|
||||
include_url: false,
|
||||
test_hardware: false,
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
let invoked = Counter::new();
|
||||
let mock_invoked = invoked.clone();
|
||||
test.mock
|
||||
.set(
|
||||
Command::mock("work_dir/crashreporter"),
|
||||
Box::new(|cmd| assert_mock_memtest(cmd)),
|
||||
)
|
||||
.set(
|
||||
net::report::MockReport,
|
||||
Box::new(move |report| {
|
||||
mock_invoked.inc();
|
||||
assert_eq!(
|
||||
report.extra.get("MemtestOutput").and_then(|v| v.as_str()),
|
||||
Some("memtest output")
|
||||
);
|
||||
Ok(Ok(format!("CrashID={MOCK_REMOTE_CRASH_ID}")))
|
||||
}),
|
||||
);
|
||||
test.run(|interact| {
|
||||
interact.element("test-hardware", |_style, c: &model::Checkbox| {
|
||||
c.checked.set(true)
|
||||
});
|
||||
interact.element("quit", |_style, b: &model::Button| b.click.fire(&()));
|
||||
});
|
||||
|
||||
invoked.assert_one();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn toggle_memtest_kill() {
|
||||
let mut test = GuiTest::new();
|
||||
test.config.run_memtest = true;
|
||||
test.files.add_dir("data_dir").add_file(
|
||||
"data_dir/crashreporter_settings.json",
|
||||
Settings {
|
||||
submit_report: true,
|
||||
include_url: false,
|
||||
test_hardware: true,
|
||||
}
|
||||
.to_string(),
|
||||
);
|
||||
let ran_process = Counter::new();
|
||||
let mock_ran_process = ran_process.clone();
|
||||
test.mock
|
||||
.set(
|
||||
Command::mock("work_dir/crashreporter"),
|
||||
Box::new(move |cmd| {
|
||||
// To allow accurate count of ran_process, Early return when spawning
|
||||
if cmd.spawning {
|
||||
return Ok(crate::std::process::success_output());
|
||||
}
|
||||
mock_ran_process.inc();
|
||||
assert_mock_memtest(cmd)
|
||||
}),
|
||||
)
|
||||
.set(
|
||||
net::report::MockReport,
|
||||
Box::new(move |report| {
|
||||
assert!(report.extra.get("MemtestOutput").is_none());
|
||||
Ok(Ok(format!("CrashID={MOCK_REMOTE_CRASH_ID}")))
|
||||
}),
|
||||
);
|
||||
test.run(|interact| {
|
||||
interact.element("test-hardware", |_style, c: &model::Checkbox| {
|
||||
c.checked.set(false)
|
||||
});
|
||||
interact.element("quit", |_style, b: &model::Button| b.click.fire(&()));
|
||||
});
|
||||
|
||||
ran_process.assert_one();
|
||||
}
|
||||
|
||||
fn assert_mock_memtest(cmd: &Command) -> std::io::Result<Output> {
|
||||
use ::std::borrow::Borrow;
|
||||
assert_eq!(cmd.args.len(), 3);
|
||||
assert_eq!(cmd.args[0], "--memtest");
|
||||
assert!(cmd.args[1].to_string_lossy().parse::<u32>().is_ok());
|
||||
assert!(serde_json::from_str::<memtest::MemtestRunnerArgs>(
|
||||
cmd.args[2].to_string_lossy().borrow()
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
let mut output = crate::std::process::success_output();
|
||||
output.stdout = "memtest output".into();
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn comment() {
|
||||
const COMMENT: &str = "My program crashed";
|
||||
|
||||
@@ -111,6 +111,7 @@ pub fn error_dialog<M: std::fmt::Debug>(config: &Config, message: M) {
|
||||
pub enum SubmitState {
|
||||
#[default]
|
||||
Initial,
|
||||
WaitingHardwareTests,
|
||||
InProgress,
|
||||
Success,
|
||||
Failure,
|
||||
@@ -128,6 +129,7 @@ pub struct ReportCrashUI {
|
||||
pub struct ReportCrashUIState {
|
||||
pub send_report: data::Synchronized<bool>,
|
||||
pub include_address: data::Synchronized<bool>,
|
||||
pub test_hardware: data::Synchronized<bool>,
|
||||
pub show_details: data::Synchronized<bool>,
|
||||
pub details: data::Synchronized<String>,
|
||||
pub comment: data::OnDemand<String>,
|
||||
@@ -143,11 +145,13 @@ impl ReportCrashUI {
|
||||
) -> Self {
|
||||
let send_report = data::Synchronized::new(initial_settings.submit_report);
|
||||
let include_address = data::Synchronized::new(initial_settings.include_url);
|
||||
let test_hardware = data::Synchronized::new(initial_settings.test_hardware);
|
||||
|
||||
ReportCrashUI {
|
||||
state: Arc::new(ThreadBound::new(ReportCrashUIState {
|
||||
send_report,
|
||||
include_address,
|
||||
test_hardware,
|
||||
show_details: Default::default(),
|
||||
details: Default::default(),
|
||||
comment: Default::default(),
|
||||
@@ -182,6 +186,7 @@ impl ReportCrashUI {
|
||||
send_report,
|
||||
include_address,
|
||||
show_details,
|
||||
test_hardware,
|
||||
details,
|
||||
comment,
|
||||
submit_state,
|
||||
@@ -196,6 +201,11 @@ impl ReportCrashUI {
|
||||
let v = *v;
|
||||
logic.push(move |s| s.settings.borrow_mut().include_url = v);
|
||||
}});
|
||||
test_hardware.on_change(cc! { (logic) move |v| {
|
||||
let v = *v;
|
||||
logic.push(move |s| {s.settings.borrow_mut().test_hardware = v;
|
||||
s.update_memtest_setting();});
|
||||
}});
|
||||
|
||||
let input_enabled = submit_state.mapped(|s| s == &SubmitState::Initial);
|
||||
let send_report_and_input_enabled =
|
||||
@@ -204,6 +214,7 @@ impl ReportCrashUI {
|
||||
let submit_status_text = submit_state.mapped(cc! { (config) move |s| {
|
||||
config.string(match s {
|
||||
SubmitState::Initial => "crashreporter-submit-status",
|
||||
SubmitState::WaitingHardwareTests => "crashreporter-submit-waiting-hardware-tests",
|
||||
SubmitState::InProgress => "crashreporter-submit-in-progress",
|
||||
SubmitState::Success => "crashreporter-submit-success",
|
||||
SubmitState::Failure => "crashreporter-submit-failure",
|
||||
@@ -211,6 +222,7 @@ impl ReportCrashUI {
|
||||
}});
|
||||
|
||||
let progress_visible = submit_state.mapped(|s| s == &SubmitState::InProgress);
|
||||
let hardware_test_checkbox_visible = data::Synchronized::new(self.config.run_memtest);
|
||||
|
||||
let details_window = ui! {
|
||||
Window["crash-details-window"] title(config.string("crashreporter-view-report-title"))
|
||||
@@ -237,7 +249,10 @@ impl ReportCrashUI {
|
||||
Label text(config.string("crashreporter-apology")) bold(true),
|
||||
Label text(config.string("crashreporter-crashed-and-restore")),
|
||||
Label text(config.string("crashreporter-plea")),
|
||||
Checkbox["send"] checked(send_report) label(config.string("crashreporter-send-report"))
|
||||
Checkbox["test-hardware"] checked(test_hardware)
|
||||
label(config.string("crashreporter-checkbox-test-hardware"))
|
||||
enabled(&input_enabled) visible(&hardware_test_checkbox_visible),
|
||||
Checkbox["send"] checked(send_report) label(config.string("crashreporter-checkbox-send-report"))
|
||||
enabled(&input_enabled),
|
||||
VBox margin_start(20) spacing(5) halign(Alignment::Fill) valign(Alignment::Fill) {
|
||||
Button["details"] enabled(&send_report_and_input_enabled) on_click(cc! { (config, details, show_details, logic) move || {
|
||||
@@ -259,7 +274,7 @@ impl ReportCrashUI {
|
||||
halign(Alignment::Fill) valign(Alignment::Fill)
|
||||
},
|
||||
Checkbox["include-url"] checked(include_address)
|
||||
label(config.string("crashreporter-include-url")) enabled(&send_report_and_input_enabled),
|
||||
label(config.string("crashreporter-checkbox-include-url")) enabled(&send_report_and_input_enabled),
|
||||
Label text(&submit_status_text) margin_top(20),
|
||||
Progress halign(Alignment::Fill) visible(&progress_visible),
|
||||
},
|
||||
|
||||
@@ -25,8 +25,10 @@
|
||||
#include "mozilla/RuntimeExceptionModule.h"
|
||||
#include "mozilla/ScopeExit.h"
|
||||
#include "mozilla/Sprintf.h"
|
||||
#include "mozilla/StaticPrefs_browser.h"
|
||||
#include "mozilla/StaticMutex.h"
|
||||
#include "mozilla/SyncRunnable.h"
|
||||
#include "mozilla/ToString.h"
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/Unused.h"
|
||||
|
||||
@@ -1503,6 +1505,21 @@ static void WriteCrashEventFile(time_t crashTime, const char* crashTimeString,
|
||||
}
|
||||
}
|
||||
|
||||
void SetUpMemtestEnv() {
|
||||
if (StaticPrefs::browser_crashReporter_memtest()) {
|
||||
const char* env = "MOZ_CRASHREPORTER_RUN_MEMTEST=1";
|
||||
PR_SetEnv(env);
|
||||
|
||||
auto memtestKindsLock = StaticPrefs::browser_crashReporter_memtestKinds();
|
||||
if (!memtestKindsLock->IsEmpty()) {
|
||||
char* env = strdup(
|
||||
("MOZ_CRASHREPORTER_MEMTEST_KINDS=" + ToString(*memtestKindsLock))
|
||||
.c_str());
|
||||
PR_SetEnv(env);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Callback invoked from breakpad's exception handler, this writes out the
|
||||
// last annotations after a crash occurs and launches the crash reporter client.
|
||||
//
|
||||
@@ -1583,6 +1600,8 @@ bool MinidumpCallback(
|
||||
WriteAnnotationsForMainProcessCrash(apiData, addrInfo, crashTime);
|
||||
}
|
||||
|
||||
SetUpMemtestEnv();
|
||||
|
||||
if (doReport && isSafeToDump && !BackgroundTasks::IsBackgroundTaskMode()) {
|
||||
// We launch the crash reporter client/dialog only if we've been explicitly
|
||||
// asked to report crashes and if we weren't already trying to unset the
|
||||
|
||||
@@ -24,11 +24,14 @@ crashreporter-comment-prompt = Add a comment (comments are publicly visible)
|
||||
|
||||
crashreporter-report-info = This report also contains technical information about the state of the application when it crashed.
|
||||
|
||||
crashreporter-send-report = Tell { -vendor-short-name } about this crash so they can fix it.
|
||||
crashreporter-checkbox-test-hardware = Check for hardware and configuration problems on my device.
|
||||
|
||||
crashreporter-include-url = Include the address of the page I was on.
|
||||
crashreporter-checkbox-send-report = Tell { -vendor-short-name } about this crash so they can fix it.
|
||||
|
||||
crashreporter-checkbox-include-url = Include the address of the page I was on.
|
||||
|
||||
crashreporter-submit-status = Your crash report will be submitted before you quit or restart.
|
||||
crashreporter-submit-waiting-hardware-tests = Checking for hardware and configuration problems…
|
||||
crashreporter-submit-in-progress = Submitting your report…
|
||||
crashreporter-submit-success = Report submitted successfully!
|
||||
crashreporter-submit-failure = There was a problem submitting your report.
|
||||
|
||||
Reference in New Issue
Block a user