Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/en/fastlane.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ explicit `JAVA_HOME` fails early. `bundle exec fastlane lanes` lists the checked
| `bundle exec fastlane android python_checks` | Checks Python formatting/import order and runs unit tests. |
| `bundle exec fastlane android native_fuzz` | Fuzzes native parsers for 20 seconds with two workers. |
| `bundle exec fastlane android native_tests` | Runs all native Go tests and local uQUIC patch regressions with the race detector. |
| `bundle exec fastlane android native_benchmarks` | Measures native CPU/allocation costs with ten samples; [comparison and profiling](native-benchmarks.md). Available options: `bench`, `count`, `benchtime`, `cpu`, `output`, `profile:true`. |
| `bundle exec fastlane android native_integration` | Tests production dialers against real GOST/OpenSSH servers in Docker with the race detector. |
| `bundle exec fastlane android android_checks` | Builds the native AAR, resolves pinned Robolectric SDKs through Gradle, runs Android unit tests and lint, builds a debug APK, then builds and verifies an unsigned release APK. It rejects any release-signing environment variables. |
| `bundle exec fastlane android device_test_build` | Build the native AAR, debug APK and instrumentation APK. |
Expand Down
79 changes: 79 additions & 0 deletions docs/en/native-benchmarks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Native core benchmarks

Run the Go benchmarks through Fastlane. The default suite requires no emulator, Docker,
credentials or external servers and leaves production code unchanged:

```sh
bundle exec fastlane android native_benchmarks
```

Defaults: ten samples per case, `200ms` per sample, `GOMAXPROCS=1`, no race detector or profiling.
Results overwrite `test-results/native/benchmarks/`: `bench.txt` in Go benchmark format,
`command.txt` with exact arguments and `environment.txt` with commit, working-tree status,
Go version and architecture. Use separate `output` directories to retain comparisons.

| Benchmark | Measured boundary |
| --- | --- |
| `CorePacketFilter` | Existing direct dispatcher versus strict header filtering, IPv4/TCP and IPv6/UDP, 64/1400/65535-byte buffers. Both strict levels share this filter. Buffers are reused: this measures steady-state inspection, not TUN input or buffer creation. |
| `CoreUDPRead` | Real `sourceUDP.Read` and `ReadFrom`, disabled/standard/advanced, 64/1400/65507-byte payloads. Includes the same in-memory copy in every mode and the production query semaphore in strict modes. Excludes initial flow admission. |
| `CoreUDPRecheck` | Standard mode's cache-hit and expired-cache paths with deterministic timestamps. Excludes `time.Now`; `CoreUDPRead` includes it. |
| `CoreAdmission` | New TCP/UDP flow dispatch, source-address formatting, goroutine scheduling and ownership query budget. Downstream handling is a stub, with no dial, handshake or packet forwarding. Disabled includes the same completion channel. |
| `SOCKS5ReadAllocation` | Existing production SOCKS5 UDP packet decoding against an in-memory socket. |

`ns/op`, `B/op`, `allocs/op` describe each boundary. `owner_queries/op` counts validator calls:
advanced UDP should have one per datagram; disabled has zero; standard only rechecks expired
flows. The validator returns immediately: **JNI, Android's socket-owner lookup, UID policy and
Binder costs are not simulated or included**. `MB/s` is processed-buffer throughput, not VPN
bandwidth. The suite does not measure encryption, real proxy transport, full gVisor forwarding,
concurrent saturation, radio behavior, power consumption or Android heap pressure.

## Compare modes and revisions

Close emulators and other CPU-heavy work. Keep the same host, Go version, CPU count, payload,
benchmark code and flags. Warm up once; for important changes collect before/after in alternating
order to expose thermal drift. A shared CI runner is unsuitable for strict timing thresholds;
these benchmarks are intentionally outside required checks.

```sh
bundle exec fastlane android native_benchmarks output:test-results/native/before
# Change revision or implementation, keeping the same benchmark definitions.
bundle exec fastlane android native_benchmarks output:test-results/native/after
benchstat test-results/native/before/bench.txt test-results/native/after/bench.txt

# Compare the three modes inside one run (use an installed benchstat).
benchstat -filter '.name:CoreUDPRead' -row '/bytes,/method' -col /mode \
test-results/native/after/bench.txt
```

[Go's benchstat](https://pkg.go.dev/golang.org/x/perf/cmd/benchstat) reports medians,
confidence intervals and significance. Do not turn noisy nanosecond changes into regression
claims or average unrelated operations into one performance score. Keep the raw samples.

## Locate CPU and allocation hot spots

Profile a narrow workload separately; profiling changes timing. `profile:true` saves CPU and
sampled allocation profiles plus the matching `mobile.test` binary in the output directory:

```sh
bundle exec fastlane android native_benchmarks \
'bench:BenchmarkCoreUDPRead/bytes=1400/method=ReadFrom/mode=advanced$' \
count:1 benchtime:5s profile:true output:test-results/native/profile

go tool pprof -top test-results/native/profile/mobile.test test-results/native/profile/cpu.pprof
go tool pprof -top -alloc_space test-results/native/profile/mobile.test test-results/native/profile/heap.pprof
```

Use `go tool pprof -http=127.0.0.1:8080 ...` for interactive analysis. The heap profile includes
setup/runtime allocations too; `allocs/op` is the timed operation's allocation count.
For existing controlled local handshake/fallback benchmarks, select
`'bench:Benchmark(HTTP2ColdStart|DNSFallbackLatency)' count:3 benchtime:1x`; their artificial
delays test scheduling policies rather than Internet speed. `bench:BenchmarkSourcePacketFilter`
retains the historical header-only/full-copy comparison.

Actual strict-mode overhead needs a separate device experiment on Android 10+ (API 26 cannot
provide socket-owner validation), with the same device, private proxy/origin, flow count and
traffic pattern. Measure throughput, latency, CPU, memory and ownership-query frequency with
strict disabled, standard and advanced. These host microbenchmarks identify Go hot spots;
they do not predict that device result.

[Русская версия](../ru/native-benchmarks.md)
1 change: 1 addition & 0 deletions docs/ru/fastlane.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Ruby 3.4.10 (`.ruby-version`), затем выполните `bundle install`.
| `bundle exec fastlane android python_checks` | Проверяет форматирование, порядок импортов и Python-тесты. |
| `bundle exec fastlane android native_fuzz` | Запускает fuzz-тесты нативных парсеров на 20 секунд с двумя worker-процессами. |
| `bundle exec fastlane android native_tests` | Запускает все нативные Go-тесты и регрессии локальных патчей uQUIC с race detector. |
| `bundle exec fastlane android native_benchmarks` | Измеряет время и аллокации ядра в десяти сэмплах; [сравнение и профилирование](native-benchmarks.md). Опции: `bench`, `count`, `benchtime`, `cpu`, `output`, `profile:true`. |
| `bundle exec fastlane android native_integration` | Проверяет сетевой код с настоящими GOST/OpenSSH в Docker и race detector. |
| `bundle exec fastlane android android_checks` | Собирает native AAR, получает закреплённые SDK Robolectric через Gradle, запускает Android unit-тесты и lint, собирает debug APK, затем собирает и проверяет unsigned release APK. Команда отклоняет переменные release-подписи. |
| `bundle exec fastlane android device_test_build` | Собирает native AAR, debug APK и instrumentation APK. |
Expand Down
80 changes: 80 additions & 0 deletions docs/ru/native-benchmarks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Бенчмарки сетевого ядра

Запускайте Go-бенчмарки через Fastlane. Набор по умолчанию не требует эмулятора, Docker,
учётных данных или внешних серверов и не меняет рабочий код:

```sh
bundle exec fastlane android native_benchmarks
```

По умолчанию: десять сэмплов на сценарий, `200ms` на сэмпл, `GOMAXPROCS=1`, без race detector
и профилирования. Результаты перезаписывают `test-results/native/benchmarks/`: `bench.txt`
в формате Go, `command.txt` с точными аргументами и `environment.txt` с коммитом, состоянием
рабочего дерева, версией Go и архитектурой. Разные `output` сохраняют сравниваемые запуски.

| Бенчмарк | Что измеряется |
| --- | --- |
| `CorePacketFilter` | Прямой dispatcher и фильтр заголовков строгого режима, IPv4/TCP и IPv6/UDP, буферы 64/1400/65535 байт. Фильтр одинаков для обоих строгих уровней. Буферы переиспользуются: измеряется установившаяся обработка, без чтения TUN и создания буферов. |
| `CoreUDPRead` | Настоящие `sourceUDP.Read` и `ReadFrom`, выключенный/стандартный/расширенный режимы, данные 64/1400/65507 байт. Во всех режимах одинаковое копирование в памяти; в строгих — рабочий семафор запросов владельца. Без первоначального допуска потока. |
| `CoreUDPRecheck` | Попадание в кеш стандартного режима и истечение срока с заданными временными метками. Без `time.Now`; в `CoreUDPRead` его стоимость учитывается. |
| `CoreAdmission` | Допуск нового TCP/UDP-потока: диспетчеризация, форматирование адресов, запуск горутины и бюджет запросов владельца. Обработчик-заглушка не создаёт соединения и не передаёт пакеты. В выключенном режиме тот же канал завершения. |
| `SOCKS5ReadAllocation` | Существующий бенчмарк рабочего декодера SOCKS5 UDP с сокетом в памяти. |

`ns/op`, `B/op`, `allocs/op` относятся к указанной операции. `owner_queries/op` считает
вызовы валидатора: в расширенном UDP — один на датаграмму, в выключенном — ноль,
в стандартном — при истечении кеша. Валидатор отвечает сразу: **JNI, системный поиск
владельца сокета Android, политика UID и Binder не моделируются и не учитываются**.
`MB/s` — скорость обработки буферов, не пропускная способность VPN. Набор не измеряет
шифрование, настоящий прокси-транспорт, полный путь gVisor, конкурентное насыщение,
радиосвязь, энергопотребление или нагрузку на память Android.

## Сравнение режимов и ревизий

Закройте эмуляторы и другие тяжёлые процессы. Сохраняйте одну машину, версию Go, число CPU,
размеры данных, код бенчмарков и флаги. Сделайте прогрев; важные изменения сравнивайте,
чередуя запуски до/после, чтобы заметить влияние нагрева. Общий CI runner не подходит для
жёстких порогов времени; эти бенчмарки намеренно не входят в обязательные проверки.

```sh
bundle exec fastlane android native_benchmarks output:test-results/native/before
# Сменить ревизию или реализацию, сохранив одинаковые определения бенчмарков.
bundle exec fastlane android native_benchmarks output:test-results/native/after
benchstat test-results/native/before/bench.txt test-results/native/after/bench.txt

# Три режима внутри одного запуска (нужен установленный benchstat).
benchstat -filter '.name:CoreUDPRead' -row '/bytes,/method' -col /mode \
test-results/native/after/bench.txt
```

[Go benchstat](https://pkg.go.dev/golang.org/x/perf/cmd/benchstat) показывает медианы,
доверительные интервалы и значимость различий. Не объявляйте шум в наносекундах регрессией
и не усредняйте разные операции в один балл производительности. Сохраняйте исходные сэмплы.

## Поиск затрат CPU и аллокаций

Профилируйте узкий сценарий отдельно: профилирование влияет на время. `profile:true`
сохраняет CPU-профиль, выборочный профиль аллокаций и соответствующий `mobile.test`:

```sh
bundle exec fastlane android native_benchmarks \
'bench:BenchmarkCoreUDPRead/bytes=1400/method=ReadFrom/mode=advanced$' \
count:1 benchtime:5s profile:true output:test-results/native/profile

go tool pprof -top test-results/native/profile/mobile.test test-results/native/profile/cpu.pprof
go tool pprof -top -alloc_space test-results/native/profile/mobile.test test-results/native/profile/heap.pprof
```

Для интерактивного анализа используйте `go tool pprof -http=127.0.0.1:8080 ...`. Heap-профиль
включает также подготовку и runtime; `allocs/op` относится к измеряемой операции.
Существующие локальные бенчмарки handshake/fallback выбираются через
`'bench:Benchmark(HTTP2ColdStart|DNSFallbackLatency)' count:3 benchtime:1x`: искусственные
задержки проверяют стратегию ожидания, а не скорость Интернета.
`bench:BenchmarkSourcePacketFilter` сохраняет историческое сравнение заголовков и полного копирования.

Реальную стоимость строгого режима нужно измерять отдельным экспериментом на Android 10+
(API 26 не предоставляет поиск владельца сокета), с одним устройством, приватным прокси
и origin, одинаковыми числом потоков и нагрузкой. Сравнивайте пропускную способность,
задержку, CPU, память и частоту запросов владельца в выключенном, стандартном и расширенном
режимах. Эти микробенчмарки находят узкие места Go, но не предсказывают результат устройства.

[English version](../en/native-benchmarks.md)
26 changes: 26 additions & 0 deletions fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,32 @@ platform :android do
end
end

desc "Benchmark the native core locally; save samples and optional CPU/heap profiles"
lane :native_benchmarks do |options|
output = File.expand_path(options.fetch(:output, "test-results/native/benchmarks").to_s, project_root)
FileUtils.mkdir_p(output)
%w[bench.txt cpu.pprof heap.pprof mobile.test].each { |name| FileUtils.rm_f(File.join(output, name)) }
Dir.chdir(File.join(project_root, "native")) do
metadata = "commit: #{sh("git", "rev-parse", "HEAD", log: false).strip}\n"
metadata += "working tree:\n#{sh("git", "status", "--porcelain", log: false)}"
metadata += sh("go", "version", log: false)
metadata += sh("go", "env", "-json", "GOOS", "GOARCH", "GOAMD64", "GOARM64", "GOFLAGS", "GOTOOLCHAIN", "CGO_ENABLED", log: false)
File.write(File.join(output, "environment.txt"), metadata)
args = ["go", "test", "./mobile", "-run=^$", "-bench=#{options.fetch(:bench, 'Benchmark(Core|SOCKS5ReadAllocation)')}",
"-benchmem", "-count=#{options.fetch(:count, 10)}", "-benchtime=#{options.fetch(:benchtime, '200ms')}",
"-cpu=#{options.fetch(:cpu, 1)}", "-timeout=15m"]
if options[:profile].to_s == "true"
args += ["-cpuprofile=#{File.join(output, 'cpu.pprof')}", "-memprofile=#{File.join(output, 'heap.pprof')}",
"-o", File.join(output, "mobile.test")]
end
File.write(File.join(output, "command.txt"), args.join("\n") + "\n")
result = sh(*args)
UI.user_error!("No native benchmarks matched") unless result.match?(/^Benchmark\S+\s+\d+\s+/)
File.write(File.join(output, "bench.txt"), result)
end
UI.success("Native benchmark results: #{output}")
end

desc "Test production dialers against disposable GOST and OpenSSH servers (requires Docker)"
lane :native_integration do
report = File.join(project_root, "test-results/native/integration.xml")
Expand Down
8 changes: 8 additions & 0 deletions fastlane/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ Regenerate committed artwork from MegaProxyIdentity SVGs

Run the native Go tests with the race detector

### android native_benchmarks

```sh
[bundle exec] fastlane android native_benchmarks
```

Benchmark the native core locally; save samples and optional CPU/heap profiles

### android native_integration

```sh
Expand Down
Loading
Loading