دریافت اطلاعات سیستم با استفاده از conky
 برای دیدن در اندازهٔ بزرگ کلیک کنید
و فعلا چند اشکال به آن وارد است: اول این که دارم از یک برنامهٔ خیلی گنده، برای یک کار ساده استفاده میکنم. دوم این که در حالت شل قدرت زیادی برای انگولک خروجی Conky ندارم (مثلا آن قسمت درصد استفاده از CPU برای درصدهای مختلف تک رقمی، دو رقمی و سه رقمی، طولهای متفاوتی میگیرد و کمی قضیه را لوس میکند) و سوم این که اگر به ماندن در همین وضعیت رضایت دهم، سوادم هیچ وقت زیاد نمیشود D:
پس تصمیم میگیرم که خروجی فعلی کانکی را (البته با چند تغییر کوچک) با ابزار سادهتری جایگزین کنم. خوب کار تقریبا پیچیده و سختی به نظر میرسد (وقتی که از لینوکس هیچ چیز ندانم) اما خوب همین Conky هم اطلاعاتش را از روی هوا نمیگیرد که! از طرفی مسالهٔ زبان پایش به میان میآید. به چه زبانی بنویسم؟ من که ته تهش پایتون را (آن هم دست و پا شکسته) میدانم.
بعد از کمی جستجو و دو دو تا چهار تا با دانستههای قبلیام، مساله را اینطور حل میکنم (از راست به چپ تصویر):
- زمان: روی توزیعم با دستور date میتوانم زمان حاضر را به طور کامل ببینم و البته میتوانم آن را به صورت دلخواهم فرمت کنم.
- صدا: حقیقتش کشف کردم نیازی به این ندارم و زیاد دنبال واکشی مقدارش نگشتم. ولی مطمئنم یک راه ساده برای فهمیدنش هست ;-)
- مجموع آپلود/دانلود نشست حاضر (TU/TD): خوب بعد از کمی جستجو، با دوست قدیمی ولی ناشناختهام proc آشنا شدم، که از قرار خیلی خیلی، بیشتر از چیزهایی که میخواهم را بهم میرساند. فعلا /proc/net/dev جواب تا اینجای کار است.
- سرعت دانلود/آپلود (D/U): راستش را بخواهید، این یکی مرا خیلی در کف نگه داشت! فکر میکردم باید جایی مثلا در proc برای این قضیه باشد. ولی نکتهاش اینجا بود که من اصلا نفهمیده بودم که چه میخواهم. که البته با تقلب از روی این [آموزش](tuxradar.com/content/code-project-monitor-p.. "code-project-monitor-proc-python-and-clutter") آن هم به صورت اتفاقی قضیه را درک کردم و شبیه سازی (البته نکاتی هم باقی میماند که در ادامه مطرح میکنم).
- وضعیت رم: خوب این یکی هم مثل date. میدانستم که دستوری به اسم free برای اینکار ساخته شده. ولی باید کمی خروجیاش را انگولک میکردم و چیزی که میخواستم را بیرون میکشیدم.
- وضعیت CPU: این یکی ترکیبی از همهٔ کارهاییست که در بالا باید انجام دهم به علاوه کلی چیز جدید که باید یاد بگیرم. فایل /proc/stat وضعیت فعلی CPU را در خودش نگه میدارد که با اسکریپتی که در [اینجا](github.com/moisespsena/linux-cpu-usage/blob.. "cpu usage monitor script on github") دیدم فهمیدم که چطور وضعیت کل CPU را واکشی کنم. اما من وضعیت کل را نمیخواستم، بلکه به دنبال وضعیت تک تک هستهها بودم که خوب باید دست و بالم رو کثیفتر میکردم ;-)
خوب تمام این کارها را میشود با پایتون هم انجام داد (با هر زبان دیگری هم میشود) ولی، دم دستیترین چیز ممکن shell scripting است. هم احتمالا کمتر از مفسر پایتون به سیستم فشار میآورد و هم این که اصلا چیزی در موردش نمیدانم (نه این که اصلا، ولی نه در حد یک اسکریپت نویس ;-)). این میشود که طی دو روز گذشته، تمام فکر و ذکرم را میگذارم روی نوشتن این اسکریپت، تا هم چیز یاد بگیرم، و هم مستقلتر شوم ;-)
اسکریپت حاضر است و از [اینجا](github.com/shahinism/Scripts/blob/master/Sh.. "sysdata script on github") قابل دریافت. در ادامه ذره، ذره به بررسیاش میپردازیم:
تاریخ
خوب اولین چیزی که لازم داشتیم، خروجی گرفتن زمان فعلی سیستم بود که گفتیم با دستور date قادر به انجامش هستیم. به صورت زیر:
date=$(date +"%a %Y.%m.%d %H:%M")
نکتهٔ این دستور آنجاست که من برای مقدار دهی به متغیر date، از خروجی یک دستور استفاده کردهام (کاری که در ادامه خیلی زیاد انجام میدهم). برای این کار کافیست دستور مورد نظر را در $() بنویسیم. به همین راحتی!
اطلاعات شبکه
اولین چیزهایی که میخواهیم، این است که بفهمیم چقدر دانلود یا آپلود کردهایم. که همانطور که گفتم این اطلاعات را در فایل /proc/net/dev میتوان یافت. همینطور، با محاسبهٔ این که در یک ثانیهٔ گذشته، چقدر دانلود/آپلود کردهایم، میتوان سرعت دانلود بر ثانیه را محاسبه کرد. این است که برای تمیزی کار، اول یک تابع مینویسیم که از proc ذکر شده، مقدار بایتهای ارسالی یا دریافتی را واکشی کند:
function getNetBytes
{
Brecived=$(cat $netInterface | grep "eth0" | awk '{print($2)}')
Btransmited=$(cat $netInterface | grep "eth0" | awk '{print($10)}')
}
متغیر $netInterface اشاره به همان فایل /proc/net/dev میکند که در اول اسکریپت تعریفش کردهام. بعد هم با grep تنها خطی که مربوط به اینترفیسی که میخواهم است را جدا میکنم (eth0) و حالا با awk خانهٔ شمارهٔ ۲ را برای بایتهای دریافت شده، و خانهٔ شمارهٔ ۱۰ این خروجی را برای بایتهای فرستاده شده جدا میکنم. این کار را به روشهای دیگری هم میشد انجام داد، ولی به نظرم با این روش، خطهای کمتری تایپ کردم ;-)
حالا با دستورات زیر، چیزهایی که میخواهم را محاسبه میکنم:
dlSpeed=$(echo $(( $Brecived-$oldBRecived )) | awk '{printf( "%.2f", $1/1024)}')
upSpeed=$(echo $(( $Btransmited-$oldBTransmited )) | awk '{printf( "%.2f", $1/1024)}')
traffic=$(echo "$Brecived $Btransmited" | awk '{printf( "%.2f", ($1+$2)/1024/1024 )}')
طی این دستورات، از قابلیت متغیرگیری به صورت $num و همینطور، پرینت فرمت شده (برای نمایش حداکثر دو رقم اعشار) awk استفاده کردهام. این کارها با dc هم قابل انجام بود، ولی خروجی به تمیزی awk نبود. همانطور که میبینید در این دستورات از متغیرهای $oldBRecived و $oldBTransmited استفاده شده که در واقع وضعیت یک ثانیه قبل دانلود/آپلود را در خود نگاه میدارند. تنها کافیست دستورات فوق را هر یک ثانیه اجرا کنیم تا خروجی درست را بگیریم. (حلقهٔ کامل این دستورات در آخر مطلب توضیح داده میشود)
نکتهای که میماند دقت این محاسبه است. حقیقتش همانطور که گفتم من این روش را از اینجا [link] یاد گرفتم. اما دقیقا همان را پیاده سازی نکردم. مساله سر زمان محاسبه بود. نمیدانم چرا نویسنده در آنجا از epoch (تاریخ ۱/۱/۱۹۷۰) استفاده کرده بود (خوشحال میشوم اگر کسی میداند به من هم توضیح دهد.) نویسنده در آنجا میگوید مساله سر چند کیلوبایت است و از این حرفها!
وضعیت RAM
گفتم که دستور free -m اینکارها را انجام میدهد، پس:
function getRamInfo
{
totalMemory=$(free -m | grep "Mem:" | awk '{printf( "%.2f", $2/1024)}' )
usedMemory=$(free -m | grep "buffers/cache" | awk '{printf( "%.2f", $3/1024)}')
memInPercent=$(echo "$totalMemory $usedMemory" | awk '{printf( "%.0f", $2/($1/100)) }')
}
فکر نکنم نکتهای باقی مانده باشد که قبلا توضیح نداده باشم. پس دیگر توضیح نمیدهم!
وضعیت CPU
خوب گفتم که برای یادگیری این کار، از این اسکریپت[link] استفاده کردم. ولی این اسکریپت تنها وضعیت کل CPU را بر میگرداند. من آن را طوری تغییر دادم که هر بار، آدرس هستهای که میخواهم اطلاعاتش را بدانم در ورودی بگیرد، و فقط همان را هسته را محاسبه کند و در خروجی نشان دهد:
function getCpuInfo()
{
CORE=$1
addr="^cpu${CORE}"
local CPU=(`cat /proc/stat | grep $addr`) # Get the total CPU statistics.
unset CPU[0] # Discard the "cpu" prefix.
local IDLE=${CPU[4]} # Get the idle CPU time.
# Calculate the total CPU time.
local TOTAL=0
for VALUE in "${CPU[@]}"; do
let "TOTAL=$TOTAL+$VALUE"
done
# Catch current cores last state
PREV_IDLE=$(eval echo \PREV_IDLE$CORE)
PREV_TOTAL=$(eval echo \PREV_TOTAL$CORE)
# Calculate the CPU usage since we last checked.
let "DIFF_IDLE=$IDLE-$PREV_IDLE"
let "DIFF_TOTAL=$TOTAL-$PREV_TOTAL"
let "DIFF_USAGE=(1000\*($DIFF_TOTAL-$DIFF_IDLE)/$DIFF_TOTAL+5)/10"
eval \PREV_TOTAL$CORE="$TOTAL"
eval \PREV_IDLE$CORE="$IDLE"
}
برای این کار از یک متغیر برای تابع استفاده میکنم و مقدارش را به متغیر CORE اختصاص میدهم. با استفاده از این آدرس یک عبارت با قائده (Regex) به صورت "^cpu${CORE}" میسازم. این عبارت باقائده با خطوطی که در اولشان عبارت cpuN را دارند مطابقت میکند. و سپس با استفاده از دادههایی که دارم یک آرایه با استفاده از پردازش فایل /proc/stat میسازم و آن را به متغیر CPU نسبت میدهم. خوب حالا چون اولین خانهٔ این آرایه اسم CPU را دارد، و به درد محاسبات بعدی نمیخورد، آن را از آرایه بیرون میاندازم (unset CPU[0]) و همینطور مقدار خانهٔ IDLE را هم در متغیری به همین نام ذخیره میکنم.
با جمع کردن تمامی مقادیر موجود در آرایهٔ CPU میتوانم کل وضعیت کارکرد CPU را به دست آورم. این میشود که به صورت زیر این کار را انجام میدهم:
local TOTAL=0
for VALUE in "${CPU[@]}"; do
let "TOTAL=$TOTAL+$VALUE"
done
ساده است اینطور نیست؟ آن let آنجا همان کار (())$ را میکند. حالا وقت انجام محاسبات است. چون هر بار نیاز است که وضعیت قبلی CPU مورد نظر را داشته باشیم، پس لازم است برای هر CPU متغیرهای مربوطه را بسازیم. که این کار را با دستورات زیر انجام میدهیم:
eval \PREV_TOTAL$CORE="$TOTAL"
eval \PREV_IDLE$CORE="$IDLE"
این دستور eval فوقالعاده است. با استفاده از آدرس CPU فعلی (CORE) متغیرهای مورد نظرم را میسازد و مقادیر مربوطه را درشان ذخیره میکند. اما چون نمیخواهم زیادی کثیف کاری شود، قبل از انجام محاسبات، متغیرهایی که ساختهایم را به یک نام ثابت در میآورم تا کارم را آسان کنم:
PREV_IDLE=$(eval echo \PREV_IDLE$CORE)
PREV_TOTAL=$(eval echo \PREV_TOTAL$CORE)
حالا این دو متغیر، وضعیت قبلی CPU را در خود نگه میدارند. پس نوبت این است که محاسباتم را انجام دهم:
let "DIFF_IDLE=$IDLE-$PREV_IDLE"
let "DIFF_TOTAL=$TOTAL-$PREV_TOTAL"
let "DIFF_USAGE=(1000\*($DIFF_TOTAL-$DIFF_IDLE)/$DIFF_TOTAL+5)/10"
چیز خاصی نیست دیگر، همه چیز روشن است ;-)
پایان کار
while true;
do
getNetBytes
getRamInfo
for (( COUNT=0; COUNT < $CORES; COUNT++ ));do
getCpuInfo $COUNT
eval \cpu$COUNT=$DIFF_USAGE
done
dlSpeed=$(echo $(( $Brecived-$oldBRecived )) | awk '{printf( "%.2f", $1/1024)}')
upSpeed=$(echo $(( $Btransmited-$oldBTransmited )) | awk '{printf( "%.2f", $1/1024)}')
traffic=$(echo "$Brecived $Btransmited" | awk '{printf( "%.2f", ($1+$2)/1024/1024 )}')
printf \
"CPU: %3s,%3s,%3s,%3s | RAM: %sG/%sG (%s%%) | LAN: ↓: %4sƘ ↑: %4sƘ T↕: %6s | %s\n" \
"$cpu0" "$cpu1" "$cpu2" "$cpu3" "$usedMemory" \
"$totalMemory" "$memInPercent" "$dlSpeed" \
"$upSpeed" "$traffic" "$date"
oldBRecived=$Brecived
oldBTransmited=$Btransmited
sleep 1
done
حالا نوبت این است که کار را تمام کنم. یک حلقهٔ همیشه درست میسازم و فقط بهش میگویم بعد از این که همهٔ کارهایت را انجام دادی یک ثانیه استراحت کن (برای این که سرعت اینترنت را درست محاسبه کند، این مقدار ضروری است). بر همین اساس تابعهای getNetBytes و getRamInfo را صدا میکنم تا متغیرهای جدیدشان را تولید کنند. اما در مورد تابع getCpuInfo قضیه کمی فرق دارد. قرار است که چهار بار این تابع را با متغیرهای متفاوت صدا کنیم و نتیجهٔ حاصلهاش را در متغیرهای جداگانه ذخیره کنیم. این است که آن حلقهٔ for سر و کلهاش پیدا میشود. یک حلقهٔ for به سبک C که خداییش به نظرم کاملترین حلقههای for است D: حالا تابع را درش به صورت getCpuInfo $COUNT صدا میزنم و متغیر $DIFF_USAGE که همان طرصد مصرف است را به یک متغیر به صورت cpuN نسبت میدهم. این N همان آدرس هسته است.
همانطور که میبینید در ادامه محاسبهٔ سرعت اینترنت میآید که قبلا توضیحش دادهام. printf را فعلا ول کنید و به دو خط بعدش نگاهی کنید. همان دو متغیری که وضعیت قبلی ترافیک را نگه میدارند. فکر کنم دیگر همه چیز روشن شده باشد.
حالا برسیم به آن printf. حتما میپرسید چرا از echo استفاده نکردم. echo خوب است، ولی خیلی ساده هم هست. آن مشکل کنترل فضای پرینت که در Conky گرفتارش بودم، اینجا هم پیدا میشد. یک نگاهی به این خروجی بیاندازید:
CPU: 48,3,40,3 | RAM: 1.11G/1.95G (57%) | LAN: ↓:0.00Ƙ ↑:0.00Ƙ T↕:221.20 | Sat 2012.11.24 14:41
CPU: 8,4,76,4 | RAM: 1.11G/1.95G (57%) | LAN: ↓:0.00Ƙ ↑:0.00Ƙ T↕:221.20 | Sat 2012.11.24 14:41
CPU: 24,5,64,6 | RAM: 1.11G/1.95G (57%) | LAN: ↓:0.00Ƙ ↑:0.00Ƙ T↕:221.20 | Sat 2012.11.24 14:41
میبینید، هنوز هم طول خروجی کم و زیاد میشود که اصلا خوب نیست. این است که باید دنبال دستوری باشم که خروجی را آرایش کند و بیرون بدهد. printf سالاری که از زمان یادگیری C میشناسم این کار را برایم میکند. کافیست فضای متغیرها را به صورت %Ns که N همان طول مینیموم رشته است را مشخص کنم. خودش باقی کارها را انجام میدهد. خوب همین دیگر. کارمان تمام شد. مرحلهٔ بعدی این است که یک خروجی json دار درست حسابی بسازم که i3 بتواند رنگی رنگیاش کند ;-)
 برای دیدن در اندازهٔ بزرگتر کلیک کنید