test_benchmark_compile_pages.py 16 KB

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