test_benchmark_compile_pages.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. """Benchmark tests for apps with varying page numbers."""
  2. from __future__ import annotations
  3. import functools
  4. import time
  5. from typing import Generator
  6. import pytest
  7. from benchmarks import WINDOWS_SKIP_REASON
  8. from reflex import constants
  9. from reflex.compiler import utils
  10. from reflex.testing import AppHarness, chdir
  11. from reflex.utils import build
  12. def render_multiple_pages(app, num: int):
  13. """Add multiple pages based on num.
  14. Args:
  15. app: The App object.
  16. num: number of pages to render.
  17. """
  18. from typing import Tuple
  19. from rxconfig import config # type: ignore
  20. import reflex as rx
  21. docs_url = "https://reflex.dev/docs/getting-started/introduction/"
  22. filename = f"{config.app_name}/{config.app_name}.py"
  23. college = [
  24. "Stanford University",
  25. "Arizona",
  26. "Arizona state",
  27. "Baylor",
  28. "Boston College",
  29. "Boston University",
  30. ]
  31. class State(rx.State):
  32. """The app state."""
  33. position: str
  34. college: str
  35. age: Tuple[int, int] = (18, 50)
  36. salary: Tuple[int, int] = (0, 25000000)
  37. comp1 = rx.center(
  38. rx.theme_panel(),
  39. rx.vstack(
  40. rx.heading("Welcome to Reflex!", size="9"),
  41. rx.text("Get started by editing ", rx.code(filename)),
  42. rx.button(
  43. "Check out our docs!",
  44. on_click=lambda: rx.redirect(docs_url),
  45. size="4",
  46. ),
  47. align="center",
  48. spacing="7",
  49. font_size="2em",
  50. ),
  51. height="100vh",
  52. )
  53. comp2 = rx.vstack(
  54. rx.hstack(
  55. rx.vstack(
  56. rx.select(
  57. ["C", "PF", "SF", "PG", "SG"],
  58. placeholder="Select a position. (All)",
  59. on_change=State.set_position, # type: ignore
  60. size="3",
  61. ),
  62. rx.select(
  63. college,
  64. placeholder="Select a college. (All)",
  65. on_change=State.set_college, # type: ignore
  66. size="3",
  67. ),
  68. ),
  69. rx.vstack(
  70. rx.vstack(
  71. rx.hstack(
  72. rx.badge("Min Age: ", State.age[0]),
  73. rx.divider(orientation="vertical"),
  74. rx.badge("Max Age: ", State.age[1]),
  75. ),
  76. rx.slider(
  77. default_value=[18, 50],
  78. min=18,
  79. max=50,
  80. on_value_commit=State.set_age, # type: ignore
  81. ),
  82. align_items="left",
  83. width="100%",
  84. ),
  85. rx.vstack(
  86. rx.hstack(
  87. rx.badge("Min Sal: ", State.salary[0] // 1000000, "M"),
  88. rx.divider(orientation="vertical"),
  89. rx.badge("Max Sal: ", State.salary[1] // 1000000, "M"),
  90. ),
  91. rx.slider(
  92. default_value=[0, 25000000],
  93. min=0,
  94. max=25000000,
  95. on_value_commit=State.set_salary, # type: ignore
  96. ),
  97. align_items="left",
  98. width="100%",
  99. ),
  100. ),
  101. spacing="4",
  102. ),
  103. width="100%",
  104. )
  105. for i in range(1, num + 1):
  106. if i % 2 == 1:
  107. app.add_page(comp1, route=f"page{i}")
  108. else:
  109. app.add_page(comp2, route=f"page{i}")
  110. def AppWithOnePage():
  111. """A reflex app with one page."""
  112. from rxconfig import config # type: ignore
  113. import reflex as rx
  114. docs_url = "https://reflex.dev/docs/getting-started/introduction/"
  115. filename = f"{config.app_name}/{config.app_name}.py"
  116. class State(rx.State):
  117. """The app state."""
  118. pass
  119. def index() -> rx.Component:
  120. return rx.center(
  121. rx.chakra.input(
  122. id="token", value=State.router.session.client_token, is_read_only=True
  123. ),
  124. rx.vstack(
  125. rx.heading("Welcome to Reflex!", size="9"),
  126. rx.text("Get started by editing ", rx.code(filename)),
  127. rx.button(
  128. "Check out our docs!",
  129. on_click=lambda: rx.redirect(docs_url),
  130. size="4",
  131. ),
  132. align="center",
  133. spacing="7",
  134. font_size="2em",
  135. ),
  136. height="100vh",
  137. )
  138. app = rx.App(state=rx.State)
  139. app.add_page(index)
  140. def AppWithTenPages():
  141. """A reflex app with 10 pages."""
  142. import reflex as rx
  143. app = rx.App(state=rx.State)
  144. render_multiple_pages(app, 10)
  145. def AppWithHundredPages():
  146. """A reflex app with 100 pages."""
  147. import reflex as rx
  148. app = rx.App(state=rx.State)
  149. render_multiple_pages(app, 100)
  150. def AppWithThousandPages():
  151. """A reflex app with Thousand pages."""
  152. import reflex as rx
  153. app = rx.App(state=rx.State)
  154. render_multiple_pages(app, 1000)
  155. def AppWithTenThousandPages():
  156. """A reflex app with ten thousand pages."""
  157. import reflex as rx
  158. app = rx.App(state=rx.State)
  159. render_multiple_pages(app, 10000)
  160. @pytest.fixture(scope="session")
  161. def app_with_one_page(
  162. tmp_path_factory,
  163. ) -> Generator[AppHarness, None, None]:
  164. """Create an app with 10000 pages at tmp_path via AppHarness.
  165. Args:
  166. tmp_path_factory: pytest tmp_path_factory fixture
  167. Yields:
  168. an AppHarness instance
  169. """
  170. root = tmp_path_factory.mktemp(f"app1")
  171. yield AppHarness.create(root=root, app_source=AppWithOnePage) # type: ignore
  172. @pytest.fixture(scope="session")
  173. def app_with_ten_pages(
  174. tmp_path_factory,
  175. ) -> Generator[AppHarness, None, None]:
  176. """Create an app with 10 pages at tmp_path via AppHarness.
  177. Args:
  178. tmp_path_factory: pytest tmp_path_factory fixture
  179. Yields:
  180. an AppHarness instance
  181. """
  182. root = tmp_path_factory.mktemp(f"app10")
  183. yield AppHarness.create(root=root, app_source=functools.partial(AppWithTenPages, render_comp=render_multiple_pages)) # type: ignore
  184. @pytest.fixture(scope="session")
  185. def app_with_hundred_pages(
  186. tmp_path_factory,
  187. ) -> Generator[AppHarness, None, None]:
  188. """Create an app with 100 pages at tmp_path via AppHarness.
  189. Args:
  190. tmp_path_factory: pytest tmp_path_factory fixture
  191. Yields:
  192. an AppHarness instance
  193. """
  194. root = tmp_path_factory.mktemp(f"app100")
  195. yield AppHarness.create(
  196. root=root,
  197. app_source=functools.partial(
  198. AppWithHundredPages, render_comp=render_multiple_pages # type: ignore
  199. ),
  200. ) # type: ignore
  201. @pytest.fixture(scope="session")
  202. def app_with_thousand_pages(
  203. tmp_path_factory,
  204. ) -> Generator[AppHarness, None, None]:
  205. """Create an app with 1000 pages at tmp_path via AppHarness.
  206. Args:
  207. tmp_path_factory: pytest tmp_path_factory fixture
  208. Yields:
  209. an AppHarness instance
  210. """
  211. root = tmp_path_factory.mktemp(f"app1000")
  212. yield AppHarness.create(
  213. root=root,
  214. app_source=functools.partial( # type: ignore
  215. AppWithThousandPages, render_comp=render_multiple_pages # type: ignore
  216. ),
  217. ) # type: ignore
  218. @pytest.fixture(scope="session")
  219. def app_with_ten_thousand_pages(
  220. tmp_path_factory,
  221. ) -> Generator[AppHarness, None, None]:
  222. """Create an app with 10000 pages at tmp_path via AppHarness.
  223. Args:
  224. tmp_path_factory: pytest tmp_path_factory fixture
  225. Yields:
  226. running AppHarness instance
  227. """
  228. root = tmp_path_factory.mktemp(f"app10000")
  229. yield AppHarness.create(
  230. root=root,
  231. app_source=functools.partial(
  232. AppWithTenThousandPages, render_comp=render_multiple_pages # type: ignore
  233. ),
  234. ) # type: ignore
  235. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  236. @pytest.mark.benchmark(
  237. group="Compile time of varying page numbers",
  238. timer=time.perf_counter,
  239. disable_gc=True,
  240. warmup=False,
  241. )
  242. def test_app_1_compile_time_cold(benchmark, app_with_one_page):
  243. """Test the compile time on a cold start for an app with 1 page.
  244. Args:
  245. benchmark: The benchmark fixture.
  246. app_with_one_page: The app harness.
  247. """
  248. def setup():
  249. with chdir(app_with_one_page.app_path):
  250. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  251. app_with_one_page._initialize_app()
  252. build.setup_frontend(app_with_one_page.app_path)
  253. def benchmark_fn():
  254. with chdir(app_with_one_page.app_path):
  255. app_with_one_page.app_instance.compile_()
  256. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  257. @pytest.mark.benchmark(
  258. group="Compile time of varying page numbers",
  259. min_rounds=5,
  260. timer=time.perf_counter,
  261. disable_gc=True,
  262. warmup=False,
  263. )
  264. def test_app_1_compile_time_warm(benchmark, app_with_one_page):
  265. """Test the compile time on a warm start for an app with 1 page.
  266. Args:
  267. benchmark: The benchmark fixture.
  268. app_with_one_page: The app harness.
  269. """
  270. with chdir(app_with_one_page.app_path):
  271. app_with_one_page._initialize_app()
  272. build.setup_frontend(app_with_one_page.app_path)
  273. def benchmark_fn():
  274. with chdir(app_with_one_page.app_path):
  275. app_with_one_page.app_instance.compile_()
  276. benchmark(benchmark_fn)
  277. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  278. @pytest.mark.benchmark(
  279. group="Compile time of varying page numbers",
  280. timer=time.perf_counter,
  281. disable_gc=True,
  282. warmup=False,
  283. )
  284. def test_app_10_compile_time_cold(benchmark, app_with_ten_pages):
  285. """Test the compile time on a cold start for an app with 10 page.
  286. Args:
  287. benchmark: The benchmark fixture.
  288. app_with_ten_pages: The app harness.
  289. """
  290. def setup():
  291. with chdir(app_with_ten_pages.app_path):
  292. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  293. app_with_ten_pages._initialize_app()
  294. build.setup_frontend(app_with_ten_pages.app_path)
  295. def benchmark_fn():
  296. with chdir(app_with_ten_pages.app_path):
  297. app_with_ten_pages.app_instance.compile_()
  298. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  299. @pytest.mark.benchmark(
  300. group="Compile time of varying page numbers",
  301. min_rounds=5,
  302. timer=time.perf_counter,
  303. disable_gc=True,
  304. warmup=False,
  305. )
  306. def test_app_10_compile_time_warm(benchmark, app_with_ten_pages):
  307. """Test the compile time on a warm start for an app with 10 page.
  308. Args:
  309. benchmark: The benchmark fixture.
  310. app_with_ten_pages: The app harness.
  311. """
  312. with chdir(app_with_ten_pages.app_path):
  313. app_with_ten_pages._initialize_app()
  314. build.setup_frontend(app_with_ten_pages.app_path)
  315. def benchmark_fn():
  316. with chdir(app_with_ten_pages.app_path):
  317. app_with_ten_pages.app_instance.compile_()
  318. benchmark(benchmark_fn)
  319. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  320. @pytest.mark.benchmark(
  321. group="Compile time of varying page numbers",
  322. timer=time.perf_counter,
  323. disable_gc=True,
  324. warmup=False,
  325. )
  326. def test_app_100_compile_time_cold(benchmark, app_with_hundred_pages):
  327. """Test the compile time on a cold start for an app with 100 page.
  328. Args:
  329. benchmark: The benchmark fixture.
  330. app_with_hundred_pages: The app harness.
  331. """
  332. def setup():
  333. with chdir(app_with_hundred_pages.app_path):
  334. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  335. app_with_hundred_pages._initialize_app()
  336. build.setup_frontend(app_with_hundred_pages.app_path)
  337. def benchmark_fn():
  338. with chdir(app_with_hundred_pages.app_path):
  339. app_with_hundred_pages.app_instance.compile_()
  340. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  341. @pytest.mark.benchmark(
  342. group="Compile time of varying page numbers",
  343. min_rounds=5,
  344. timer=time.perf_counter,
  345. disable_gc=True,
  346. warmup=False,
  347. )
  348. def test_app_100_compile_time_warm(benchmark, app_with_hundred_pages):
  349. """Test the compile time on a warm start for an app with 100 page.
  350. Args:
  351. benchmark: The benchmark fixture.
  352. app_with_hundred_pages: The app harness.
  353. """
  354. with chdir(app_with_hundred_pages.app_path):
  355. app_with_hundred_pages._initialize_app()
  356. build.setup_frontend(app_with_hundred_pages.app_path)
  357. def benchmark_fn():
  358. with chdir(app_with_hundred_pages.app_path):
  359. app_with_hundred_pages.app_instance.compile_()
  360. benchmark(benchmark_fn)
  361. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  362. @pytest.mark.benchmark(
  363. group="Compile time of varying page numbers",
  364. timer=time.perf_counter,
  365. disable_gc=True,
  366. warmup=False,
  367. )
  368. def test_app_1000_compile_time_cold(benchmark, app_with_thousand_pages):
  369. """Test the compile time on a cold start for an app with 1000 page.
  370. Args:
  371. benchmark: The benchmark fixture.
  372. app_with_thousand_pages: The app harness.
  373. """
  374. def setup():
  375. with chdir(app_with_thousand_pages.app_path):
  376. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  377. app_with_thousand_pages._initialize_app()
  378. build.setup_frontend(app_with_thousand_pages.app_path)
  379. def benchmark_fn():
  380. with chdir(app_with_thousand_pages.app_path):
  381. app_with_thousand_pages.app_instance.compile_()
  382. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  383. @pytest.mark.benchmark(
  384. group="Compile time of varying page numbers",
  385. min_rounds=5,
  386. timer=time.perf_counter,
  387. disable_gc=True,
  388. warmup=False,
  389. )
  390. def test_app_1000_compile_time_warm(benchmark, app_with_thousand_pages):
  391. """Test the compile time on a warm start for an app with 1000 page.
  392. Args:
  393. benchmark: The benchmark fixture.
  394. app_with_thousand_pages: The app harness.
  395. """
  396. with chdir(app_with_thousand_pages.app_path):
  397. app_with_thousand_pages._initialize_app()
  398. build.setup_frontend(app_with_thousand_pages.app_path)
  399. def benchmark_fn():
  400. with chdir(app_with_thousand_pages.app_path):
  401. app_with_thousand_pages.app_instance.compile_()
  402. benchmark(benchmark_fn)
  403. @pytest.mark.skip
  404. @pytest.mark.benchmark(
  405. group="Compile time of varying page numbers",
  406. timer=time.perf_counter,
  407. disable_gc=True,
  408. warmup=False,
  409. )
  410. def test_app_10000_compile_time_cold(benchmark, app_with_ten_thousand_pages):
  411. """Test the compile time on a cold start for an app with 10000 page.
  412. Args:
  413. benchmark: The benchmark fixture.
  414. app_with_ten_thousand_pages: The app harness.
  415. """
  416. def setup():
  417. with chdir(app_with_ten_thousand_pages.app_path):
  418. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  419. app_with_ten_thousand_pages._initialize_app()
  420. build.setup_frontend(app_with_ten_thousand_pages.app_path)
  421. def benchmark_fn():
  422. with chdir(app_with_ten_thousand_pages.app_path):
  423. app_with_ten_thousand_pages.app_instance.compile_()
  424. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  425. @pytest.mark.skip
  426. @pytest.mark.benchmark(
  427. group="Compile time of varying page numbers",
  428. min_rounds=5,
  429. timer=time.perf_counter,
  430. disable_gc=True,
  431. warmup=False,
  432. )
  433. def test_app_10000_compile_time_warm(benchmark, app_with_ten_thousand_pages):
  434. """Test the compile time on a warm start for an app with 10000 page.
  435. Args:
  436. benchmark: The benchmark fixture.
  437. app_with_ten_thousand_pages: The app harness.
  438. """
  439. def benchmark_fn():
  440. with chdir(app_with_ten_thousand_pages.app_path):
  441. app_with_ten_thousand_pages.app_instance.compile_()
  442. benchmark(benchmark_fn)