تزويد التطبيق بأدوات القياس
الإشارات الذهبية الأربع، واصطلاحات التسمية، والتكلفة الفعلية للقياس.
تزويد تطبيقك بأدوات القياس
يتسلّم فريق خدمةً لم يكتبها. نقطة الجمع موجودة، و/metrics تستجيب، وتحتوي على أربعمئة واثني عشر مقياسًا: عدّادات جامع القمامة (garbage collector)، وإحصاءات مجمّع الاتصالات، وأحجام الطوابير الداخلية لمكتبة HTTP، وأعمار الشهادات. ينقص شيء واحد فقط: عدد الطلبات التي تفشل. لا أحد يستطيع أن يجد في هذه القائمة إجابةً عن السؤال الذي تطرحه الإدارة التجارية منذ يومين. أدوات القياس موجودة، بل وافرة، لكنها عديمة الفائدة.
ورد الفعل المعاكس مكلف بالقدر نفسه. فتزويد كل دالة بأدوات القياس «تحسبًا» ينتج عشرات الآلاف من السلاسل، وفاتورة تخزين تتضاعف كل ربع سنة، وزمن استجابة إضافيًا يمكن قياسه على المسارات الساخنة. القياس المفيد عملية انتقاء لا استقصاء شامل: عدد قليل من المقاييس المختارة للإجابة عن أسئلة محددة خير من كل ما تعرف المكتبة العميلة إنتاجه افتراضيًا. يقدّم هذا الدرس معيار الانتقاء، وطريقة تسمية ما نحتفظ به، ومرتبة التكلفة التي نقبل بها.
كشف نقطة جمع
في Python، تكشف المكتبة الرسمية خادم HTTP مخصصًا، أو تندمج في تطبيقك القائم.
from prometheus_client import Counter, Histogram, Gauge, make_asgi_app
from fastapi import FastAPI, Request
import time
app = FastAPI()
# نقطة الجمع مركّبة على العملية نفسها، وبذلك تعكس المقاييس فعلًا
# النسخة المستعلَم عنها. أما وجود مجمِّع وسيط فسيشوّه القراءة.
app.mount("/metrics", make_asgi_app())
REQUETES = Counter(
"http_requests_total",
"Nombre total de requêtes HTTP traitées.",
["method", "route", "status"],
)
DUREE = Histogram(
"http_request_duration_seconds",
"Durée de traitement des requêtes HTTP.",
["method", "route"],
# شرائح مواءمة لالتزام الخدمة (95 % دون 500 ms)،
# لا لمتوالية زخرفية من قوى العشرة.
buckets=(0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0),
)
EN_VOL = Gauge("http_requests_in_flight", "Requêtes en cours de traitement.")
@app.middleware("http")
async def mesurer(request: Request, call_next):
# route.path هو القالب (/commandes/{id})، وليس العنوان الفعلي أبدًا:
# استخدام request.url.path سيُنشئ سلسلة لكل معرّف طلب.
gabarit = request.scope.get("route").path if request.scope.get("route") else "inconnu"
debut = time.perf_counter()
EN_VOL.inc()
try:
reponse = await call_next(request)
statut = reponse.status_code
return reponse
except Exception:
statut = 500
raise
finally:
EN_VOL.dec()
DUREE.labels(request.method, gabarit).observe(time.perf_counter() - debut)
REQUETES.labels(request.method, gabarit, statut).inc()
التعليقات
التعليقات مدعومة عبر GitHub Discussions
سجّل الدخول باستخدام GitHub للمشاركة في النقاش