暑くなってきたので Windows PC の Disk の温度を windows_exporter で取得して監視したい

# 暑くなってきたので Windows PC の Disk の温度を windows_exporter で取得して監視したい

## 免責・ご注意

- 当方あまり Windows に詳しくありません(他にもやりようはあるかと...)
- Administrator ユーザーを有効にし、タスクスケジューラーにて PowerShell スクリプトを Administrator ユーザーで実行しています
    - Get-StorageReliabilityCounter コマンドで必要という理解
    ```powershell
    Get-StorageReliabilityCounter : Access to a CIM resource was not available to the client.
    At line:1 char:15
    + $gsrc=($gpd | Get-StorageReliabilityCounter)
    +               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo          : PermissionDenied: (PS_StorageCmdlets:ROOT/Microsoft/..._StorageCmdlets) [Get-StorageReliabilityCounter], CimException
        + FullyQualifiedErrorId : MI RESULT 2,Get-StorageReliabilityCounter
    ````

## 作成の経緯

- 昨年の夏以降に作業PC(Windows11Pro)の C ドライブのディスクを PCIe® Gen3 x4 にリプレイス
- 春辺りまでは気にならなかったものの、昨今(2024/05)の温かさもあって CrystalDiskInfo をインストールして見てみると結構高温に...70度とか、これは対処しないとまずいなと。
- 急いでファン付きの大きめの m.2 SSD 用ヒートシンクを付けて対応するも、このあと本格的な夏を迎えるにあたってディスクの温度を計測・収集しておきたい
- そこで、もともと動かしていた Prometheus を活用すべく windows_exporter を導入してみるもディスクの温度は取れず
- ただ windows_exporter には textfile collector という機能があり prom 形式でテキストファイルに書き出せば読み込んでくれるらしい
    - https://github.com/prometheus-community/windows_exporter/blob/master/docs/collector.textfile.md
- PowerShell あまり得意じゃないけどやってみるか...

## 参考にしたサイト

- https://belial6.hateblo.jp/entry/2020/04/28/234205
- Stackoverflow Site
- MS Site
- windows_exporter Site

## 現状の制約

- ドライブレター(C とか D とか)を振れていない
    - Get-PSDrive コマンドでドライブレターを取れるのは分かったが、後述の 2 PowerShell コマンドと明確に関連付けられる値が見つからず...内蔵ディスクなら Number, DeviceId 順な気もするが...
    - ハードコードすればドライブレターを表示することも可能だろうが、今のところはデバイスのモデル名になっています

## システム

- クライアント
    - Windows 11 Pro (Lenovo ThinkCentre M80t Mini-Tower Gen 3)
        - windows_exporter-0.25.1-amd64.exe
- 監視サーバー
    - ubuntu 22.04.3 LTS
        - grafana/stable 11.0.0 amd64
        - prometheus-2.49.1.linux-amd64

## 処理概要

1. Get-PhysicalDisk でディスクの名前(FriendlyName)を取得
    - https://learn.microsoft.com/en-us/powershell/module/storage/get-physicaldisk?view=windowsserver2022-ps
1. Get-StorageReliabilityCounter でディスクの温度(Temperature)を取得
    - https://learn.microsoft.com/en-us/powershell/module/storage/get-storagereliabilitycounter?view=windowsserver2022-ps
1. デバイスIDで 1. 2. の値を JOIN
    - https://devblogs.microsoft.com/powershell/join-object/
1. prom 形式でフラットファイルに書き出す PowerShell スクリプトをタスクスケジューラーで 5 分毎に実行
    - Get-StorageReliabilityCounter の実行に少し時間を要するようなので極端に短くはせず

## まずは結果から
### PowerShellスクリプト
```powershell:disktemp.ps1
$prom_location1 = "C:\Temp\textfile_inputs\disktemp.prom"

$gpd=Get-PhysicalDisk
$gsrc=($gpd | Get-StorageReliabilityCounter)

$out_values = (Join-Object -LeftObject $gpd -RightObject $gsrc -Where {$args[0].DeviceId -eq $args[1].DeviceId} | select-object -Property FriendlyName,Temperature)

Set-Content -Path $prom_location1 -Encoding Ascii -Value ""
Add-Content -Path $prom_location1 -Encoding Ascii -Value "# HELP Disk Temperature from Get-StorageReliabilityCounter."
Add-Content -Path $prom_location1 -Encoding Ascii -Value "# TYPE win_disktemp gauge"

foreach ( $v in $out_values) {
    Add-Content -Path $prom_location1 -Encoding Ascii -Value "win_disktemp{name=""$($v.FriendlyName)""} $($v.Temperature)"
}

```
### prom ファイル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67892/a36afc5a-a63d-05bc-86ee-f08380a5a234.png)

### windows_exporter 実行時引数 
```powershell
C:\Windows\System32>C:\Prometheus\windows_exporter-0.25.1-amd64.exe --collector.textfile.directories="C:\Temp\textfile_inputs"
```

### Prometheus Query
- 温度情報が得られなかったりなどで USB 外付け Disk を除外して内蔵 SSD のみにしています
```prometheus
win_disktemp{name!="SP PD60",name!="SanDisk Extreme SSD",name!="Realtek RTL9210 NVME"}
```

### Grafana
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67892/d6e8f8ff-2acc-b5cf-5854-d738271a3c2d.png)

## 説明パート

### 1. Get-PhysicalDisk の出力結果
```powershell
PS C:\temp\textfile_inputs> Get-PhysicalDisk | select-object -Property DeviceId,FriendlyName,MediaType,HealthStatus,Size | Format-Table

DeviceId FriendlyName          MediaType HealthStatus          Size
-------- ------------          --------- ------------          ----
0        KIOXIA-EXCERIA G2 SSD SSD       Healthy      1000204886016
1        SPCC M.2 PCIe SSD     SSD       Healthy      1024209543168
```
### 2. Get-StorageReliabilityCounter の出力結果
```powershell
PS C:\temp\textfile_inputs> Get-PhysicalDisk | Get-StorageReliabilityCounter | select-object -Property DeviceId,Temperature

DeviceId Temperature
-------- -----------
0                 47
1                 41
```
:::note
DeviceId で JOIN すれば FriendlyName と Temperature を組み合わせた情報を取得できそうです
:::
### 3. 実際に JOIN してみる
```powershell
$gpd=Get-PhysicalDisk
$gsrc=($gpd | Get-StorageReliabilityCounter)

Join-Object -LeftObject $gpd -RightObject $gsrc -Where {$args[0].Number -eq $args[1].DeviceId} | select-object -Property FriendlyName,Temperature

FriendlyName          Temperature
------------          -----------
KIOXIA-EXCERIA G2 SSD          45
SPCC M.2 PCIe SSD              34
```

### 4. タスクスケジューラーに設定して単体実行し prom ファイルが書き出されていることを確認する
### 5. Prometheus にて値が取得できていることを確認する

- http://\<WindowsPC IPaddr>:9182/metrics
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/67892/15d82af7-1f3e-ac93-0057-38c15625c462.png)

### 6. Grafana でよしなにパネルを作る

## 今後の課題、拡張など

Get-PhysicalDisk と Get-StorageReliabilityCounter で取得可能なその他のメトリックの収集

- Get-PhysicalDisk で取得可能なメトリック一覧 (NULL あり)
```
ObjectId
PassThroughClass
PassThroughIds
PassThroughNamespace
PassThroughServer
UniqueId
Description
FriendlyName
HealthStatus
Manufacturer
Model
OperationalDetails
OperationalStatus
PhysicalLocation
SerialNumber
AdapterSerialNumber
AllocatedSize
BusType
CannotPoolReason
CanPool
DeviceId
EnclosureNumber
FirmwareVersion
FruId
IsIndicationEnabled
IsPartial
LogicalSectorSize
MediaType
OtherCannotPoolReasonDescription
PartNumber
PhysicalSectorSize
Size
SlotNumber
SoftwareVersion
SpindleSpeed
StoragePoolUniqueId
SupportedUsages
UniqueIdFormat
Usage
VirtualDiskFootprint
PSComputerName
ClassName
``` 
- Get-PhysicalDisk | Get-StorageReliabilityCounter で取得可能なメトリック一覧 (NULL あり)
```
ObjectId
PassThroughClass
PassThroughIds
PassThroughNamespace
PassThroughServer
UniqueId
DeviceId
FlushLatencyMax
LoadUnloadCycleCount
LoadUnloadCycleCountMax
ManufactureDate
PowerOnHours
ReadErrorsCorrected
ReadErrorsTotal
ReadErrorsUncorrected
ReadLatencyMax
StartStopCycleCount
StartStopCycleCountMax
Temperature
TemperatureMax
Wear
WriteErrorsCorrected
WriteErrorsTotal
WriteErrorsUncorrected
WriteLatencyMax
PSComputerName
```

## 変数追加
- BusType, MediaType を追加
- NVMe, USB 等でフィルター可能に
```powershell
Join-Object -LeftObject $gpd -RightObject $gsrc -Where {$args[0].DeviceId -eq $args[1].DeviceId} | select-object -Property FriendlyName,BusType,MediaType,Temperature

FriendlyName          BusType MediaType   Temperature
------------          ------- ---------   -----------
KIOXIA-EXCERIA G2 SSD NVMe    SSD                  48
SPCC M.2 PCIe SSD     NVMe    SSD                  39
Realtek RTL9210 NVME  USB     SSD                  39
KIOXIA TransMemory    USB     Unspecified           0
SP PD60               USB     SSD                   0
SanDisk Extreme SSD   USB     SSD                   0
```
```powershell:disktemp.ps1
$prom_location1 = "C:\Temp\textfile_inputs\disktemp.prom"

$gpd=Get-PhysicalDisk
$gsrc=($gpd | Get-StorageReliabilityCounter)

$out_values = (Join-Object -LeftObject $gpd -RightObject $gsrc -Where {$args[0].DeviceId -eq $args[1].DeviceId} | select-object -Property FriendlyName,BusType,MediaType,Temperature)

Set-Content -Path $prom_location1 -Encoding Ascii -Value ""
Add-Content -Path $prom_location1 -Encoding Ascii -Value "# HELP Disk Temperature from Get-StorageReliabilityCounter."
Add-Content -Path $prom_location1 -Encoding Ascii -Value "# TYPE win_disktemp gauge"

foreach ( $v in $out_values) {
    Add-Content -Path $prom_location1 -Encoding Ascii -Value "win_disktemp{name=""$($v.FriendlyName)"",bustype=""$($v.BusType)"",mediatype=""$($v.MediaType)""} $($v.Temperature)"
}
```
```disktemp.prom
# HELP Disk Temperature from Get-StorageReliabilityCounter.
# TYPE win_disktemp gauge
win_disktemp{name="KIOXIA-EXCERIA G2 SSD",bustype="NVMe",mediatype="SSD"} 50
win_disktemp{name="SPCC M.2 PCIe SSD",bustype="NVMe",mediatype="SSD"} 39
win_disktemp{name="Realtek RTL9210 NVME",bustype="USB",mediatype="SSD"} 39
win_disktemp{name="KIOXIA TransMemory",bustype="USB",mediatype="Unspecified"} 0
win_disktemp{name="SP PD60",bustype="USB",mediatype="SSD"} 0
win_disktemp{name="SanDisk Extreme SSD",bustype="USB",mediatype="SSD"} 0
```
```Prometheus:Prometheus
# HELP win_disktemp Metric read from C:\\Temp\\textfile_inputs\\disktemp.prom
# TYPE win_disktemp gauge
win_disktemp{bustype="NVMe",mediatype="SSD",name="KIOXIA-EXCERIA G2 SSD"} 50
win_disktemp{bustype="NVMe",mediatype="SSD",name="SPCC M.2 PCIe SSD"} 39
win_disktemp{bustype="USB",mediatype="SSD",name="Realtek RTL9210 NVME"} 39
win_disktemp{bustype="USB",mediatype="SSD",name="SP PD60"} 0
win_disktemp{bustype="USB",mediatype="SSD",name="SanDisk Extreme SSD"} 0
win_disktemp{bustype="USB",mediatype="Unspecified",name="KIOXIA TransMemory"} 0
```
```prometheus:Prometheus Query
win_disktemp{bustype="NVMe"}
```
## Health も追加
```powershell
$prom_location1 = "C:\Temp\textfile_inputs\disktemp.prom"
$prom_location2 = "C:\Temp\textfile_inputs\diskhealth.prom"

$gpd=Get-PhysicalDisk
$gsrc=($gpd | Get-StorageReliabilityCounter)

$out_values = (Join-Object -LeftObject $gpd -RightObject $gsrc -Where {$args[0].DeviceId -eq $args[1].DeviceId} | select-object -Property FriendlyName,BusType,MediaType,Temperature,HealthStatus)

Set-Content -Path $prom_location1 -Encoding Ascii -Value ""
Add-Content -Path $prom_location1 -Encoding Ascii -Value "# HELP Disk Temperature from Get-StorageReliabilityCounter."
Add-Content -Path $prom_location1 -Encoding Ascii -Value "# TYPE win_disktemp gauge"

foreach ( $v1 in $out_values) {
    Add-Content -Path $prom_location1 -Encoding Ascii -Value "win_disktemp{name=""$($v1.FriendlyName)"",bustype=""$($v1.BusType)"",mediatype=""$($v1.MediaType)""} $($v1.Temperature)"
}

Set-Content -Path $prom_location2 -Encoding Ascii -Value ""
Add-Content -Path $prom_location2 -Encoding Ascii -Value "# HELP Disk Health from Get-StorageReliabilityCounter."
Add-Content -Path $prom_location2 -Encoding Ascii -Value "# TYPE win_diskhealth gauge"

foreach ( $v2 in $out_values) {
	if ($v2.HealthStatus -eq "Healthy") {
		$healthvalue = 0 

		Add-Content -Path $prom_location2 -Encoding Ascii -Value 	"win_diskhealth{name=""$($v2.FriendlyName)"",bustype=""$($v2.BusType)"",mediatype=""$($v2.MediaType)"",diskhealth=""$($v2.HealthStatus)""} $healthvalue"
	}
	else {
		$healthvalue = 1
	}
}
```