Skip to content

Add pprof to ledger service - #8631

Open
zhangchiqing wants to merge 2 commits into
masterfrom
leo/add-pprof-tol-ledger-service
Open

Add pprof to ledger service#8631
zhangchiqing wants to merge 2 commits into
masterfrom
leo/add-pprof-tol-ledger-service

Conversation

@zhangchiqing

@zhangchiqing zhangchiqing commented Jul 30, 2026

Copy link
Copy Markdown
Member

Add the pprof feature to the ledger service through admin tool, so that we can better analyze the memory usage.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features
    • Added optional auto-profiling for the ledger service with configurable output directory, interval, and duration.
    • Added admin API commands to view and update profiler-related runtime configuration without restarting.
    • Added on-demand profiling endpoints for triggering runs and downloading heap profiles.
  • Documentation
    • Expanded ledger admin documentation with usage examples and details about enabling the admin server and accessing profiling endpoints (restricted to loopback).
  • Bug Fixes
    • Improved admin set-config error handling to return client errors for validation issues.

@zhangchiqing
zhangchiqing requested a review from a team as a code owner July 30, 2026 02:22
@github-actions

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The ledger service now initializes an auto-profiler, exposes runtime profiler settings through admin commands, restricts pprof access to loopback requests, and documents profiling operations.

Changes

Ledger profiler

Layer / File(s) Summary
Profiler initialization and runtime configuration
cmd/ledger/main.go, cmd/ledger/README.md
Adds profiling flags, creates the profiler and updatable configuration manager, registers runtime profiler settings, configures HTTP timeouts, and waits for profiler shutdown.
Admin profiler endpoints and configuration commands
cmd/ledger/admin.go, cmd/ledger/README.md, admin/README.md
Registers loopback-restricted pprof routes, adds get-config and set-config commands with validation and responses, and documents profiler usage through the admin server.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: alexhentschel, j1010001, janezpodhostnik

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding pprof support to the ledger service.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch leo/add-pprof-tol-ledger-service

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov-commenter

codecov-commenter commented Jul 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 118 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
cmd/ledger/main.go 0.00% 62 Missing ⚠️
cmd/ledger/admin.go 0.00% 56 Missing ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@admin/README.md`:
- Around line 94-105: Update the “Ledger service profiler” section in
admin/README.md to state that the ledger admin server is disabled by default
because --admin-addr defaults to an empty value, and add the required startup
example using --admin-addr=127.0.0.1:9003 before the curl commands.

In `@cmd/ledger/admin.go`:
- Around line 51-56: Update newAdminHandler so pprof handlers are not exposed
through the publicly bindable admin mux; either restrict the admin listener to
loopback or serve pprof through a separate authenticated/authorized listener,
while preserving the existing admin endpoints.

In `@cmd/ledger/main.go`:
- Line 304: Configure the shared adminServer HTTP server with ReadHeaderTimeout,
ReadTimeout, and IdleTimeout to bound slow admin and pprof connections, while
setting a sufficiently long WriteTimeout for intentional CPU/profile captures.
Update the server initialization associated with newAdminHandler without
changing the handler registrations.
- Around line 111-116: Synchronize access to currentBlockRate across the getter
and setter registered in RegisterUintConfig, protecting both dereference and
pointer replacement with the same mutex. Keep runtime.SetBlockProfileRate in the
setter and ensure concurrent get-config and set-config requests are race-free.
- Around line 75-80: Validate *profilerInterval before constructing
profiler.ProfilerConfig or calling profiler.New, rejecting values less than or
equal to zero through the command’s existing handled-error path; only create
autoProfiler after this validation succeeds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4bd7f2e6-c918-4b56-9ae0-e7d66dbcaa1e

📥 Commits

Reviewing files that changed from the base of the PR and between 73f2c26 and 6d17279.

📒 Files selected for processing (3)
  • admin/README.md
  • cmd/ledger/admin.go
  • cmd/ledger/main.go

Comment thread admin/README.md
Comment thread cmd/ledger/admin.go Outdated
Comment thread cmd/ledger/main.go
Comment thread cmd/ledger/main.go Outdated
Comment thread cmd/ledger/main.go
Comment thread cmd/ledger/admin.go
return
}
oldValue := field.Get()
if err := field.Set(configValue); err != nil {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Field.Set can fail with non-validation errors, e.g. TriggerRun returns "profiling is already in progress", which is server state, not a bad request. Check updatable_configs.IsValidationError(err) and return 400 only for validation errors, 500 otherwise, matching admin/commands/common/set_config.go.

Comment thread cmd/ledger/main.go Outdated
profilerDir = flag.String("profiler-dir", "profiler", "Directory to create auto-profiler profiles")
profilerInterval = flag.Duration("profiler-interval", 15*time.Minute, "Interval between auto-profiler runs")
profilerDuration = flag.Duration("profiler-duration", 10*time.Second, "Duration of each auto-profiler run")
profileUploaderEnabled = flag.Bool("profile-uploader-enabled", false, "Whether to upload profiles to a remote uploader (disabled for ledger service)")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this flag's only effect is a warning that it is ignored.

Comment thread cmd/ledger/main.go

// Initialize updatable config manager and auto-profiler.
// The profiler is configured via admin get-config/set-config commands.
configManager := updatable_configs.NewManager()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: profiler.New creates the profile directory and starts a goroutine before the required-address validation below can fatal. Move this block after that validation so misconfigured invocations have no side effects.

Comment thread cmd/ledger/main.go Outdated
Comment on lines +129 to +132
go func() {
<-autoProfiler.Ready()
logger.Info().Bool("enabled", autoProfiler.Enabled()).Msg("auto-profiler ready")
}()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: autoProfiler.Done() is never awaited at shutdown, so an in-flight profile run is truncated at exit. The goroutine is also unnecessary: with no readiness checks, Ready() closes almost immediately.

Comment thread admin/README.md Outdated
```

### Ledger service profiler
The standalone ledger service (`cmd/ledger`) exposes the same profiler configuration through its admin server (default port `9003`). It also registers the `net/http/pprof` endpoints on the admin server for on-demand profiling.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: cmd/ledger/README.md documents the admin commands and flags but was not updated with the new profiler flags and the get-config/set-config commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cmd/ledger/admin.go (1)

129-162: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Protect profiler configuration commands from remote callers.

The README shows --admin-addr bound to 0.0.0.0, while /admin/run_command has no access control. A remote client can invoke set-config to enable profiling, trigger captures, or increase runtime profiling overhead. Restrict get-config/set-config to loopback or require authenticated admin access.

As per coding guidelines, “treat all inputs as potentially byzantine.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ledger/admin.go` around lines 129 - 162, Restrict the get-config and
set-config branches in the admin command handler, including the set-config flow
around h.configManager.GetField, to loopback callers or authenticated admin
requests before reading or mutating configuration. Reject unauthorized remote
requests with the existing HTTP error mechanism, while preserving access for
trusted local or authenticated admin clients.

Source: Coding guidelines

🧹 Nitpick comments (1)
cmd/ledger/admin.go (1)

40-57: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Add regression coverage for the new admin boundary and config paths.

Patch coverage is 0% for this file. Add table-driven tests for loopback rejection/acceptance and get-config/set-config success, validation-error, and internal-error responses.

Also applies to: 116-163

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ledger/admin.go` around lines 40 - 57, Add table-driven regression tests
covering requireLoopback rejection of non-loopback and malformed RemoteAddr
values, acceptance of loopback requests, and the get-config/set-config handlers’
success, validation-error, and internal-error responses. Exercise the existing
handler and configuration symbols directly, asserting HTTP status codes and
relevant response bodies so the new admin boundary and config paths are covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/ledger/main.go`:
- Around line 384-386: Update the auto-profiler startup in New to launch
runForever through p.unit.Launch instead of a bare goroutine, ensuring
AutoProfiler.Done waits for the worker and any active trace to finish; leave the
shutdown await in cmd/ledger unchanged.

---

Outside diff comments:
In `@cmd/ledger/admin.go`:
- Around line 129-162: Restrict the get-config and set-config branches in the
admin command handler, including the set-config flow around
h.configManager.GetField, to loopback callers or authenticated admin requests
before reading or mutating configuration. Reject unauthorized remote requests
with the existing HTTP error mechanism, while preserving access for trusted
local or authenticated admin clients.

---

Nitpick comments:
In `@cmd/ledger/admin.go`:
- Around line 40-57: Add table-driven regression tests covering requireLoopback
rejection of non-loopback and malformed RemoteAddr values, acceptance of
loopback requests, and the get-config/set-config handlers’ success,
validation-error, and internal-error responses. Exercise the existing handler
and configuration symbols directly, asserting HTTP status codes and relevant
response bodies so the new admin boundary and config paths are covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5f66dda7-7c15-4d4f-af5c-255e7785afe5

📥 Commits

Reviewing files that changed from the base of the PR and between 6d17279 and aa3e283.

📒 Files selected for processing (4)
  • admin/README.md
  • cmd/ledger/README.md
  • cmd/ledger/admin.go
  • cmd/ledger/main.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • admin/README.md

Comment thread cmd/ledger/main.go
Comment on lines +384 to +386
logger.Info().Msg("shutting down auto-profiler...")
<-autoProfiler.Done()
logger.Info().Msg("auto-profiler stopped")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
fd -a 'main.go|component.go|profiler.go|profiler' . | sed 's#^\./##' | head -80

echo "== cmd/ledger/main.go outline around shutdown =="
if [ -f cmd/ledger/main.go ]; then
  wc -l cmd/ledger/main.go
  sed -n '340,410p' cmd/ledger/main.go | nl -ba -v340
fi

echo "== locate AutoProfiler and profiler implementation =="
rg -n "type .*AutoProfiler|func .*AutoProfiler|AutoProfiler\.Done|runForever|runOnce|Launch|unit\.Launch|func .*Done" . -S --glob '*.go' | head -200

Repository: onflow/flow-go

Length of output: 1959


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== candidate files =="
git ls-files | rg 'module/component/component\.go|cmd/ledger/main\.go|.*profiler\.go$' | sort

echo "== component interface =="
if [ -f module/component/component.go ]; then
  sed -n '1,220p' module/component/component.go | nl -ba
fi

echo "== auto-profile lifecycle in cmd/ledger/main.go =="
python3 - <<'PY'
from pathlib import Path
p=Path('cmd/ledger/main.go')
if p.exists():
    lines=p.read_text().splitlines()
    targets=['NewAutoProfiler','StartAutoProfiler','AutoProfiler','Done()']
    for needle in targets:
        for i,l in enumerate(lines,1):
            if needle in l:
                start=max(1,i-20); end=min(len(lines),i+40)
                print(f'\n-- {p}:{i} containing {needle} --')
                for n in range(start,end+1):
                    print(f'{n:4}: {lines[n-1]}')
PY

echo "== profiler source =="
for f in $(git ls-files | rg '(^|/)profiler\.go$|auto.*profiler\.go$' | head -20); do
  echo "\n== $f =="
  wc -l "$f"
  ast-grep outline "$f" --view expanded 2>/dev/null || true
  sed -n '1,260p' "$f" | nl -ba
done

Repository: onflow/flow-go

Length of output: 317


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== cmd/ledger/main.go relevant sections =="
awk 'NR>=340 && NR<=410 { printf("%4d: %s\n", NR, $0) }' cmd/ledger/main.go

echo "== module/component/component.go =="
awk '{ printf("%4d: %s\n", NR, $0) }' module/component/component.go

echo "== profiler implementation =="
awk '{ printf("%4d: %s\n", NR, $0) }' module/profiler/profiler.go

echo "== references to NewAutoProfiler, StartAutoProfiler, AutoProfiler.Done =="
rg -n "NewAutoProfiler|StartAutoProfiler|AutoProfiler\.Done|runForever|runOnce|Launch\(.*run|unit\.Launch|Done \(\)" cmd/ledger/main.go module/profiler/profiler.go module/component/component.go -S

Repository: onflow/flow-go

Length of output: 27351


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== unit implementation =="
git ls-files 'module/unit/*.go' 'module/engine/*.go' | rg 'unit|engine' | sort
for f in $(git ls-files 'module/unit/*.go' 'module/engine/*.go' | rg 'unit|engine' | sort); do
  echo "\n== $f =="
  ast-grep outline "$f" --view expanded 2>/dev/null || true
  sed -n '1,260p' "$f" | awk '{ printf("%4d: %s\n", NR, $0) }'
done

echo "== NewAutoProfiler and AutoProfiler usages =="
rg -n "NewAutoProfiler|AutoProfiler|RunComponent|ComponentManagerBuilder|Component" cmd module engine -S --glob '*.go' | head -250

Repository: onflow/flow-go

Length of output: 178


🏁 Script executed:

#!/bin/bash
set EUo pipefail

echo "== files containing unit implementation/imports =="
rg -n "type Unit|func .*Unit|func \\(.*\\) Launch|func NewUnit|Shutdown\\(\\).*Done|Done\\(\\).*Quit|Quit\\(\\)" . -S --glob '*.go' | head -200

echo "== go.mod dependencies for unit definitions (if present) =="
rg -n "module|go " go.mod | head -20

echo "== focused grep for NewUnit definitions =="
rg -n "func NewUnit\\(" . -S --glob '*.go'
rg -n "type Unit struct" . -S --glob '*.go'
rg -n "func \\(.*Unit.*\\) Launch|func \\([^ ]*\\) Launch\\(" . -S --glob '*.go'

echo "== NewAutoProfiler and component usages =="
rg -n "NewAutoProfiler|AutoProfiler|RunComponent|ComponentManagerBuilder|ComponentInterface|Component" cmd module engine -S --glob '*.go' | head -250

Repository: onflow/flow-go

Length of output: 27908


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== engine/unit.go =="
awk '{ printf("%4d: %s\n", NR, $0) }' engine/unit.go

echo "== AutoProfiler construction context =="
python3 - <<'PY'
from pathlib import Path
for p in Path('.').rglob('*.go'):
    s=str(p)
    if 'go' not in s and not s.endswith('.go'):
        continue
    txt=p.read_text(errors='ignore')
    if 'NewAutoProfiler' in txt or 'AutoProfiler' in txt:
        lines=txt.splitlines()
        for i,l in enumerate(lines,1):
            if 'NewAutoProfiler' in l:
                for n in range(max(1,i-35), min(len(lines), i+80)+1):
                    print(f'{p}:{n}: {lines[n-1]}')
                break
PY

Repository: onflow/flow-go

Length of output: 5084


Track the profiler worker in the unit lifecycle.

AutoProfiler.Done() only waits on the profile directory creation in New() because p.runForever() is started with a bare go before that WaitGroup action is added. Launch the worker with p.unit.Launch(p.runForever) so await in cmd/ledger/main.go waits for the running worker to stop and any active profile trace to complete.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ledger/main.go` around lines 384 - 386, Update the auto-profiler startup
in New to launch runForever through p.unit.Launch instead of a bare goroutine,
ensuring AutoProfiler.Done waits for the worker and any active trace to finish;
leave the shutdown await in cmd/ledger unchanged.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants