66 - main
77
88jobs :
9- benchmark :
9+ run- benchmark :
1010 runs-on : ubuntu-latest
1111 steps :
1212 - name : Checkout code
@@ -33,14 +33,238 @@ jobs:
3333 mkdir -p /tmp/artifacts/
3434 ARTIFACT_PATH=/tmp/artifacts make test-benchmark
3535
36- - name : Compare with baseline
36+ - name : Convert Benchmark Output to Prometheus Metrics
3737 run : |
38- go install golang.org/x/perf/cmd/benchstat@latest
39- benchstat benchmarks/baseline.txt /tmp/artifacts/new.txt | tee /tmp/artifacts/output
38+ mkdir -p /tmp/artifacts/prometheus/
39+ echo "RUN_ID=${{ github.run_id }}" >> $GITHUB_ENV
40+ cat << 'EOF' > benchmark_to_prometheus.py
41+ import sys
42+ import re
43+ import os
4044
41- - name : Upload benchmark results
45+ def parse_benchmark_output(benchmark_output):
46+ metrics = []
47+ round = 0
48+ value = os.getenv('RUN_ID') #get the github action run id so that those metrics cannot be overwritten
49+ for line in benchmark_output.split("\n"):
50+ match = re.match(r"Benchmark([\w\d]+)-\d+\s+\d+\s+([\d]+)\s+ns/op\s+([\d]+)\s+B/op\s+([\d]+)\s+allocs/op", line)
51+ if match:
52+ benchmark_name = match.group(1).lower()
53+ time_ns = match.group(2)
54+ memory_bytes = match.group(3)
55+ allocs = match.group(4)
56+
57+ metrics.append(f"benchmark_{benchmark_name}_ns {{run_id=\"{value}\", round=\"{round}\"}} {time_ns}")
58+ metrics.append(f"benchmark_{benchmark_name}_allocs {{run_id=\"{value}\", round=\"{round}\"}} {allocs}")
59+ metrics.append(f"benchmark_{benchmark_name}_mem_bytes {{run_id=\"{value}\", round=\"{round}\"}} {memory_bytes}")
60+ round+=1
61+
62+ return "\n".join(metrics)
63+
64+ if __name__ == "__main__":
65+ benchmark_output = sys.stdin.read()
66+ metrics = parse_benchmark_output(benchmark_output)
67+ print(metrics)
68+ EOF
69+
70+ cat /tmp/artifacts/new.txt | python3 benchmark_to_prometheus.py | tee /tmp/artifacts/prometheus/metrics.txt
71+
72+ # - name: Compare with baseline
73+ # run: |
74+ # go install golang.org/x/perf/cmd/benchstat@latest
75+ # benchstat benchmarks/baseline.txt /tmp/artifacts/new.txt | tee /tmp/artifacts/output
76+
77+ - name : Upload Benchmark Metrics
4278 uses : actions/upload-artifact@v4
4379 with :
44- name : benchmark-artifacts
45- path : /tmp/artifacts/
80+ name : benchmark-metrics
81+ path : /tmp/artifacts/prometheus/
82+
83+ run-prometheus :
84+ needs : run-benchmark
85+ runs-on : ubuntu-latest
86+ steps :
87+ - name : Checkout code
88+ uses : actions/checkout@v4
89+ with :
90+ fetch-depth : 0
91+
92+ # ToDo: use GitHub REST API to download artifact across repos
93+ - name : Download Prometheus Snapshot
94+ run : |
95+ echo "Available Artifacts in this run:"
96+ gh run list --repo operator-framework/operator-controller --limit 5
97+ gh run download --repo operator-framework/operator-controller --name prometheus-snapshot --dir .
98+ ls -lh ./
99+ env :
100+ GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
101+
102+ # #this step is invalid if download the artifacts in a different job
103+ # - name: Download Prometheus Snapshot2
104+ # uses: actions/download-artifact@v4
105+ # with:
106+ # name: prometheus-snapshot
107+ # path: ./
108+ # env:
109+ # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
110+
111+ - name : Download Benchmark Metrics
112+ uses : actions/download-artifact@v4
113+ with :
114+ name : benchmark-metrics
115+ path : ./
116+
117+ - name : Set Up Prometheus Config
118+ run : |
119+ cat << 'EOF' > prometheus.yml
120+ global:
121+ scrape_interval: 5s
122+ scrape_configs:
123+ - job_name: 'benchmark_metrics'
124+ static_configs:
125+ - targets: ['localhost:9000']
126+ EOF
127+ mkdir -p ${{ github.workspace }}/prometheus-data
128+ sudo chown -R 65534:65534 ${{ github.workspace }}/prometheus-data
129+ sudo chmod -R 777 ${{ github.workspace }}/prometheus-data
130+
131+ - name : Extract and Restore Prometheus Snapshot
132+ run : |
133+ SNAPSHOT_ZIP="${{ github.workspace }}/prometheus-snapshot.zip"
134+ SNAPSHOT_TAR="${{ github.workspace }}/prometheus_snapshot.tar.gz"
135+ SNAPSHOT_DIR="${{ github.workspace }}/prometheus-data/snapshots"
136+
137+ mkdir -p "$SNAPSHOT_DIR"
138+
139+ if [[ -f "$SNAPSHOT_ZIP" ]]; then
140+ echo "📦 Detected ZIP archive: $SNAPSHOT_ZIP"
141+ unzip -o "$SNAPSHOT_ZIP" -d "$SNAPSHOT_DIR"
142+ echo "✅ Successfully extracted ZIP snapshot."
143+ elif [[ -f "$SNAPSHOT_TAR" ]]; then
144+ echo "📦 Detected TAR archive: $SNAPSHOT_TAR"
145+ tar -xzf "$SNAPSHOT_TAR" -C "$SNAPSHOT_DIR"
146+ echo "✅ Successfully extracted TAR snapshot."
147+ else
148+ echo "⚠️ WARNING: No snapshot file found. Skipping extraction."
149+ fi
150+
151+ - name : Run Prometheus
152+ run : |
153+ docker run -d --name prometheus -p 9090:9090 \
154+ --user=root \
155+ -v ${{ github.workspace }}/prometheus.yml:/etc/prometheus/prometheus.yml \
156+ -v ${{ github.workspace }}/prometheus-data:/prometheus \
157+ prom/prometheus --config.file=/etc/prometheus/prometheus.yml \
158+ --storage.tsdb.path=/prometheus \
159+ --storage.tsdb.retention.time=1h \
160+ --web.enable-admin-api
161+
162+ - name : Wait for Prometheus to start
163+ run : sleep 10
164+
165+ - name : Check Prometheus is running
166+ run : |
167+ set -e
168+ curl -s http://localhost:9090/-/ready || (docker logs prometheus && exit 1)
46169
170+ - name : Start HTTP Server to Expose Metrics
171+ run : |
172+ cat << 'EOF' > server.py
173+ from http.server import SimpleHTTPRequestHandler, HTTPServer
174+
175+ class MetricsHandler(SimpleHTTPRequestHandler):
176+ def do_GET(self):
177+ if self.path == "/metrics":
178+ self.send_response(200)
179+ self.send_header("Content-type", "text/plain")
180+ self.end_headers()
181+ with open("metrics.txt", "r") as f:
182+ self.wfile.write(f.read().encode())
183+ else:
184+ self.send_response(404)
185+ self.end_headers()
186+
187+ if __name__ == "__main__":
188+ server = HTTPServer(('0.0.0.0', 9000), MetricsHandler)
189+ print("Serving on port 9000...")
190+ server.serve_forever()
191+ EOF
192+
193+ nohup python3 server.py &
194+
195+ - name : Wait for Prometheus to Collect Data
196+ run : sleep 30
197+
198+ - name : Check Prometheus targets page
199+ run : |
200+ http_status=$(curl -o /dev/null -s -w "%{http_code}" http://localhost:9090/targets)
201+ if [ "$http_status" -eq 200 ]; then
202+ echo "Prometheus targets page is reachable."
203+ else
204+ echo "Error: Prometheus targets page is not reachable. Status code: $http_status"
205+ exit 1
206+ fi
207+
208+ - name : Debug via SSH
209+ uses : mxschmitt/action-tmate@v3
210+
211+ - name : Check Benchmark Metrics Against Threshold
212+ run : |
213+ MAX_TIME_NS=1200000000 # 1.2s
214+ MAX_ALLOCS=4000
215+ MAX_MEM_BYTES=450000
216+
217+ # Query Prometheus Metrics, get the max value
218+ time_ns=$(curl -s "http://localhost:9090/api/v1/query?query=max(benchmark_createclustercatalog_ns)" | jq -r '.data.result[0].value[1]')
219+ allocs=$(curl -s "http://localhost:9090/api/v1/query?query=max(benchmark_createclustercatalog_allocs)" | jq -r '.data.result[0].value[1]')
220+ mem_bytes=$(curl -s "http://localhost:9090/api/v1/query?query=max(benchmark_createclustercatalog_mem_bytes)" | jq -r '.data.result[0].value[1]')
221+
222+ echo "⏳ Benchmark Execution Time: $time_ns ns"
223+ echo "🛠️ Memory Allocations: $allocs"
224+ echo "💾 Memory Usage: $mem_bytes bytes"
225+
226+ # threshold checking
227+ if (( $(echo "$time_ns > $MAX_TIME_NS" | bc -l) )); then
228+ echo "❌ ERROR: Execution time exceeds threshold!"
229+ exit 1
230+ fi
231+
232+ if (( $(echo "$allocs > $MAX_ALLOCS" | bc -l) )); then
233+ echo "❌ ERROR: Too many memory allocations!"
234+ exit 1
235+ fi
236+
237+ if (( $(echo "$mem_bytes > $MAX_MEM_BYTES" | bc -l) )); then
238+ echo "❌ ERROR: Memory usage exceeds threshold!"
239+ exit 1
240+ fi
241+
242+ echo "✅ All benchmarks passed within threshold!"
243+
244+ - name : Trigger Prometheus Snapshot
245+ run : |
246+ set -e
247+ curl -X POST http://localhost:9090/api/v1/admin/tsdb/snapshot || (docker logs prometheus && exit 1)
248+
249+ - name : Find and Upload Prometheus Snapshot
250+ run : |
251+ SNAPSHOT_PATH=$(ls -td ${{ github.workspace }}/prometheus-data/snapshots/* 2>/dev/null | head -1 || echo "")
252+ if [[ -z "$SNAPSHOT_PATH" ]]; then
253+ echo "❌ No Prometheus snapshot found!"
254+ docker logs prometheus
255+ exit 1
256+ fi
257+
258+ echo "✅ Prometheus snapshot stored in: $SNAPSHOT_PATH"
259+ tar -czf $GITHUB_WORKSPACE/prometheus_snapshot.tar.gz -C "$SNAPSHOT_PATH" .
260+
261+
262+ - name : Stop Prometheus
263+ run : docker stop prometheus
264+
265+ - name : Upload Prometheus Snapshot
266+ uses : actions/upload-artifact@v4
267+ with :
268+ name : prometheus-snapshot
269+ path : prometheus_snapshot.tar.gz
270+
0 commit comments