diff --git a/GPUTemperatureMonitoring.sln b/GPUTemperatureMonitoring.sln new file mode 100644 index 0000000..3a19118 --- /dev/null +++ b/GPUTemperatureMonitoring.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33530.505 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GPUTemperatureMonitoring", "GPUTemperatureMonitoring\GPUTemperatureMonitoring.csproj", "{E1D50143-93C5-4585-8516-A3C2B465A444}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E1D50143-93C5-4585-8516-A3C2B465A444}.Debug|x64.ActiveCfg = Debug|x64 + {E1D50143-93C5-4585-8516-A3C2B465A444}.Debug|x64.Build.0 = Debug|x64 + {E1D50143-93C5-4585-8516-A3C2B465A444}.Release|x64.ActiveCfg = Release|x64 + {E1D50143-93C5-4585-8516-A3C2B465A444}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3B80A9E7-0F7F-4B03-9113-38A67896EEBB} + EndGlobalSection +EndGlobal diff --git a/GPUTemperatureMonitoring/App.config b/GPUTemperatureMonitoring/App.config new file mode 100644 index 0000000..1afacce --- /dev/null +++ b/GPUTemperatureMonitoring/App.config @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GPUTemperatureMonitoring/GPUTemperatureMonitoring.csproj b/GPUTemperatureMonitoring/GPUTemperatureMonitoring.csproj new file mode 100644 index 0000000..4e9eca6 --- /dev/null +++ b/GPUTemperatureMonitoring/GPUTemperatureMonitoring.csproj @@ -0,0 +1,124 @@ + + + + + Debug + AnyCPU + {E1D50143-93C5-4585-8516-A3C2B465A444} + WinExe + GPUTemperatureMonitoring + GPUTemperatureMonitoring + v4.7.2 + 512 + true + true + + + + + app.manifest + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + 7.3 + prompt + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + 7.3 + prompt + true + + + Icon.ico + + + false + + + false + + + LocalIntranet + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + PreserveNewest + + + + + 0.9.2 + + + 7.1.3 + + + 7.0.2 + + + + + False + Microsoft .NET Framework 4.7.2 %28x86 および x64%29 + true + + + False + .NET Framework 3.5 SP1 + false + + + + \ No newline at end of file diff --git a/GPUTemperatureMonitoring/Icon.ico b/GPUTemperatureMonitoring/Icon.ico new file mode 100644 index 0000000..5d06b9f Binary files /dev/null and b/GPUTemperatureMonitoring/Icon.ico differ diff --git a/GPUTemperatureMonitoring/Program.cs b/GPUTemperatureMonitoring/Program.cs new file mode 100644 index 0000000..57c15f1 --- /dev/null +++ b/GPUTemperatureMonitoring/Program.cs @@ -0,0 +1,317 @@ +using LibreHardwareMonitor.Hardware; +using LibreHardwareMonitor.Hardware.Cpu; +using Microsoft.Toolkit.Uwp.Notifications; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using System.Timers; +using System.Web; +using System.Windows.Forms; +using static System.Windows.Forms.VisualStyles.VisualStyleElement; + + +namespace GPUTemperatureMonitoring +{ + public class MonitoringSetting + { + public string SensorPath { get; set; } = string.Empty; + public string LINENotifyToken { get; set; } = string.Empty; + public int MonitoringIntervalMS { get; set; } = 0; + public int TemperatureThreshold { get; set; } = 0; + public int FailedNotifyIntervalS { get; set; } = 0; + + public MonitoringSetting(string SensorPath, string LINENotifyToken, int MonitoringIntervalMS, int TemperatureThreshold, int FailedNotifyIntervalS) + { + this.SensorPath = SensorPath; + this.LINENotifyToken = LINENotifyToken; + this.MonitoringIntervalMS = MonitoringIntervalMS; + this.TemperatureThreshold = TemperatureThreshold; + this.FailedNotifyIntervalS = FailedNotifyIntervalS; + } + } + + internal class TemperatureSensor : IDisposable + { + class UpdateVisitor : IVisitor + { + public void VisitComputer(IComputer computer) + { + computer.Traverse(this); + } + public void VisitHardware(IHardware hardware) + { + hardware.Update(); + foreach (IHardware subHardware in hardware.SubHardware) subHardware.Accept(this); + } + public void VisitSensor(ISensor sensor) { } + public void VisitParameter(IParameter parameter) { } + } + + private Computer mComputer; + private string mSensorPath; + + public TemperatureSensor(string sensorPath) + { + this.mSensorPath = sensorPath; + + mComputer = new Computer + { + IsGpuEnabled = true, + }; + mComputer.Open(); + mComputer.Accept(new UpdateVisitor()); + } + + public void Dispose() + { + mComputer.Close(); + } + + public float GetTemperature() + { + float temperature = -1.0f; + foreach (IHardware hardware in mComputer.Hardware) + { + hardware.Update(); + foreach (ISensor sensor in hardware.Sensors) + { + //Debug.Print("\tSensor: {0}", sensor.Identifier.ToString()); + + if (sensor.Identifier.ToString() == mSensorPath) + { + temperature = sensor.Value.GetValueOrDefault(); + goto LOOPEND; + } + } + } + LOOPEND: + return temperature; + } + }; + + internal static class Program + { + private static MonitoringSetting mSetting; + private static TemperatureSensor mSensor; + private static System.Timers.Timer mTimer; + private static NotifyIcon mIcon; + + /// + /// アプリケーションのメイン エントリ ポイントです。 + /// + [STAThread] + static void Main() + { + mSetting = ReadSetting(); + + using (mSensor = new TemperatureSensor(mSetting.SensorPath)) + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + CreateNotifyIcon(); + SetTimer(); + + OnInit(); + + Application.Run(); + + mIcon.Dispose(); + mTimer.Dispose(); + } + } + + static void OnInit() + { + new ToastContentBuilder() + .AddText("GPU温度監視開始", AdaptiveTextStyle.Default) + .Show(); + } + + static MonitoringSetting ReadSetting() + { + MonitoringSetting ret; + + string settingPath = Path.Combine(GetExeDir(), "Setting.json"); + using (FileStream fileStream = File.OpenRead(settingPath)) + { + using (StreamReader reader = new StreamReader(fileStream, System.Text.Encoding.UTF8)) + { + ret = JsonSerializer.Deserialize(reader.ReadToEnd()); + } + } + + return ret; + } + + static string GetExeDir() + { + return Path.GetDirectoryName(Application.ExecutablePath); + } + + private static void LINENotify(string message) + { + string LINE_url = "https://notify-api.line.me/api/notify"; + Encoding enc = Encoding.UTF8; + string payload = "message=" + HttpUtility.UrlEncode(message, enc); + + using (WebClient client = new WebClient()) + { + client.Encoding = enc; + client.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); + client.Headers.Add("Authorization", "Bearer " + mSetting.LINENotifyToken); + client.UploadString(LINE_url, payload); + } + } + + private static void SetTimer() + { + mTimer = new System.Timers.Timer(mSetting.MonitoringIntervalMS); + mTimer.Elapsed += OnTimedEvent; + mTimer.AutoReset = true; + mTimer.Enabled = true; + } + + private static void CreateNotifyIcon() + { + // 常駐アプリ(タスクトレイのアイコン)を作成 + mIcon = new NotifyIcon(); + mIcon.Icon = new Icon("Icon.ico"); + mIcon.ContextMenuStrip = ContextMenu(); + mIcon.Text = "GPU温度監視"; + mIcon.Visible = true; + } + + private static ContextMenuStrip ContextMenu() + { + // アイコンを右クリックしたときのメニューを返却 + var menu = new ContextMenuStrip(); + menu.Items.Add("終了", null, (s, e) => + { + Application.Exit(); + }); + return menu; + } + + private static void OnTimedEvent(Object source, ElapsedEventArgs e) + { + var temperature = mSensor.GetTemperature(); + Debug.Print("{0}", temperature); + mIcon.Text = string.Format("GPU温度監視: {0}℃", temperature); + ChangeState(temperature); + } + + static bool mIsBeforeOverThreshold = false; + static TimeSpan mBeforeNotifyOverThresholdTime; + static object mLockObj = new object(); + + static void ChangeState(float temperature) + { + lock (mLockObj) + { + bool isOverThreshold = temperature > mSetting.TemperatureThreshold; + try + { + if (isOverThreshold) + { + if (!mIsBeforeOverThreshold) // 前回OKだったけど今回オーバーした + { + mBeforeNotifyOverThresholdTime = DateTime.Now.TimeOfDay; + OnStartOverThreshold(temperature); + } + else // 引き続きオーバーした + { + var diff = DateTime.Now.TimeOfDay - mBeforeNotifyOverThresholdTime; + if (diff.TotalSeconds >= mSetting.FailedNotifyIntervalS) + { + mBeforeNotifyOverThresholdTime = DateTime.Now.TimeOfDay; + OnContinueOverThreshold(temperature); + } + } + } + else + { + if (mIsBeforeOverThreshold) // 前回オーバーしたしたけど今回OKだった + { + OnFixedOverThreshold(temperature); + } + } + } + finally + { + mIsBeforeOverThreshold = isOverThreshold; + } + } + } + + static void OnStartOverThreshold(float temperature) + { + mIcon.Icon = SystemIcons.Warning; + + new ToastContentBuilder() + .AddText("GPU温度警告!", AdaptiveTextStyle.Caption) + .AddText(string.Format("GPU温度が閾値を超えています!")) + .AddText(string.Format("{0}℃", temperature)) + .Show(); + + string lineMessage = string.Format("警告\nGPU温度が閾値を超えています!\n{0}℃", temperature); + if (!EventLog.SourceExists("GPUTemperatureMonitoring")) + { + EventLog.CreateEventSource("GPUTemperatureMonitoring", "Application"); + } + EventLog.WriteEntry("GPUTemperatureMonitoring", lineMessage, + System.Diagnostics.EventLogEntryType.Warning, 0, 0); + + LINENotify(lineMessage); + } + + static void OnContinueOverThreshold(float temperature) + { + mIcon.Icon = SystemIcons.Warning; + + new ToastContentBuilder() + .AddText("GPU温度警告!", AdaptiveTextStyle.Caption) + .AddText(string.Format("GPU温度が閾値を超えています!")) + .AddText(string.Format("{0}℃", temperature)) + .Show(); + + string lineMessage = string.Format("警告\nGPU温度が閾値を超えています!\n{0}℃", temperature); + if (!EventLog.SourceExists("GPUTemperatureMonitoring")) + { + EventLog.CreateEventSource("GPUTemperatureMonitoring", "Application"); + } + EventLog.WriteEntry("GPUTemperatureMonitoring", lineMessage, + System.Diagnostics.EventLogEntryType.Warning, 0, 1); + + LINENotify(lineMessage); + } + + static void OnFixedOverThreshold(float temperature) + { + mIcon.Icon = new Icon("Icon.ico"); + + new ToastContentBuilder() + .AddText("GPU温度正常化", AdaptiveTextStyle.Default) + .AddText(string.Format("GPU温度が閾値以下に下がりました。")) + .AddText(string.Format("{0}℃", temperature)) + .Show(); + + string lineMessage = string.Format("GPU温度正常化\nGPU温度が閾値以下に下がりました。\n{0}℃", temperature); + if (!EventLog.SourceExists("GPUTemperatureMonitoring")) + { + EventLog.CreateEventSource("GPUTemperatureMonitoring", "Application"); + } + EventLog.WriteEntry("GPUTemperatureMonitoring", lineMessage, + System.Diagnostics.EventLogEntryType.Information, 1, 0); + + LINENotify(lineMessage); + } + } +} diff --git a/GPUTemperatureMonitoring/Properties/AssemblyInfo.cs b/GPUTemperatureMonitoring/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a8d0be0 --- /dev/null +++ b/GPUTemperatureMonitoring/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// アセンブリに関する一般的な情報は、次の方法で制御されます +// 制御されます。アセンブリに関連付けられている情報を変更するには、 +// これらの属性値を変更します。 +[assembly: AssemblyTitle("GPUTemperatureMonitoring")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("GPUTemperatureMonitoring")] +[assembly: AssemblyCopyright("Copyright © 2023")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// ComVisible を false に設定すると、このアセンブリ内の型は COM コンポーネントから +// 参照できなくなります。COM からこのアセンブリ内の型にアクセスする必要がある場合は、 +// その型の ComVisible 属性を true に設定してください。 +[assembly: ComVisible(false)] + +// このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります +[assembly: Guid("e1d50143-93c5-4585-8516-a3c2b465a444")] + +// アセンブリのバージョン情報は、以下の 4 つの値で構成されています: +// +// メジャー バージョン +// マイナー バージョン +// ビルド番号 +// リビジョン +// +// すべての値を指定するか、次を使用してビルド番号とリビジョン番号を既定に設定できます +// 既定値にすることができます: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/GPUTemperatureMonitoring/Properties/Resources.Designer.cs b/GPUTemperatureMonitoring/Properties/Resources.Designer.cs new file mode 100644 index 0000000..c69d1e5 --- /dev/null +++ b/GPUTemperatureMonitoring/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// このコードはツールによって生成されました。 +// ランタイム バージョン:4.0.30319.42000 +// +// このファイルへの変更は、以下の状況下で不正な動作の原因になったり、 +// コードが再生成されるときに損失したりします +// +//------------------------------------------------------------------------------ + +namespace GPUTemperatureMonitoring.Properties +{ + + + /// + /// ローカライズされた文字列などを検索するための、厳密に型指定されたリソース クラスです。 + /// + // このクラスは StronglyTypedResourceBuilder クラスが ResGen + // または Visual Studio のようなツールを使用して自動生成されました。 + // メンバーを追加または削除するには、.ResX ファイルを編集して、/str オプションと共に + // ResGen を実行し直すか、または VS プロジェクトをリビルドします。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// このクラスで使用されるキャッシュされた ResourceManager インスタンスを返します。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("GPUTemperatureMonitoring.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// すべてについて、現在のスレッドの CurrentUICulture プロパティをオーバーライドします + /// 現在のスレッドの CurrentUICulture プロパティをオーバーライドします。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/GPUTemperatureMonitoring/Properties/Resources.resx b/GPUTemperatureMonitoring/Properties/Resources.resx new file mode 100644 index 0000000..ffecec8 --- /dev/null +++ b/GPUTemperatureMonitoring/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/GPUTemperatureMonitoring/Properties/Settings.Designer.cs b/GPUTemperatureMonitoring/Properties/Settings.Designer.cs new file mode 100644 index 0000000..fb33394 --- /dev/null +++ b/GPUTemperatureMonitoring/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace GPUTemperatureMonitoring.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/GPUTemperatureMonitoring/Properties/Settings.settings b/GPUTemperatureMonitoring/Properties/Settings.settings new file mode 100644 index 0000000..abf36c5 --- /dev/null +++ b/GPUTemperatureMonitoring/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/GPUTemperatureMonitoring/app.manifest b/GPUTemperatureMonitoring/app.manifest new file mode 100644 index 0000000..f2c47a2 --- /dev/null +++ b/GPUTemperatureMonitoring/app.manifest @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Setting.json b/Setting.json new file mode 100644 index 0000000..dec11de --- /dev/null +++ b/Setting.json @@ -0,0 +1,7 @@ +{ + "SensorPath": "/gpu-nvidia/0/temperature/0", + "LINENotifyToken": "", + "MonitoringIntervalMS": 5000, + "TemperatureThreshold": 70, + "FailedNotifyIntervalS": 300 +}