235 lines
7.6 KiB
Go
235 lines
7.6 KiB
Go
//go:build windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"overlord-client/cmd/agent/capture"
|
|
)
|
|
|
|
func main() {
|
|
var frames int
|
|
var captureFrames int
|
|
var captureDisplay int
|
|
var captureFPS int
|
|
var captureMaxHeight int
|
|
var jsonOut bool
|
|
var includeCapture bool
|
|
var includeNVENC bool
|
|
var includeNVENCD3D11 bool
|
|
var resArg string
|
|
var fpsArg string
|
|
var providersArg string
|
|
flag.IntVar(&frames, "frames", 30, "frames to feed each encoder case")
|
|
flag.IntVar(&captureFrames, "capture-frames", 30, "frames to capture for each capture backend")
|
|
flag.IntVar(&captureDisplay, "display", 0, "display index for capture smoke")
|
|
flag.IntVar(&captureFPS, "capture-fps", 0, "target FPS for direct H.264 capture smoke")
|
|
flag.IntVar(&captureMaxHeight, "capture-max-height", 0, "max capture output height; use -1 to bypass the resolution cap")
|
|
flag.BoolVar(&jsonOut, "json", false, "print JSON instead of a table")
|
|
flag.BoolVar(&includeCapture, "capture", false, "also time DXGI-preferred and BitBlt capture backends")
|
|
flag.BoolVar(&includeNVENC, "nvenc", false, "also probe the native NVIDIA NVENC API and nvidia-smi")
|
|
flag.BoolVar(&includeNVENCD3D11, "nvenc-d3d11", false, "also encode test frames through NVENC using a D3D11 NV12 texture")
|
|
flag.StringVar(&resArg, "res", "1280x720,1920x1080,2560x1440,3840x2160", "comma-separated resolutions")
|
|
flag.StringVar(&fpsArg, "fps", "30,60,120", "comma-separated requested FPS values")
|
|
flag.StringVar(&providersArg, "providers", "hardware,software", "comma-separated providers: hardware,software")
|
|
flag.Parse()
|
|
|
|
opts := capture.DefaultH264SmokeOptions()
|
|
opts.Frames = frames
|
|
opts.Resolutions = parseResolutions(resArg)
|
|
opts.FPS = parseInts(fpsArg)
|
|
opts.Providers = parseStrings(providersArg)
|
|
|
|
results := capture.RunH264Smoke(opts)
|
|
var captureResults []capture.CaptureSmokeResult
|
|
if includeCapture {
|
|
captureResults = capture.RunCaptureSmoke(capture.CaptureSmokeOptions{Display: captureDisplay, Frames: captureFrames, FPS: captureFPS, MaxHeight: captureMaxHeight})
|
|
}
|
|
var nvencResult *capture.NVENCSmokeResult
|
|
var nvidiaSMI []string
|
|
if includeNVENC {
|
|
result := capture.RunNVENCSmoke()
|
|
nvencResult = &result
|
|
nvidiaSMI = queryNvidiaSMI()
|
|
}
|
|
var nvencD3D11Results []capture.NVENCD3D11SmokeResult
|
|
if includeNVENCD3D11 {
|
|
nvencD3D11Results = runNVENCD3D11SmokeCases(opts.Resolutions, opts.FPS, frames)
|
|
if !includeNVENC {
|
|
nvidiaSMI = queryNvidiaSMI()
|
|
}
|
|
}
|
|
if jsonOut {
|
|
enc := json.NewEncoder(os.Stdout)
|
|
enc.SetIndent("", " ")
|
|
payload := struct {
|
|
H264 []capture.H264SmokeResult `json:"h264"`
|
|
Capture []capture.CaptureSmokeResult `json:"capture,omitempty"`
|
|
NVENC *capture.NVENCSmokeResult `json:"nvenc,omitempty"`
|
|
NVENCD3D11 []capture.NVENCD3D11SmokeResult `json:"nvenc_d3d11,omitempty"`
|
|
Nvidia []string `json:"nvidia_smi,omitempty"`
|
|
}{H264: results, Capture: captureResults, NVENC: nvencResult, NVENCD3D11: nvencD3D11Results, Nvidia: nvidiaSMI}
|
|
if err := enc.Encode(payload); err != nil {
|
|
fmt.Fprintf(os.Stderr, "json encode failed: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
return
|
|
}
|
|
printTable(results)
|
|
if includeCapture {
|
|
fmt.Println()
|
|
printCaptureTable(captureResults)
|
|
}
|
|
if includeNVENC {
|
|
fmt.Println()
|
|
printNVENCSummary(nvencResult, nvidiaSMI)
|
|
}
|
|
if includeNVENCD3D11 {
|
|
fmt.Println()
|
|
printNVENCD3D11Table(nvencD3D11Results)
|
|
if !includeNVENC {
|
|
for _, line := range nvidiaSMI {
|
|
fmt.Println("nvidia-smi:", line)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseResolutions(raw string) []capture.H264SmokeResolution {
|
|
parts := parseStrings(raw)
|
|
out := make([]capture.H264SmokeResolution, 0, len(parts))
|
|
for _, part := range parts {
|
|
fields := strings.Split(strings.ToLower(part), "x")
|
|
if len(fields) != 2 {
|
|
continue
|
|
}
|
|
w, werr := strconv.Atoi(strings.TrimSpace(fields[0]))
|
|
h, herr := strconv.Atoi(strings.TrimSpace(fields[1]))
|
|
if werr == nil && herr == nil {
|
|
out = append(out, capture.H264SmokeResolution{Width: w, Height: h})
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func parseInts(raw string) []int {
|
|
parts := parseStrings(raw)
|
|
out := make([]int, 0, len(parts))
|
|
for _, part := range parts {
|
|
if v, err := strconv.Atoi(part); err == nil {
|
|
out = append(out, v)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func parseStrings(raw string) []string {
|
|
fields := strings.Split(raw, ",")
|
|
out := make([]string, 0, len(fields))
|
|
for _, field := range fields {
|
|
field = strings.TrimSpace(field)
|
|
if field != "" {
|
|
out = append(out, field)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func printTable(results []capture.H264SmokeResult) {
|
|
fmt.Printf("%-18s %-5s %9s %7s %7s %8s %9s %8s %7s %9s %9s %s\n",
|
|
"provider", "input", "size", "reqfps", "cfgfps", "config", "first_ms", "avg_ms", "frames", "avg_bytes", "total", "error")
|
|
for _, r := range results {
|
|
status := "fail"
|
|
if r.ConfigureOK && r.EncodeOK {
|
|
status = "ok"
|
|
} else if r.ConfigureOK {
|
|
status = "no-data"
|
|
}
|
|
fmt.Printf("%-18s %-5s %4dx%-4d %7d %7d %8s %9.2f %8.2f %7d %9.0f %9d %s\n",
|
|
r.Provider, r.Input, r.Width, r.Height, r.RequestedFPS, r.ConfiguredFPS, status,
|
|
r.FirstOutputMS, r.AvgEncodeMS, r.FramesTried, r.AvgBytes, r.TotalBytes, r.Error)
|
|
}
|
|
}
|
|
|
|
func printCaptureTable(results []capture.CaptureSmokeResult) {
|
|
fmt.Printf("%-18s %-6s %7s %7s %7s %9s %9s %9s %9s %9s %9s %9s %s\n", "backend", "format", "display", "ok", "attempt", "size", "avg_ms", "cap_ms", "enc_ms", "first_enc", "steady", "avg_bytes", "error")
|
|
for _, r := range results {
|
|
fmt.Printf("%-18s %-6s %7d %7d %7d %4dx%-4d %9.2f %9.2f %9.2f %9.2f %9.2f %9.0f %s\n",
|
|
r.Backend, r.Format, r.Display, r.OK, r.Attempts, r.Width, r.Height, r.AvgMS, r.AvgCapMS, r.AvgEncMS, r.FirstEncMS, r.SteadyEncMS, r.AvgBytes, r.Error)
|
|
}
|
|
}
|
|
|
|
func printNVENCSummary(result *capture.NVENCSmokeResult, smi []string) {
|
|
if result == nil {
|
|
return
|
|
}
|
|
status := "unavailable"
|
|
if result.Available {
|
|
status = fmt.Sprintf("available api=%d.%d raw=%d", result.APIMajor, result.APIMinor, result.RawAPI)
|
|
}
|
|
fmt.Printf("nvenc: %s dll=%s", status, result.DLL)
|
|
if result.Error != "" {
|
|
fmt.Printf(" error=%s", result.Error)
|
|
}
|
|
fmt.Println()
|
|
for _, line := range smi {
|
|
fmt.Println("nvidia-smi:", line)
|
|
}
|
|
}
|
|
|
|
func runNVENCD3D11SmokeCases(resolutions []capture.H264SmokeResolution, fpsValues []int, frames int) []capture.NVENCD3D11SmokeResult {
|
|
out := make([]capture.NVENCD3D11SmokeResult, 0, len(resolutions)*len(fpsValues))
|
|
for _, res := range resolutions {
|
|
for _, fps := range fpsValues {
|
|
out = append(out, capture.RunNVENCD3D11Smoke(capture.NVENCD3D11SmokeOptions{
|
|
Width: res.Width,
|
|
Height: res.Height,
|
|
FPS: fps,
|
|
Frames: frames,
|
|
}))
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func printNVENCD3D11Table(results []capture.NVENCD3D11SmokeResult) {
|
|
fmt.Printf("%-12s %9s %7s %7s %9s %8s %9s %9s %s\n",
|
|
"nvenc-d3d11", "size", "fps", "frames", "first_ms", "avg_ms", "avg_bytes", "total", "error")
|
|
for _, r := range results {
|
|
status := "fail"
|
|
if r.OK {
|
|
status = "ok"
|
|
}
|
|
avgBytes := float64(0)
|
|
if r.Frames > 0 {
|
|
avgBytes = float64(r.TotalBytes) / float64(r.Frames)
|
|
}
|
|
fmt.Printf("%-12s %4dx%-4d %7d %7d %9.2f %8.2f %9.0f %9d %s\n",
|
|
status, r.Width, r.Height, r.FPS, r.Frames, r.FirstMS, r.AvgMS, avgBytes, r.TotalBytes, r.Error)
|
|
}
|
|
}
|
|
|
|
func queryNvidiaSMI() []string {
|
|
cmd := exec.Command("nvidia-smi", "--query-gpu=name,driver_version,encoder.stats.sessionCount,encoder.stats.averageFps,encoder.stats.averageLatency", "--format=csv,noheader")
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
return []string{err.Error()}
|
|
}
|
|
lines := strings.Split(strings.TrimSpace(string(out)), "\n")
|
|
result := make([]string, 0, len(lines))
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line != "" {
|
|
result = append(result, line)
|
|
}
|
|
}
|
|
return result
|
|
}
|