【C#入門】HDD/SSD情報を読み込む|S.M.A.R.Tを取得する方法

SMART_C#

HDD/SSDの情報はS.M.A.R.Tを取得することで読み込むことができます。

この記事を読めば、SMARTの説明とプログラミング言語C#を用いてSMART情報を取得する方法がわかります。

この記事はこんな人におすすめ
  • HDDの稼働時間や書き込み回数などの情報を読み込みたい
  • 独自ツールを作成して読み込みたい
目次

HDD情報を読み込めるS.M.A.R.Tとは?

SMARTとは「Self-Monitoring Analysis and Reporting Technology」の略でSSDやHDDの自己診断機能になります。

SMARTを使うことでハードディスクのさまざまな情報を取得することができます。

SMARTを使うメリット

ハードディスクの故障予知をする上でSMARTの情報は超重要です。

SMART情報には、「稼働時間」や「温度」、「読み書きエラー」、「シークアウトエラー」、「起動回数」などの値が含まれており、これらの情報を活用することでハードディスク利用者に故障予想を提示することができます。

つまり、利用者の貴重な情報資源を守ることができるのです。

SMARTで読み込めるパラメータ

読み込めるパラメータはこちら。(IDは16進数表示)

ハードディスクのメーカーによって読み込める項目が異なるので注意が必要です。

IDAttribute NameDescription
01Raw Read Error Rate読み取りエラー発生率
02Throughput Performanceハードディスクの全般的な処理能力
03Spin Up TimeHDDが設計上定められた回転数に達するまでにかかった平均時間
04Start/Stop Countモーターが回転/停止した回数
05Reallocated Sectors Count代替処理されたセクター数
07Seek Error Rateヘッドのシークエラー率
08Seek Time Performanceヘッドのシーク平均時間
09Power-On HoursHDDが通電されている時間の合計
0ASpin Retry Countヘッドのスピンアップ再試行回数
0BRecalibration Retries歪み補正の再試行回数
0CDevice Power Cycle Count電源ON/OFF回数
0DSoft Read Error Rateプログラムリードエラー回数
BFG-sense Error Late外的な衝撃によって引き起こされたプログラムエラーの頻度
C0Power-off Retract Count電源オフ時にヘッドを退避(アンロード)した回数
C1Load/Unload Cycle Countヘッドを復帰/退避(ロード/アンロード)した回数
C2Temperature温度
C3Hardware ECC recoveredECC(誤り訂正符号)により検知されたエラーの頻度
C4Reallocation Event Count不良セクタの代替処理した回数
C5Current Pending Sector Countバッドセクタ(利用に適さないセクタ)と見なされ、代替処理待ちとなっているセクタ数
C6Off-Line Scan Uncorrectable Sector Countオフラインスキャン時に訂正できなかったセクタ数
C7UltraDMA CRC Error Count通信エラーの回数
C8Write Error Rate (Multi Zone Error Rate)書き込みエラー発生率
C9Soft Read Error Rateヘッドの位置がトラックの中心からずれてデータを正しく読み出すことができなかったエラー(オフトラック)の数
CAData Address Mark Errorデータアドレスマーク(DMA)に関係するエラーの発生頻度
CBRun Out CancelECC(誤り訂正符号)によって検知されたエラーの数
CCSoft ECC CorrectionソフトウェアECC(誤り訂正符号)によって修正されたエラーの数
CDThermal Asperity Rateヘッドとプラッターの衝突や摩擦による熱に起因するエラーの数
CEFlying Heightヘッドの高さ。低すぎるとプラッタとぶつかりやすい。高すぎるとread/writeエラーが起こりやすい
CFSpin High Currentドライブを回転させるために使用した最大電流の値
D0Spin Buzzヘッドがドライブに衝突するのを避けるためにヘッドを垂直方向に跳ね上げた数
D1Offline Seek Performanceオフラインスキャンによって測定されたシーク性能の値
D3Vibration During Write書き込み中に発生した振動の数
D4Shock During Write書き込み中に発生した衝撃の数
DCDisk Shift外部からの衝撃などによって、固定されているプラッターが本来固定されていた位置からずれてしまった距離
DDG-Sense Error Rate外的な衝撃によって引き起こされたプログラムエラーの頻度
DELoaded Hoursデータロード作業におけるアクチュエーターの動作時間
DFLoad/Unload Retry Countヘッドがランディングゾーンに退避し、再度プラッター上に戻ろうとする動作(ロード/アンロード)の再試行回数再試行回数
E0Load Friction機械部品の摩擦によって生じたアクチュエーターの負荷
E1Load Cycle Countハードディスクの電源を完全にON/OFFした回数
E2Load-in Timeヘッドが動作可能状態にある時間の合計
E3Torque Amplification Countディスク回転時に必要な瞬間的トルク増幅回数
E4Power-Off Retract Count電源OFF等によりread/write要求が撤回され、ヘッドが緊急退避した回数
E6GMR Head Amplitude駆動時における、GMRヘッドの振動の振り幅
E7SSD Life LeftSSDのおよその寿命
E8Available Reserved Space利用可能な予備領域 (寿命)
E9Media Wearout Indicatorメディア消耗指標
EAAverage erase count AND Maximum Erase Countbyte 0-1-2;平均消去数。byte 3-4-5:最大消去数(どちらもbig endian).
EBGood Block Count AND System(Free) Block Countbyte 0-1-2:使用可能なブロックの数(big endian)。byte 3-4:システム領域ブロック数
F0Head Flying Hoursヘッドの位置調整に費やした時間
F1Total LBAs Written総書き込み量 (ホスト)
F2Total LBAs Read総読み込み量 (ホスト)
F3Total LBAs Written Expanded上位5byte:デバイスが書き込んだ総LBA数。下位7byte:属性 F1(Total LBAs Written)の値
F4Total LBAs Read Expanded上位5byte:total デバイスが読み込んだ総LBA数. 下位7byte:F2(Total LBAs Read)の値
F9NAND_Writes_1GiB総書き込み量 (NAND)
FARead Error Retry Rate読み込みエラー発生率
FBMinimum Spares Remaining利用可能な予備領域の割合
FCNewly Added Bad Flash Block工場出荷時の初期化で発見された不良ブロックの総数
FEFree Fall Protection落下検出回数

SMARTで重要なパラメータ

HDDの保守において重要なのは不良セクタがいくつあるかです。

  • Reallocated Sectors Count
  • Reallocation Event Count
  • Current Pending Sector Count

以上の3点はすべて不良セクタに関するもので、いずれか1つが「しきい値」を超えてしまうといつ故障が起きてもおかしくない状態です。

つまり、「ハードディスクを今すぐに新品交換しましょう」という合図なのです。

SMARTが読み込めるツール

SMART情報はこちらのツールから読み込むことができます。

一番有名どころは「CristalDiskInfo」ですね。ディスクの異常を検知してメールや音声で通知してくれるディスクユーティリティです。

  1. CristalDiskInfo
  2. ファイナルハードディスク診断
  3. HDD Life
  4. hdd health

C#でSMARTを取得してみた

SMART情報をXMLファイルに保存するC#コードを作成してみました。

SMARTを取得するためのサンプルコード

using System;
using System.Collections.Generic;
using System.Management;
using System.Runtime.InteropServices;

namespace GetSmart
{
    public enum SmartAttributeType : byte
    {
        ReadErrorRate = 0x01,
        ThroughputPerformance = 0x02,
        SpinUpTime = 0x03,
        StartStopCount = 0x04,
        ReallocatedSectorsCount = 0x05,
        ReadChannelMargin = 0x06,
        SeekErrorRate = 0x07,
        SeekTimePerformance = 0x08,
        PowerOnHoursPOH = 0x09,
        SpinRetryCount = 0x0A,
        CalibrationRetryCount = 0x0B,
        PowerCycleCount = 0x0C,
        SoftReadErrorRate = 0x0D,
        SATADownshiftErrorCount = 0xB7,
        EndtoEnderror = 0xB8,
        HeadStability = 0xB9,
        InducedOpVibrationDetection = 0xBA,
        ReportedUncorrectableErrors = 0xBB,
        CommandTimeout = 0xBC,
        HighFlyWrites = 0xBD,
        AirflowTemperatureWDC = 0xBE,
        TemperatureDifferencefrom100 = 0xBE,
        GSenseErrorRate = 0xBF,
        PoweroffRetractCount = 0xC0,
        LoadCycleCount = 0xC1,
        Temperature = 0xC2,
        HardwareECCRecovered = 0xC3,
        ReallocationEventCount = 0xC4,
        CurrentPendingSectorCount = 0xC5,
        UncorrectableSectorCount = 0xC6,
        UltraDMACRCErrorCount = 0xC7,
        MultiZoneErrorRate = 0xC8,
        WriteErrorRateFujitsu = 0xC8,
        OffTrackSoftReadErrorRate = 0xC9,
        DataAddressMarkerrors = 0xCA,
        RunOutCancel = 0xCB,
        SoftECCCorrection = 0xCC,
        ThermalAsperityRateTAR = 0xCD,
        FlyingHeight = 0xCE,
        SpinHighCurrent = 0xCF,
        SpinBuzz = 0xD0,
        OfflineSeekPerformance = 0xD1,
        VibrationDuringWrite = 0xD3,
        ShockDuringWrite = 0xD4,
        DiskShift = 0xDC,
        GSenseErrorRateAlt = 0xDD,
        LoadedHours = 0xDE,
        LoadUnloadRetryCount = 0xDF,
        LoadFriction = 0xE0,
        LoadUnloadCycleCount = 0xE1,
        LoadInTime = 0xE2,
        TorqueAmplificationCount = 0xE3,
        PowerOffRetractCycle = 0xE4,
        GMRHeadAmplitude = 0xE6,
        DriveTemperature = 0xE7,
        HeadFlyingHours = 0xF0,
        TransferErrorRateFujitsu = 0xF0,
        TotalLBAsWritten = 0xF1,
        TotalLBAsRead = 0xF2,
        ReadErrorRetryRate = 0xFA,
        FreeFallProtection = 0xFE,
    }

    public class SmartData
    {
        readonly Dictionary<SmartAttributeType, SmartAttribute> attributes;
        readonly ushort structureVersion;

        public SmartData(byte[] arrVendorSpecific)
        {
            attributes = new Dictionary<SmartAttributeType, SmartAttribute>();
            for (int offset = 2; offset < arrVendorSpecific.Length;)
            {
                var a = FromBytes<SmartAttribute>(arrVendorSpecific, ref offset, 12);
                // Attribute values 0x00, 0xfe, 0xff are invalid
                if (a.AttributeType != 0x00 && (byte)a.AttributeType != 0xfe && (byte)a.AttributeType != 0xff)
                {
                    attributes[a.AttributeType] = a;
                }
            }
            structureVersion = (ushort)(arrVendorSpecific[0] * 256 + arrVendorSpecific);
        }

        public ushort StructureVersion
        {
            get
            {
                return this.structureVersion;
            }
        }

        public SmartAttribute this[SmartAttributeType v]
        {
            get
            {
                return this.attributes[v];
            }
        }

        public IEnumerable<SmartAttribute> Attributes
        {
            get
            {
                return this.attributes.Values;
            }
        }

        static T FromBytes<T>(byte[] bytearray, ref int offset, int count)
        {
            IntPtr ptr = IntPtr.Zero;

            try
            {
                ptr = Marshal.AllocHGlobal(count);
                Marshal.Copy(bytearray, offset, ptr, count);
                offset += count;
                return (T)Marshal.PtrToStructure(ptr, typeof(T));
            }
            finally
            {
                if (ptr != IntPtr.Zero)
                {
                    Marshal.FreeHGlobal(ptr);
                }
            }
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct SmartAttribute
    {
        public SmartAttributeType AttributeType;
        public ushort Flags;
        public byte Value;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[] VendorData;

        public bool Advisory
        {
            get
            {
                return (Flags & 0x1) == 0x0; // Bit 0 unset?
            }
        }
        public bool FailureImminent
        {
            get
            {
                return (Flags & 0x1) == 0x1; // Bit 0 set?
            }
        }
        public bool OnlineDataCollection
        {
            get
            {
                return (Flags & 0x2) == 0x2; // Bit 0 set?
            }
        }

    }

    class Program
    {
        public static void Main()
        {
            HddModel hddModel = new HddModel();
            hddModel.Info = new List<Model>();

            try
            {
                var searcher = new ManagementObjectSearcher("root\\WMI", "SELECT * FROM MSStorageDriver_ATAPISmartData");

                foreach (ManagementObject queryObj in searcher.Get())
                {


                    Console.WriteLine("-----------------------------------");
                    Console.WriteLine("MSStorageDriver_ATAPISmartData instance");
                    Console.WriteLine("-----------------------------------");

                    var arrVendorSpecific = (byte[])queryObj.GetPropertyValue("VendorSpecific");

                    // Create SMART data from 'vendor specific' array
                    var d = new SmartData(arrVendorSpecific);
                    foreach (var b in d.Attributes)
                    {
                        Console.WriteLine("AttributeType:{0}, Value:{1}, Flag:{2}", b.AttributeType, b.Value, b.Flags);

                        b.VendorData[6] = 0x00;
                        b.VendorData[7] = 0x00;
                        foreach (byte vendorByte in b.VendorData)
                        {
                            Console.Write("{0:x} ", vendorByte);
                        }
                        Console.WriteLine($"Data:{BitConverter.ToInt32(b.VendorData)}");
                                               
                        // XMLファイルに保存するためのモデルを作成
                        Model model = new Model();

                        model.Id = (int)b.AttributeType;

                        model.Item = b.AttributeType.ToString();
                        model.CurrentValue = b.Value;
                        model.ThresholdValue = b.Flags;
                        string data = "";
                        for (int index = (b.VendorData.Length - 1); 0 <= index; index--)
                        {
                            data += string.Format("{0:X2}", b.VendorData[index]);
                        }
                        model.RowValue = data;

                        hddModel.Info.Add(model);
                    }
                }

                //出力先XMLのストリーム
                System.IO.FileStream stream = new System.IO.FileStream(@"sample.xml", System.IO.FileMode.Create);
                System.IO.StreamWriter writer = new System.IO.StreamWriter(stream, System.Text.Encoding.UTF8);

                //名前空間出力抑制
                System.Xml.Serialization.XmlSerializerNamespaces ns = new System.Xml.Serialization.XmlSerializerNamespaces();
                ns.Add(String.Empty, String.Empty);

                //シリアライズ
                System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(typeof(HddModel));
                serializer.Serialize(writer, hddModel, ns);

                writer.Flush();
                writer.Close();
            }
            catch (ManagementException e)
            {
                Console.WriteLine("An error occurred while querying for WMI data: " + e.Message);
            }
        }
    }
}
namespace GetSmart
{
    [System.Xml.Serialization.XmlRoot("hdd")]
    public class HddModel
    {
        /// <summary>
        /// 情報
        /// </summary>
        [System.Xml.Serialization.XmlElement("info")]
        public System.Collections.Generic.List<GetSmart.Model> Info { get; set; }
    }

    public class Model
    {
        /// <summary>
        /// ID
        /// </summary>
        [System.Xml.Serialization.XmlAttribute("id")]
        public int Id { get; set; }
        /// <summary>
        /// 項目
        /// </summary>
        [System.Xml.Serialization.XmlElement("item")]
        public string Item { get; set; }
        /// <summary>
        /// 現在値
        /// </summary>
        [System.Xml.Serialization.XmlElement("current")]
        public int CurrentValue { get; set; }
        /// <summary>
        /// 最悪値
        /// </summary>
        [System.Xml.Serialization.XmlElement("worst")]
        public int WorstValue { get; set; }
        /// <summary>
        /// しきい値
        /// </summary>
        [System.Xml.Serialization.XmlElement("treshold")]
        public int ThresholdValue { get; set; }
        /// <summary>
        /// 生データ
        /// </summary>
        [System.Xml.Serialization.XmlElement("row")]
        public int RowValue { get; set; }
    }
}

SMART情報を読み込んだ結果

無事にXMLファイルのタグにAttributeの値が入ってきました。

<?xml version="1.0" encoding="utf-8"?>
<hdd>
  <info id="1">
    <item>ReadErrorRate</item>
    <current>166</current>
    <worst>0</worst>
    <treshold>42496</treshold>
    <row>0000000000000000</row>
  </info>
  <info id="5">
    <item>ReallocatedSectorsCount</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000000</row>
  </info>
  <info id="9">
    <item>PowerOnHoursPOH</item>
    <current>95</current>
    <worst>0</worst>
    <treshold>24320</treshold>
    <row>0000000000001204</row>
  </info>
  <info id="12">
    <item>PowerCycleCount</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000DD4</row>
  </info>
  <info id="100">
    <item>100</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>000000000008069B</row>
  </info>
  <info id="168">
    <item>168</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000001</row>
  </info>
  <info id="169">
    <item>169</item>
    <current>99</current>
    <worst>0</worst>
    <treshold>25344</treshold>
    <row>0000000000000034</row>
  </info>
  <info id="174">
    <item>174</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>000000000000000A</row>
  </info>
  <info id="175">
    <item>175</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000000</row>
  </info>
  <info id="176">
    <item>176</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000000</row>
  </info>
  <info id="177">
    <item>177</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>000000000000000E</row>
  </info>
  <info id="178">
    <item>178</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000003</row>
  </info>
  <info id="179">
    <item>179</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000004</row>
  </info>
  <info id="180">
    <item>180</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000368</row>
  </info>
  <info id="181">
    <item>181</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000000</row>
  </info>
  <info id="182">
    <item>182</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000000</row>
  </info>
  <info id="184">
    <item>EndtoEnderror</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000004</row>
  </info>
  <info id="187">
    <item>ReportedUncorrectableErrors</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000004</row>
  </info>
  <info id="188">
    <item>CommandTimeout</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>000000000000002B</row>
  </info>
  <info id="194">
    <item>Temperature</item>
    <current>55</current>
    <worst>0</worst>
    <treshold>11264</treshold>
    <row>000000150037002C</row>
  </info>
  <info id="195">
    <item>HardwareECCRecovered</item>
    <current>45</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000000</row>
  </info>
  <info id="196">
    <item>ReallocationEventCount</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000000</row>
  </info>
  <info id="198">
    <item>UncorrectableSectorCount</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000020</row>
  </info>
  <info id="199">
    <item>UltraDMACRCErrorCount</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000000</row>
  </info>
  <info id="204">
    <item>SoftECCCorrection</item>
    <current>1</current>
    <worst>0</worst>
    <treshold>256</treshold>
    <row>000000000162FB5C</row>
  </info>
  <info id="212">
    <item>ShockDuringWrite</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>000000000000E791</row>
  </info>
  <info id="234">
    <item>234</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>00000003624CEB90</row>
  </info>
  <info id="238">
    <item>238</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>0000000000000064</row>
  </info>
  <info id="241">
    <item>TotalLBAsWritten</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>000000047EB8646E</row>
  </info>
  <info id="242">
    <item>TotalLBAsRead</item>
    <current>100</current>
    <worst>0</worst>
    <treshold>25600</treshold>
    <row>00000003AA1A81CB</row>
  </info>
</hdd>

気になる不良セクタのスコア

不良セクタの値はすべて「しきい値以下」ということが確認できました。対象はSSD(SK hynix SC401 SATA 512GB)で使ってまだ1年経っていないので納得の結果です。

なぜか、Current Pending Sector Countは取得できない結果となりました。

不良セクタしきい値現在値
Reallocated Sectors Count25600100
Reallocation Event Count25600100
Current Pending Sector Count

まとめ

HDD/SSD情報をSMARTを用いて取得するには、CristalDiskInfoなどのツールを使えば簡単に読み込めます。

ただし、取得したデータを自動的に2次利用したい、といった場合は自作ツールを作ってしまうのも一つの手です!

今回提示したサンプルコードが参考になれば幸いです。

よかったらシェアしてね!

この記事を書いた人

インドア派で体育会系のソフトウェアエンジニアです。
普段はパソコンをしながらコーヒーを飲むのが至福の時間です。
日々の生活を快適にするための情報を随時アップしていきます。

コメント

コメントする

目次
閉じる