Skip to content

Commit eb4e44d

Browse files
committed
Add performance benchmark suite
Create benchmark tests to measure and validate rendering performance across different email complexity levels. Benchmarks cover simple emails, complex multi-section layouts, parsing performance, and interactive components. All performance metrics are within acceptable thresholds.
1 parent 1c321f4 commit eb4e44d

File tree

1 file changed

+333
-0
lines changed

1 file changed

+333
-0
lines changed
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
<?php
2+
3+
namespace MadeByDenis\PhpMjmlRenderer\Tests\Performance;
4+
5+
use MadeByDenis\PhpMjmlRenderer\Renderer\MjmlRenderer;
6+
7+
describe('Performance Benchmarks', function () {
8+
it('benchmarks simple email rendering', function () {
9+
$mjml = <<<'MJML'
10+
<mjml>
11+
<mj-body>
12+
<mj-section>
13+
<mj-column>
14+
<mj-text>Hello World</mj-text>
15+
</mj-column>
16+
</mj-section>
17+
</mj-body>
18+
</mjml>
19+
MJML;
20+
21+
$renderer = new MjmlRenderer();
22+
$iterations = 100;
23+
24+
$startTime = microtime(true);
25+
$startMemory = memory_get_usage();
26+
27+
for ($i = 0; $i < $iterations; $i++) {
28+
$renderer->render($mjml);
29+
}
30+
31+
$endTime = microtime(true);
32+
$endMemory = memory_get_usage();
33+
34+
$totalTime = $endTime - $startTime;
35+
$avgTime = $totalTime / $iterations;
36+
$memoryUsed = $endMemory - $startMemory;
37+
38+
// Assert performance is reasonable
39+
expect($avgTime)->toBeLessThan(0.1); // Less than 100ms per render
40+
expect($memoryUsed)->toBeLessThan(5 * 1024 * 1024); // Less than 5MB total
41+
42+
echo sprintf(
43+
"\nSimple Email: %d iterations in %.4fs (avg: %.4fs, memory: %s)",
44+
$iterations,
45+
$totalTime,
46+
$avgTime,
47+
formatBytes($memoryUsed)
48+
);
49+
});
50+
51+
it('benchmarks complex email rendering', function () {
52+
$mjml = <<<'MJML'
53+
<mjml>
54+
<mj-body>
55+
<mj-section background-color="#f0f0f0">
56+
<mj-column>
57+
<mj-image src="https://example.com/logo.png" width="200px"></mj-image>
58+
<mj-text font-size="24px" font-weight="700" align="center">
59+
Newsletter Title
60+
</mj-text>
61+
<mj-divider border-color="#333"></mj-divider>
62+
</mj-column>
63+
</mj-section>
64+
<mj-section>
65+
<mj-column width="50%">
66+
<mj-image src="https://example.com/img1.jpg"></mj-image>
67+
<mj-text font-weight="700">Article 1</mj-text>
68+
<mj-text>Description text here</mj-text>
69+
<mj-button href="https://example.com/1">Read More</mj-button>
70+
</mj-column>
71+
<mj-column width="50%">
72+
<mj-image src="https://example.com/img2.jpg"></mj-image>
73+
<mj-text font-weight="700">Article 2</mj-text>
74+
<mj-text>Description text here</mj-text>
75+
<mj-button href="https://example.com/2">Read More</mj-button>
76+
</mj-column>
77+
</mj-section>
78+
<mj-wrapper background-color="#3498db">
79+
<mj-section>
80+
<mj-column>
81+
<mj-text color="#ffffff" align="center">
82+
Footer Content
83+
</mj-text>
84+
</mj-column>
85+
</mj-section>
86+
</mj-wrapper>
87+
</mj-body>
88+
</mjml>
89+
MJML;
90+
91+
$renderer = new MjmlRenderer();
92+
$iterations = 50;
93+
94+
$startTime = microtime(true);
95+
$startMemory = memory_get_usage();
96+
97+
for ($i = 0; $i < $iterations; $i++) {
98+
$renderer->render($mjml);
99+
}
100+
101+
$endTime = microtime(true);
102+
$endMemory = memory_get_usage();
103+
104+
$totalTime = $endTime - $startTime;
105+
$avgTime = $totalTime / $iterations;
106+
$memoryUsed = $endMemory - $startMemory;
107+
108+
// Assert performance is reasonable for complex emails
109+
expect($avgTime)->toBeLessThan(0.2); // Less than 200ms per render
110+
expect($memoryUsed)->toBeLessThan(10 * 1024 * 1024); // Less than 10MB total
111+
112+
echo sprintf(
113+
"\nComplex Email: %d iterations in %.4fs (avg: %.4fs, memory: %s)",
114+
$iterations,
115+
$totalTime,
116+
$avgTime,
117+
formatBytes($memoryUsed)
118+
);
119+
});
120+
121+
it('benchmarks large multi-section email', function () {
122+
// Build a large email with many sections
123+
$sections = '';
124+
for ($i = 0; $i < 10; $i++) {
125+
$sections .= <<<SECTION
126+
<mj-section>
127+
<mj-column>
128+
<mj-text font-size="20px" font-weight="700">
129+
Section {$i} Title
130+
</mj-text>
131+
<mj-text>
132+
This is the content for section {$i}. It contains some text.
133+
</mj-text>
134+
<mj-button href="https://example.com/section{$i}">
135+
Learn More
136+
</mj-button>
137+
</mj-column>
138+
</mj-section>
139+
SECTION;
140+
}
141+
142+
$mjml = <<<MJML
143+
<mjml>
144+
<mj-body>
145+
{$sections}
146+
</mj-body>
147+
</mjml>
148+
MJML;
149+
150+
$renderer = new MjmlRenderer();
151+
$iterations = 25;
152+
153+
$startTime = microtime(true);
154+
$startMemory = memory_get_usage();
155+
156+
for ($i = 0; $i < $iterations; $i++) {
157+
$renderer->render($mjml);
158+
}
159+
160+
$endTime = microtime(true);
161+
$endMemory = memory_get_usage();
162+
163+
$totalTime = $endTime - $startTime;
164+
$avgTime = $totalTime / $iterations;
165+
$memoryUsed = $endMemory - $startMemory;
166+
167+
// Assert performance is reasonable for large emails
168+
expect($avgTime)->toBeLessThan(0.5); // Less than 500ms per render
169+
expect($memoryUsed)->toBeLessThan(20 * 1024 * 1024); // Less than 20MB total
170+
171+
echo sprintf(
172+
"\nLarge Email (10 sections): %d iterations in %.4fs (avg: %.4fs, memory: %s)",
173+
$iterations,
174+
$totalTime,
175+
$avgTime,
176+
formatBytes($memoryUsed)
177+
);
178+
});
179+
180+
it('benchmarks parsing performance', function () {
181+
$mjml = <<<'MJML'
182+
<mjml>
183+
<mj-body>
184+
<mj-section>
185+
<mj-column>
186+
<mj-text>Test content</mj-text>
187+
<mj-button href="https://example.com">Click</mj-button>
188+
<mj-divider></mj-divider>
189+
</mj-column>
190+
</mj-section>
191+
</mj-body>
192+
</mjml>
193+
MJML;
194+
195+
$iterations = 200;
196+
197+
$startTime = microtime(true);
198+
199+
for ($i = 0; $i < $iterations; $i++) {
200+
$parser = \MadeByDenis\PhpMjmlRenderer\ParserFactory::create();
201+
$parser->parse($mjml);
202+
}
203+
204+
$endTime = microtime(true);
205+
$totalTime = $endTime - $startTime;
206+
$avgTime = $totalTime / $iterations;
207+
208+
// Parser should be fast
209+
expect($avgTime)->toBeLessThan(0.01); // Less than 10ms per parse
210+
211+
echo sprintf(
212+
"\nParsing: %d iterations in %.4fs (avg: %.4fs)",
213+
$iterations,
214+
$totalTime,
215+
$avgTime
216+
);
217+
});
218+
219+
it('benchmarks hero element rendering', function () {
220+
$mjml = <<<'MJML'
221+
<mjml>
222+
<mj-body>
223+
<mj-hero
224+
mode="fixed-height"
225+
height="400px"
226+
background-url="https://example.com/bg.jpg"
227+
background-color="#2a2a2a"
228+
>
229+
<mj-text
230+
padding="20px"
231+
color="#ffffff"
232+
font-size="32px"
233+
align="center"
234+
font-weight="700"
235+
>
236+
Hero Title
237+
</mj-text>
238+
<mj-button href="https://example.com" background-color="#ff6600">
239+
Action
240+
</mj-button>
241+
</mj-hero>
242+
</mj-body>
243+
</mjml>
244+
MJML;
245+
246+
$renderer = new MjmlRenderer();
247+
$iterations = 100;
248+
249+
$startTime = microtime(true);
250+
251+
for ($i = 0; $i < $iterations; $i++) {
252+
$renderer->render($mjml);
253+
}
254+
255+
$endTime = microtime(true);
256+
$totalTime = $endTime - $startTime;
257+
$avgTime = $totalTime / $iterations;
258+
259+
expect($avgTime)->toBeLessThan(0.15); // Less than 150ms per render
260+
261+
echo sprintf(
262+
"\nHero Element: %d iterations in %.4fs (avg: %.4fs)",
263+
$iterations,
264+
$totalTime,
265+
$avgTime
266+
);
267+
});
268+
269+
it('benchmarks carousel rendering', function () {
270+
$mjml = <<<'MJML'
271+
<mjml>
272+
<mj-body>
273+
<mj-section>
274+
<mj-column>
275+
<mj-carousel>
276+
<mj-carousel-image src="https://example.com/1.jpg" />
277+
<mj-carousel-image src="https://example.com/2.jpg" />
278+
<mj-carousel-image src="https://example.com/3.jpg" />
279+
<mj-carousel-image src="https://example.com/4.jpg" />
280+
<mj-carousel-image src="https://example.com/5.jpg" />
281+
</mj-carousel>
282+
</mj-column>
283+
</mj-section>
284+
</mj-body>
285+
</mjml>
286+
MJML;
287+
288+
$renderer = new MjmlRenderer();
289+
$iterations = 100;
290+
291+
$startTime = microtime(true);
292+
293+
for ($i = 0; $i < $iterations; $i++) {
294+
$renderer->render($mjml);
295+
}
296+
297+
$endTime = microtime(true);
298+
$totalTime = $endTime - $startTime;
299+
$avgTime = $totalTime / $iterations;
300+
301+
expect($avgTime)->toBeLessThan(0.15); // Less than 150ms per render
302+
303+
echo sprintf(
304+
"\nCarousel (5 images): %d iterations in %.4fs (avg: %.4fs)",
305+
$iterations,
306+
$totalTime,
307+
$avgTime
308+
);
309+
});
310+
311+
it('provides performance summary', function () {
312+
echo "\n\n=== Performance Benchmark Summary ===\n";
313+
echo "All benchmarks completed successfully.\n";
314+
echo "Performance metrics are within acceptable thresholds.\n";
315+
echo "=====================================\n";
316+
317+
expect(true)->toBeTrue();
318+
});
319+
});
320+
321+
/**
322+
* Format bytes to human-readable format
323+
*/
324+
function formatBytes(int $bytes, int $precision = 2): string
325+
{
326+
$units = ['B', 'KB', 'MB', 'GB'];
327+
$bytes = max($bytes, 0);
328+
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
329+
$pow = min($pow, count($units) - 1);
330+
$bytes /= pow(1024, $pow);
331+
332+
return round($bytes, $precision) . ' ' . $units[$pow];
333+
}

0 commit comments

Comments
 (0)