2013年1月28日 星期一

Linux Control Group 介紹

Control Group 簡稱 cgroup,是 linux kernel 裡的一個功能, 用來限制、記錄、隔離資源使用。
最早於 2006 年由 google 工程師開發,後來合併到 kernel 2.6.24 中。

cgroup 的特點有以下幾種:

1. 限制資源的使用:執行某個應用程式,卻佔掉大部份的系統資源,導致別的程式根本無法執行,或在多核心的機器上,讓某應用程式只能使用某些核心,保留一些給其他程式用,或限制某程式最多使用 2G 的記憶體。

2. 使用資源的優先權:有沒有辦法調整讓 A 程式比 B 程式多用一些 cpu 呢?

3. 記錄 cpu 使用量以收取費用:對某些公司來說,cpu 的使用是要收費的,但要如何算出使用者到底使用了多少 cpu 呢? 早期這類的工作是很繁雜的,一般是透過 psacct 來收取整個系統上所有指令使用的 cpu 時間,再寫一些 scripts 來分析出各個使用者的 cpu 使用量,產生報表,現在透過 cgroup 可以很容易的抓出資料。

4. 隔離不同群組的 process:對於不同群組的 process 做到隔離,可用於 lxc (linux container)。

5. freezing groufreezing groups or checkpointing and restarting: 假設一個程式執行要 8 小時 cpu 時間才能完成,但跑到 7.3 小時卻因為某狀況導致失敗,等故障排除後,要再重頭開始跑 8 小時,這樣很沒效率。若在中間有做 checkpoint,假設每小時一次,則可以從 7 小時的部份接續下去跑,理論上是很完美,只是不知道現在 cgroup 能做到什麼程度就是。

這邊以實例來操作 cgroup ,目前在 debian wheezy 上有遇到一些問題,只能使用 root 使用者來分配資源,而無法以一般使用者,還沒找出問題在哪,所以先用 arch linux 為例,基本上,目前 cgroup 的實作,試過幾個 distribution,都大同小異,fedora、ubuntu、arch linux,都是類似,試過都沒問題,但 debian wheezy 則有點不同。


進入系統後,首先檢查 cgroup 是否有掛載起來:
$ mount | grep cgroup
tmpfs on /sys/fs/cgroup type tmpfs (rw,nosuid,nodev,noexec,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (rw,nosuid,nodev,noexec,relatime,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (rw,nosuid,nodev,noexec,relatime,cpuacct,cpu)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,nosuid,nodev,noexec,relatime,net_cls)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,nosuid,nodev,noexec,relatime,blkio)

在 arch linux 中,可看到 cgroup 掛載於 /sys/fs/cgroup/ 底下,接著檢查系統的 cgroup 支援哪些 controller.

$ cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset      2     3     1
cpu           3     14   1
cpuacct    3     14   1
memory    4     1     1
devices     5     1     1
freezer     6     1     1
net_cls     7     1     1
blkio         8     1     1

debian wheezy 預設 memory 是沒啟動的,可在開機時 grub 加入 cgroup_enable=memory 來開啟。

由於 cgroup 的 controller 滿多,功能也很廣,這邊只針對幾個做說明。
cpuset: 設定使用那幾個 cpu core.
cpu 和 cpuacct 是同一個:設定 cpu shares,share 值越大,能用 cpu 的時間越多。
memory:限制記憶體使用量。


要使用 cgroup,可透過底下幾個方法:
1. 直接以命令列指令存取 /sys/fs/cgroup 來達到。
2. 使用 libcgroup 的一些指令,像 cgcreate, cgexec, cgclassify 來操作。
3. 透過 LXC 來實作。

這邊介紹 1, 2 種方法,lxc 則留著以後再分享。


以命令列指令存取 cgroup:

操作前準備:
以 root 來建立 low 群組,low 繼承了 cpu controller 的所有屬性
$ sudo mkdir /sys/fs/cgroup/cpu/low

將權限開給 behappy 使用者
$ sudo chown behappy:users -R /sys/fs/cgroup/cpu/low

cpu share 預設為 1024
$ cat /sys/fs/cgroup/cpu/low/cpu.shares
1024

以一般使用者將之 low 群組設成 512
$ echo 512 > /sys/fs/cgroup/cpu/low/cpu.shares

重覆上面,建立 high 群組,cpu share 設為 2048
$ sudo mkdir /sys/fs/cgroup/cpu/high
$ sudo chown behappy:users -R /sys/fs/cgroup/cpu/high
$ echo 2048 > /sys/fs/cgroup/cpu/high/cpu.shares

另外建立 first_core、second_core 群組,為 cpu 第一個核心及第二核心,位於 cpuset controller 底下。
$ sudo mkdir /sys/fs/cgroup/cpuset/first_core
$ sudo chown behappy:users -R /sys/fs/cgroup/first_core

$ sudo mkdir /sys/fs/cgroup/cpuset/second_core
$ sudo chown behappy:users -R /sys/fs/cgroup/second_core

檢視原本 cpuset 裡的設定:
$ cat /sys/fs/cgroup/cpuset/cpuset.cpus
0-1
有二個 cpu core 0 和 1

而我們建立的 first_core 及 second_core 裡的設定是空的
$ cat /sys/fs/cgroup/cpuset/first_core/cpuset.cpus

將 first_core 群組設成第一個 cpu core:
$ echo 0 > /sys/fs/cgroup/cpuset/first_core/cpuset.cpus

將 second_core 群組設成第二個 cpu core:
$ echo 1 > /sys/fs/cgroup/cpuset/second_core/cpuset.cpus

這樣大致上準備完成,接著開始試驗。

拿 /usr/bin/yes 這指令來操 cpu,為了易於閱讀,設置二個執行檔:
$ cp /usr/bin/yes /tmp/yes_low
$ cp /usr/bin/yes /tmp/yes_high

另外開個 top 在旁邊觀看 cpu 使用情形,按 1 來顯示各別 cpu core 使用情形
$ /tmp/yes_low &>/dev/null &
$ /tmp/yes_high &>/dev/null &

$ top
top - 13:14:41 up 3:34, 2 users, load average: 0.85, 0.34, 0.16
Tasks: 119 total, 3 running, 114 sleeping, 2 stopped, 0 zombie
%Cpu0 : 99.3 us, 0.7 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 99.7 us, 0.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem: 3951368 total, 1223052 used, 2728316 free, 150640 buffers
KiB Swap: 3145724 total, 0 used, 3145724 free, 531504 cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
9860 behappy 20 0 7120 356 284 R 99.9 0.0 0:11.32 yes_low
9861 behappy 20 0 7120 356 284 R 98.6 0.0 0:08.98 yes_hig

下方可看到二個程式,各佔 cpu 99%,而上面則顯示目前二個 cpu core 都是 99% 使用率,這是正常的狀況下。

接著我們將 yes_low 加入到 low 群組
$ echo 9860 > /sys/fs/cgroup/cpu/low/tasks

將 yes_high 加入到 high 群組
$ echo 9861 > /sys/fs/cgroup/cpu/low/tasks

理論上 top 所看到的 cpu 使用狀況應該有變化,但是並沒有,因為有二個 cpu core,而使用 cpu 的程式也是二個而已,所以在夠用的情況下,二個程式都可以取得所需的資源,因此一樣佔用 99%,所以我們要讓二個程式使用同一個 cpu core 才行。

$ echo 9860 > /sys/fs/cgroup/cpuset/first_core/tasks
-bash: echo: write error: No space left on device

哦哦,失敗,要先設定 cpuset.mems 才行,不知道為什麼 :)
$ echo 0 > /sys/fs/cgroup/cpuset/first_core/cpuset.mems

再設定一次就可成功
$ echo 9860 > /sys/fs/cgroup/cpuset/first_core/tasks

將 yes_high 也加入 first_core 群組
$ echo 9861 > /sys/fs/cgroup/cpuset/first_core/tasks

過個三秒後,應該可看到 top 上的變化
$ top
top - 13:14:41 up 3:34, 2 users, load average: 0.85, 0.34, 0.16
Tasks: 119 total, 3 running, 114 sleeping, 2 stopped, 0 zombie
%Cpu0 : 99.8 us, 0.7 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
%Cpu1 : 1.7 us, 0.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem: 3951368 total, 1223052 used, 2728316 free, 150640 buffers
KiB Swap: 3145724 total, 0 used, 3145724 free, 531504 cached

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
9861 behappy 20 0 7120 356 284 R 79.9 0.0 0:11.32 yes_high
9860 behappy 20 0 7120 356 284 R 20.2 0.0 0:08.98 yes_low

很明顯上方可看到,第一個 cpu core 用了 99.8%,而第二個就只有 1.7%,因為我們設定 yes_high 及 yes_low 都只使用第一個 cpu core;接著下方,由於 yes_low 比重是 512,yes_high 是 2048,為 1:4,因此 cpu loading 也趨近於 1:4 ( 20.2 : 79.9 )。

這樣空下一顆 cpu core 可以用來做其他事情,而不會導致系統變得很頓。

cpuacct 的部份,在剛建立一個 cpu 或 cpuacct 群組時,cpuacct.usage 為 0,像剛才建立的 high 群組
$ cat /sys/fs/cgroup/cpu/high/cpuacct.usage
0

經過執行了 yes_high 並加入 high 群組後,cpuacct.usage 就會開始累計,所以看內容就可知道用了多少 cpu time,單位為 ns (nano second,十的負九次方秒)。
$ cat /sys/fs/cgroup/cpu/high/cpuacct.usage
22493005441
這樣大概就是 22.5 秒,但觀察 cpuacct.stat 可看到,這 22.5 秒是 user + system 全部總合,因此要看 cpuacct.stat 裡面 user 的部份才是真正 user 使用的 cpu time,約 22.44 秒。

$ cat /sys/fs/cgroup/cpu/high/cpuacct.stat
user 2244
system 7

另外,由於只用到第一個 cpu core,因此在 cpuacct.usage_percpu 中看到第二個 cpu core cpu time 為 0
$ cat /sys/fs/cgroup/cpu/high/cpuacct.usage_percpu
22493005441 0

memory 的部份,一樣先建立一個群組,設定好限制,再將程式加入此群組即可。
以 root 建立一個 mymemory 群組
$ sudo mkdir /sys/fs/cgroup/memory/mymemory
$ sudo chown -R behappy:users /sys/fs/cgroup/memory/mymemory

用一般使用者設定記憶體上限為 10M Bytes
$ echo 10000000 > /sys/fs/cgroup/memory/mymemory/memory.limit_in_bytes

在旁邊先開個 terminal 來監控記憶體運作情形,使用 free,來觀察記憶體變化
$ watch free

然後開啟一個 shell,並將 shell 加入 mymemory 群組,這樣從這個 shell 裡所執行的程式,都屬於 mymemory 群組。
$ bash

找出剛才執行 bash 的 pid
$ echo $$
5312

將 bash 加入 mymemory 群組
$ echo 5312 > /sys/fs/cgroup/memory/mymemory/tasks

加入後,執行一個超過 10M 記憶體的程式,如 libreoffice、或看看影片,可立刻看到記憶體監控視窗中,已開始用到 swap 了,因為只給 10M,但不夠用,會自動使用 swap,印證我們限制記憶體使用是成功的。

操作上大致是如此,但是若每次都以存取 /sys 來動作,似乎很麻煩,因此有 libcgroup 的工具可用。


以 libcgroup 存取 cgroup:


fedora 是 libcgroup,直接裝上即可,而 debian 及 ubuntu 除了安裝 libcgroup1 還要再加上 cgroup-bin。

arch linux 從 AUR 中裝上:
$ yaourt -S libcgroup

以 libcgroup 來重覆上面建立 first_core, second_core, high, low 群組的指令。
建立 first_core 群組
$ sudo cgcreate -a behappy:users -t behappy:users -g cpuset:/first_core

建立 second_core 群組
$ sudo cgcreate -a behappy:users -t behappy:users -g cpuset:/second_core

建立 high 群組
$ sudo cgcreate -a behappy:users -t behappy:users -g cpu:/high

建立 low 群組
$ sudo cgcreate -a behappy:users -t behappy:users -g cpu:/low

設定 first_core 群組的 cpuset.cpus 為第一個 cpu core
$ cgset -r cpuset.cpus=0 /first_core

設定 second_core 群組的 cpuset.cpus 為第二個 cpu core
$ cgset -r cpuset.cpus=1 /second_core

設定 high 群組的 cpu.shares 為 2048
$ cgset -r cpu.shares=2048 /high

設定 low 群組的 cpu.shares 為 512
$ cgset -r cpu.shares=512 /low

指定 yes_high 到 first_core 群組
$ cgclassify -g cpuset:/first_core 9861

指定 yes_low 到 first_core 群組
$ cgclassify -g cpuset:/first_core 9860

指定 yes_high 到 high 群組
$ cgclassify -g cpu:/high 9861

指定 yes_low 到 low 群組
$ cgclassify -g cpu:/low 9860

cgclassify 是用於將已執行中的程式指到一個群組,而 cgexec 則用來執行一個程式,並將之加入一個群組。
執行 /tmp/yes_high &>/dev/null & 這指令,並將之加入 first_core 及 high 群組
$ cgexec -g cpu:/high -g cpuset:/first_core /tmp/yes_high &>/dev/null &

執行 /tmp/yes_low &>/dev/null & 這指令,並將之加入 first_core 及 low 群組
$ cgexec -g cpu:/low -g cpuset:/first_core /tmp/yes_low &>/dev/null &

檢視 yes_low 使用了多少 cpu time:
$ cgget -r cpuacct.usage /low
/low:
 cpuacct.usage: 22493005441

memory 的部份:
$ sudo cgcreate -a behappy:users -t behappy:users -g memory:/mymemory
$ cgset -r memory.limit_in_bytes=10000000 /mymemory


還不錯用,至少比 echo xx > 什麼的方便多了不是嗎?


但我們建立的群組,重新開機後就會不見,因此要寫在 /etc/cgconfig.conf 中,然後開機時啟動 cgconfig service
$ cat /etc/cgconfig.conf
group first_core {
        perm {
                task {
                      uid = behappy;
                }
                admin {
                      uid = behappy;
                }
        }
        cpuset {
                cpuset.mems = "0";
                cpuset.cpus = "0";
        }
}

group second_core {
         perm {
                task {
                        uid = behappy;
                }
                admin {
                        uid = behappy;
                }
         }
         cpuset {
                  cpuset.mems = "0";
                  cpuset.cpus = "1";
         }
}

group low {
         perm {
                   task {
                           uid = behappy;
                   }
                   admin {
                            uid = behappy;
                   }
          }
           cpu {
                  cpu.shares = "512";
           }
}

group high {
           perm {
                     task {
                               uid = behappy;
                     }
                     admin {
                               uid = behappy;
                     }
            }
            cpu {
                     cpu.shares = "2048";
            }
}

group mymemory {
            perm {
                       task {
                               uid = 1000;
                       }
                       admin {
                               uid = 1000;
                        }
             }
             memory {
                        memory.limit_in_bytes = "10000000";
             }
}

$ sudo systemctl start cgconfig

可是,若每次執行程式,都要手動加入到某個群組,還是很麻煩啊?沒問題,有 cgrulesengd 這個 daemon 來幫助我們,依照 /etc/cgrules.conf 所指定的,自動在程式執行後加入群組中,開機時要啟動 cgred service。
$ cat /etc/cgrules.conf
#《user》                                  《controllers》《destination》
#《user》:《process name》   《controllers》《destination》
behappy:yes_high                      cpuset              first_core
%                                                  cpu,cpuacct     high

behappy:/tmp/yes_low                cpuset              first_core
%                                                  cpu,cpuacct     low


$ sudo systemctl start cgred

排除註解總共 4 行,第 1 行是將使用者 behappy 執行的 yes_high 加入 cpuset controller 中的 first_core 群組
第 2 行前面是 %,表示接續第一行過來,若前面寫得和第一行一樣,則在找到第一行符合條件套用後就會離開,加 % 表示第 2 行和第 1 行是同一行指令,所以會 1,2 行都套用後才離開。

所以 1,2 行一組,表示將 behappy 的 yes_high 加入 first_core 及 high 群組。
3,4 行表示將 behappy 的 yes_low 加入 first_core 及 low 群組。

記得,在 cgrulesengd 啟動後,先前已執行的程式並不會變動,只有從 cgrulesengd 啟動後才執行的程式會自動加入群組。

另外,libcgroup 還有幾個指令:
$ cgdelete: 刪除某個群組
$ sudo cgdelete -g cpuset:first_core

cgclear: 清除所有群組 (這指令儘量不要下,否則全空了),若全部不見了,寫個 /tmp/cgconfig.conf,內容如下,並以 cgconfigparser 來載入:
mount {
     cpuset = /sys/fs/cgroup/cpuset;
     cpu = /sys/fs/cgroup/cpu,cpuacct;
     cpuacct = /sys/fs/cgroup/cpu,cpuacct;
     memory = /sys/fs/cgroup/memory;
     devices = /sys/fs/cgroup/devices;
     freezer = /sys/fs/cgroup/freezer;
     net_cls = /sys/fs/cgroup/net_cls;
     blkio = /sys/fs/cgroup/blkio;
}

cgconfigparser: 檢查 cgconfig.conf 設定檔的格式錯誤,若沒問題則載入。
$ cgconfigparser -l /tmp/cgconfig.conf

cgsnapshot: 將目前 cgroup 狀態 dump 下來,可存成設定檔。
$ cgsnapshot > /tmp/cgconfig.conf


總結:
Cgroup 是非常複雜的東東,這邊只做基本介紹,有興趣可到 /sys/fs/cgroup 裡去看看每個檔案的內容。
現在 linux 有了 cgroup 後,簡直是如虎添翼,擁有早期只有大型主機 + 商用unix 才有的功能,現在 linux + PC 就可達到,真是越來越強了。


參考文件:
1. http://en.wikipedia.org/wiki/Cgroup
2. https://wiki.archlinux.org/index.php/Cgroups
3. http://www.kernel.org/doc/Documentation/cgroups/cgroups.txt
4.  https://access.redhat.com/knowledge/docs/en-US/Red_Hat_Enterprise_Linux/6/html/Resource_Management_Guide/index.html

2013年1月12日 星期六

MagicScroll - 網頁如何捲動 (scrolling) 才不會破壞閱讀的節奏

很有趣的研究,的確如作者說的,scrolling 對瀏覽地圖或試算表來說,是很聰明的方式,但是對於文字來說,會破壞了閱讀的節奏。

直接看網站就知道是如何運作:
http://www.magicscroll.net/ScrollTheWeb.html

底下有 chrome 的外掛可安裝。

若是 firefox 則將最下方連結直接拖拉到 firefox 的 "書籤工具列" 上,然後開啟要閱讀的網站,按下書籤工具列上的 "Read With MagicScroll",即可開始舒服的閱讀。

2013年1月11日 星期五

使用工具來調整螢幕色溫,讓眼睛不容易疲勞

由於長時間在電腦螢幕前,有時候越看越覺得刺眼,因此都會手動調整亮度,尤其是白天和晚上的亮度都要調,有點麻煩。

最近看到了可調整螢幕的色溫來讓眼睛不容易疲勞,而且有 linux 的版本可用,因此試了一下,果然非常好用,雖然調整色溫可從螢幕的設定裡面改,但是一天若要調好幾次還是很麻煩,若用程式隨時調整不是更方便。

目前有看到二套,一套是 flux (註1),支援多平台。flux 是 command line 工具,有另一個 gui 介面可使用 fluxgui (註2)

另一套叫 redshift (註3),也支援多個平台,一樣是 command line 工具,也有另一個 gui 可用,叫 gtk-redshift。

這類軟體都是用所在經緯度來判斷目前是白天或晚上,然後套用預設的色溫,就僅僅如此而已。

由於要知道是白天或晚上,所以要自行設定所在經緯度,可從 google map 以中文地址來查詢 (註4),或是由另一個網頁直接以中文地址查詢 (註5),比用 google map 還方便

debian 只包 redshift 及 gtk-redshift,所以就介紹 redshift。

$ sudo apt-get install redshift gtk-redshift

執行:
$ gtk-redshift  -l  緯度:經度

例如台中二中:
$ gtk-redshift  -l  24.15220:120.67541

基本上整個台灣的白天和晚上切換都差不了幾分鐘,所以指向台北也沒關係。


redshift 預設色溫,白天為 5500K,晚上為 3700K。若是預設的不喜歡,可自行以 -t 調整。

gtk-redshift 似乎沒有使用設定檔,所以每次都要下這個指令來啟動。


而在 arch linux 下的 redshiftgui (註6) 就更好用了,除了可儲存設定,還可以直接用滑鼠來隨時調整色溫,可從 aur 中安裝。

不過要特別注意,redshiftgui 的官方網站已變成廣告網站,不要到處抓 redshiftgui binary 執行檔,只從 github 中抓原始檔自己編就好,否則有可能抓到有的沒的。


參考連結:
1. http://stereopsis.com/flux/
2. https://github.com/Kilian/f.lux-indicator-applet
3. http://jonls.dk/redshift/
4. http://blog.soft.idv.tw/?p=1190
5. http://universimmedia.pagesperso-orange.fr/geo/loc.htm 
6. https://github.com/maoserr/redshiftgui