1
0

test_benchmark_compile_pages.py 16 KB

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