| CDEFTUTORIAL(1) | rrdtool | CDEFTUTORIAL(1) |
نام (NAME)
cdeftutorial - راهنمای CDEF از Alex van den Bogaerdt
توضیحات (DESCRIPTION)
هدف این سند: ارائه مثالهایی از بخشهای پرکاربرد زبان CDEF در RRDtool.
اگر فکر میکنید قابلیت مهمی به درستی توضیح داده نشده است، و افزودن آن به این سند برای بیشتر کاربران مفید خواهد بود، لطفاً از من بخواهید آن را اضافه کنم. سپس تلاش خواهم کرد در نسخهٔ بعدی این راهنما به آن بپردازم. هیچ بازخوردی برابر با هیچ تغییری نیست! پیشنهادات و مطالب تکمیلی برای این سند نیز مورد استقبال است. -- Alex van den Bogaerdt <alex@vandenbogaerdt.nl>
چرا این راهنما؟ (Why this tutorial?)
یکی از قابلیتهای قدرتمند RRDtool توانایی آن در انجام انواع محاسبات بر روی دادههای بازیابیشده از پایگاههای دادهاش است. با این حال، گزینهها و نحو گستردهٔ RRDtool درک آن را برای کاربر معمولی دشوار میسازد. دفترچههای راهنما در توضیح اینکه این گزینهها چه کاری انجام میدهند خوب عمل میکنند؛ اما آنها بهطور مفصل توضیح نمیدهند (و نباید هم بدهند) که چرا این گزینهها مفید هستند. همانند راهنمای RRDtool من: اگر سندی ساده با زبانی روان میخواهید، باید این راهنما را بخوانید. اگر از مستندات رسمی راضی هستید، ممکن است این سند را بیش از حد ساده یا حتی کسلکننده بیابید. اگر تصمیم به خواندن این آموزش گرفتید، انتظار دارم که راهنمای دیگر من را نیز خوانده و کاملاً درک کرده باشید.
مطالعهٔ بیشتر (More reading)
اگر با روش توضیح من مشکلی دارید، لطفاً rpntutorial نوشتهٔ Steve Rader را بخوانید. این سند ممکن است به درک چگونگی کارکرد این سازوکار به شما کمک کند.
CDEFها چه هستند؟ (What are CDEFs?)
هنگام بازیابی دادهها از یک RRD، شما از یک "DEF" برای کار با آن دادهها استفاده میکنید. آن را به عنوان متغیری تصور کنید که در طول زمان تغییر میکند (که در آن، زمان محور x است). مقدار این متغیر همان چیزی است که در آن زمان مشخص در پایگاهداده وجود دارد و شما نمیتوانید هیچ تغییری در آن ایجاد کنید. کاربرد CDEFها دقیقاً همین است: آنها مقادیر را از DEFها میگیرند و محاسبات را روی آنها انجام میدهند.
سینتکس (SYNTAX)
DEF:var_name_1=some.rrd:ds_name:CF CDEF:var_name_2=RPN_expression
شما ابتدا "var_name_1" را به عنوان دادههای جمعآوریشده از منبع دادهٔ "ds_name" موجود در RRD به نام "some.rrd" با تابع تجمیع "CF" تعریف میکنید.
فرض کنید شمارندهٔ SNMP به نام ifInOctets در فایل mrtg.rrd به عنوان DS با نام "in" ذخیره شده است. آنگاه DEF زیر متغیری را برای میانگین آن منبع داده تعریف میکند:
DEF:inbytes=mrtg.rrd:in:AVERAGE
فرض کنید میخواهید بیت در ثانیه را نمایش دهید (به جای بایت در ثانیه که در پایگاهداده ذخیره شده است). باید یک محاسبه (از این رو "CDEF") روی متغیر "inbytes" تعریف کنید و از آن متغیر (inbits) به جای متغیر اصلی استفاده نمایید:
CDEF:inbits=inbytes,8,*
این دستور به RRDtool میگوید که inbytes را در هشت ضرب کند تا inbits به دست آید. بعداً توضیح خواهم داد که این روند چگونه کار میکند. در توابع رسم نمودار یا چاپ، اکنون میتوانید از inbits در جاهایی که در حالت عادی از inbytes استفاده میکردید، بهره ببرید.
توجه داشته باشید که نام متغیر استفادهشده در CDEF (یعنی inbits) نباید با نام متغیر تعریفشده در DEF (یعنی inbytes) یکسان باشد!.SH "عبارتهای RPN (RPN-expressions)" عبارت RPN مخفف «نشانهگذاری معکوس لهستانی» (Reverse Polish Notation) است و به شرح زیر کار میکند: شما متغیرها یا اعداد را روی یک پشته (stack) قرار میدهید. همچنین عملیات (کارهایی که باید انجام شوند) را نیز در پشته قرار میدهید و سپس این پشته پردازش میشود. نتیجه درون پشته قرار خواهد گرفت. در پایان، باید دقیقاً یک عدد باقی بماند: خروجی حاصل از سلسله عملیاتها. اگر دقیقاً یک عدد باقی نماند، RRDtool بهشدت اعتراض خواهد کرد (خطا صادر میکند).
عملیات ضرب در هشت که در بالا ذکر شد، به این صورت خواهد بود:
- 1.
- با یک پشته خالی شروع کنید
- 2.
- محتوای متغیر inbytes را روی پشته قرار دهید
- 3.
- عدد هشت را روی پشته قرار دهید
- 4.
- عملیات ضرب را روی پشته قرار دهید
- 5.
- پشته را پردازش کنید
- 6.
- مقدار را از پشته بازیابی کرده و در متغیر inbits قرار دهید
اکنون مثالی با اعداد واقعی میزنیم. فرض کنید متغیر inbytes مقدار 10 را داشته باشد، پشته به این شکل خواهد بود:
- 1.
- ||
- 2.
- |10|
- 3.
- |10|8|
- 4.
- |10|8|*|
- 5.
- |80|
- 6.
- ||
پردازش پشته (گام ۵) یک مقدار را از پشته بازیابی میکند (از سمت راست در گام ۴). این مقدار همان عملیات ضرب است و دو مقدار را به عنوان ورودی از پشته برمیدارد. نتیجه مجدداً روی پشته قرار میگیرد (در این حالت مقدار 80). برای ضرب، ترتیب عملوندها اهمیتی ندارد، اما برای عملیاتهای دیگر مانند تفریق و تقسیم مهم است. به طور کلی شما ترتیب زیر را دارید:
y = A - B --> y=minus(A,B) --> CDEF:y=A,B,-
این چندان شهودی نیست (دستکم اکثر افراد چنین فکری نمیکنند). برای تابع f(A,B) شما موقعیت "f" را معکوس میکنید، اما ترتیب متغیرها را برعکس نمیکنید.
تبدیل خواستهها به RPN (Converting your wishes to RPN)
ابتدا، تصویر روشنی از آنچه میخواهید انجام دهید به دست آورید. مسئله را به بخشهای کوچکتر خرد کنید تا جایی که دیگر نتوان آنها را تفکیک کرد. سپس تبدیل ایدههایتان به RPN نسبتاً ساده خواهد بود.
فرض کنید چندین RRD دارید و میخواهید برخی از شمارندهها را در آنها با یکدیگر جمع کنید. این شمارندهها میتوانند، برای نمونه، شمارندههای هر پیوند WAN باشند که در حال پایش آن هستید.
شما موارد زیر را در اختیار دارید:
router1.rrd with link1in link2in router2.rrd with link1in link2in router3.rrd with link1in link2in
فرض کنید میخواهید همه این شمارندهها را با هم جمع کنید، به جز link2in در router2.rrd. باید اینگونه عمل کنید:
(در این مثال، "router1.rrd:link1in" به معنای DS به نام link1in درون فایل router1.rrd است)
router1.rrd:link1in router1.rrd:link2in router2.rrd:link1in router3.rrd:link1in router3.rrd:link2in -------------------- + (outcome of the sum)
این عبارت به عنوان یک تابع ریاضی میتواند به شکل زیر نوشته شود:
"add(router1.rrd:link1in , router1.rrd:link2in , router2.rrd:link1in , router3.rrd:link1in , router3.rrd:link2.in)"
با RRDtool و RPN، ابتدا ورودیها را تعریف کنید:
DEF:a=router1.rrd:link1in:AVERAGE DEF:b=router1.rrd:link2in:AVERAGE DEF:c=router2.rrd:link1in:AVERAGE DEF:d=router3.rrd:link1in:AVERAGE DEF:e=router3.rrd:link2in:AVERAGE
اکنون، تابع ریاضی به این شکل درمیآید: "add(a,b,c,d,e)"
در RPN هیچ عملگری وجود ندارد که بیش از دو مقدار را با هم جمع کند، بنابراین باید چندین عمل جمع انجام دهید. شما a و b را جمع میکنید، سپس c را به حاصل آن اضافه میکنید، d را به نتیجه میافزایید و در نهایت e را به نتیجه حاصل اضافه میکنید.
push a: a stack contains the value of a push b and add: b,+ stack contains the result of a+b push c and add: c,+ stack contains the result of a+b+c push d and add: d,+ stack contains the result of a+b+c+d push e and add: e,+ stack contains the result of a+b+c+d+e
آنچه در اینجا محاسبه شد به این صورت نوشته میشود:
( ( ( (a+b) + c) + d) + e) >
این عبارت در RPN به شکل زیر است: "CDEF:result=a,b,+,c,+,d,+,e,+"
این روش صحیح است اما میتواند برای انسان خواناتر شود. فرقی نمیکند که ابتدا a را با b جمع کنید و سپس c را به حاصل اضافه کنید، یا ابتدا b را با c جمع کرده و سپس a را به نتیجه بیفزایید. این ویژگی بازنویسی RPN را به صورت "CDEF:result=a,b,c,d,e,+,+,+,+" ممکن میسازد که به شیوه متفاوتی ارزیابی میشود:
push value of variable a on the stack: a push value of variable b on the stack: a b push value of variable c on the stack: a b c push value of variable d on the stack: a b c d push value of variable e on the stack: a b c d e push operator + on the stack: a b c d e + and process it: a b c P (where P == d+e) push operator + on the stack: a b c P + and process it: a b Q (where Q == c+P) push operator + on the stack: a b Q + and process it: a R (where R == b+Q) push operator + on the stack: a R + and process it: S (where S == a+R)
همانطور که میبینید عبارت RPN به صورت "a,b,c,d,e,+,+,+,+" به شکل "((((d+e)+c)+b)+a)" ارزیابی میشود و نتیجهای یکسان با "a,b,+,c,+,d,+,e,+" دارد. این خاصیت «قانون شرکتپذیری و جابهجایی در جمع» نامیده میشود، اما تا زمانی که مفهوم آن را به خاطر داشته باشید، میتوانید این نام را فوراً فراموش کنید.
اکنون به عبارتی نگاه کنید که شامل یک ضرب است:
ابتدا در ریاضیات معمولی: "let result = a+b*c". در این حالت نمیتوانید خودتان ترتیب را تعیین کنید، بلکه باید ابتدا ضرب را انجام دهید و سپس a را به آن اضافه کنید. شما میتوانید جای b و c را تغییر دهید، اما نباید جای a و b را تغییر دهید.
هنگام تبدیل این عبارت به RPN باید این نکته را مد نظر قرار دهید. آن را به این صورت بخوانید: «حاصل b*c را به a اضافه کن» و در این صورت نوشتن عبارت RPN آسان خواهد بود: "result=a,b,c,*,+" عبارت دیگری که نتیجهای یکسان بازمیگرداند: "result=b,c,*,a,+"
در ریاضیات معمولی ممکن است با عبارتی مانند "a*(b+c)" مواجه شوید؛ این عبارت نیز میتواند به RPN تبدیل شود. پرانتزها فقط مشخص میکنند که ابتدا باید b و c جمع شوند و سپس a در حاصلجمع ضرب شود. باز هم نوشتن آن در RPN آسان است: "result=a,b,c,+,*". توجه داشته باشید که این عبارت بسیار شبیه به یکی از عبارتهای پاراگراف پیشین است و تنها جای ضرب و جمع تغییر کرده است.
هرگاه با RPN دچار مشکل شدید یا RRDtool خطا داد، معمولاً ایده خوبی است که وضعیت پشته را روی یک تکه کاغذ یادداشت کنید و روند را گامبهگام ببینید. دفترچه راهنما را دم دست داشته باشید و فرض کنید خود RRDtool هستید. کافی است تمام محاسبات را دستی روی کاغذ انجام دهید تا مشخص شود چه اتفاقی میافتد؛ مطمئناً این روش بیشتر، اگر نه همه، مشکلاتی را که با آنها مواجه میشوید حل خواهد کرد..SH "برخی اعداد ویژه (Some special numbers)"
مقدار ناشناخته (The unknown value)
گاه جمعآوری دادههای شما با شکست مواجه میشود. این امر، بهویژه هنگام پرسوجو از طریق پیوندهای شلوغ (busy links)، بسیار متداول است. ابزار RRDtool میتواند طوری پیکربندی شود که به یک (یا حتی چند) مقدار ناشناخته اجازه دهد و بهروزرسانیِ از دسترفته را محاسبه کند. برای مثال، میتوانید دستگاه خود را در هر دقیقه پرسوجو کنید. این کار در هر دقیقه یک اصطلاحاً PDP یا primary data point (نقطه داده اولیه) ایجاد میکند. اگر RRD خود را طوری تعریف کردهاید که شامل یک RRA ذخیرهکننده مقادیر ۵ دقیقهای باشد، برای ایجاد یک CDP (نقطه داده تجمیعی یا consolidated data point) به پنج مورد از این PDPها نیاز دارید. این PDPها در دو حالت میتوانند ناشناخته (unknown) شوند:
- 1.
- فاصله زمانی بهروزرسانیها بیش از حد زیاد باشد. این مورد با استفاده از تنظیم "heartbeat" تعیین میشود.
- 2.
- بهروزرسانی با عدم درج هیچ مقداری (با استفاده از گزینه template) یا با استفاده از "U" به عنوان مقدار درجشده، به صورت عمدی روی ناشناخته تنظیم شده باشد.
هنگامی که یک CDP محاسبه میشود، سازوکار دیگری تعیین میکند که آیا این CDP معتبر است یا خیر. اگر تعداد بیش از حدی از PDPها ناشناخته باشند، CDP نیز ناشناخته خواهد شد. این موضوع توسط ضریب xff تعیین میشود. لطفاً توجه داشته باشید که یک بهروزرسانی نامشخص شمارنده میتواند منجر به دو PDP ناشناخته شود! اگر فقط به یک PDP ناشناخته به ازای هر CDP اجازه دهید، این امر باعث ناشناخته شدن CDP میشود!
فرض کنید شمارنده در هر ثانیه یک واحد افزایش مییابد و شما آن را در هر دقیقه دریافت میکنید:
counter value resulting rate 10'000 10'060 1; (10'060-10'000)/60 == 1 10'120 1; (10'120-10'060)/60 == 1 unknown unknown; you don't know the last value 10'240 unknown; you don't know the previous value 10'300 1; (10'300-10'240)/60 == 1
اگر قرار بود CDP از پنج بهروزرسانی اخیر محاسبه شود، دو PDP ناشناخته و سه PDP شناختهشده دریافت میکرد. اگر xff روی 0.5 تنظیم شده بود (که ضمناً ضریبی بسیار رایج است)، CDP مقداری شناختهشده برابر با 1 میداشت. اگر xff روی 0.2 تنظیم شده بود، آنگاه CDP حاصل ناشناخته میشد.
شما باید مقادیر مناسب را برای heartbeat، تعداد PDPها به ازای هر CDP و ضریب xff تعیین کنید. همانطور که از متن بالا مشخص است، این متغیرها رفتار RRA شما را تعریف میکنند.
کار با دادههای ناشناخته در پایگاهداده (Working with unknown data in your database)
همانطور که در بخش قبلی خواندید، مقادیر ورودی در یک RRA میتوانند روی مقدار ناشناخته تنظیم شوند. اگر با این نوع مقدار محاسباتی انجام دهید، نتیجه نیز باید ناشناخته باشد. این بدان معناست که عبارتی مانند "result=a,b,+" در صورتی که هر یک از a یا b ناشناخته باشند، ناشناخته خواهد بود. نادیده گرفتن مقدار ناشناخته و بازگرداندن مقدار پارامتر دیگر اشتباه خواهد بود؛ چرا که با انجام این کار، فرض میکنید که «unknown» به معنای «صفر» است و این درست نیست.
موردی وجود داشته که در آن شخصی بیش از یک سال داده جمعآوری میکرده است. تجهیزات جدیدی نصب شد، یک RRD جدید ایجاد شد و اسکریپتها تغییر یافتند تا یک شمارنده از پایگاهداده قدیمی با یک شمارنده از پایگاهداده جدید جمع شود. نتیجه ناامیدکننده بود؛ به نظر میرسید بخش بزرگی از آمار به شکلی مرموز ناپدید شده است... البته که ناپدید نشده بودند؛ مقادیر پایگاهداده قدیمی (مقادیر شناختهشده) با مقادیر پایگاهداده جدید (مقادیر ناشناخته) جمع شده بودند و در نتیجه خروجی ناشناخته شده بود.
در این حالت، کاملاً منطقی است که از یک CDEF استفاده کنید که دادههای ناشناخته را به صفر تغییر دهد. شمارندههای دستگاه ناشناخته بودند (هر چه باشد، هنوز نصب نشده بود!) اما میدانید که نرخ دادههای عبوری از دستگاه میبایست صفر باشد (به همان دلیل: هنوز نصب نشده بود).
در ادامه چند مثال آورده شده است که این تغییر را اعمال میکنند.
بینهایت (Infinity)
دادههای بینهایت شکل دیگری از یک عدد ویژه هستند. نمیتوان آنها را روی نمودار ترسیم کرد، زیرا طبق تعریف هرگز به مقدار بینهایت دست نخواهید یافت. بسته به موقعیت نسبت به صفر، میتوانید بینهایت مثبت یا منفی را در نظر بگیرید.
ابزار RRDtool قادر است بینهایت را با متوقف شدن در بیشینه فعلی (برای بینهایت مثبت) یا کمینه فعلی (برای بینهایت منفی) بدون دانستن این بیشینه (کمینه)، نمایش دهد (-نه اینکه رسم کند!-).
بینهایت در RRDtool عمدتاً برای رسم یک AREA بدون دانستن ابعاد عمودی آن استفاده میشود. میتوانید آن را به عنوان رسم یک AREA با ارتفاع بینهایت و نمایش تنها بخشی که در نمودار فعلی قابل مشاهده است در نظر بگیرید. این احتمالاً روش مناسبی برای تقریب بینهایت است و قطعاً امکان پیادهسازی ترفندهای جالبی را فراهم میکند. برای مثالها به ادامه مراجعه کنید.
کار با دادههای ناشناخته و بینهایت (Working with unknown data and infinity)
گاهی مایلید دادههای ناشناخته را نادیده بگیرید و وانمود کنید صفر هستند (یا هر مقدار دیگری) و گاهی میخواهید وانمود کنید دادههای شناختهشده، ناشناخته هستند (برای کنار گذاشتن دادههایی که نادرست بودنشان مشخص است). به همین دلیل است که CDEFها از دادههای ناشناخته پشتیبانی میکنند. همچنین مثالهایی در دسترس است که دادههای ناشناخته را با استفاده از بینهایت نشان میدهند.
چند مثال (Some examples)
مثال: استفاده از یک RRD بهتازگی ایجادشده (Example: using a recently created RRD)
اکنون بیش از یک سال است که آمارهای مسیریاب خود را نگهداری میکنید. اخیراً یک مسیریاب اضافی نصب کردهاید و مایلید توان عملیاتی (throughput) ترکیبی این دو دستگاه را نمایش دهید.
اگر صرفاً شمارندههای router.rrd و router2.rrd را با هم جمع کنید، برای بخش بزرگی از آمارتان، دادههای شناختهشده (از router.rrd) را با دادههای ناشناخته (از router2.rrd) جمع خواهید کرد. میتوانید این مسئله را به چند روش حل کنید:
- هنگام ایجاد پایگاهداده جدید، آن را از ابتدا تا زمان حال با صفر پر کنید. باید کاری کنید که پایگاهداده در قدیمیترین زمان پایگاهداده دیگر یا قبل از آن آغاز شود.
- در روشی دیگر، میتوانید از CDEF استفاده کرده و دادههای ناشناخته را به صفر تبدیل کنید.
هر دو روش مزایا و معایب خود را دارند. روش اول دردسرساز است و اگر بخواهید آن را انجام دهید باید خودتان راه آن را بیابید. امکان ایجاد یک پایگاهداده که از پیش با صفر پر شده باشد وجود ندارد و باید آنها را بهصورت دستی وارد کنید. پیادهسازی روش دوم در ادامه شرح داده شده است:
آنچه ما میخواهیم این است: «اگر مقدار ناشناخته است، آن را با صفر جایگزین کن». این کار را میتوان در شبهکد به این صورت نوشت: if (value is unknown) then (zero) else (value). هنگام خواندن راهنمای rrdgraph متوجه تابع "UN" میشوید که مقدار صفر یا یک را بازمیگرداند. همچنین متوجه تابع "IF" میشوید که صفر یا یک را به عنوان ورودی میپذیرد.
ابتدا به تابع "IF" نگاه کنید. این تابع سه مقدار را از پشته دریافت میکند؛ مقدار اول نقطه تصمیمگیری است، اگر ارزیابی برابر با "true" باشد، مقدار دوم به پشته بازگردانده میشود و در غیر این صورت، مقدار سوم به پشته بازمیگردد. ما میخواهیم تابع "UN" درباره آنچه رخ میدهد تصمیم بگیرد، بنابراین این دو تابع را در یک CDEF ترکیب میکنیم.
بیایید دو مسیر ممکن را برای تابع "IF" بنویسیم:
if true return a if false return b
در RPN: "result=x,a,b,IF" که در آن "x" یا true است یا false.
اکنون باید "x" را پر کنیم؛ این مقدار باید بخش "(value is unknown)" باشد و در RPN به این صورت است: "result=value,UN"
حال آنها را ترکیب میکنیم: "result=value,UN,a,b,IF" و هنگامی که مقادیر مناسب را به جای "a" و "b" قرار دهیم، کار تمام است:
"CDEF:result=value,UN,0,value,IF"
اگر با روش توضیح این مثال آخر مشکل دارید، شاید بهتر باشد راهنمای RPN استیو ریدر (Steve Rader) را مطالعه کنید.
اگر میخواهید این عبارت RPN را بررسی کنید، کافی است رفتار RRDtool را شبیهسازی کنید:
For any known value, the expression evaluates as follows: CDEF:result=value,UN,0,value,IF (value,UN) is not true so it becomes 0 CDEF:result=0,0,value,IF "IF" will return the 3rd value CDEF:result=value The known value is returned For the unknown value, this happens: CDEF:result=value,UN,0,value,IF (value,UN) is true so it becomes 1 CDEF:result=1,0,value,IF "IF" sees 1 and returns the 2nd value CDEF:result=0 Zero is returned
البته، اگر مایلید مقداری به غیر از صفر نمایش داده شود، میتوانید از آن مقدار دیگر استفاده کنید.
در نهایت، هنگامی که همه دادههای ناشناخته از RRD حذف شدند، ممکن است بخواهید این قاعده را حذف کنید تا دادههای ناشناخته بهدرستی نمایش داده شوند..SS "مثال: مدیریت بهتر دادههای ناشناخته، با استفاده از زمان" مثال بالا یک ایراد دارد. اگر پس از نصب تجهیزات جدیدتان، دادههای ناشناخته را در پایگاهداده خود ثبت کنید، این دادهها نیز به صفر تبدیل میشوند و بنابراین متوجه بروز مشکل نخواهید شد. این وضعیت مطلوب نیست و آنچه واقعاً میخواهید انجام دهید این است:
- اگر داده ناشناختهای وجود دارد، به زمانی که این نمونه ثبت شده نگاه کنید.
- اگر مقدار ناشناخته پیش از زمان xxx است، آن را به صفر تبدیل کنید.
- اگر پس از زمان xxx است، آن را به عنوان داده ناشناخته باقی بگذارید.
این کار شدنی است: میتوانید زمان نمونهبرداری را با یک زمان مشخص مقایسه کنید. با این فرض که پایش دستگاه خود را در روز جمعه ۱۷ سپتامبر ۱۹۹۹ ساعت 00:35:57 MET DST آغاز کردهاید. این زمان را به ثانیههای سپریشده از 1970-01-01 تبدیل کنید تا برابر با 937'521'357 شود. اگر مقادیر ناشناختهای را پردازش میکنید که پس از این زمان دریافت شدهاند، میخواهید آنها را ناشناخته باقی بگذارید و اگر قبل از این زمان «دریافت» شدهاند، آنها را به صفر تبدیل کنید (تا بتوانید هنگام افزودن آنها به شمارندههای سایر مسیریابهایتان عملاً نادیدهشان بگیرید).
تبدیل جمعه ۱۷ سپتامبر ۱۹۹۹ ساعت 00:35:57 MET DST به 937'521'357 میتواند برای نمونه با استفاده از gnu date انجام شود:
date -d "19990917 00:35:57" +%s
همچنین میتوانید از پایگاهداده نسخه پشتیبان متنی (dump) تهیه کنید و ببینید دادهها از کجا شناختهشده میشوند. چندین روش دیگر برای انجام این کار وجود دارد، فقط یکی را انتخاب کنید.
اکنون باید آن ترفند کارآمد را بسازیم که به ما امکان دهد مقادیر ناشناخته را بسته به زمانی که نمونه ثبت شده، به شکل متفاوتی پردازش کنیم. این یک فرایند سهمرحلهای است:
- 1.
- اگر برچسب زمانی (timestamp) مقدار، پس از 937'521'357 است، آن را دستنخورده باقی بگذارید.
- 2.
- اگر مقدار یک مقدار شناختهشده است، آن را دستنخورده باقی بگذارید.
- 3.
- مقدار ناشناخته را به صفر تبدیل کنید.
بیایید به بخش نخست نگاه کنیم:
if (true) return the original value
این را بازنویسی میکنیم:
if (true) return "a" if (false) return "b"
ما باید درستی (true) یا نادرستی (false) را از مرحله ۱ محاسبه کنیم. تابعی وجود دارد که برچسب زمانی نمونه فعلی را برمیگرداند. در کمال شگفتی، نام این تابع “TIME” است. این زمان باید با یک عدد ثابت مقایسه شود، بنابراین به “GT” نیاز داریم. خروجی “GT” درست یا نادرست است و این ورودی مناسبی برای “IF” خواهد بود. ما عبارت “if (time > 937521357) then (return a) else (return b)” را میخواهیم.
این فرایند پیشتر در فصل قبلی بهطور کامل شرح داده شد، پس بیایید سریع آن را انجام دهیم:
if (x) then a else b where x represents "time>937521357" where a represents the original value where b represents the outcome of the previous example time>937521357 --> TIME,937521357,GT if (x) then a else b --> x,a,b,IF substitute x --> TIME,937521357,GT,a,b,IF substitute a --> TIME,937521357,GT,value,b,IF substitute b --> TIME,937521357,GT,value,value,UN,0,value,IF,IF
در نهایت به این عبارت میرسیم: "CDEF:result=TIME,937521357,GT,value,value,UN,0,value,IF,IF"
این عبارت بسیار پیچیده به نظر میرسد، با این حال همانطور که میبینید، دستیابی به آن چندان دشوار نبود.
مثال: وانمود کردن به اینکه دادههای نامتعارف وجود ندارند
فرض کنید مشکلی دارید که بهصورت جهشهای شدید (spikes) در نمودار شما نمایان میشود. میدانید این اتفاق میافتد و دلیل آن را نیز میدانید، بنابراین تصمیم میگیرید مشکل را دور بزنید. شاید در شب از شبکهتان برای پشتیبانگیری استفاده میکنید و به این ترتیب به سرعتی نزدیک به 10mb/s میرسید، در حالی که سایر فعالیتهای شبکه شما اعدادی بالاتر از 100kb/s تولید نمیکنند.
دو راهکار وجود دارد:
- 1.
- اگر مقدار از 100kb/s فراتر رفت نادرست است و میخواهید با تبدیل آن به مقدار ناشناخته، آن را بپوشانید (ماسک کنید).
- 2.
- نمیخواهید نمودار مقداری بیشتر از 100kb/s را نشان دهد.
شبهکد: if (number > 100) then unknown else number یا شبهکد: if (number > 100) then 100 else number.
«مسئله» دوم را میتوان با استفاده از گزینه rigid در RRDtool graph نیز حل کرد، اما این کار نتیجه یکسانی ندارد. در این مثال ممکن است در نهایت نموداری داشته باشید که دارای مقیاسبندی خودکار (autoscaling) است. همچنین اگر از این مقادیر برای نمایش بیشینهها استفاده کنید، روی 100kb/s تنظیم خواهند شد.
دوباره از “IF” و “GT” استفاده میکنیم. عبارت “if (x) then (y) else (z)” بهصورت “CDEF:result=x,y,z,IF” نوشته میشود؛ اکنون x، y و z را پر کنید. برای x عبارت «عدد بزرگتر از 100kb/s» را قرار میدهید که به “number,100000,GT” تبدیل میشود (کیلو برابر با 1'000 است و b/s چیزی است که اندازه میگیریم!). بخش “z” در هر دو حالت “number” است و بخش “y” یا “UNKN” برای داده ناشناخته است یا “100000” برای 100kb/s.
دو عبارت CDEF به این صورت خواهند بود:
CDEF:result=number,100000,GT,UNKN,number,IF
CDEF:result=number,100000,GT,100000,number,IF
اگر نموداری میخواهید که چند هفته را در بر میگیرد، اما تنها مایلید دادههای برخی مسیریابها را برای یک هفته مشاهده کنید، باید باقی بازهٔ زمانی را «پنهان» کنید. نپرسید این کار کجا مفید خواهد بود، این فقط برای مثال اینجا آمده است :)
ما باید برچسب زمان (time stamp) را با یک تاریخ آغاز و یک تاریخ پایان مقایسه کنیم. مقایسه کردن دشوار نیست:
TIME,begintime,GE TIME,endtime,LE
این دو بخش از CDEF یا مقدار 0 را برای false (نادرست) و یا 1 را برای true (درست) تولید میکنند. حال میتوانیم با استفاده از چند دستور IF بررسی کنیم که آیا هر دو 0 (یا 1) هستند یا خیر، اما همانطور که واتارو ساتو (Wataru Satoh) اشاره کرد، میتوانیم از توابع «*» یا «+» بهعنوان AND منطقی و OR منطقی استفاده کنیم.
برای «*»، اگر هر یک از دو عملوند برابر با صفر باشد، نتیجه صفر (false) خواهد بود. برای «+»، نتیجه تنها زمانی false (0) خواهد بود که دو عملوندِ false (0) با یکدیگر جمع شوند. هشدار: *هر* عددی غیر از 0 به عنوان «true» در نظر گرفته میشود. این بدان معناست که برای نمونه، «-1,1,+» (که باید معادل «true or true» باشد) برابر با FALSE خواهد شد... به بیان دیگر، از «+» تنها زمانی استفاده کنید که مطمئن باشید فقط اعداد مثبت (یا صفر) در اختیار دارید.
بیایید CDEF کامل را سرهم کنیم:
DEF:ds0=router1.rrd:AVERAGE CDEF:ds0modified=TIME,begintime,GT,TIME,endtime,LE,*,ds0,UNKN,IF
اگر هر دو مقایسه مقدار true بازگردانند، این عبارت مقدار ds0 را برمیگرداند. همچنین میتوانید عکس این عمل را انجام دهید:
DEF:ds0=router1.rrd:AVERAGE CDEF:ds0modified=TIME,begintime,LT,TIME,endtime,GT,+,UNKN,ds0,IF
در صورتی که هر یک از مقایسهها مقدار true بازگرداند، این عبارت UNKNOWN را برمیگرداند.
مثال: مشکوک به وجود مشکل هستید و میخواهید دادههای نامشخص (unknown) را ببینید
فرض کنید تعداد کاربران فعال روی چندین پایانهٔ سرور (terminal server) را با یکدیگر جمع میزنید. اگر یکی از آنها پاسخی ندهد (یا پاسخی نادرست بدهد)، در پایگاهداده مقدار «NaN» («عدد نیست» یا Not a Number) دریافت میکنید و NaN به عنوان Unknown ارزیابی میشود.
در این حالت، مایلید از این موضوع مطلع شوید و مجموع مقادیر باقیمانده ارزشی برای شما ندارد.
چیزی شبیه به این خواهد بود:
DEF:users1=location1.rrd:onlineTS1:LAST DEF:users2=location1.rrd:onlineTS2:LAST DEF:users3=location2.rrd:onlineTS1:LAST DEF:users4=location2.rrd:onlineTS2:LAST CDEF:allusers=users1,users2,users3,users4,+,+,+
اگر اکنون allusers را رسم کنید، دادههای نامشخص در یکی از مقادیر users1..users4 به شکل یک شکاف در نمودار شما نمایان میشود. شما میخواهید این رفتار را تغییر دهید تا به جای شکاف، یک خط قرمز روشن نمایش داده شود.
یک CDEF اضافی تعریف کنید که اگر همهچیز درست بود، مقدار آن unknown باشد و اگر یک مقدار نامشخص وجود داشت، مقدار آن بینهایت (INF) شود:
CDEF:wrongdata=allusers,UN,INF,UNKN,IF
عبارت «allusers,UN» مقدار true یا false را ارزیابی میکند؛ این بخش (x) تابع «IF» است و بررسی میکند که آیا allusers نامشخص (unknown) است یا خیر. بخش (y) تابع «IF» روی «INF» (به معنای بینهایت) تنظیم شده است و بخش (z) تابع مقدار «UNKN» را برمیگرداند.
منطق به این صورت است: if (allusers == unknown) then return INF else return UNKN.
اکنون میتوانید از AREA برای نمایش این «wrongdata» با رنگ قرمز روشن استفاده کنید. اگر نامشخص باشد (چون allusers مشخص است)، آنگاه AREA قرمز نمایش داده نخواهد شد. اگر مقدار آن INF باشد (چون allusers نامشخص است)، آنگاه AREA قرمز در آن زمان مشخص در نمودار پر خواهد شد.
AREA:allusers#0000FF:combined user count AREA:wrongdata#FF0000:unknown data
همان مثال، کاربردی همراه با دادههای پُشتهشده (STACKed):
اگر در مثال قبلی از stack استفاده کنید (کاری که من انجام میدهم)، مقادیر را با هم جمع نمیکنید. بنابراین، هیچ رابطهای میان این چهار مقدار وجود ندارد و شما یک مقدار واحد برای آزمودن دریافت نمیکنید. فرض کنید users3 در یک نقطه از زمان نامشخص باشد: users1 رسم میشود، users2 روی users1 پشته میشود، users3 نامشخص است و در نتیجه اتفاقی نمیافتد، users4 روی users2 پشته میشود. با این حال CDEFهای اضافی را اضافه کنید و از آنها برای همپوشانی روی نمودار «عادی» بهره ببرید:
DEF:users1=location1.rrd:onlineTS1:LAST DEF:users2=location1.rrd:onlineTS2:LAST DEF:users3=location2.rrd:onlineTS1:LAST DEF:users4=location2.rrd:onlineTS2:LAST CDEF:allusers=users1,users2,users3,users4,+,+,+ CDEF:wrongdata=allusers,UN,INF,UNKN,IF AREA:users1#0000FF:users at ts1 STACK:users2#00FF00:users at ts2 STACK:users3#00FFFF:users at ts3 STACK:users4#FFFF00:users at ts4 AREA:wrongdata#FF0000:unknown data
اگر دادههای نامشخصی در یکی از users1..users4 وجود داشته باشد، AREA مربوط به «wrongdata» رسم خواهد شد و از آنجا که از محور X آغاز میشود و ارتفاع بینهایت دارد، به طور مؤثری بخشهای STACKed را بازنویسی میکند.
اگر مایل باشید میتوانید دو خط CDEF را در یک خط ترکیب کنید (ما از «allusers» استفاده نمیکنیم). با این حال دلایل خوبی برای نوشتن دو CDEF وجود دارد:
- خوانایی اسکریپت را بهبود میبخشد.
- میتوان از آن در داخل GPRINT برای نمایش تعداد کل کاربران استفاده کرد.
اگر ترکیب آنها را برگزینید، میتوانید در CDEF دوم عبارت بعد از علامت مساوی از خط اول را جایگزین «allusers» کنید:
CDEF:wrongdata=users1,users2,users3,users4,+,+,+,UN,INF,UNKN,IF
اگر چنین کنید، دیگر قادر به استفاده از این GPRINTهای بعدی نخواهید بود:
COMMENT:"Total number of users seen" GPRINT:allusers:MAX:"Maximum: %6.0lf" GPRINT:allusers:MIN:"Minimum: %6.0lf" GPRINT:allusers:AVERAGE:"Average: %6.0lf" GPRINT:allusers:LAST:"Current: %6.0lf\n"
درجهٔ سلسیوس در برابر درجهٔ فارنهایت (Degrees Celsius vs. Degrees Fahrenheit)
برای تبدیل سلسیوس به فارنهایت از فرمول زیر استفاده کنید: F=9/5*C+32
rrdtool graph demo.png --title="Demo Graph" \ DEF:cel=demo.rrd:exhaust:AVERAGE \ CDEF:far=9,5,/,cel,*,32,+ \ LINE2:cel#00a000:"D. Celsius" \ LINE2:far#ff0000:"D. Fahrenheit\c"
این مثال DS با نام «exhaust» را از پایگاهدادهٔ «demo.rrd» دریافت کرده و مقادیر آن را در متغیر «cel» قرار میدهد. CDEF استفادهشده به شرح زیر ارزیابی میشود:
CDEF:far=9,5,/,cel,*,32,+ 1. push 9, push 5 2. push function "divide" and process it the stack now contains 9/5 3. push variable "cel" 4. push function "multiply" and process it the stack now contains 9/5*cel 5. push 32 6. push function "plus" and process it the stack contains now the temperature in Fahrenheit
تبدیل ناشناخته به صفر (Changing unknown into zero)
rrdtool graph demo.png --title="Demo Graph" \ DEF:idat1=interface1.rrd:ds0:AVERAGE \ DEF:idat2=interface2.rrd:ds0:AVERAGE \ DEF:odat1=interface1.rrd:ds1:AVERAGE \ DEF:odat2=interface2.rrd:ds1:AVERAGE \ CDEF:agginput=idat1,UN,0,idat1,IF,idat2,UN,0,idat2,IF,+,8,* \ CDEF:aggoutput=odat1,UN,0,odat1,IF,odat2,UN,0,odat2,IF,+,8,* \ AREA:agginput#00cc00:Input Aggregate \ LINE1:aggoutput#0000FF:Output Aggregate
این دو CDEF از چندین تابع ساخته شدهاند. تجزیهٔ آنها هنگام بررسی عملکردشان میتواند به درک بهتر کمک کند. با شروع از نخستین CDEF خواهیم داشت:
idat1,UN --> a 0 --> b idat1 --> c if (a) then (b) else (c)
بنابراین، اگر گزارهٔ برابری «idat1» با «UN» درست باشد، نتیجه «0» خواهد بود. در غیر این صورت، مقدار اصلی «idat1» به پشته بازگردانده میشود. بیایید این پاسخ را «d» بنامیم. این فرایند برای پنج مورد بعدی روی پشته نیز تکرار میشود؛ دقیقاً به همان شکل عمل کرده و پاسخ «h» را بازمیگرداند. بنابراین وضعیت پشته به «d,h» تبدیل میشود. اکنون عبارت به «d,h,+,8,*» ساده شده است و به روشنی میتوان دریافت که ما «d» و «h» را با هم جمع کرده و نتیجه را در هشت ضرب میکنیم.
نتیجهٔ نهایی این است که ما «idat1» و «idat2» را با یکدیگر جمع کردهایم و در طول این فرایند عملاً مقادیر ناشناخته (unknown) را نادیده گرفتهایم. نتیجه در هشت ضرب میشود، که به احتمال زیاد برای تبدیل بایت بر ثانیه (bytes/s) به بیت بر ثانیه (bits/s) است.
نمایش بینهایت (Infinity demo)
rrdtool graph example.png --title="INF demo" \ DEF:val1=some.rrd:ds0:AVERAGE \ DEF:val2=some.rrd:ds1:AVERAGE \ DEF:val3=some.rrd:ds2:AVERAGE \ DEF:val4=other.rrd:ds0:AVERAGE \ CDEF:background=val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF \ CDEF:wipeout=val1,val2,val3,val4,+,+,+,UN,INF,UNKN,IF \ AREA:background#F0F0F0 \ AREA:val1#0000FF:Value1 \ STACK:val2#00C000:Value2 \ STACK:val3#FFFF00:Value3 \ STACK:val4#FFC000:Value4 \ AREA:whipeout#FF0000:Unknown
این نمونه دو روش استفاده از بینهایت (INF) را نشان میدهد. درک آنچه در CDEF متغیر «background» رخ میدهد کمی دشوار است.
"val4,POP,TIME,7200,%,3600,LE,INF,UNKN,IF"
این عبارت RPN مقدار «val4» را به عنوان ورودی دریافت کرده و سپس بیدرنگ با استفاده از «POP» آن را از پشته برمیدارد. اکنون پشته خالی است، اما به عنوان یک اثر جانبی، زمان ثبت این نمونه مشخص میشود. این مقدار زمان توسط تابع «TIME» روی پشته قرار میگیرد.
عبارت «TIME,7200,%» باقیماندهٔ تقسیم زمان بر 7200 (که معادل دو ساعت است) را محاسبه میکند. مقدار حاصل روی پشته، عددی در بازهٔ 0 تا 7199 خواهد بود.
برای کسانی که با تابع باقیمانده (modulo) آشنا نیستند: این تابع، باقیمانده پس از یک تقسیم صحیح را نشان میدهد. اگر 16 را بر 3 تقسیم کنید، خارجقسمت 5 و باقیمانده 1 خواهد بود؛ بنابراین «16,3,%» مقدار 1 را بازمیگرداند.
اکنون نتیجهٔ «TIME,7200,%» را روی پشته داریم؛ بیایید آن را «a» بنامیم. بخش ابتدایی عبارت RPN تبدیل به «a,3600,LE» شده است که بررسی میکند آیا «a» کوچکتر یا مساوی با «3600» هست یا خیر. این شرط در نیمی از زمانها برقرار است. حال باید بقیهٔ عبارت RPN را پردازش کنیم که تنها یک تابع سادهٔ «IF» است و بسته به زمان، مقدار «INF» یا «UNKN» را برمیگرداند. این مقدار در متغیر «background» قرار میگیرد.
دومین CDEF پیشتر در این سند مورد بحث قرار گرفته است، بنابراین در اینجا دوباره به آن نمیپردازیم.
اکنون میتوانید لایههای گوناگون را رسم کنید. از پسزمینه آغاز کنید که یا ناشناخته (چیزی برای نمایش نیست) یا بینهایت (تمام بخش مثبت نمودار پر میشود) است.
سپس دادهها را روی این پسزمینه رسم میکنید، به طوری که روی پسزمینه قرار میگیرند. فرض کنید یکی از مقادیر val1..val4 ناشناخته باشد؛ در چنین حالتی تنها سه میله روی یکدیگر انباشته میشوند. شما تمایلی به دیدن این وضعیت ندارید زیرا دادهها تنها زمانی معتبرند که هر چهار متغیر معتبر باشند. به همین دلیل از دومین CDEF استفاده میکنید؛ این دستور دادهها را با یک AREA میپوشاند تا دادهها دیگر دیده نشوند.
اگر دادههای شما مقادیر منفی نیز داشته باشند، باید نیمهٔ دیگر نمودار خود را نیز بپوشانید. این کار را میتوان به روشی نسبتاً ساده انجام داد: تنها کافی است از متغیر «wipeout» استفاده کرده و یک علامت منفی در آن ضرب کنید: “CDEF:wipeout2=wipeout,-1,*”.SS "فیلتر کردن دادهها (Filtering data)" شما میتوانید فیلترهای پیچیدهای را روی دادهها اعمال کنید:
MEDIAN FILTER: filters shot noise DEF:var=database.rrd:traffic:AVERAGE CDEF:prev1=PREV(var) CDEF:prev2=PREV(prev1) CDEF:median=var,prev1,prev2,3,SORT,POP,EXC,POP LINE3:median#000077:filtered LINE1:prev2#007700:'raw data' DERIVATE: DEF:var=database.rrd:traffic:AVERAGE CDEF:prev1=PREV(var) CDEF:time=var,POP,TIME CDEF:prevtime=PREV(time) CDEF:derivate=var,prev1,-,time,prevtime,-,/ LINE3:derivate#000077:derivate LINE1:var#007700:'raw data'
فعلاً ایدهای نیست (Out of ideas for now)
این سند بر اساس پرسشهایی ایجاد شده است که توسط خود من یا افراد دیگر در فهرست پستی RRDtool مطرح شده بودند. لطفاً اگر خطایی در آن یافتید یا در درک آن با مشکلی مواجه شدید، به من اطلاع دهید. اگر فکر میکنید بخشی باید اضافه شود، برای من ایمیل بفرستید: <alex@vandenbogaerdt.nl>
به یاد داشته باشید: عدم ارسال بازخورد به معنی عدم اعمال تغییرات است!
همچنین ببینید (SEE ALSO)
صفحات راهنمای RRDtool
نویسنده (AUTHOR)
Alex van den Bogaerdt <alex@vandenbogaerdt.nl>
| 2026-08-08 | 1.11.0 |