How to Monitor bannedbook/fanqiang Performance: Profiling the DNS Proxy and Go Core
Monitor bannedbook/fanqiang performance by instrumenting the ChannelMonitor class in LocalDnsServer.kt for DNS latency metrics, enabling pprof endpoints in the Go libcore for CPU and memory profiling, and using Android Studio Profiler to analyze UI thread stalls.
The bannedbook/fanqiang repository implements a sophisticated circumvention VPN combining a Kotlin-based Android DNS proxy with a high-performance Go networking core. Understanding how to monitor bannedbook/fanqiang performance requires observing the non-blocking I/O operations in the ChannelMonitor selector loop and the packet processing routines in the Go libcore. This guide identifies the exact source file locations and instrumentation code needed to capture comprehensive latency, throughput, and resource utilization metrics.
Instrumenting the DNS Proxy ChannelMonitor
The Android DNS proxy relies on LocalDnsServer.kt and ChannelMonitor.kt in fqnews/core/src/main/java/com/github/shadowsocks/net/ to manage non-blocking NIO channel operations. The ChannelMonitor class runs a dedicated selector thread that processes registrations and readiness events, avoiding NetworkOnMainThreadException while handling DNS requests.
The register() method sends channel registration requests to a pendingRegistrations channel. By adding timestamp logging at the entry point of this method and when channels become ready, you can calculate per-operation latency. The class uses a writeCompat extension function to handle API compatibility for WritableByteChannel operations, and printLog(e) for error handling.
Add timestamp instrumentation to the register() method:
// ChannelMonitor.kt – add simple timestamp logging
private suspend fun WritableByteChannel.writeCompat(src: ByteBuffer) =
if (Build.VERSION.SDK_INT <= 23) withContext(Dispatchers.Default) { write(src) } else write(src)
suspend fun register(channel: SelectableChannel, ops: Int, block: (SelectionKey) -> Unit): SelectionKey {
val registration = Registration(channel, ops, block)
pendingRegistrations.send(registration)
println("[${System.currentTimeMillis()}] REGISTER ${channel} ops=$ops")
ByteBuffer.allocateDirect(1).also { junk ->
loop@ while (running) when (registrationPipe.sink().writeCompat(junk)) {
0 -> kotlinx.coroutines.yield()
1 -> break@loop
else -> throw IOException("Failed to register in the channel")
}
}
if (!running) throw CancellationException()
return registration.result.await()
}
Similarly, instrument the wait() coroutine to emit completion timestamps when channels become ready for OP_READ or OP_WRITE operations.
Profiling the Go Core with pprof
The Go networking components reside in fqnews2/libcore, including the STUN client implementation in stun/client.go and DNS handling in dns_box.go. To monitor bannedbook/fanqiang performance at the network layer, integrate the Go pprof package to expose runtime metrics.
Add a pprof HTTP endpoint in fqnews2/libcore/http.go:
package libcore
import (
"log"
"net/http"
_ "net/http/pprof" // registers /debug/pprof/* handlers
)
func init() {
go func() {
log.Println("pprof listening on :6060")
if err := http.ListenAndServe(":6060", nil); err != nil {
log.Fatalf("pprof failed: %v", err)
}
}()
}
With this endpoint active, capture CPU profiles while the VPN processes traffic:
go tool pprof -seconds 30 http://localhost:6060/debug/pprof/profile
This profiles the exact functions in stun/client.go and dns_box.go that handle packet processing, revealing CPU hotspots and goroutine counts. You can also use expvar hooks to export custom counters for DNS lookup latency and STUN round-trip times.
Capturing Android System Metrics
For the Android UI layer located in app/src/main, use Android Studio Profiler to monitor frame rendering times, memory usage, and battery impact. The DNS proxy uses UDP/TCP under the hood, which appears in the Profiler's Network pane. Ensure AndroidManifest.xml contains the necessary network permissions for profiling that uses network sockets.
Capture runtime logs from the ChannelMonitor thread using adb:
# Run once per test session
adb logcat -v time -s ChannelMonitor > /tmp/channel_monitor.log &
# … after exercising the app …
adb logcat -c # clear the buffer for next run
Analyzing Latency from ChannelMonitor Logs
Process the captured log files to calculate DNS operation latency. The logs contain REGISTER events (when operations start) and WAIT events (when channels become ready).
Extract latency metrics using this awk script:
awk '
/REGISTER/ { start[$4] = $1 }
/WAIT/ && $4 in start { printf "%s %dms\n", $4, $1 - start[$4] }
' /tmp/channel_monitor.log | sort -k2 -n
This calculates the time delta between registration and readiness for each channel, sorting results to identify slow operations.
Aggregating Metrics for Unified Monitoring
Combine these data sources into a centralized monitoring stack:
- Logcat ingestion: Pipe Android logs to Fluent Bit → Elasticsearch for searchable, time-series log analysis
- Go metrics: Export pprof data via a Prometheus exporter such as Pyroscope for continuous profiling
- Custom Kotlin counters: Expose DNS latency histograms via a lightweight HTTP
/metricsendpoint for Prometheus scraping
This unified approach provides a dashboard showing DNS request latency distributions, Go routine CPU usage, and Android UI frame-time histograms.
Summary
- Instrument ChannelMonitor: Modify
fqnews/core/src/main/java/com/github/shadowsocks/net/ChannelMonitor.ktto emit timestamps inregister()and wait handlers to capture DNS proxy latency - Enable Go pprof: Add
net/http/pprofimports tofqnews2/libcore/http.goto expose CPU and memory profiles for the networking core - Capture Android logs: Use
adb logcat -s ChannelMonitorto collect runtime data and process withawkto calculate per-operation latency - Profile system-wide: Use Android Studio Profiler for UI thread analysis and resource monitoring on the Android frontend
Frequently Asked Questions
How do I measure DNS request latency in bannedbook/fanqiang?
Modify the register() method in ChannelMonitor.kt to log System.currentTimeMillis() when channels register and when they become ready. The difference between these timestamps represents the selector latency for each DNS operation, which you can extract using the provided awk script.
Where should I add pprof endpoints in the Go libcore?
Add the pprof HTTP server initialization to fqnews2/libcore/http.go within an init() function. This exposes /debug/pprof/ endpoints for the entire libcore package, including the STUN client routines in stun/client.go and DNS handlers in dns_box.go.
Can I monitor fanqiang performance without modifying the source code?
You can capture basic metrics using Android Studio Profiler and standard adb logcat without code changes, but precise DNS latency measurement requires adding timestamp instrumentation to ChannelMonitor.kt. The Go pprof endpoints also require the minimal code addition shown above to expose runtime metrics.
What tools analyze the ChannelMonitor logs most effectively?
Use awk for quick command-line analysis of latency patterns, or import the log data into Grafana or Elasticsearch for visualization. The printLog function calls in the Kotlin source can be redirected to structured logging systems like ELK for historical trend analysis.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →