PromQL زبان کوئری Prometheus است و تقریباً همهی کارهایی که با مانیتورینگ میکنید، داشبورد، هشدار و بررسی مشکل، از آن عبور میکند. این زبان با SQL فرق دارد و در نگاه اول عجیب است، ولی تعداد مفاهیم کلیدی آن کم است: انواع متریک، انتخابگر برچسب، rate برای counterها و تجمیع با sum by. در این راهنما همینها را با کوئریهایی که روزانه لازم میشود یاد میگیریم.
انواع متریک
| نوع | رفتار | مثال |
|---|---|---|
| Counter | فقط افزایش مییابد (با ریاستارت صفر میشود) | http_requests_total |
| Gauge | بالا و پایین میرود | node_memory_MemAvailable_bytes |
| Histogram | توزیع مقدارها در bucketها (برای تأخیر و حجم) | http_request_duration_seconds_bucket |
| Summary | چندکِ محاسبهشده در خود برنامه | rpc_duration_seconds{quantile="0.99"} |
نوع متریک تعیین میکند چه تابعی روی آن معنا دارد: روی counter همیشه rate یا increase میگیرید و روی gauge مقدار را مستقیم میخوانید.
انتخابگر و برچسبها
هر متریک چند سری زمانی با برچسبهای مختلف دارد. انتخابگر با آکولاد مشخص میکند کدامها:
http_requests_total # همهی سریها
http_requests_total{job="api"} # برابر
http_requests_total{status!="200"} # نابرابر
http_requests_total{status=~"5.."} # regex: کدهای 5xx
http_requests_total{status!~"2.."} # نقیض regex
عبارتهای regex در PromQL کاملاً لنگردار هستند (کل مقدار باید مطابقت کند)، و موتور آن RE2 است. اگر الگو را مطمئن نیستید، قبل از استفاده در هشدار آن را آزمایش کنید.
/.*/Regex Testerالگوی regex برای label matcher را روی نمونه مقدارها امتحان کنید (توجه: PromQL از RE2 استفاده میکند).rate و increase روی counter
مقدار خام counter فقط بالا میرود و معنایی ندارد. آنچه میخواهید «نرخ» است:
rate(http_requests_total[5m]) # درخواست در ثانیه، میانگین ۵ دقیقه
increase(http_requests_total[1h]) # تعداد درخواست در ساعت گذشته
sum(rate(http_requests_total[5m])) # نرخ کل همهی سریها
- بازهی داخل
[ ]را دستکم چهار برابر فاصلهی scrape بگیرید؛ با scrape هر ۱۵ ثانیه،[1m]کمترین حد است و[5m]رایج. rateریست counter (بعد از ریاستارت برنامه) را خودش جبران میکند.- برای نمودار در Grafana میتوانید از
$__rate_intervalاستفاده کنید تا بازه خودکار با بزرگنمایی تنظیم شود.
تجمیع با sum، avg و by
sum by (job) (rate(http_requests_total[5m])) # نرخ هر job
sum by (instance) (rate(http_requests_total{status=~"5.."}[5m]))
avg by (instance) (node_load1)
max by (mountpoint) (node_filesystem_size_bytes)
topk(5, sum by (instance) (rate(container_cpu_usage_seconds_total[5m])))
by فقط برچسبهای فهرستشده را نگه میدارد و بقیه را جمع میکند. without (…) برعکس عمل میکند. توابع پرکاربرد: sum، avg، min، max، count، topk، bottomk.
کوئریهای ضروری روزمره
درصد خطا
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
خروجی عددی بین ۰ و ۱ است؛ برای درصد در ۱۰۰ ضرب کنید. تقسیم دو بردار فقط وقتی کار میکند که برچسبهای هر دو طرف مطابقت داشته باشند؛ با تجمیع کامل (sum بدون by) این مشکل پیش نمیآید.
تأخیر p95 از histogram
histogram_quantile(
0.95,
sum by (le) (rate(http_request_duration_seconds_bucket[5m]))
)
برچسب le را حتماً نگه دارید؛ histogram_quantile بدون آن کار نمیکند. و برای تأخیر هرگز به میانگین اکتفا نکنید: میانگین، دم بلند توزیع را پنهان میکند، در حالی که کاربران ناراضی همان صدکهای بالا هستند. دقت p95 به مرزهای bucketها بستگی دارد؛ اگر bucket مناسب نزدیک هدف تأخیر شما نباشد، عدد تقریبی و گمراهکننده است.
میزبان
# درصد CPU مصرفی هر سرور
100 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100
# درصد حافظهی مصرفی
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
# فضای آزاد دیسک (به درصد)
node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}
/ node_filesystem_size_bytes{fstype!~"tmpfs|overlay"} * 100
# پیشبینی: دیسک تا ۴ ساعت دیگر پر میشود؟
predict_linear(node_filesystem_avail_bytes[1h], 4 * 3600) < 0
کانتینرها (cAdvisor)
sum by (name) (rate(container_cpu_usage_seconds_total{name!=""}[5m]))
sum by (name) (container_memory_working_set_bytes{name!=""})
در دسترسبودن
up == 0 # هدفهای در دسترسنبودن
avg_over_time(up[1d]) * 100 # درصد در دسترسبودن در ۲۴ ساعت
عملگرها، تطبیق برچسب و کوئریهای API
عملگرهای ریاضی و مقایسهای (+ - * / > < ==) بین دو بردار بر اساس برچسبهای یکسان عمل میکنند. اگر برچسبها فرق دارند از on(...) و ignoring(...) کمک بگیرید، و برای رابطهی یک به چند از group_left. عملگر مقایسهای بهصورت فیلتر عمل میکند: node_load1 > 4 فقط سریهایی را برمیگرداند که شرط را دارند؛ و همینطور هشدارها در Prometheus نوشته میشوند.
خروجی API با /api/v1/query یک JSON است که خواندنش آسان نیست؛ برای بررسی آن از ابزار فرمت JSON استفاده کنید، و timestampهای آن (ثانیهی Unix) را با مبدل تاریخ بخوانید:
curl -s 'http://localhost:9090/api/v1/query' --data-urlencode 'query=sum(rate(http_requests_total[5m]))'
{ }JSON Formatterپاسخ JSON کوئری API را فرمت کنید و ساختار result را ببینید.TSUnix Timestamp Convertertimestamp ثانیهای نتایج API را به تاریخ خوانا تبدیل کنید.
اشتباههای رایج
- گرفتن rate از gauge یا نگرفتن rate از counter.
- بازهی خیلی کوتاه نسبت به فاصلهی scrape، که نمودار خالی یا بریده میدهد.
- میانگینگرفتن از صدکها (مثلاً avg روی p95 چند سرور)؛ از ریاضی معتبر نیست. ابتدا histogram را تجمیع کنید، بعد
histogram_quantileبگیرید. - برچسبهای با تنوع بینهایت (userID، URL کامل) که تعداد سریها را منفجر میکنند.
- فراموشکردن فیلتر
fstypeدر متریکهای دیسک و شمارش tmpfs یا overlay.
جمعبندی و مطالعهی بعدی
برای شروع، چهار الگو را تمرین کنید: rate روی counter، sum by برای تجمیع، نسبت خطا و histogram_quantile برای تأخیر. بعد از آن، کوئریها را به قواعد هشدار تبدیل کنید؛ هشداردهی اصولی نشان میدهد کدام کوئریها ارزش هشداردادن دارند. اگر هنوز Prometheus ندارید، راهاندازی Prometheus و Grafana با Docker Compose را اجرا کنید.
پرسشهای متداول
فرق rate و irate چیست؟
rate میانگین نرخ افزایش را در کل پنجره حساب میکند و برای نمودار و هشدار مناسب است. irate فقط دو نمونهی آخر را میبیند و برای نمودارهای بسیار ریز و نوسانی کاربرد دارد؛ در هشدار از آن استفاده نکنید.چرا rate() روی gauge معنا ندارد؟
rate برای counterها طراحی شده و ریستشدن آنها (صفرشدن بعد از ریاستارت) را جبران میکند. روی gauge، تغییرهای کاهشی را بهعنوان ریست تفسیر میکند و نتیجه گمراهکننده میشود. برای gauge از deriv یا delta استفاده کنید.چرا نمودار من بریده یا خالی است؟
[…] از فاصلهی scrape کوچکتر است، یا سری در بازهی انتخابی نمونه ندارد (هدف پایین بوده)، یا برچسبها در دو طرف عملگر تطابق ندارند. با کوئری up وضعیت هدف را بررسی کنید.تفاوت sum by و sum without؟
by فقط برچسبهای فهرستشده را نگه میدارد؛ without برچسبهای فهرستشده را حذف و بقیه را نگه میدارد. اگر مجموعهی برچسبها ممکن است عوض شود، without مقاومتر است.