test_benchmark_compile_pages.py 17 KB

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