اختبار الأداء والتحميل

تحليل أداء التطبيقات (Profiling)

18 دقيقة الدرس 6 من 28

تحليل أداء التطبيقات (Profiling)

تُخبرك اختبارات الحمل بأن خدمتك بطيئة، أما تحليل الأداء (Profiling) فيُخبرك بالسبب. بدون هذا التحليل تصبح عملية التحسين مجرد تخمين: يقضي المهندسون أياماً في ضبط connection pools أو إضافة ذاكرة تخزين مؤقت، بينما دالة واحدة ساخنة في طبقة التطبيق تستهلك 60 % من كل دورة CPU. يهدف هذا الدرس إلى إيجاد تلك المسارات الساخنة بمنهجية علمية، على مستوى استدعاءات الدوال والتخصيص في الذاكرة، بنفس الانضباط الذي تطبّقه على تحليل معدل احتراق SLO.

المحوران: المعالج والذاكرة

كل تراجع في الأداء يقع على أحد محورين أو كليهما:

  • تحليل أداء المعالج (CPU Profiling) — يقيس أين يُقضى الوقت: أي الدوال تعمل على المعالج وكم من الوقت. يُلتقط بأخذ عينات من call stack بفاصل ثابت (مثلاً 100 Hz) أو بتطعيم كل دخول/خروج للدالة.
  • تحليل أداء الذاكرة (Memory Profiling) — يقيس أين تتم عمليات التخصيص: أي مسار استدعاء أطلق malloc أو ضغط على جامع القمامة. مفيد حين يكون العرض OOMKill أو تأخر GC مرتفعاً أو نمواً مستمراً في RSS (تسرب ذاكرة).

في بيئة الحاويات المكدّسة ستلجأ في الغالب إلى تحليل المعالج أولاً، لأن إشباع CPU هو السبب الرئيسي لتراجع الأداء في الخدمات المعتمدة على الحساب. يأتي تحليل الذاكرة لاحقاً حين تكشف مقاييس RSS أو GC من مكدس Prometheus الخاص بك عن شذوذ.

أخذ العينات مقابل التطعيم

محللات أداء أخذ العينات (perf، pprof في Go، async-profiler للـ JVM) تقاطع التنفيذ دورياً وتسجّل call stack الحالي. الأعباء الإضافية منخفضة (1–3 % من CPU) وآمنة للاستخدام المؤقت في الإنتاج. محللات الأداء بالتطعيم (JaCoCo، Python cProfile، Pyroscope بوضع eBPF) تُغلّف كل استدعاء دالة مما يعطي أعداداً دقيقة لكنه يضيف 10–40 % أعباء إضافية — احتفظ بها للبيئات التدريبية أو نوافذ الكناري الخاضعة للسيطرة.

قاعدة عمل في الإنتاج: محلل أداء بالعينات يعمل بـ 100 Hz لمدة 60 ثانية على pod واحد يضيف قرابة 1–2 % من أعباء CPU. هذا مقبول أثناء حادثة حية أو كناري خاضع للتحكم بنسبة 5 %. لا تُشغّل محلل أداء بالتطعيم على 100 % من حركة مرور الإنتاج.

مخططات اللهب: قراءة المسار الساخن

مخطط اللهب (Flame Graph، من اختراع Brendan Gregg) يضغط آلاف عينات المكدس في صورة واحدة. المحور الأفقي يمثّل عدد العينات (العرض = حصة الوقت)، والمحور الرأسي يمثّل عمق الاستدعاء (الأسفل = نقطة الدخول، الأعلى = الأوراق). الإطارات الأوسع في قمة البرج هي المسار الساخن — الكود الذي يستهلك أكبر وقت CPU دون استدعاء أي شيء آخر.

Flame Graph — CPU hot path visualisation Flame Graph — CPU Profile (60 s sample) Call Depth (stack frames) Sample count → time share (wider = more CPU time) main() / goroutine entrypoint http.HandleFunc → ServeHTTP() runtime.schedule / idle queryUserOrders() renderJSON() db.Query() — 42 % json.Marshal N+1 loop — 34 % net/tcp sql.Exec() HOT PATH Hot path (>30 % CPU) Warm path (10–30 %) Cold path (<10 %)
يكشف مخطط اللهب عن حلقة قاعدة بيانات N+1 تستهلك 34 % من المعالج — الإطار الأحمر الأعرض في قمة البرج هو هدف التحسين.

الرؤية الجوهرية: لا تُحسّن الإطارات العريضة في الأسفل (تلك هي موزّع الطلبات في إطارك البرمجي ولا يمكنك تغييرها). تُحسّن الإطارات العريضة في قمة أطول الأبراج، لأن تلك هي الدوال الورقية التي ينفّذها المعالج فعلياً.

Go: pprof في الإنتاج

تشحن Go مكتبة net/http/pprof مع المكتبة القياسية. استوردها وأظهر نقطة النهاية debug (خلف ingress داخلي أو network policy، وليس أبداً على المنفذ العام):

import _ "net/http/pprof" // registers /debug/pprof/* handlers // In your internal metrics mux (port 6060, not 8080): go func() { log.Fatal(http.ListenAndServe(":6060", nil)) }()

التقط ملف تعريف CPU لمدة 30 ثانية وأنشئ مخطط لهب محلياً:

# Port-forward to the pod under load kubectl port-forward pod/api-6d8f9b-xkz2p 6060:6060 -n production # Collect 30 s CPU profile go tool pprof -http=:9090 \ http://localhost:6060/debug/pprof/profile?seconds=30 # The browser opens an interactive flame graph at http://localhost:9090 # Switch to "Flame Graph" view; sort by "cum" (cumulative) for call chains
التحليل المستمر للأداء في الإنتاج: أدوات مثل Pyroscope (مفتوح المصدر) أو Google Cloud Profiler / Datadog Continuous Profiler تُشغّل عميل أخذ عينات منخفض الأعباء على كل pod طوال اليوم وتخزّن الملفات مفهرسةً برقم commit SHA والخدمة والوقت. حين يُطلق تنبيه SLO لتأخر الاستجابة، يمكنك مقارنة مخطط اللهب بين النشر الحالي والسابق — الإطار المتوسّع هو الانتكاسة.

JVM: async-profiler

يتجنّب async-profiler تحيّز safepoint في محللات أداء JVMTI (JProfiler، YourKit) باستخدام Linux perf_events أو AsyncGetCallTrace لأخذ عينات من الخيوط بغضّ النظر عن حالة safepoint في الـ JVM. هذه الأداة الصحيحة لتحليل الأداء في إنتاج JVM:

# Attach to a running JVM (PID auto-detected in a container via /proc) ./profiler.sh -d 30 -f /tmp/flamegraph.html \ -e cpu --jfrsync cpu \ $(jps | grep MyService | awk '{print $1}') # In Kubernetes: exec into the pod first kubectl exec -it svc-pod-abc123 -n production -- bash cd /opt/async-profiler-3.0 ./profiler.sh -d 30 -f /tmp/cpu.html -e cpu 1 # Copy out the HTML flame graph kubectl cp production/svc-pod-abc123:/tmp/cpu.html ./cpu.html

Linux: perf مع FlameGraph لـ Brendan Gregg

للعمليات الأصيلة (C، C++، Rust) أو مسارات kernel-space الساخنة (استدعاءات النظام، برامج eBPF)، يبقى perf المرجع الأساسي:

# Record 30 s, all CPUs, call graphs via frame pointers perf record -F 99 -a -g -- sleep 30 # Generate perf script output, fold stacks, render SVG perf script | stackcollapse-perf.pl | flamegraph.pl > cpu-flame.svg # For kernel + userspace combined (requires DWARF or frame pointers) perf record -F 99 --call-graph dwarf -p $(pgrep myservice) -- sleep 30 perf script | stackcollapse-perf.pl | flamegraph.pl \ --color=java --title="myservice 30s CPU" > combined-flame.svg
الرموز المفقودة في مخططات لهب perf هي أكثر مزالق الإنتاج شيوعاً. الحاويات المبنية بـ -O2 والثنائيات المُجرّدة تُنتج إطارات مُسمّاة [unknown]. الحلول: (1) قم بالترجمة مع -fno-omit-frame-pointer (تفعل Go هذا افتراضياً منذ 1.12؛ أضف -XX:+PreserveFramePointer للـ JVM)؛ (2) ثبّت حزم debuginfo؛ (3) استخدم محللات أداء مبنية على eBPF (Parca، Pyroscope بوضع eBPF) التي تحلّ الرموز من DWARF في sidecar دون لمس ثنائي التطبيق.

تحليل الذاكرة: إيجاد التسربات وضغط GC

حين يرتفع process_resident_memory_bytes في Prometheus بنسبة 2 % كل ساعة أو ترتفع jvm_gc_pause_seconds تحت الحمل، الجأ إلى تحليل التخصيص:

  • Go heap profile: curl localhost:6060/debug/pprof/heap > heap.pb.gz ثم go tool pprof -http=:9090 heap.pb.gz. تبديل بين "alloc_space" (إجمالي التخصيصات منذ البداية) و"inuse_space" (الكومة الحية الحالية) — inuse_space يكشف التسربات الحية، وalloc_space يكشف ضغط التخصيص المسبّب لارتفاع GC.
  • JVM: jcmd <pid> VM.native_memory summary للذاكرة خارج الكومة؛ async-profiler مع -e alloc لمخططات لهب التخصيص. تغذية مفرغات الكومة (jmap -dump:live) إلى Eclipse MAT للكشف عن التسربات.
  • Python: memray (من Bloomberg) يطعّم التخصيصات على مستوى C — memray run --live myservice.py أو memray attach <pid> لعملية قيد التشغيل.

التحليل في CI: بوابات الأداء

دمج تحليل الأداء في خط CI الخاص بك يمنع الانتكاسات من الوصول إلى الإنتاج. خطوة نموذجية في GitHub Actions بعد مهمة اختبار الحمل:

- name: Collect pprof profile during load test run: | # k6 load test runs in background (started by previous step) sleep 10 # let load ramp up go tool pprof -top -nodecount=20 \ http://localhost:6060/debug/pprof/profile?seconds=20 \ 2>&1 | tee pprof-top.txt # Fail if any single function exceeds 25% cumulative CPU HOT=$(grep -m1 "%" pprof-top.txt | awk '{print $3}' | tr -d '%') if [ "$HOT" -gt 25 ]; then echo "FAIL: hot function at ${HOT}% CPU — regression detected" exit 1 fi - name: Upload flame graph artifact uses: actions/upload-artifact@v4 with: name: cpu-flamegraph path: pprof-top.txt

طبقة الحكم الهندسي الأعلى

بيانات تحليل الأداء لا قيمة لها إلا بقدر الإجراء الذي تقود إليه. على مستوى كبار/موظفي الهندسة، الانضباط لا يقتصر على قراءة مخطط اللهب، بل على ترجمته إلى تغيير بتأثير مُتوقَّع ومقيس:

  1. الخط الأساسي أولاً. سجّل مخطط اللهب قبل أي تغيير. بدون خط أساسي لا يمكنك إثبات أن الإصلاح حسّن الإنتاجية فعلاً.
  2. متغيّر واحد في كل مرة. حسّن إطاراً ساخناً واحداً، أعد اختبار الحمل، أعد التحليل. الجمع بين تغييرات متعددة يجعل تحديد سبب الانتكاسة مستحيلاً.
  3. احذر التحسين المبكر الدقيق. دالة تستهلك 3 % من CPU وتستغرق ساعتين لإصلاحها صفقة سيئة. ركّز على الإطارات التي تتجاوز 10–15 % أولاً.
  4. الكمون مقابل الإنتاجية. التحسين على المعالج الذي يُنصّف استخدام CPU قد لا يُنصّف تأخر p99 إذا كان الاختناق في استدعاء خارجي — تحقق من آثار التتبع الموزّع من Jaeger/Tempo جانباً مع مخطط اللهب.
حلقة تحليل الأداء في شركات التكنولوجيا الكبرى: تحليل الأداء المستمر الآلي (Pyroscope / Parca منشوراً على كل كلاستر) يُظهر أعلى 5 مستهلكين للمعالج لكل خدمة في مراجعة هندسية أسبوعية. المهندسون المسؤولون عن الخدمات في القائمة الساخنة مُلزمون بتسجيل تذكرة وتخفيض التكلفة خلال سبرينتين. هذا يجعل الأداء مقياساً هندسياً من الدرجة الأولى مع الموثوقية.