본문 바로가기
My Projects

저전력 서버 구축 Windows 10 전원 옵션 자동 변경 프로그램

by BestUgi 2020. 12. 8.

powersave.zip
0.00MB

이전에 자작 NAS’와 관련된 포스팅에서 시스템의 CPU 사용량에 따라 전원 옵션을 자동으로 변경해 주는 프로그램에 대해서 언급한 적이 있었다. 이번 포스팅에서는 해당 프로그램에 대해서 간략하게 공유하고자 글을 쓰게 되었다.

 

우선 Windows 10의 경우 윈도우 키를 누르고 전원 관리 옵션 선택키워드를 입력하면 아래와 같은 전원 옵션을 확인할 수 있다.

 

 

해당 화면에서 현재 내가 선택할 수 있는 전원 관리 옵션을 확인할 수 있는데 필자의 제어판 화면에서는 기본으로 제공되는 균형 조정절전옵션이 출력이 되는 것을 확인할 수 있다. 관리 옵션은 사용자가 원할 경우 추가로 생성하거나 특정 관리 옵션을 입맛에 맞게 변경할 수 있을 것이다.

 

참고로 고성능옵션을 생성하는 방법은 다음과 같다.

‘전원 관리 옵션 선택’ 화면에서 ‘전원 관리 옵션 만들기’ 선택 > ‘고성능’ 선택, 전원 관리 옵션 이름 지정 후 ‘다음’ > 디스플레이 끄기/절전모드 설정 수정 > 만들기

 

본 포스팅에서는 전원 관리 옵션이 여러 개일 경우, 그리고 CPU 사용량에 기반하여 관리 옵션을 자동으로 변경해주는 프로그램을 제작해보고자 한다. 이 프로그램을 제작한 이유는 자작 NAS 포스팅에서도 언급하였지만, 24시간 동작하는 NAS 서버에서 저전력균형 조정’/’고성능옵션에 따라 CPU가 사용하는 전력이 다르다고 판단되었고, 1W라도 소비 전력을 줄여 보고자 제작하게 되었다. , 고성능이 필요한 상황에서는 균형 조정혹은 고성능옵션으로 동작하고 일반적인 IDLE 상황에서는 저전력옵션으로 자동으로 변경해주는 것이다.

 

CLI(Command Line Interface)를 사용한 전력 옵션 변경

우선 프로그램을 제작하기에 앞서, GUI가 아닌 CLI로 전원 옵션을 변경하는 방법을 알아야 할 것이다. CLI로 전원 옵션을 변경할 수 있다면 프로그램에서도 손쉽게 해당 CLI를 호출하여 전원 옵션을 변경할 수 있기 때문이다.

 

Window의 경우 cmd/powershell에서 powercfg 명령어를 사용하여 전원 옵션을 변경하는 것이 가능하다. PowerShell을 기동하고 아래의 명령어를 입력해 보도록 하자.

 

  >  Powercfg /list

  

 

제어판의 전원 관리 옵션 선택화면에서 볼 수 있었던 전원 옵션 목록을 모두 볼 수 있다. 옵션 목록에서 별표(*)는 현재 선택된 옵션이다. 내가 원하는 전원 옵션으로 변경하고자 할 경우에는 전원 옵션 목록에 출력된 GUID(위의 그림에서 균형 조정의 GUID‘381b4222-f694-41f0-9685-ff5bb260df2e’)를 복사하여 아래의 명령어를 입력해 보도록 하자.

 

  > powercfg /setactive 381b4222-f694-41f0-9685-ff5bb260df2e

 

 

위의 그림과 같이 전원 옵션이 균형 조절로 변경된 것을 확인할 수 있다.

 

CPU 모니터링 및 전원 옵션 자동 변경 프로그램(C#)

 

프로그램은 주기적으로 시스템 CPU의 사용량을 검사하고 LIST에 해당 사용량을 저장한다. 그리고, 사용량의 평균을 계산하여 지정된 상향 임계치(60%) 이상일 경우 지정된 전원 옵션(고성능 혹은 균형 조정)으로 변경하고 평균이 하향 임계치(10%) 이하일 경우 지정된 전원 옵션(저전력)으로 변경하는 프로그램이다.

 

아래에 소스코드를 추가하였으니 확인해 보길 바란다.

 

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;

namespace PowerSaver
{
    public partial class MainForm : Form
    {
        private const int POLLING_DELAY_NORM = 3000; // 3 SECONDS
        private const int POLLING_DELAY_IDLE = 6000; // 6 SECONDS
        private const int POLLING_DELAY_BUSY = 1000; // 1 SECOND

        private const String STR_MODE_IDLE = "Idle (<10%)";
        private const String STR_MODE_NORM = "Normal (<=60%)";
        private const String STR_MODE_BUSY = "Busy (>60%)";

        private const float CPU_THRESHOLD_INTO_IDLE = 5.0f; // If CPU usage < 3, IDLE MODE
        private const float CPU_THRESHOLD_INTO_BUSY = 60.0f; // If CPU usage > 60, BUSY MODE

        private float acc_cpu = 0;
        private float current_cpu = 0;

        private int current_mode = -1;
        private int current_polling = POLLING_DELAY_BUSY;

        private const int USAGE_MAX_COUNT = 12;
        private int uidx = 0;
        private float[] usages = new float[USAGE_MAX_COUNT];

        private PerformanceCounter cpu = new PerformanceCounter("Processor", "% Processor Time", "_Total");
        private PerformanceCounter ram = new PerformanceCounter("Memory", "Available MBytes");
        string process_name = Process.GetCurrentProcess().ProcessName;
        private PerformanceCounter prcess_cpu = new PerformanceCounter("Process", "% Processor Time", Process.GetCurrentProcess().ProcessName);
        private bool loop_state = true;

        public MainForm()
        {
            InitializeComponent();

            for (int i = 0; i < USAGE_MAX_COUNT; i++)
            {
                usages[i] = 0.0f;
            }
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            current_mode = POLLING_DELAY_IDLE;
            changeMode("a1841308-3541-4fab-bc81-f71556f20b4a");

            Thread my_thread = new Thread(check_system);
            my_thread.Start();
        }

        private void changeMode(String uuid)
        {
            Process.Start("powercfg", "-SETACTIVE " + uuid);

            txt_last.Text = "powercfg -SETACTIVE " + uuid;
        }

        private void checkUsage(float usage)
        {
            if (usage < CPU_THRESHOLD_INTO_IDLE)
            {
                current_polling = POLLING_DELAY_IDLE;
            }
            else if (usage <= CPU_THRESHOLD_INTO_BUSY)
            {
                current_polling = POLLING_DELAY_NORM;
            }
            else
            {
                if (current_mode == POLLING_DELAY_BUSY)
                {
                    current_polling = POLLING_DELAY_NORM;
                } 
                else
                {
                    current_polling = POLLING_DELAY_BUSY;
                }
            }

            this.lbl_delay.Text = current_polling.ToString() + " ms";

            this.acc_cpu -= usages[uidx];
            this.acc_cpu += usage;

            usages[uidx++] = usage;
            if (uidx >= USAGE_MAX_COUNT)
            {
                uidx = 0;
            }

            float avg_cpu = acc_cpu / USAGE_MAX_COUNT;
            this.lbl_avg.Text = avg_cpu.ToString();

            if (avg_cpu < CPU_THRESHOLD_INTO_IDLE)
            {
                this.lbl_mode.Text = STR_MODE_IDLE;

                if (current_mode != POLLING_DELAY_IDLE)
                {
                    current_mode = POLLING_DELAY_IDLE;
                    changeMode("a1841308-3541-4fab-bc81-f71556f20b4a");
                }
            } else if (avg_cpu > CPU_THRESHOLD_INTO_BUSY) {
                this.lbl_mode.Text = STR_MODE_BUSY;

                if (current_mode != POLLING_DELAY_BUSY)
                {
                    current_mode = POLLING_DELAY_BUSY;
                    changeMode("381b4222-f694-41f0-9685-ff5bb260df2e");
                }
            } else {
                this.lbl_mode.Text = STR_MODE_NORM;
            }
        }



        private void check_system()
        {
            Action updateAction = new Action(() =>
            {
                checkUsage(this.current_cpu);
                this.lbl_cpu.Text = this.current_cpu.ToString() + " %";
                this.lbl_ram.Text = ram.NextValue().ToString() + " MB";
                this.lbl_cpu2.Text = process_name + " : " + prcess_cpu.NextValue().ToString() + " %";
            });

            do
            {
                this.current_cpu = cpu.NextValue();

                if (this.InvokeRequired)
                {
                    this.lbl_cpu.BeginInvoke(updateAction);
                }
                else
                {
                    checkUsage(this.current_cpu);
                    this.lbl_cpu.Text = cpu.NextValue().ToString() + " %";
                    this.lbl_ram.Text = ram.NextValue().ToString() + " MB";
                    this.lbl_cpu2.Text = process_name + " : " + prcess_cpu.NextValue().ToString() + " %";
                }

                Thread.Sleep(this.current_polling);
            } while (loop_state);
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            loop_state = false;  // for worker thread exit....
        }
    }
}

 

프로젝트의 소스 코드와 연관된 파일을 압축하여 첨부하니 참고하시기 바란다.

댓글