1
0

test_benchmark_compile_pages.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  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(
  184. root=root,
  185. app_source=functools.partial(
  186. AppWithTenPages,
  187. render_comp=render_multiple_pages, # type: ignore
  188. ),
  189. )
  190. @pytest.fixture(scope="session")
  191. def app_with_hundred_pages(
  192. tmp_path_factory,
  193. ) -> Generator[AppHarness, None, None]:
  194. """Create an app with 100 pages at tmp_path via AppHarness.
  195. Args:
  196. tmp_path_factory: pytest tmp_path_factory fixture
  197. Yields:
  198. an AppHarness instance
  199. """
  200. root = tmp_path_factory.mktemp(f"app100")
  201. yield AppHarness.create(
  202. root=root,
  203. app_source=functools.partial(
  204. AppWithHundredPages,
  205. render_comp=render_multiple_pages, # type: ignore
  206. ),
  207. ) # type: ignore
  208. @pytest.fixture(scope="session")
  209. def app_with_thousand_pages(
  210. tmp_path_factory,
  211. ) -> Generator[AppHarness, None, None]:
  212. """Create an app with 1000 pages at tmp_path via AppHarness.
  213. Args:
  214. tmp_path_factory: pytest tmp_path_factory fixture
  215. Yields:
  216. an AppHarness instance
  217. """
  218. root = tmp_path_factory.mktemp(f"app1000")
  219. yield AppHarness.create(
  220. root=root,
  221. app_source=functools.partial( # type: ignore
  222. AppWithThousandPages,
  223. render_comp=render_multiple_pages, # type: ignore
  224. ),
  225. ) # type: ignore
  226. @pytest.fixture(scope="session")
  227. def app_with_ten_thousand_pages(
  228. tmp_path_factory,
  229. ) -> Generator[AppHarness, None, None]:
  230. """Create an app with 10000 pages at tmp_path via AppHarness.
  231. Args:
  232. tmp_path_factory: pytest tmp_path_factory fixture
  233. Yields:
  234. running AppHarness instance
  235. """
  236. root = tmp_path_factory.mktemp(f"app10000")
  237. yield AppHarness.create(
  238. root=root,
  239. app_source=functools.partial(
  240. AppWithTenThousandPages,
  241. render_comp=render_multiple_pages, # type: ignore
  242. ),
  243. ) # type: ignore
  244. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  245. @pytest.mark.benchmark(
  246. group="Compile time of varying page numbers",
  247. timer=time.perf_counter,
  248. disable_gc=True,
  249. warmup=False,
  250. )
  251. def test_app_1_compile_time_cold(benchmark, app_with_one_page):
  252. """Test the compile time on a cold start for an app with 1 page.
  253. Args:
  254. benchmark: The benchmark fixture.
  255. app_with_one_page: The app harness.
  256. """
  257. def setup():
  258. with chdir(app_with_one_page.app_path):
  259. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  260. app_with_one_page._initialize_app()
  261. build.setup_frontend(app_with_one_page.app_path)
  262. def benchmark_fn():
  263. with chdir(app_with_one_page.app_path):
  264. app_with_one_page.app_instance._compile()
  265. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  266. app_with_one_page._reload_state_module()
  267. @pytest.mark.benchmark(
  268. group="Compile time of varying page numbers",
  269. min_rounds=5,
  270. timer=time.perf_counter,
  271. disable_gc=True,
  272. warmup=False,
  273. )
  274. def test_app_1_compile_time_warm(benchmark, app_with_one_page):
  275. """Test the compile time on a warm start for an app with 1 page.
  276. Args:
  277. benchmark: The benchmark fixture.
  278. app_with_one_page: The app harness.
  279. """
  280. with chdir(app_with_one_page.app_path):
  281. app_with_one_page._initialize_app()
  282. build.setup_frontend(app_with_one_page.app_path)
  283. def benchmark_fn():
  284. with chdir(app_with_one_page.app_path):
  285. app_with_one_page.app_instance._compile()
  286. benchmark(benchmark_fn)
  287. app_with_one_page._reload_state_module()
  288. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  289. @pytest.mark.benchmark(
  290. group="Compile time of varying page numbers",
  291. timer=time.perf_counter,
  292. disable_gc=True,
  293. warmup=False,
  294. )
  295. def test_app_10_compile_time_cold(benchmark, app_with_ten_pages):
  296. """Test the compile time on a cold start for an app with 10 page.
  297. Args:
  298. benchmark: The benchmark fixture.
  299. app_with_ten_pages: The app harness.
  300. """
  301. def setup():
  302. with chdir(app_with_ten_pages.app_path):
  303. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  304. app_with_ten_pages._initialize_app()
  305. build.setup_frontend(app_with_ten_pages.app_path)
  306. def benchmark_fn():
  307. with chdir(app_with_ten_pages.app_path):
  308. app_with_ten_pages.app_instance._compile()
  309. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  310. app_with_ten_pages._reload_state_module()
  311. @pytest.mark.benchmark(
  312. group="Compile time of varying page numbers",
  313. min_rounds=5,
  314. timer=time.perf_counter,
  315. disable_gc=True,
  316. warmup=False,
  317. )
  318. def test_app_10_compile_time_warm(benchmark, app_with_ten_pages):
  319. """Test the compile time on a warm start for an app with 10 page.
  320. Args:
  321. benchmark: The benchmark fixture.
  322. app_with_ten_pages: The app harness.
  323. """
  324. with chdir(app_with_ten_pages.app_path):
  325. app_with_ten_pages._initialize_app()
  326. build.setup_frontend(app_with_ten_pages.app_path)
  327. def benchmark_fn():
  328. with chdir(app_with_ten_pages.app_path):
  329. app_with_ten_pages.app_instance._compile()
  330. benchmark(benchmark_fn)
  331. app_with_ten_pages._reload_state_module()
  332. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  333. @pytest.mark.benchmark(
  334. group="Compile time of varying page numbers",
  335. timer=time.perf_counter,
  336. disable_gc=True,
  337. warmup=False,
  338. )
  339. def test_app_100_compile_time_cold(benchmark, app_with_hundred_pages):
  340. """Test the compile time on a cold start for an app with 100 page.
  341. Args:
  342. benchmark: The benchmark fixture.
  343. app_with_hundred_pages: The app harness.
  344. """
  345. def setup():
  346. with chdir(app_with_hundred_pages.app_path):
  347. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  348. app_with_hundred_pages._initialize_app()
  349. build.setup_frontend(app_with_hundred_pages.app_path)
  350. def benchmark_fn():
  351. with chdir(app_with_hundred_pages.app_path):
  352. app_with_hundred_pages.app_instance._compile()
  353. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  354. app_with_hundred_pages._reload_state_module()
  355. @pytest.mark.benchmark(
  356. group="Compile time of varying page numbers",
  357. min_rounds=5,
  358. timer=time.perf_counter,
  359. disable_gc=True,
  360. warmup=False,
  361. )
  362. def test_app_100_compile_time_warm(benchmark, app_with_hundred_pages):
  363. """Test the compile time on a warm start for an app with 100 page.
  364. Args:
  365. benchmark: The benchmark fixture.
  366. app_with_hundred_pages: The app harness.
  367. """
  368. with chdir(app_with_hundred_pages.app_path):
  369. app_with_hundred_pages._initialize_app()
  370. build.setup_frontend(app_with_hundred_pages.app_path)
  371. def benchmark_fn():
  372. with chdir(app_with_hundred_pages.app_path):
  373. app_with_hundred_pages.app_instance._compile()
  374. benchmark(benchmark_fn)
  375. app_with_hundred_pages._reload_state_module()
  376. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  377. @pytest.mark.benchmark(
  378. group="Compile time of varying page numbers",
  379. timer=time.perf_counter,
  380. disable_gc=True,
  381. warmup=False,
  382. )
  383. def test_app_1000_compile_time_cold(benchmark, app_with_thousand_pages):
  384. """Test the compile time on a cold start for an app with 1000 page.
  385. Args:
  386. benchmark: The benchmark fixture.
  387. app_with_thousand_pages: The app harness.
  388. """
  389. def setup():
  390. with chdir(app_with_thousand_pages.app_path):
  391. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  392. app_with_thousand_pages._initialize_app()
  393. build.setup_frontend(app_with_thousand_pages.app_path)
  394. def benchmark_fn():
  395. with chdir(app_with_thousand_pages.app_path):
  396. app_with_thousand_pages.app_instance._compile()
  397. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  398. app_with_thousand_pages._reload_state_module()
  399. @pytest.mark.benchmark(
  400. group="Compile time of varying page numbers",
  401. min_rounds=5,
  402. timer=time.perf_counter,
  403. disable_gc=True,
  404. warmup=False,
  405. )
  406. def test_app_1000_compile_time_warm(benchmark, app_with_thousand_pages):
  407. """Test the compile time on a warm start for an app with 1000 page.
  408. Args:
  409. benchmark: The benchmark fixture.
  410. app_with_thousand_pages: The app harness.
  411. """
  412. with chdir(app_with_thousand_pages.app_path):
  413. app_with_thousand_pages._initialize_app()
  414. build.setup_frontend(app_with_thousand_pages.app_path)
  415. def benchmark_fn():
  416. with chdir(app_with_thousand_pages.app_path):
  417. app_with_thousand_pages.app_instance._compile()
  418. benchmark(benchmark_fn)
  419. app_with_thousand_pages._reload_state_module()
  420. @pytest.mark.skip
  421. @pytest.mark.benchmark(
  422. group="Compile time of varying page numbers",
  423. timer=time.perf_counter,
  424. disable_gc=True,
  425. warmup=False,
  426. )
  427. def test_app_10000_compile_time_cold(benchmark, app_with_ten_thousand_pages):
  428. """Test the compile time on a cold start for an app with 10000 page.
  429. Args:
  430. benchmark: The benchmark fixture.
  431. app_with_ten_thousand_pages: The app harness.
  432. """
  433. def setup():
  434. with chdir(app_with_ten_thousand_pages.app_path):
  435. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  436. app_with_ten_thousand_pages._initialize_app()
  437. build.setup_frontend(app_with_ten_thousand_pages.app_path)
  438. def benchmark_fn():
  439. with chdir(app_with_ten_thousand_pages.app_path):
  440. app_with_ten_thousand_pages.app_instance._compile()
  441. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  442. app_with_ten_thousand_pages._reload_state_module()
  443. @pytest.mark.skip
  444. @pytest.mark.benchmark(
  445. group="Compile time of varying page numbers",
  446. min_rounds=5,
  447. timer=time.perf_counter,
  448. disable_gc=True,
  449. warmup=False,
  450. )
  451. def test_app_10000_compile_time_warm(benchmark, app_with_ten_thousand_pages):
  452. """Test the compile time on a warm start for an app with 10000 page.
  453. Args:
  454. benchmark: The benchmark fixture.
  455. app_with_ten_thousand_pages: The app harness.
  456. """
  457. def benchmark_fn():
  458. with chdir(app_with_ten_thousand_pages.app_path):
  459. app_with_ten_thousand_pages.app_instance._compile()
  460. benchmark(benchmark_fn)
  461. app_with_ten_thousand_pages._reload_state_module()