گزارش ظرفیت وقتی مفیدتر می‌شود که بتواند وضعیت را هم مشخص کند. در این شب با شرط، مصرف دیسک را دسته‌بندی می‌کنیم و با حلقه، اطلاعات چند سرویس را پردازش می‌کنیم. پیش‌نیاز، دریافت ورودی و تبدیل عدد در شب اول است. در پایان برنامه‌ای دارید که چند نتیجهٔ HTTP را می‌گیرد و تعداد سرویس‌های سالم و ناسالم را گزارش می‌دهد.

شرط یعنی انتخاب مسیر اجرا#

واژهٔ if یک عبارت را بررسی می‌کند؛ اگر درست باشد، بدنهٔ تورفتهٔ آن اجرا می‌شود. elif شرط بعدی را فقط زمانی می‌سنجد که شرط قبلی برقرار نباشد. else به شرط تازه‌ای نیاز ندارد و مسیر باقی‌مانده است. پایتون محدودهٔ هر بدنه را با تورفتگی مشخص می‌کند. در این دوره برای هر سطح چهار فاصله بگذارید و فاصله و Tab را مخلوط نکنید.

در ~/python-backend/night-02 فایلی به نام storage_health.py بسازید. کد زیر ادامهٔ گزارش قبلی است؛ هنوز فرض می‌کنیم ورودی‌ها عددی و ظرفیت‌ها معتبرند. پیش از محاسبه باید فضای کل مثبت باشد؛ برای تمرین از کل 1000 و مصرف 820 استفاده کنید. در شب چهارم خود برنامه ورودی نامعتبر را تشخیص خواهد داد.

mkdir -p ~/python-backend/night-02
cd ~/python-backend/night-02
server = input("Server name: ")
total_gb = int(input("Total storage in GB: "))
used_gb = int(input("Used storage in GB: "))
usage = used_gb / total_gb * 100
free_gb = total_gb - used_gb

if usage >= 90:
    health = "CRITICAL"
elif usage >= 75:
    health = "WARNING"
else:
    health = "HEALTHY"

print(f"Server: {server}")
print(f"Used: {round(usage, 1)}%")
print(f"Free: {free_gb} GB")
print(f"Status: {health}")
python3 storage_health.py

برای دادهٔ نمونه، مصرف 82.0% و وضعیت WARNING انتظار می‌رود. بررسی را با مقدارهای مرزی ادامه دهید: مصرف 750 از 1000 باید هشدار و مصرف 900 باید بحرانی شود. در مقابل 749 هنوز سالم است. چنین بررسی‌هایی روشن می‌کنند علامت برابری در آستانه واقعاً در کدام شاخه قرار دارد.

علامت >= به معنی بزرگ‌تر یا مساوی است؛ > برابری را شامل نمی‌شود. == برابری و != نابرابری را می‌سنجد. علامت تک = همچنان فقط برای نسبت‌دادن مقدار است. وقتی نتیجه برخلاف انتظار بود، علاوه بر مقدار ورودی، خود عملگر را بررسی کنید؛ جابه‌جایی > و >= دقیقاً رفتار مرز را تغییر می‌دهد.

ترتیب شاخه‌ها هم بخشی از منطق است. مقدار 95 هم از 75 بزرگ‌تر است و هم از 90. اگر نخست شرط usage >= 75 نوشته شود، همان شاخه اجرا می‌شود و شرط بحرانی دیگر بررسی نمی‌شود. در این زنجیره فقط نخستین شاخهٔ مطابق اجرا می‌شود. این قاعده با چند if مستقل فرق دارد؛ چند شرط مستقل ممکن است همگی بدنهٔ خود را اجرا کنند.

ترکیب چند معیار#

عملگر and برقرار بودن هر دو شرط را می‌خواهد و or برقرار بودن دست‌کم یکی را کافی می‌داند. not درستی یک مقدار را برعکس می‌کند. مثلاً وضعیت پاسخ 200 همراه با زمان کمتر از 500 را می‌توان با status_code == 200 and response_ms < 500 نوشت. با این حال همیشه یک عبارت بلند بهترین انتخاب نیست؛ چند شاخهٔ کوتاه می‌تواند اولویت تصمیم را روشن‌تر نشان دهد.

تمرین service_health.py را با همین نگاه بنویسید. نام سرویس، کد پاسخ عدد صحیح و زمان پاسخ عدد اعشاری را بگیرید. نخست هر کد غیر از 200 را DOWN حساب کنید. سپس زمان حداقل 1000 را DEGRADED، زمان حداقل 300 را SLOW و بقیه را HEALTHY بدانید. این یک قرارداد آموزشی است؛ در سرویس واقعی، کدهای قابل قبول و آستانه‌ها به قرارداد همان سرویس بستگی دارند.

کدزمان به میلی‌ثانیهنتیجهٔ مورد انتظار
20080HEALTHY
200450SLOW
50320DOWN
2001300DEGRADED

پاسخ سریع 503 در قرارداد ما سالم نیست. این مثال نشان می‌دهد چرا ترتیب معیارها باید پیش از نوشتن شرط تعیین شود. همچنین زمان را پیش از دسته‌بندی گرد نکنید؛ گردکردن برای نمایش است و می‌تواند مقداری نزدیک مرز را به دستهٔ دیگری ببرد. وضعیت را از مقدار اصلی بسازید و سپس عدد نمایشی را آماده کنید.

حلقه برای تکرار یک کار#

حلقهٔ for بدنه را برای هر عضو یک دنباله اجرا می‌کند. تابع range(1, 4) سه عدد 1، 2 و 3 تولید می‌کند؛ انتهای بازه داخل آن نیست. این تفاوت میان «مقدارهای تولیدشده» و «تعداد مقدارها» را به خاطر بسپارید. اگر کاربر تعداد تلاش را سه انتخاب کند، برای شماره‌گذاری از یک باید انتها را چهار قرار دهیم.

در فایل retry_plan.py مثال زیر را اجرا کنید. این برنامه فقط برنامهٔ تلاش‌ها را چاپ می‌کند و واقعاً سرویس را بررسی نمی‌کند؛ فعلاً درخواست شبکه یا مکث میان تلاش‌ها نداریم. هدف، فهم تکرار و شمارنده است و نباید پیام چاپی را با نتیجهٔ پایش واقعی اشتباه گرفت.

service = input("Service name: ")
max_attempts = int(input("Maximum attempts: "))
attempts_run = 0

for attempt in range(1, max_attempts + 1):
    print(f"Attempt {attempt}: checking {service}")
    attempts_run += 1

print(f"Completed {attempts_run} attempts")

عبارت attempts_run += 1 یک واحد به شمارنده اضافه می‌کند. شمارنده باید پیش از حلقه مقدار اولیه داشته باشد. اگر آن را داخل حلقه صفر کنید، در هر دور نتیجهٔ دورهای قبلی از بین می‌رود. اگر اصلاً مقدار اولیه ندهید، هنگام استفاده با NameError روبه‌رو می‌شوید. جای دستور نسبت به تورفتگی در اینجا مستقیماً بر نتیجه اثر می‌گذارد.

تمرین گزارش گروهی#

فایل batch_health.py باید تعداد سرویس‌ها را بگیرد و به همان تعداد نام و کد پاسخ دریافت کند. پیش از حلقه دو شمارندهٔ healthy_count و down_count را صفر کنید. داخل هر دور، کد 200 را سالم و سایر کدها را ناسالم حساب کنید و شمارندهٔ مربوط را افزایش دهید. نتیجهٔ هر سرویس همان لحظه چاپ شود؛ گزارش مجموع پس از پایان حلقه قرار بگیرد.

با دو ورودی nexus: 200 و graylog: 503، مجموع دو، سالم یک و ناسالم یک انتظار می‌رود. با سه کد 200، تعداد ناسالم باید صفر بماند. حالت تعداد صفر را هم امتحان کنید؛ حلقه اجرا نمی‌شود ولی گزارش نهایی باید صفرها را نشان دهد. تعداد منفی را فعلاً وارد نکنید؛ بعداً اعتبارسنجی دامنه را اضافه می‌کنیم.

برای یافتن خطا، تعداد کل را با مجموع دو شمارنده مقایسه کنید. اگر برابر نیستند، بررسی کنید افزایش شمارنده داخل شاخهٔ صحیح قرار دارد و صفرکردن فقط یک بار انجام می‌شود. اگر گزارش نهایی چند بار چاپ می‌شود، احتمالاً آن را داخل حلقه گذاشته‌اید. برای خودآزمایی، بدون اجرای کد توضیح دهید range(1, 4) چه مقدارهایی دارد و چرا شرط بحرانی باید پیش از هشدار بررسی شود.

برای دیدن مرز حلقه و شرط، برنامه را با یک سرویس ناسالم اجرا و مسیر اجرای هر خط را روی کاغذ دنبال کنید. مقدار اولیهٔ هر شمارنده صفر است، یک دور ورودی گرفته می‌شود و فقط شاخهٔ ناسالم آن را افزایش می‌دهد. پس از خروج از حلقه، مجموع باید یک باشد. این دنبال‌کردن دستی برای نمونهٔ کوچک، روش مناسبی برای پیدا کردن تورفتگی اشتباه است و پیش از افزودن ورودی‌های بیشتر فهم مسئله را ساده می‌کند.

منابع#