Performance Profiling
Relevant source files
The following files were used as context for generating this wiki page:
Performance profiling in the GitHub MCP server provides minimal, high-impact performance metrics for critical operations. The system is designed to track execution duration, memory allocation deltas, and data throughput metrics (such as line counts and byte counts) to ensure the server remains efficient under load, particularly when handling large datasets like GitHub Actions logs.
The profiling infrastructure is primarily contained within the internal/profiler package, which offers both a global singleton for easy access across the codebase and instance-based profilers for targeted measurement. It integrates with standard Go runtime.MemStats to provide accurate visibility into memory behavior during tool execution.
Sources: internal/profiler/profiler.go:1-15, pkg/github/actions.go:510-515
Core Architecture
The profiling system is built around the Profiler struct and the Profile data model. The Profiler acts as a controller that determines if profiling is active and handles the lifecycle of a performance measurement.
Data Model: The Profile Struct
The Profile struct captures the state of an operation at a specific point in time.
| Field | Type | Description |
|---|---|---|
Operation | string | Name of the task being profiled. |
Duration | time.Duration | Total execution time in nanoseconds. |
MemoryBefore | uint64 | Memory allocated (in bytes) before the operation starts. |
MemoryAfter | uint64 | Memory allocated (in bytes) after the operation completes. |
MemoryDelta | int64 | The net change in memory allocation. |
LinesCount | int | Optional count of processed lines (useful for log processing). |
BytesCount | int64 | Optional count of processed bytes. |
Timestamp | time.Time | When the profile was recorded. |
Sources: internal/profiler/profiler.go:16-26
Memory Delta Logic
Calculating the memory delta requires careful handling of uint64 to int64 conversion to avoid overflow errors when comparing MemoryAfter and MemoryBefore. The safeMemoryDelta function manages these edge cases by capping differences at math.MaxInt64 or math.MinInt64 if the actual difference exceeds the capacity of a signed 64-bit integer.
Sources: internal/profiler/profiler.go:38-57
Implementation Flow
The profiler follows a standard lifecycle for every profiled operation, whether automated via function wrappers or handled manually.
The diagram above illustrates the logic used by ProfileFunc and ProfileFuncWithMetrics to wrap operations.
Sources: internal/profiler/profiler.go:69-90
Profiling Patterns
The system supports three distinct patterns for instrumentation:
- Function Wrapper (
ProfileFunc): Wraps a function that returns anerror. - Metrics Wrapper (
ProfileFuncWithMetrics): Wraps a function that returns line/byte counts along with anerror. - Manual Start/Finish (
Start): Returns a closure that, when called, finalizes the profile. This is ideal for asynchronous or complex control flows.
The sequence diagram shows the manual Start pattern used in performance-sensitive areas like downloadLogContent.
Sources: internal/profiler/profiler.go:118-148, pkg/github/actions.go:545-550
Configuration and Environment
Profiling is disabled by default to minimize overhead. It is activated via environment variables, typically during development or troubleshooting sessions.
| Environment Variable | Type | Description |
|---|---|---|
GITHUB_MCP_PROFILING_ENABLED | bool | Set to true to enable performance tracking and logging. |
The InitFromEnv function allows the server to initialize the global profiler instance based on these settings during startup.
Sources: internal/profiler/profiler.go:153-170, pkg/github/actions_test.go:1015-1020
Use Case: GitHub Actions Log Processing
A primary application of this profiling system is in pkg/github/actions.go. When downloading large job logs, the server uses a ring buffer to maintain a "sliding window" of the most recent logs. Profiling was used to validate that this approach significantly reduces memory pressure compared to loading the entire log into memory.
Profiling in downloadLogContent
func downloadLogContent(ctx context.Context, logURL string, tailLines int, maxLines int) (string, int, *http.Response, error) {
prof := profiler.New(nil, profiler.IsProfilingEnabled())
finish := prof.Start(ctx, "log_buffer_processing")
// ... processing logic ...
_ = finish(len(lines), int64(len(finalResult)))
return finalResult, totalLines, httpResp, nil
}
Sources: pkg/github/actions.go:545-575
Performance Validation
Automated tests use the profiler to compare the MemoryDelta between different buffer implementations. For example, Test_MemoryUsage_SlidingWindow_vs_NoWindow uses the ProfileFuncWithMetrics method to confirm that the sliding window uses substantially less memory when processing 100,000 log lines.
Sources: pkg/github/actions_test.go:1005-1050
Summary of Profiler Capabilities
| Capability | Supported | Source |
|---|---|---|
| Duration Tracking | Yes | profiler.go:20 |
| Memory Allocation Delta | Yes | profiler.go:23 |
| Line Counting | Yes | profiler.go:24 |
| Throughput (Bytes) | Yes | profiler.go:25 |
| Structured Logging | Yes | profiler.go:87 |
| Global Singleton | Yes | profiler.go:151 |
The performance profiling module ensures that resource-intensive tasks, such as log tailing and repository searches, are monitored without introducing significant latency to the MCP server's response times.
Sources: internal/profiler/profiler.go