Continuous Profiling и eBPF в production
Зачем знать на Middle 3: Production-сервисы редко падают из-за очевидных багов — они деградируют постепенно: utility-функция стала медленнее на 5%, GC начал работать чаще, новая зависимость съела +200 МБ памяти. Без continuous profiling эти проблемы заметит клиент, а не вы. На уровне Senior нужно: разворачивать Pyroscope/Parca, читать flame graph diff, понимать архитектуру eBPF-агентов, ловить регрессии в CI до merge.
Содержание
Заголовок раздела «Содержание»- Концепция continuous profiling
- Глубже / production-практики (Pyroscope, Parca, eBPF, pprof labels)
- Gotchas
- Real cases
- Вопросы (25)
- Practice
- Источники
1. Концепция
Заголовок раздела «1. Концепция»1.1 Что такое continuous profiling
Заголовок раздела «1.1 Что такое continuous profiling»Классический подход: разработчик ловит проблему в production, идёт по SSH, делает curl /debug/pprof/profile, скачивает, смотрит в go tool pprof. Проблема: к моменту, когда заметили, инцидент уже шёл часами. И нет baseline — что было «до».
Continuous profiling — это профилирование всех инстансов сервиса непрерывно, 24/7, с низким overhead, с долгосрочным хранением и UI для сравнения.
Ключевые свойства:
- Production-safe sampling: типично 100 Гц CPU profile = ~1% overhead.
- Сравнение во времени: сегодня vs вчера, до релиза vs после.
- Сравнение между инстансами: один pod аномально медленный? Сравните его профиль с соседними.
- Долгосрочное хранение: 30–90 дней горячих данных, дольше в cold storage.
- Низкая стоимость: компрессия профилей, дельта-encoding.
1.2 Зачем нужно
Заголовок раздела «1.2 Зачем нужно»Реальные сценарии:
- Регрессия после релиза. Деплоили в 14:00, через час latency p99 вырос с 50 мс до 80 мс. Diff-flame graph: новая JSON-библиотека добавила allocation в hot path.
- Сезонный leak. Память pod растёт с 200 МБ до 1.5 ГБ за 7 дней.
inuse_spacediff между snapshots: один из cache TTL не работает. - Costlysearch on Sunday. Один customer запускает огромный отчёт раз в неделю. Профиль показывает 90% CPU в SQL-парсере.
- Compare across versions. Развернули PR в canary 5% — профиль canary против stable: hot path функция стала на 20% медленнее.
1.3 Архитектура
Заголовок раздела «1.3 Архитектура»┌──────────┐ ┌──────────┐ ┌──────────┐│ Pod A │ │ Pod B │ │ Pod C │ ← target apps└────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ │ pprof │ │ ▼ ▼ ▼┌─────────────────────────────────────┐│ Profiling Agent (sidecar / DaemonSet)││ - scrapes /debug/pprof/profile ││ - or eBPF kernel sampling ││ - tags: service, version, env │└──────────────┬──────────────────────┘ │ push (gzip+protobuf) ▼┌─────────────────────────────────────┐│ Profiling Backend (Pyroscope / Parca)││ - hot storage: ScyllaDB / TimescaleDB││ - cold storage: Parquet on S3 ││ - dedupe stack traces (symbol cache)│└──────────────┬──────────────────────┘ │ ▼ ┌──────────────┐ │ UI / API │ flame graphs, diff, top │ Grafana │ └──────────────┘Push vs pull: Pyroscope исторически был push (агент кидает в сервер), сейчас поддерживает оба. Parca — pull (Prometheus-style scraping).
1.4 Sampling rate
Заголовок раздела «1.4 Sampling rate»- CPU profiling: 100 Гц = 100 samples/sec. Overhead ~0.5–1.5%. Это де-факто стандарт.
- Memory (alloc) profiling: каждый 512 КБ allocation (
runtime.MemProfileRate). Можно регулировать. - Block / mutex profiling: по умолчанию выключено, надо включать (overhead значительный).
- Goroutine profiling: snapshot всех goroutine — overhead зависит от их количества.
2. Глубже / production-практики
Заголовок раздела «2. Глубже / production-практики»2.1 pprof labels (Go 1.9+)
Заголовок раздела «2.1 pprof labels (Go 1.9+)»Главная фича для production. Позволяет тегировать профилирующие данные:
import ( "context" "runtime/pprof")
func handleRequest(ctx context.Context, req *Request) { labels := pprof.Labels( "endpoint", req.Endpoint, "tenant", req.TenantID, "method", req.Method, ) pprof.Do(ctx, labels, func(ctx context.Context) { // вся работа в этом блоке будет помечена labels processRequest(ctx, req) })}В UI можно фильтровать flame graph по tenant=acme, endpoint=/api/v2/orders. Это критично для multi-tenant сервисов: один tenant загружает CPU, а вы не знаете кто.
⚠️ Labels не работают для всех типов профилей. CPU profile — да. Heap (memory) labels требуют Go 1.21+ (runtime.SetCPUProfileRate + runtime.SetBlockProfileRate отдельно). Проверять документацию для своей версии Go.
⚠️ Labels добавляют overhead на сами вызовы pprof.Do — несколько сот наносекунд. В horror-сценарии — не оборачивайте каждый микро-вызов.
2.2 Включение pprof endpoint безопасно
Заголовок раздела «2.2 Включение pprof endpoint безопасно»import ( _ "net/http/pprof" // регистрирует handlers на DefaultServeMux "net/http")
func main() { // ⚠️ НИКОГДА не вешайте pprof на основной listener! go func() { // отдельный listener только для admin/internal http.ListenAndServe("127.0.0.1:6060", nil) }()
// основной API на другом порту http.ListenAndServe(":8080", apiMux)}В Kubernetes: pprof endpoint на 127.0.0.1:6060, агент пробрасывает порт изнутри пода. Никогда не экспонируйте 6060 наружу — /debug/pprof/profile?seconds=60 это бесплатный DoS-вектор.
2.3 Pyroscope (Grafana, OSS)
Заголовок раздела «2.3 Pyroscope (Grafana, OSS)»Архитектура:
- Pyroscope Server: HTTP API, хранение в собственном storage (forked Loki).
- Pyroscope Agents: Go-агент в коде или sidecar.
- Grafana plugin: визуализация.
Установка агента в Go-приложение:
import "github.com/grafana/pyroscope-go"
func main() { _, err := pyroscope.Start(pyroscope.Config{ ApplicationName: "order-service", ServerAddress: "http://pyroscope:4040", Logger: pyroscope.StandardLogger, Tags: map[string]string{ "env": "prod", "version": Version, "region": "eu-west-1", }, ProfileTypes: []pyroscope.ProfileType{ pyroscope.ProfileCPU, pyroscope.ProfileAllocObjects, pyroscope.ProfileAllocSpace, pyroscope.ProfileInuseObjects, pyroscope.ProfileInuseSpace, pyroscope.ProfileGoroutines, pyroscope.ProfileMutexCount, pyroscope.ProfileMutexDuration, pyroscope.ProfileBlockCount, pyroscope.ProfileBlockDuration, }, }) if err != nil { log.Fatal(err) }}Хранение Pyroscope (с версии 1.x): использует BoltDB-подобное хранилище плюс Parquet для долгосрочного. Сжатие через дедупликацию stack traces (одна и та же цепочка функций кодируется ID).
2.4 Parca (CNCF, eBPF-based)
Заголовок раздела «2.4 Parca (CNCF, eBPF-based)»Принципиальное отличие от Pyroscope: Parca-agent работает через eBPF и профилирует любые процессы на хосте, включая те, которые не имеют pprof-инструментации.
┌─────────────────────┐│ Linux Kernel ││ ┌──────────────┐ ││ │ eBPF program │ ←── perf_event_open (99 Hz)│ │ stack walker │ ││ └──────┬───────┘ │└─────────┼───────────┘ │ stacks ▼ ┌──────────────┐ │ Parca Agent │ (DaemonSet) │ - symbolize │ │ - dedupe │ └──────┬───────┘ │ push (gRPC) ▼ ┌──────────────┐ │ Parca Server │ └──────────────┘Stack unwinding для Go: исторически Go не использовал frame pointers, что делало kernel-level unwinding сложным (приходилось парсить .gopclntab или использовать DWARF). С Go 1.20+ frame pointers включены по умолчанию на amd64 и arm64 — это упростило eBPF профилирование.
⚠️ Если бинарь компилируется с -ldflags="-s -w", символы strip-нуты. Parca будет показывать адреса вместо имён функций, если debuginfod недоступен. Решение: хранить debuginfo отдельно, либо НЕ стрипать в проде (Go и так компактен).
2.5 Datadog Continuous Profiler (commercial)
Заголовок раздела «2.5 Datadog Continuous Profiler (commercial)»- Закрытый код, но богатый UI и интеграция с APM/logs.
- Использует pprof под капотом + дополнительные пробы.
- Тэгирование автоматическое через DD env vars.
- Цена: ~$2/host/month в нижнем тарифе, но за объём data может быть существенно больше.
Подключение тривиально:
import "gopkg.in/DataDog/dd-trace-go.v1/profiler"
func main() { err := profiler.Start( profiler.WithService("order-service"), profiler.WithEnv("prod"), profiler.WithVersion(Version), profiler.WithProfileTypes( profiler.CPUProfile, profiler.HeapProfile, profiler.MutexProfile, profiler.GoroutineProfile, ), ) if err != nil { log.Fatal(err) } defer profiler.Stop()}2.6 Polar Signals Cloud / New Relic / Google Cloud Profiler
Заголовок раздела «2.6 Polar Signals Cloud / New Relic / Google Cloud Profiler»- Polar Signals: commercial вариант Parca (от автора).
- New Relic: интегрирован с APM, less mature чем Datadog для Go.
- Google Cloud Profiler: бесплатный для проектов на GCP, использует свой агент, хранит в Cloud Storage.
2.7 Flame graph diff
Заголовок раздела «2.7 Flame graph diff»Это главная фишка для регрессий.
[BEFORE deploy v2.1.0] [AFTER deploy v2.1.0] [DIFF]
main.handle 50% main.handle 65% +15% 🔴├─ parseJSON 10% ├─ parseJSON 25% +15% 🔴├─ dbQuery 20% ├─ dbQuery 20% 0%└─ render 20% └─ render 20% 0%В UI: красный = новая версия медленнее, синий = быстрее, серый = без изменений. Вы за 30 секунд видите, что новая parseJSON — bottleneck. Идёте в коммит, который её менял.
2.8 Custom profile types
Заголовок раздела «2.8 Custom profile types»Стандартные:
cpu,heap,goroutine,block,mutex,threadcreate.
Можно добавлять свои через pprof.NewProfile:
var dbConnProfile = pprof.NewProfile("db_connections")
func acquireConn(ctx context.Context) (*sql.Conn, error) { conn, err := db.Conn(ctx) if err != nil { return nil, err } dbConnProfile.Add(conn, 1) // stack trace где взяли connection return conn, nil}
func releaseConn(conn *sql.Conn) { dbConnProfile.Remove(conn) conn.Close()}Если у вас «pool exhausted» — посмотрите dbConnProfile снимок и увидите, кто держит соединения.
2.9 eBPF profiling в деталях
Заголовок раздела «2.9 eBPF profiling в деталях»Что такое eBPF (extended Berkeley Packet Filter): это виртуальная машина внутри ядра Linux. Можно загрузить программу, которая срабатывает на kernel events (syscall, hardware interrupt, tracepoint), читает регистры, пишет в BPF map. Это безопаснее, чем kernel module — verifier проверяет программу перед загрузкой.
Профилирование через eBPF:
SEC("perf_event")int profile_cpu(struct bpf_perf_event_data *ctx) { u32 pid = bpf_get_current_pid_tgid() >> 32; if (!is_target_pid(pid)) return 0;
// получить kernel stack u32 kernel_stack_id = bpf_get_stackid(ctx, &stack_traces, 0); // получить user stack u32 user_stack_id = bpf_get_stackid(ctx, &stack_traces, BPF_F_USER_STACK);
struct key_t key = { .pid = pid, .kstack = kernel_stack_id, .ustack = user_stack_id }; increment_counter(&counts, &key); return 0;}User-space агент периодически читает counts map, символизирует stack IDs (через ELF parsing), отправляет в backend.
Преимущества eBPF:
- Не нужно модифицировать приложение.
- Работает для любого языка (Go, Rust, Python, Node, Java).
- Может профилировать ядро вместе с user-space.
- Низкий overhead (kernel-side).
Ограничения:
- Только Linux ≥ 4.9 (реально нужно ≥ 5.4 для современного API).
- Требует
CAP_BPF/CAP_SYS_ADMIN(privileged DaemonSet). - В Kubernetes managed (GKE, EKS) могут быть ограничения.
2.10 eBPF для Go-specific метрик
Заголовок раздела «2.10 eBPF для Go-specific метрик»Pixie (CNCF, ex-New Relic), Parca-agent могут делать:
- Goroutine count через чтение
runtime.allgsslice (нужны DWARF debug info или адреса из symbol table). - GC events через uprobe на
runtime.gcStart/runtime.gcMark. - Network syscalls per goroutine — связав syscall с current G через TLS register.
- HTTP server requests — uprobe на
net/http.(*ServeMux).ServeHTTP.
# Пример: Pixie скрипт для top endpoint per servicepx run px/http_data
# auto-instruments всё, что слушает HTTP, без рекомпиляции2.11 Pyroscope vs Parca: выбор
Заголовок раздела «2.11 Pyroscope vs Parca: выбор»| Аспект | Pyroscope | Parca |
|---|---|---|
| Подход | Pull/push, инструментация в коде | Pull, eBPF (whole-system) |
| Storage | Custom (forked Loki + Parquet) | FrostDB (Parquet-based) |
| UI | Standalone + Grafana plugin | Standalone + Grafana plugin |
| Owner | Grafana Labs | Polar Signals + CNCF Sandbox |
| Maturity | Production-ready (since 2020) | Production, но моложе |
| Стоимость | Self-hosted free, Grafana Cloud $$ | Self-hosted free, Polar Signals $$ |
| Go-friendly | Native pprof, простая интеграция | eBPF — zero code changes |
Рекомендация: если у вас Go-моноязычный стек — Pyroscope проще и зрелее. Если многоязычный + нужно профилировать legacy без рекомпиляции — Parca.
2.12 Cost considerations
Заголовок раздела «2.12 Cost considerations»Storage volume:
- Один pod: ~1–5 МБ профилей в час (после дедупликации).
- 100 pods × 24h × 30d = ~360 ГБ горячих данных.
- С Parquet + S3 cold tier: $0.023/ГБ ≈ $8/mo за 30 дней.
Но! Datadog/Grafana Cloud берут не за storage, а за host-month. Для 100 хостов это $200–500/mo.
Trade-off: self-hosted Pyroscope дешевле в обслуживании, но требует команду на ops.
2.13 Differential profiling в CI (block PRs with regression)
Заголовок раздела «2.13 Differential profiling в CI (block PRs with regression)»- name: Run benchmark + profile run: | go test -cpuprofile=cpu.pprof -bench=. ./...
- name: Compare with main run: | go tool pprof -base=main-cpu.pprof -top cpu.pprof > diff.txt
- name: Fail if regression > 10% run: | python check_regression.py diff.txt --threshold=0.10Более продвинуто: benchstat от Google.
# main branchgo test -bench=. -count=10 ./... > old.txt# PR branchgo test -bench=. -count=10 ./... > new.txtbenchstat old.txt new.txt# вывод: BenchmarkX 100ms ± 1% → 115ms ± 2% (+15%, p=0.000)CI блокирует merge если регрессия statistically significant.
2.14 Profiling в Kubernetes
Заголовок раздела «2.14 Profiling в Kubernetes»DaemonSet с Parca-agent:
apiVersion: apps/v1kind: DaemonSetmetadata: { name: parca-agent }spec: template: spec: hostPID: true containers: - name: parca-agent image: ghcr.io/parca-dev/parca-agent:latest securityContext: privileged: true args: - --remote-store-address=parca-server.observability:7070 - --node=$(NODE_NAME) volumeMounts: - name: sys, mountPath: /sys, readOnly: true - name: debugfs, mountPath: /sys/kernel/debug⚠️ privileged: true для eBPF — security review обязателен.
2.15 Профиль в проде vs тесты
Заголовок раздела «2.15 Профиль в проде vs тесты»| Где | Что измеряет |
|---|---|
| Unit test bench | Microbenchmark одной функции, идеальные условия |
| Load test (k6) | End-to-end под нагрузкой в staging |
| Continuous prod | Реальный трафик, реальные данные, реальные баги |
Все три нужны. Production profiling показывает то, что нельзя воспроизвести: настоящие данные клиентов, настоящий network noise, настоящие peak loads.
2.16 Block и mutex profile в деталях
Заголовок раздела «2.16 Block и mutex profile в деталях»Block profile записывает stack traces, где goroutine заблокировался на synchronization (channel send/receive, mutex, select, sync.Cond).
runtime.SetBlockProfileRate(1) // every blocking eventRate 1 означает «каждое событие блокировки длительностью > N ns записывается». Default — выключено (0).
Полезен для:
- Channel contention (продюсер быстрее consumer-а).
- Slow downstream calls, где goroutine ждёт ответа.
- Cond.Wait в кастомных primitive.
Mutex profile записывает stack traces holder-а mutex, когда другая goroutine ждала.
runtime.SetMutexProfileFraction(5) // 1 of 5 contention eventsHelps detect:
- Hot mutex (один lock держится долго).
- RWMutex с большим количеством writers.
- Map mutex contention.
⚠️ Не оставляйте включёнными в production без причины. Overhead 5–15%.
⚠️ Pyroscope/Datadog могут собирать эти profiles периодически (e.g., 30 sec each 5 min) для minimal overhead.
2.17 Goroutine profile в инцидент-режиме
Заголовок раздела «2.17 Goroutine profile в инцидент-режиме»curl -s http://service:6060/debug/pprof/goroutine?debug=2 > goroutines.txtdebug=2 даёт full text dump со stack traces всех goroutine. Если goroutine count внезапно вырос с 100 до 50K — это первый снимок для disgnose:
- 49K goroutine все в одной строке кода → leak.
- Все ждут одного mutex → contention.
- Все на
chan receive→ producer dead.
В UI Pyroscope: goroutine profile показывает аналогично — flame graph группирует goroutines по stack trace.
3. Gotchas
Заголовок раздела «3. Gotchas»⚠️ CPU profiling меняет hot path. SetCPUProfileRate(100) добавляет signal handler. Если ваше приложение делает 1М ops/sec, signal handling становится заметным.
⚠️ Mutex/block profiling DEFAULT-OFF. Их включение через runtime.SetMutexProfileFraction(5) (1 из 5 contention events) даёт overhead 5–15%. Не включайте всем в проде, только когда нужно расследование.
⚠️ Heap profile показывает live memory, не leak rate. inuse_space snapshot между двумя моментами — это diff живых объектов. Если у вас leak медленный, нужно ждать часы.
⚠️ alloc_space даёт total allocations. Если функция делает GC-friendly код (alloc → GC сразу), alloc_space будет огромным, но inuse_space маленьким. Не путать.
⚠️ PGO (Profile-Guided Optimization) с Go 1.21+ — это другая история. Профиль используется компилятором для inline решений. Это не continuous profiling, но source can be the same.
⚠️ Frame pointers и Go 1.20+. До 1.20 на Linux/amd64 frame pointers были выключены для производительности. eBPF профилирование Go было затруднено. В 1.20+ включены по умолчанию (есть -buildmode=pie или GOFLAGS=-ldflags=-fp=true — проверить актуальную форму для своей версии Go).
⚠️ Symbol stripping (-s -w) ломает eBPF профилирование. Если стрипали — нужен debuginfod-сервер с unstripped версией.
⚠️ Контейнеры и process namespace. eBPF profiler видит PID в хост-namespace, не в контейнере. Mapping PID → pod нужен через kube-api или CRI.
⚠️ Pyroscope label cardinality. Если ставите user_id как label с миллионом значений — storage эксплодирует. Используйте только bounded labels.
⚠️ Cold start профилирования. Pyroscope-агент в Go-приложении сам делает alloc при startup. Если у вас sub-second cold start — добавит 50–200 мс.
⚠️ Sampling bias. 100 Гц = 10 мс tick. Функция, которая работает 1 мс, может вообще не попасть в семплы или попасть случайно. Long-tail latency profiling требует block profiling.
4. Real cases
Заголовок раздела «4. Real cases»Case 1: Latency regression after dependency update
Заголовок раздела «Case 1: Latency regression after dependency update»Симптом: после обновления github.com/lib/pq на новую версию p99 latency для /api/orders вырос с 80 мс до 130 мс.
Расследование:
- Pyroscope flame graph diff между v2.4.0 (старый) и v2.4.1 (новый).
- В новой версии
pq.QueryContextстала тратить больше CPU на parsing protocol response. - Конкретно: дополнительная allocation per row в
(*conn).readPacket.
Fix: pinned lib/pq назад, открыл issue. Через 2 недели вышел fix upstream.
Без continuous profiling: пришлось бы воспроизводить локально, что для 80→130 мс на сложном query почти нереально.
Case 2: Memory leak через goroutine
Заголовок раздела «Case 2: Memory leak через goroutine»Симптом: pod рестартится по OOM каждые 6 часов. RSS растёт линейно.
Расследование:
- Goroutine profile показывает 50 000 goroutine в
runtime.gopark(заблокированы на channel). - Stack trace ведёт к
pubsub.Subscribe, который создаёт goroutine на каждый запрос, но не cancel-ит при таймауте. - Каждая goroutine держит 4 КБ stack + map entry.
Fix: добавили ctx.Done() в select внутри goroutine.
Case 3: Tenant-bound CPU hot spot
Заголовок раздела «Case 3: Tenant-bound CPU hot spot»Симптом: один из shared services периодически даёт CPU 90%, immediately возвращается к 30%. Не было ясно, кто.
Расследование: pprof labels tenant= уже стояли. Pyroscope с фильтром tenant=BIG_CORP показал: их background sync job (XML import 500 МБ) делает CPU spike. Конкретно: regex компилируется на каждой строке.
Fix: precompile regex outside loop. Tenant сообщили о rate limit на background jobs.
Case 4: GC overhead
Заголовок раздела «Case 4: GC overhead»Симптом: GC pause p99 = 50 мс на heavy load.
Расследование: heap profile показал, что 70% allocations — это []byte в JSON marshaling. С labels: hottest endpoint — /internal/sync который отдаёт 10 МБ JSON ответ.
Fix: switch to streaming JSON encoder. Allocations упали, GC pause → 5 мс.
Case 5: Differential profiling блокирует bad PR
Заголовок раздела «Case 5: Differential profiling блокирует bad PR»Setup: GitHub Actions запускает benchmark suite на каждом PR, сравнивает с main через benchstat.
Срабатывание: PR с рефакторингом cache layer. benchstat сравнение:
BenchmarkCacheGet 450ns ± 2% → 1.2µs ± 5% (+167%, p=0.000)PR заблокирован автоматически. Автор увидел отчёт, обнаружил, что заменил sync.Map на map+RWMutex в hot path.
5. Вопросы (25)
Заголовок раздела «5. Вопросы (25)»- Что такое continuous profiling и чем он отличается от ad-hoc pprof?
- Какой типичный overhead 100 Hz CPU profiling в Go?
- Что такое pprof labels и для чего они в multi-tenant сервисах?
- Как правильно exposить /debug/pprof в Kubernetes pod, не открывая security дыру?
- В чём разница inuse_space vs alloc_space в heap profile?
- Как сравнить heap snapshot до и после ожидаемого leak?
- Объясните архитектуру Pyroscope (компоненты, push vs pull).
- Чем Parca отличается от Pyroscope в подходе к сбору данных?
- Что такое eBPF и почему он подходит для профилирования?
- Как eBPF делает stack unwinding для Go-программ? Какие сложности?
- Какую роль играют frame pointers (Go 1.20+) в eBPF профилировании?
- Что произойдёт с eBPF профилированием, если бинарь скомпилирован с -ldflags=“-s -w”?
- Перечислите standard profile types в Go runtime/pprof.
- Как создать custom pprof profile для трекинга, например, открытых connections?
- Что такое flame graph diff и как читать красные/синие зоны?
- Опишите differential profiling в CI: что сравниваем, как блокируем регрессию.
- Чем benchstat помогает при benchmark-сравнениях?
- Какие label cardinality проблемы могут возникнуть в Pyroscope?
- Как Pixie делает auto-instrumentation HTTP requests без модификации кода?
- Если pod рестартится по OOM, какой профиль брать первым?
- Опишите trade-off self-hosted Pyroscope vs Datadog Profiler.
- Какие security implications у DaemonSet с CAP_BPF?
- Что такое sampling bias и как он влияет на анализ коротких функций?
- Mutex profile в проде включён по умолчанию? Каков overhead включения?
- Опишите кейс, когда continuous profiling спас от инцидента / нашёл проблему.
6. Practice
Заголовок раздела «6. Practice»Задача 1: Поднять локально Pyroscope (docker-compose), подключить агента в простой Go-сервис, сгенерировать нагрузку, увидеть flame graph.
Задача 2: Добавить pprof labels в HTTP-handler по endpoint и tenant, отфильтровать профиль по конкретному tenant в UI.
Задача 3: Написать GitHub Actions workflow, который запускает go test -bench=., сохраняет результаты, сравнивает с main через benchstat и fail-ит PR при регрессии > 10%.
Задача 4: Симулировать memory leak (бесконечный slice append без cleanup), снять heap profile через 1 минуту и через 10 минут, использовать pprof -base для diff и найти allocation site.
Задача 5: Включить block + mutex profiling, измерить overhead на realistic workload (например, wrk + сервис с RWMutex contention). Зафиксировать throughput до и после.
Задача 6 (advanced): Поднять Parca с eBPF-агентом, проверить, что профилирует Go-приложение без какой-либо инструментации. Сравнить fidelity с pprof-based профилем.
Задача 7: Реализовать custom profile (pprof.NewProfile("connection_acquire")) для трекинга, кто держит DB connections дольше 1 секунды.
7. Источники
Заголовок раздела «7. Источники»- Brendan Gregg, “BPF Performance Tools”, Addison-Wesley, 2019 — основополагающий труд по eBPF profiling.
- Brendan Gregg, “Systems Performance: Enterprise and the Cloud”, 2nd ed, 2020.
- The Go Blog, “Profile-guided optimization in Go 1.21”, https://go.dev/blog/pgo
- Liz Rice, “Learning eBPF”, O’Reilly, 2023.
- Pyroscope Documentation, https://grafana.com/docs/pyroscope/
- Parca Documentation, https://www.parca.dev/docs/
- Polar Signals Blog, “Eyes on production: continuous profiling”, 2022.
- Pixie Labs, “Auto-telemetry for Kubernetes”, https://docs.px.dev/
- Datadog Continuous Profiler Docs, https://docs.datadoghq.com/profiler/
- Frederic Branczyk (CNCF Parca maintainer), KubeCon talks 2022–2024.
- Russ Cox, “Profile-guided optimization preview”, Go Blog 2023.
- Felix Geisendörfer, “Differential profiling for Go in CI”, FOSDEM 2023.
- Go runtime source:
src/runtime/pproffor label propagation internals. - Bryan Boreham, “Continuous profiling at Grafana Labs”, PromCon EU 2023.
- Tatsuhiro Tsujikawa, “eBPF for Go developers”, GopherCon 2023.