Skip to content

Instantly share code, notes, and snippets.

@book000
Last active May 26, 2024 07:47
Show Gist options
  • Save book000/775f742e497dc107e4d3c9047aae8997 to your computer and use it in GitHub Desktop.
Save book000/775f742e497dc107e4d3c9047aae8997 to your computer and use it in GitHub Desktop.
dxdiag.txtをパースするJavaScriptクラス
class DxdiagParser {
constructor(dxdiag) {
this.dxdiag = dxdiag;
this.isDebug = false;
this.parsableSections = [
"System Information",
"DxDiag Notes",
"DirectX Debug Levels",
"Display Devices",
"Sound Devices",
"Sound Capture Devices",
"Video Capture Devices",
"DirectInput Devices",
// "USB Devices",
"Gameport Devices",
// "PS/2 Devices",
"Disk & DVD/CD-ROM Drives",
// "System Devices",
// "DirectShow Filters",
// "Preferred DirectShow Filters",
// "Media Foundation File Versions",
// "Media Foundation Transforms",
// "Media Foundation Enabled Hardware Categories",
// "Media Foundation Byte Stream Handlers",
// "Media Foundation Scheme Handlers",
// "Preferred Media Foundation Transforms",
// "Disabled Media Foundation Transforms",
// "Disabled Media Sources",
// "EVR Power Information",
// "Diagnostics",
];
this.parse();
}
/**
* DxDiag テキストをパースする
*/
parse() {
const lines = this.dxdiag.split("\n");
/**
* セクション名とアイテムデータを取得する
*
* - System Information
* - Array[]
* - Time of this report: 9/1/2021, 21:00:00
*
* - Sound Devices
* - Array[]
* - Description: MAG274UPF (2- NVIDIA High Definition Audio)
* - Array[]
* - Description: NVIDIA High Definition Audio
*/
const sections = {};
let currentSectionName = null;
let nextType = null; // "SECTION_NAME" or "SECTION_SKIP_SEPARATOR" or "SECTION_BODY" or "SECTION_BODY_SUB"
let indentSize = 0;
let lastKey = null;
for (let line of lines) {
if (nextType === null && line.match(/^----/) === null) {
throw new Error("Invalid dxdiag format");
}
this.debug(nextType, line);
const nowSection = currentSectionName ? sections[currentSectionName] : [];
// "-" が 4 つ以上続いたらセクションの区切り
// その次のセクション名を取得
if (line.match(/^----/)) {
this.debug(">> This is separator");
if (nextType === "SECTION_SKIP_SEPARATOR") {
// セクションの区切り文字列をスキップ (2つ目)
this.debug(">> This is second separator, next type is SECTION_BODY");
nextType = "SECTION_BODY";
continue;
}
this.debug(">> This is first separator, next type is SECTION_NAME");
nextType = "SECTION_NAME";
continue;
}
// セクション名
if (nextType === "SECTION_NAME") {
this.debug(
">> This is section name, next type is SECTION_SKIP_SEPARATOR"
);
currentSectionName = line.trim();
sections[currentSectionName] = [{}];
nextType = "SECTION_SKIP_SEPARATOR";
continue;
}
// パース不可能なセクションの場合はスキップ
if (!this.parsableSections.includes(currentSectionName)) {
this.debug(">> This is not parsable section");
continue;
}
// セクションの本体
if (nextType === "SECTION_BODY" || nextType === "SECTION_BODY_SUB") {
// 改行のみの場合、サブセクションの可能性がある
if (line.trim() === "") {
this.debug(">> This is empty line, next type is SECTION_BODY_SUB");
nextType = "SECTION_BODY_SUB";
continue;
}
// SECTION_BODY_SUBの状態でデータが始まる場合は、セクション内に新しいサブセクションを用意する
if (nextType === "SECTION_BODY_SUB") {
// サブセクションのセクション名を取得
this.debug(">> This is sub section name, next type is SECTION_BODY");
sections[currentSectionName].push({});
nextType = "SECTION_BODY";
}
const subSection = nowSection[nowSection.length - 1];
// 前回のインデントサイズと同じであれば、前回のアイテムデータに追加
const currentIndentSize = line.match(/^\s*/)[0].length;
if (currentIndentSize === indentSize) {
this.debug(">> This is same indent size, add to last item");
subSection[lastKey] += "\n" + line.trim();
continue;
}
// インデントサイズが変わった場合は、前回と異なるアイテムデータである
// アイテムデータのキーを取得 (例: " Card name: NVIDIA GeForce RTX 4070 Ti SUPER" の "Card name")
// 最初のスペース数、キー名、バリューを取得
const match = line.match(/^(\s*)([^:]+):(.*)/);
if (!match) {
throw new Error(`Invalid line: ${line}`);
}
this.debug(">> This is new item");
const spaceIndentSize = match[1].length;
const key = match[2].trim();
const value = match[3].trim();
// インデントサイズを更新 (スペース数 + キー名の長さ + 2文字 (": "の分)
indentSize = spaceIndentSize + key.length + 2;
// アイテムデータを作成
// すでにキーがある場合はエラーとする
if (subSection[key]) {
throw new Error(`Duplicated key: ${key}`);
}
subSection[key] = value;
lastKey = key;
}
}
this.sections = sections;
}
/**
* マシン名を取得する
*
* @returns {string | null} マシン名 (取得できない場合は null)
*/
getMachineName() {
if (!this.sections["System Information"][0]["Machine name"]) {
return null;
}
return this.sections["System Information"][0]["Machine name"];
}
/**
* マシンIDを取得する
*
* @returns {string | null} マシンID (取得できない場合は null)
*/
getMachineId() {
if (!this.sections["System Information"][0]["Machine Id"]) {
return null;
}
return this.sections["System Information"][0]["Machine Id"];
}
/**
* OSを取得する
*
* @returns {string | null} OS (取得できない場合は null)
*/
getOperatingSystem() {
if (!this.sections["System Information"][0]["Operating System"]) {
return null;
}
return this.sections["System Information"][0]["Operating System"];
}
/**
* CPUを取得する
*
* @param {string} outputType 出力形式 (full: すべてを返す, brand: ブランドのみを返す, model: モデルのみを返す)
* @returns {string | null} CPU (取得できない場合は null)
*/
getCenterProcessorUnit(outputType = "full") {
if (!this.sections["System Information"][0]["Processor"]) {
return null;
}
// スペースが途中に入ることがあるので、適宜減らす
const processor = this.sections["System Information"][0]["Processor"];
const shortedProcessor = processor.replace(/ {2,}/g, " ");
if (outputType === "full") {
return shortedProcessor;
}
// Intel Core i9-9900KF @ 8x 8.3GHz
// Intel(R) Core(TM) i9-9900KF CPU @ 3.60GHz
// 13th Gen Intel(R) Core(TM) i9-13900KF (32 CPUs), ~3.0GHz
// 13th Gen Intel(R) Core(TM) i5-13400
// AMD Ryzen 9 5900X 12-Core Processor (24 CPUs), ~3.7GHz
// AMD Ryzen 7 3700X 8-Core Processor (16 CPUs), ~3.6GHz
const regexs = [
/^(?<brand>Intel) (?<model>.+) @ (?<extra>.+)$/i,
/^(?<brand>Intel)\(R\) (?<model>.+) CPU @ (?<extra>.+)$/i,
/^[0-9]+th Gen (?<brand>Intel)\(R\) (?<model>.+), (?<extra>.+)$/i,
/^[0-9]+th Gen (?<brand>Intel)\(R\) (?<model>.+)$/i,
/^(?<brand>AMD) (?<model>.+), (?<extra>.+)$/i,
];
for (const regex of regexs) {
const match = shortedProcessor.match(regex);
if (match) {
if (outputType === "brand") {
return match.groups.brand;
}
if (outputType === "model") {
return match.groups.model.replace(/\(TM\)/g, "");
}
}
}
return null;
}
/**
* メモリを取得する
*
* @returns {string | null} MB形式メモリ (取得できない場合は null)
*/
getMemory() {
if (!this.sections["System Information"][0]["Memory"]) {
return null;
}
return this.sections["System Information"][0]["Memory"];
}
/**
* メモリをバイト数で取得する
*
* @returns {number | null} メモリバイト数 (取得できない場合は null)
*/
getMemoryBytes() {
const memory = this.getMemory();
if (!memory) {
return null;
}
const match = memory.match(/(\d+)MB/);
if (!match) {
return null;
}
return parseInt(match[1]) * 1024 * 1024;
}
/**
* グラフィックカードを取得する
*
* @param {string} outputType 出力形式 (full: すべてを返す, brand: ブランドのみを返す, model: モデルのみを返す)
* @returns {string[] | null} グラフィックカード名の配列 (取得できない場合は null)
*/
getGraphicsCard(outputType = "full") {
if (!this.sections["Display Devices"]) {
return null;
}
const displayDevices = this.sections["Display Devices"];
if (displayDevices.length === 0) {
return null;
}
const cardNames = displayDevices
.map((device) => device["Card name"])
.map((cardName) => {
const match = cardName.match(/^(.+?) (.+)$/);
if (outputType === "full") {
return cardName;
}
if (outputType === "brand") {
return match[1];
}
if (outputType === "model") {
return match[2];
}
return null;
})
.filter((value) => value !== null)
.filter((value, index, self) => self.indexOf(value) === index);
return cardNames;
}
/**
* ディスクドライブのモデルを取得する
*
* @param {boolean} withDriveLetter ドライブレターを含めるかどうか (デフォルト: false)
* @returns {string[] | null} ディスクドライブのモデル名の配列 (取得できない場合は null)
*/
getDiskDrives(withDriveLetter = false) {
if (!this.sections["Disk & DVD/CD-ROM Drives"]) {
return null;
}
const diskDrives = this.sections["Disk & DVD/CD-ROM Drives"];
if (diskDrives.length === 0) {
return null;
}
const driveNames = diskDrives
// Driverがあり、かつCDROM.SYSがある場合は除外
.filter(
(drive) => !drive["Driver"] || !drive["Driver"].includes("CDROM.SYS")
)
.map(
(drive) =>
drive["Model"] + (withDriveLetter ? ` (${drive["Drive"]})` : "")
)
.filter((value, index, self) => self.indexOf(value) === index);
return driveNames;
}
/**
* デバッグメッセージを出力する
*
* @param {*} text デバッグメッセージ
*/
debug(text) {
if (!this.isDebug) {
return;
}
console.log(text);
}
}
/**
* 指定した値を持つヘッダーの列番号を取得する関数
*
* @param {string} sheet - 対象のシート
* @param {string[]} targetKeys - 検索するヘッダーの値群
* @return {number} 指定した値を持つ列の番号(見つからない場合は-1を返す)
*/
function getColumnNumbers(sheet, targetKeys) {
// シートが存在しない場合はエラーを返す
if (!sheet) {
throw new Error(`シートが見つかりませんでした`);
}
// 1行目(ヘッダー)の全ての値を取得
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
const results = {};
// 指定した値がヘッダー内のどの列にあるかを調べる
for (let index = 0; index < headers.length; index++) {
// 現在のヘッダーの値と対象値を比較、一致した場合はその列番号を返す
if (targetKeys.includes(headers[index])) {
results[headers[index]] = index + 1;
}
}
return results;
}
const fs = require("fs");
const iconv = require("iconv-lite");
function main() {
// shift_jis で読み込む
const dxdiagBuffer = fs.readFileSync("DxDiag.txt");
const dxdiag = iconv.decode(dxdiagBuffer, "shift_jis");
const parser = new DxdiagParser(dxdiag);
console.log("Machine Name:", parser.getMachineName());
console.log("Machine Id:", parser.getMachineId());
console.log("Operating System:", parser.getOperatingSystem());
console.log("CPU:", parser.getCenterProcessorUnit());
console.log("Memory:", parser.getMemory());
console.log("Memory Bytes:", parser.getMemoryBytes());
console.log("Graphics Card:", parser.getGraphicsCard());
console.log("Disk Drives:", parser.getDiskDrives());
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment