flagon-io/g1t

public

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t/scripts/perf/measure.ps1

126 lines4,800 bytesCodeBlame
1<#
2.SYNOPSIS
3 Times g1t.sh pages from this machine: time to first byte, where the Worker
4 ran (cf-placement) and where the time went (Server-Timing). Run it before
5 and after a change and compare. docs/PERFORMANCE.md explains the columns.
6
7.EXAMPLE
8 # Signed out
9 powershell -File scripts/perf/measure.ps1
10
11.EXAMPLE
12 # Signed in: copy the value of your g1t_session cookie from the browser
13 # (DevTools > Application > Cookies > https://g1t.sh) into G1T_SESSION.
14 # The script never prints it.
15 $env:G1T_SESSION = "<64 hex characters>"
16 powershell -File scripts/perf/measure.ps1 -Runs 7 -Out before.csv
17
18.NOTES
19 Uses curl.exe (Windows' own). Git Bash's curl fails here with exit 43.
20#>
21param(
22 [string]$Base = "https://g1t.sh",
23 [string]$Repo = "flagon-io/g1t",
24 [int]$Pull = 1,
25 [int]$Issue = 2,
26 [int]$Runs = 5,
27 [string[]]$Paths = @(),
28 [string]$Out = ""
29)
30
31$ErrorActionPreference = "Stop"
32
33# Pages people open most, and the data request a click inside the app makes
34# for the same page (React Router's `.data`, with only the page's own
35# loader, as an in-app navigation sends it).
36if ($Paths.Count -eq 0) {
37 $Paths = @(
38 "/",
39 "/explore",
40 "/$Repo",
41 "/$Repo/pulls",
42 "/$Repo/issues",
43 "/$Repo/pull/$Pull",
44 "/$Repo/issues/$Issue",
45 "/$Repo/pull/$Pull.data?_routes=routes%2Frepo%2Fpull",
46 "/$Repo/pulls.data?_routes=routes%2Frepo%2Fpulls",
47 "/pricing"
48 )
49}
50
51$signedIn = [bool]$env:G1T_SESSION
52$cookieArgs = @()
53if ($signedIn) { $cookieArgs = @("-H", "Cookie: g1t_session=$($env:G1T_SESSION)") }
54
55function Measure-Once([string]$url) {
56 $headerFile = [System.IO.Path]::GetTempFileName()
57 try {
58 $timing = & curl.exe -s -o NUL -D $headerFile -H "cache-control: no-cache" @cookieArgs `
59 -w "%{http_code} %{time_connect} %{time_appconnect} %{time_starttransfer} %{time_total}" $url
60 $parts = $timing.Trim().Split(" ")
61 $headers = Get-Content $headerFile
62 $serverTiming = ($headers | Where-Object { $_ -match "^server-timing:" } | ForEach-Object { ($_ -split ":", 2)[1].Trim() }) -join ", "
63 $placement = ($headers | Where-Object { $_ -match "^cf-placement:" } | ForEach-Object { ($_ -split ":", 2)[1].Trim() }) -join ""
64 $ray = ($headers | Where-Object { $_ -match "^cf-ray:" } | ForEach-Object { (($_ -split ":", 2)[1].Trim() -split "-")[-1] }) -join ""
65 [pscustomobject]@{
66 Status = [int]$parts[0]
67 # From the TLS handshake's end to the first byte: the server's share.
68 ServerMs = [math]::Round(([double]$parts[3] - [double]$parts[2]) * 1000)
69 TtfbMs = [math]::Round([double]$parts[3] * 1000)
70 TotalMs = [math]::Round([double]$parts[4] * 1000)
71 Placement = $placement
72 Edge = $ray
73 Timing = $serverTiming
74 }
75 } finally {
76 Remove-Item $headerFile -ErrorAction SilentlyContinue
77 }
78}
79
80function Percentile([double[]]$values, [double]$p) {
81 $sorted = $values | Sort-Object
82 $index = [math]::Min($sorted.Count - 1, [math]::Floor($p * $sorted.Count))
83 return $sorted[$index]
84}
85
86# The Server-Timing entries worth a column: total, and the slowest three.
87function Summarize-Timing([string]$header) {
88 if (-not $header) { return "" }
89 $entries = $header -split ",\s*(?=[A-Za-z0-9_.-]+;)" | ForEach-Object {
90 $name = ($_ -split ";")[0]
91 $dur = if ($_ -match "dur=([0-9.]+)") { [double]$Matches[1] } else { $null }
92 [pscustomobject]@{ Name = $name; Dur = $dur }
93 }
94 $total = $entries | Where-Object { $_.Name -eq "total" } | Select-Object -First 1
95 $rest = $entries | Where-Object { $_.Name -ne "total" -and $_.Dur -ne $null } | Sort-Object Dur -Descending | Select-Object -First 4
96 $shown = @()
97 if ($total) { $shown += "total=$($total.Dur)" }
98 $shown += $rest | ForEach-Object { "$($_.Name)=$($_.Dur)" }
99 return ($shown -join " ")
100}
101
102Write-Host "Measuring $Base, $Runs runs each, $(if ($signedIn) { 'signed in' } else { 'signed out' })."
103$rows = foreach ($path in $Paths) {
104 $url = "$Base$path"
105 $null = Measure-Once $url # warm the connection and the isolate
106 $samples = 1..$Runs | ForEach-Object { Measure-Once $url }
107 $server = $samples | ForEach-Object { [double]$_.ServerMs }
108 $last = $samples[-1]
109 [pscustomobject]@{
110 Path = $path
111 Status = $last.Status
112 "p50 ms" = Percentile $server 0.5
113 "p90 ms" = Percentile $server 0.9
114 "TTFB p50" = Percentile ($samples | ForEach-Object { [double]$_.TtfbMs }) 0.5
115 Placement = $last.Placement
116 Edge = $last.Edge
117 "Server-Timing (last run)" = Summarize-Timing $last.Timing
118 }
119}
120
121$rows | Format-Table -AutoSize -Wrap
122if ($Out) {
123 $rows | Export-Csv -NoTypeInformation -Path $Out
124 Write-Host "Saved to $Out"
125}
126Write-Host "p50/p90 ms: from the end of the TLS handshake to the first byte (the server's share). TTFB includes connecting."