Skip to content

Commit b913791

Browse files
authored
Merge pull request #12 from appwrite/improve-screenshots-perf
Improvements
2 parents 77cd303 + b614f34 commit b913791

File tree

5 files changed

+111
-27
lines changed

5 files changed

+111
-27
lines changed

.github/workflows/test.yml

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ jobs:
99
tests:
1010
name: "Unit and E2E Tests"
1111
runs-on: ubuntu-latest
12+
permissions:
13+
contents: read
14+
pull-requests: write
1215
steps:
1316
- name: Checkout code
1417
uses: actions/checkout@v4
@@ -28,10 +31,16 @@ jobs:
2831
run: docker compose up -d
2932

3033
- name: Wait for container to be ready
34+
id: container-startup
3135
run: |
36+
START_TIME=$(date +%s%3N)
3237
for i in {1..30}; do
3338
if curl -f http://localhost:3000/v1/health > /dev/null 2>&1; then
34-
echo "Container is ready!"
39+
END_TIME=$(date +%s%3N)
40+
STARTUP_TIME_MS=$((END_TIME - START_TIME))
41+
STARTUP_TIME=$(echo "scale=2; $STARTUP_TIME_MS / 1000" | bc)
42+
echo "startup_time=$STARTUP_TIME" >> $GITHUB_OUTPUT
43+
echo "Container is ready in ${STARTUP_TIME}s!"
3544
exit 0
3645
fi
3746
echo "Waiting for container... ($i/30)"
@@ -42,6 +51,57 @@ jobs:
4251
docker compose logs
4352
exit 1
4453
54+
- name: Collect Docker stats
55+
if: github.event_name == 'pull_request'
56+
continue-on-error: true
57+
id: docker-stats
58+
run: |
59+
# Get image size
60+
IMAGE_SIZE=$(docker images appwrite/browser:local --format "{{.Size}}")
61+
62+
# Get container stats
63+
CONTAINER_ID=$(docker compose ps -q appwrite-browser)
64+
MEMORY_USAGE=$(docker stats $CONTAINER_ID --no-stream --format "{{.MemUsage}}" | cut -d'/' -f1 | xargs)
65+
66+
# Quick screenshot benchmark (3 runs, average)
67+
TOTAL=0
68+
for i in {1..3}; do
69+
START=$(date +%s%3N)
70+
curl -s -X POST http://localhost:3000/v1/screenshots \
71+
-H "Content-Type: application/json" \
72+
-d '{"url":"https://appwrite.io"}' \
73+
-o /dev/null
74+
END=$(date +%s%3N)
75+
DURATION=$((END - START))
76+
TOTAL=$((TOTAL + DURATION))
77+
done
78+
SCREENSHOT_AVG_MS=$((TOTAL / 3))
79+
SCREENSHOT_AVG=$(echo "scale=2; $SCREENSHOT_AVG_MS / 1000" | bc)
80+
81+
# Store in GitHub output
82+
echo "image_size=$IMAGE_SIZE" >> $GITHUB_OUTPUT
83+
echo "memory_usage=$MEMORY_USAGE" >> $GITHUB_OUTPUT
84+
echo "screenshot_time=$SCREENSHOT_AVG" >> $GITHUB_OUTPUT
85+
86+
- name: Comment PR with stats
87+
if: github.event_name == 'pull_request' && steps.docker-stats.outcome == 'success'
88+
continue-on-error: true
89+
uses: marocchino/sticky-pull-request-comment@v2
90+
with:
91+
header: docker-image-stats
92+
skip_unchanged: true
93+
message: |
94+
## Docker Image Stats
95+
96+
| Metric | Value |
97+
|--------|-------|
98+
| Image Size | ${{ steps.docker-stats.outputs.image_size }} |
99+
| Memory Usage | ${{ steps.docker-stats.outputs.memory_usage }} |
100+
| Cold Start Time | ${{ steps.container-startup.outputs.startup_time }}s |
101+
| Screenshot Time | ${{ steps.docker-stats.outputs.screenshot_time }}s |
102+
103+
<sub>Screenshot benchmark: Average of 3 runs on https://appwrite.io</sub>
104+
45105
- name: Run e2e tests
46106
run: bun test:e2e
47107

src/schemas/screenshot.schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ export const screenshotSchema = z.object({
44
url: z.string().url(),
55
theme: z.enum(["light", "dark"]).default("light"),
66
headers: z.record(z.string(), z.any()).optional(),
7-
sleep: z.number().min(0).max(60000).default(3000),
7+
sleep: z.number().min(0).max(60000).default(0),
88
// Viewport options
99
viewport: z
1010
.object({

src/server.ts

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,17 @@ import {
55
handleScreenshotsRequest,
66
handleTestRequest,
77
} from "./routes";
8+
import { Router } from "./utils/router";
9+
10+
const router = new Router();
11+
router.add("POST", "/v1/screenshots", handleScreenshotsRequest);
12+
router.add("POST", "/v1/reports", handleReportsRequest);
13+
router.add("GET", "/v1/health", handleHealthRequest);
14+
router.add("GET", "/v1/test", handleTestRequest);
815

916
const server = Bun.serve({
1017
port,
11-
async fetch(req) {
12-
const url = new URL(req.url);
13-
const path = url.pathname;
14-
15-
// Route matching
16-
if (path === "/v1/screenshots" && req.method === "POST") {
17-
return await handleScreenshotsRequest(req);
18-
}
19-
20-
if (path === "/v1/reports" && req.method === "POST") {
21-
return await handleReportsRequest(req);
22-
}
23-
24-
if (path === "/v1/health" && req.method === "GET") {
25-
return await handleHealthRequest(req);
26-
}
27-
28-
if (path === "/v1/test" && req.method === "GET") {
29-
return await handleTestRequest(req);
30-
}
31-
32-
// 404 Not Found
33-
return new Response("Not Found", { status: 404 });
34-
},
18+
fetch: (request) => router.handle(request),
3519
});
3620

3721
console.log(`Server running on http://0.0.0.0:${server.port}`);

src/utils/router.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
type HTTPMethod =
2+
| "GET"
3+
| "POST"
4+
| "PUT"
5+
| "PATCH"
6+
| "DELETE"
7+
| "OPTIONS"
8+
| "HEAD";
9+
10+
type RouteHandler = (req: Request) => Promise<Response>;
11+
12+
type Route = {
13+
method: HTTPMethod;
14+
pattern: RegExp;
15+
handler: RouteHandler;
16+
};
17+
18+
export class Router {
19+
private routes: Route[] = [];
20+
21+
add(method: HTTPMethod, path: string, handler: RouteHandler): void {
22+
this.routes.push({
23+
method,
24+
pattern: new RegExp(`^${path}$`),
25+
handler,
26+
});
27+
}
28+
29+
async handle(req: Request): Promise<Response> {
30+
const url = new URL(req.url);
31+
32+
for (const route of this.routes) {
33+
if (route.method === req.method && route.pattern.test(url.pathname)) {
34+
return await route.handler(req);
35+
}
36+
}
37+
38+
return new Response("Not Found", { status: 404 });
39+
}
40+
}

tests/unit/screenshot.schema.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe("screenshotSchema", () => {
2323
expect(result.quality).toBe(90);
2424
expect(result.waitUntil).toBe("domcontentloaded");
2525
expect(result.timeout).toBe(30000);
26-
expect(result.sleep).toBe(3000);
26+
expect(result.sleep).toBe(0);
2727
});
2828

2929
test("should validate custom viewport", () => {

0 commit comments

Comments
 (0)