test_benchmark_compile_pages.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567
  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. app_with_one_page._reload_state_module()
  258. @pytest.mark.benchmark(
  259. group="Compile time of varying page numbers",
  260. min_rounds=5,
  261. timer=time.perf_counter,
  262. disable_gc=True,
  263. warmup=False,
  264. )
  265. def test_app_1_compile_time_warm(benchmark, app_with_one_page):
  266. """Test the compile time on a warm start for an app with 1 page.
  267. Args:
  268. benchmark: The benchmark fixture.
  269. app_with_one_page: The app harness.
  270. """
  271. with chdir(app_with_one_page.app_path):
  272. app_with_one_page._initialize_app()
  273. build.setup_frontend(app_with_one_page.app_path)
  274. def benchmark_fn():
  275. with chdir(app_with_one_page.app_path):
  276. app_with_one_page.app_instance.compile_()
  277. benchmark(benchmark_fn)
  278. app_with_one_page._reload_state_module()
  279. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  280. @pytest.mark.benchmark(
  281. group="Compile time of varying page numbers",
  282. timer=time.perf_counter,
  283. disable_gc=True,
  284. warmup=False,
  285. )
  286. def test_app_10_compile_time_cold(benchmark, app_with_ten_pages):
  287. """Test the compile time on a cold start for an app with 10 page.
  288. Args:
  289. benchmark: The benchmark fixture.
  290. app_with_ten_pages: The app harness.
  291. """
  292. def setup():
  293. with chdir(app_with_ten_pages.app_path):
  294. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  295. app_with_ten_pages._initialize_app()
  296. build.setup_frontend(app_with_ten_pages.app_path)
  297. def benchmark_fn():
  298. with chdir(app_with_ten_pages.app_path):
  299. app_with_ten_pages.app_instance.compile_()
  300. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  301. app_with_ten_pages._reload_state_module()
  302. @pytest.mark.benchmark(
  303. group="Compile time of varying page numbers",
  304. min_rounds=5,
  305. timer=time.perf_counter,
  306. disable_gc=True,
  307. warmup=False,
  308. )
  309. def test_app_10_compile_time_warm(benchmark, app_with_ten_pages):
  310. """Test the compile time on a warm start for an app with 10 page.
  311. Args:
  312. benchmark: The benchmark fixture.
  313. app_with_ten_pages: The app harness.
  314. """
  315. with chdir(app_with_ten_pages.app_path):
  316. app_with_ten_pages._initialize_app()
  317. build.setup_frontend(app_with_ten_pages.app_path)
  318. def benchmark_fn():
  319. with chdir(app_with_ten_pages.app_path):
  320. app_with_ten_pages.app_instance.compile_()
  321. benchmark(benchmark_fn)
  322. app_with_ten_pages._reload_state_module()
  323. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  324. @pytest.mark.benchmark(
  325. group="Compile time of varying page numbers",
  326. timer=time.perf_counter,
  327. disable_gc=True,
  328. warmup=False,
  329. )
  330. def test_app_100_compile_time_cold(benchmark, app_with_hundred_pages):
  331. """Test the compile time on a cold start for an app with 100 page.
  332. Args:
  333. benchmark: The benchmark fixture.
  334. app_with_hundred_pages: The app harness.
  335. """
  336. def setup():
  337. with chdir(app_with_hundred_pages.app_path):
  338. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  339. app_with_hundred_pages._initialize_app()
  340. build.setup_frontend(app_with_hundred_pages.app_path)
  341. def benchmark_fn():
  342. with chdir(app_with_hundred_pages.app_path):
  343. app_with_hundred_pages.app_instance.compile_()
  344. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  345. app_with_hundred_pages._reload_state_module()
  346. @pytest.mark.benchmark(
  347. group="Compile time of varying page numbers",
  348. min_rounds=5,
  349. timer=time.perf_counter,
  350. disable_gc=True,
  351. warmup=False,
  352. )
  353. def test_app_100_compile_time_warm(benchmark, app_with_hundred_pages):
  354. """Test the compile time on a warm start for an app with 100 page.
  355. Args:
  356. benchmark: The benchmark fixture.
  357. app_with_hundred_pages: The app harness.
  358. """
  359. with chdir(app_with_hundred_pages.app_path):
  360. app_with_hundred_pages._initialize_app()
  361. build.setup_frontend(app_with_hundred_pages.app_path)
  362. def benchmark_fn():
  363. with chdir(app_with_hundred_pages.app_path):
  364. app_with_hundred_pages.app_instance.compile_()
  365. benchmark(benchmark_fn)
  366. app_with_hundred_pages._reload_state_module()
  367. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  368. @pytest.mark.benchmark(
  369. group="Compile time of varying page numbers",
  370. timer=time.perf_counter,
  371. disable_gc=True,
  372. warmup=False,
  373. )
  374. def test_app_1000_compile_time_cold(benchmark, app_with_thousand_pages):
  375. """Test the compile time on a cold start for an app with 1000 page.
  376. Args:
  377. benchmark: The benchmark fixture.
  378. app_with_thousand_pages: The app harness.
  379. """
  380. def setup():
  381. with chdir(app_with_thousand_pages.app_path):
  382. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  383. app_with_thousand_pages._initialize_app()
  384. build.setup_frontend(app_with_thousand_pages.app_path)
  385. def benchmark_fn():
  386. with chdir(app_with_thousand_pages.app_path):
  387. app_with_thousand_pages.app_instance.compile_()
  388. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  389. app_with_thousand_pages._reload_state_module()
  390. @pytest.mark.benchmark(
  391. group="Compile time of varying page numbers",
  392. min_rounds=5,
  393. timer=time.perf_counter,
  394. disable_gc=True,
  395. warmup=False,
  396. )
  397. def test_app_1000_compile_time_warm(benchmark, app_with_thousand_pages):
  398. """Test the compile time on a warm start for an app with 1000 page.
  399. Args:
  400. benchmark: The benchmark fixture.
  401. app_with_thousand_pages: The app harness.
  402. """
  403. with chdir(app_with_thousand_pages.app_path):
  404. app_with_thousand_pages._initialize_app()
  405. build.setup_frontend(app_with_thousand_pages.app_path)
  406. def benchmark_fn():
  407. with chdir(app_with_thousand_pages.app_path):
  408. app_with_thousand_pages.app_instance.compile_()
  409. benchmark(benchmark_fn)
  410. app_with_thousand_pages._reload_state_module()
  411. @pytest.mark.skip
  412. @pytest.mark.benchmark(
  413. group="Compile time of varying page numbers",
  414. timer=time.perf_counter,
  415. disable_gc=True,
  416. warmup=False,
  417. )
  418. def test_app_10000_compile_time_cold(benchmark, app_with_ten_thousand_pages):
  419. """Test the compile time on a cold start for an app with 10000 page.
  420. Args:
  421. benchmark: The benchmark fixture.
  422. app_with_ten_thousand_pages: The app harness.
  423. """
  424. def setup():
  425. with chdir(app_with_ten_thousand_pages.app_path):
  426. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  427. app_with_ten_thousand_pages._initialize_app()
  428. build.setup_frontend(app_with_ten_thousand_pages.app_path)
  429. def benchmark_fn():
  430. with chdir(app_with_ten_thousand_pages.app_path):
  431. app_with_ten_thousand_pages.app_instance.compile_()
  432. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  433. app_with_ten_thousand_pages._reload_state_module()
  434. @pytest.mark.skip
  435. @pytest.mark.benchmark(
  436. group="Compile time of varying page numbers",
  437. min_rounds=5,
  438. timer=time.perf_counter,
  439. disable_gc=True,
  440. warmup=False,
  441. )
  442. def test_app_10000_compile_time_warm(benchmark, app_with_ten_thousand_pages):
  443. """Test the compile time on a warm start for an app with 10000 page.
  444. Args:
  445. benchmark: The benchmark fixture.
  446. app_with_ten_thousand_pages: The app harness.
  447. """
  448. def benchmark_fn():
  449. with chdir(app_with_ten_thousand_pages.app_path):
  450. app_with_ten_thousand_pages.app_instance.compile_()
  451. benchmark(benchmark_fn)
  452. app_with_ten_thousand_pages._reload_state_module()