test_benchmark_compile_components.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. """Benchmark tests for apps with varying component 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_component(num: int):
  13. """Generate a number of components based on num.
  14. Args:
  15. num: number of components to produce.
  16. Returns:
  17. The rendered number of components.
  18. """
  19. import reflex as rx
  20. return [
  21. rx.fragment(
  22. rx.box(
  23. rx.accordion.root(
  24. rx.accordion.item(
  25. header="Full Ingredients", # type: ignore
  26. content="Yes. It's built with accessibility in mind.", # type: ignore
  27. font_size="3em",
  28. ),
  29. rx.accordion.item(
  30. header="Applications", # type: ignore
  31. content="Yes. It's unstyled by default, giving you freedom over the look and feel.", # type: ignore
  32. ),
  33. collapsible=True,
  34. variant="ghost",
  35. width="25rem",
  36. ),
  37. padding_top="20px",
  38. ),
  39. rx.box(
  40. rx.drawer.root(
  41. rx.drawer.trigger(
  42. rx.button("Open Drawer with snap points"), as_child=True
  43. ),
  44. rx.drawer.overlay(),
  45. rx.drawer.portal(
  46. rx.drawer.content(
  47. rx.flex(
  48. rx.drawer.title("Drawer Content"),
  49. rx.drawer.description("Drawer description"),
  50. rx.drawer.close(
  51. rx.button("Close Button"),
  52. as_child=True,
  53. ),
  54. direction="column",
  55. margin="5em",
  56. align_items="center",
  57. ),
  58. top="auto",
  59. height="100%",
  60. flex_direction="column",
  61. background_color="var(--green-3)",
  62. ),
  63. ),
  64. snap_points=["148px", "355px", 1],
  65. ),
  66. ),
  67. rx.box(
  68. rx.callout(
  69. "You will need admin privileges to install and access this application.",
  70. icon="info",
  71. size="3",
  72. ),
  73. ),
  74. rx.box(
  75. rx.table.root(
  76. rx.table.header(
  77. rx.table.row(
  78. rx.table.column_header_cell("Full name"),
  79. rx.table.column_header_cell("Email"),
  80. rx.table.column_header_cell("Group"),
  81. ),
  82. ),
  83. rx.table.body(
  84. rx.table.row(
  85. rx.table.row_header_cell("Danilo Sousa"),
  86. rx.table.cell("danilo@example.com"),
  87. rx.table.cell("Developer"),
  88. ),
  89. rx.table.row(
  90. rx.table.row_header_cell("Zahra Ambessa"),
  91. rx.table.cell("zahra@example.com"),
  92. rx.table.cell("Admin"),
  93. ),
  94. rx.table.row(
  95. rx.table.row_header_cell("Jasper Eriksson"),
  96. rx.table.cell("jasper@example.com"),
  97. rx.table.cell("Developer"),
  98. ),
  99. ),
  100. )
  101. ),
  102. )
  103. ] * num
  104. def AppWithTenComponentsOnePage():
  105. """A reflex app with roughly 10 components on one page."""
  106. import reflex as rx
  107. def index() -> rx.Component:
  108. return rx.center(rx.vstack(*render_component(1)))
  109. app = rx.App(state=rx.State)
  110. app.add_page(index)
  111. def AppWithHundredComponentOnePage():
  112. """A reflex app with roughly 100 components on one page."""
  113. import reflex as rx
  114. def index() -> rx.Component:
  115. return rx.center(rx.vstack(*render_component(100)))
  116. app = rx.App(state=rx.State)
  117. app.add_page(index)
  118. def AppWithThousandComponentsOnePage():
  119. """A reflex app with roughly 1000 components on one page."""
  120. import reflex as rx
  121. def index() -> rx.Component:
  122. return rx.center(rx.vstack(*render_component(1000)))
  123. app = rx.App(state=rx.State)
  124. app.add_page(index)
  125. @pytest.fixture(scope="session")
  126. def app_with_10_components(
  127. tmp_path_factory,
  128. ) -> Generator[AppHarness, None, None]:
  129. """Start Blank Template app at tmp_path via AppHarness.
  130. Args:
  131. tmp_path_factory: pytest tmp_path_factory fixture
  132. Yields:
  133. running AppHarness instance
  134. """
  135. root = tmp_path_factory.mktemp("app10components")
  136. yield AppHarness.create(
  137. root=root,
  138. app_source=functools.partial(
  139. AppWithTenComponentsOnePage,
  140. render_component=render_component, # type: ignore
  141. ),
  142. ) # type: ignore
  143. @pytest.fixture(scope="session")
  144. def app_with_100_components(
  145. tmp_path_factory,
  146. ) -> Generator[AppHarness, None, None]:
  147. """Start Blank Template app at tmp_path via AppHarness.
  148. Args:
  149. tmp_path_factory: pytest tmp_path_factory fixture
  150. Yields:
  151. running AppHarness instance
  152. """
  153. root = tmp_path_factory.mktemp("app100components")
  154. yield AppHarness.create(
  155. root=root,
  156. app_source=functools.partial(
  157. AppWithHundredComponentOnePage,
  158. render_component=render_component, # type: ignore
  159. ),
  160. ) # type: ignore
  161. @pytest.fixture(scope="session")
  162. def app_with_1000_components(
  163. tmp_path_factory,
  164. ) -> Generator[AppHarness, None, None]:
  165. """Create an app with 1000 components at tmp_path via AppHarness.
  166. Args:
  167. tmp_path_factory: pytest tmp_path_factory fixture
  168. Yields:
  169. an AppHarness instance
  170. """
  171. root = tmp_path_factory.mktemp("app1000components")
  172. yield AppHarness.create(
  173. root=root,
  174. app_source=functools.partial(
  175. AppWithThousandComponentsOnePage,
  176. render_component=render_component, # type: ignore
  177. ),
  178. ) # type: ignore
  179. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  180. @pytest.mark.benchmark(
  181. group="Compile time of varying component numbers",
  182. timer=time.perf_counter,
  183. disable_gc=True,
  184. warmup=False,
  185. )
  186. def test_app_10_compile_time_cold(benchmark, app_with_10_components):
  187. """Test the compile time on a cold start for an app with roughly 10 components.
  188. Args:
  189. benchmark: The benchmark fixture.
  190. app_with_10_components: The app harness.
  191. """
  192. def setup():
  193. with chdir(app_with_10_components.app_path):
  194. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  195. app_with_10_components._initialize_app()
  196. build.setup_frontend(app_with_10_components.app_path)
  197. def benchmark_fn():
  198. with chdir(app_with_10_components.app_path):
  199. app_with_10_components.app_instance._compile()
  200. benchmark.pedantic(benchmark_fn, setup=setup, rounds=10)
  201. @pytest.mark.benchmark(
  202. group="Compile time of varying component numbers",
  203. min_rounds=5,
  204. timer=time.perf_counter,
  205. disable_gc=True,
  206. warmup=False,
  207. )
  208. def test_app_10_compile_time_warm(benchmark, app_with_10_components):
  209. """Test the compile time on a warm start for an app with roughly 10 components.
  210. Args:
  211. benchmark: The benchmark fixture.
  212. app_with_10_components: The app harness.
  213. """
  214. with chdir(app_with_10_components.app_path):
  215. app_with_10_components._initialize_app()
  216. build.setup_frontend(app_with_10_components.app_path)
  217. def benchmark_fn():
  218. with chdir(app_with_10_components.app_path):
  219. app_with_10_components.app_instance._compile()
  220. benchmark(benchmark_fn)
  221. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  222. @pytest.mark.benchmark(
  223. group="Compile time of varying component numbers",
  224. timer=time.perf_counter,
  225. disable_gc=True,
  226. warmup=False,
  227. )
  228. def test_app_100_compile_time_cold(benchmark, app_with_100_components):
  229. """Test the compile time on a cold start for an app with roughly 100 components.
  230. Args:
  231. benchmark: The benchmark fixture.
  232. app_with_100_components: The app harness.
  233. """
  234. def setup():
  235. with chdir(app_with_100_components.app_path):
  236. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  237. app_with_100_components._initialize_app()
  238. build.setup_frontend(app_with_100_components.app_path)
  239. def benchmark_fn():
  240. with chdir(app_with_100_components.app_path):
  241. app_with_100_components.app_instance._compile()
  242. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  243. @pytest.mark.benchmark(
  244. group="Compile time of varying component numbers",
  245. min_rounds=5,
  246. timer=time.perf_counter,
  247. disable_gc=True,
  248. warmup=False,
  249. )
  250. def test_app_100_compile_time_warm(benchmark, app_with_100_components):
  251. """Test the compile time on a warm start for an app with roughly 100 components.
  252. Args:
  253. benchmark: The benchmark fixture.
  254. app_with_100_components: The app harness.
  255. """
  256. with chdir(app_with_100_components.app_path):
  257. app_with_100_components._initialize_app()
  258. build.setup_frontend(app_with_100_components.app_path)
  259. def benchmark_fn():
  260. with chdir(app_with_100_components.app_path):
  261. app_with_100_components.app_instance._compile()
  262. benchmark(benchmark_fn)
  263. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  264. @pytest.mark.benchmark(
  265. group="Compile time of varying component numbers",
  266. timer=time.perf_counter,
  267. disable_gc=True,
  268. warmup=False,
  269. )
  270. def test_app_1000_compile_time_cold(benchmark, app_with_1000_components):
  271. """Test the compile time on a cold start for an app with roughly 1000 components.
  272. Args:
  273. benchmark: The benchmark fixture.
  274. app_with_1000_components: The app harness.
  275. """
  276. def setup():
  277. with chdir(app_with_1000_components.app_path):
  278. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  279. app_with_1000_components._initialize_app()
  280. build.setup_frontend(app_with_1000_components.app_path)
  281. def benchmark_fn():
  282. with chdir(app_with_1000_components.app_path):
  283. app_with_1000_components.app_instance._compile()
  284. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  285. @pytest.mark.benchmark(
  286. group="Compile time of varying component numbers",
  287. min_rounds=5,
  288. timer=time.perf_counter,
  289. disable_gc=True,
  290. warmup=False,
  291. )
  292. def test_app_1000_compile_time_warm(benchmark, app_with_1000_components):
  293. """Test the compile time on a warm start for an app with roughly 1000 components.
  294. Args:
  295. benchmark: The benchmark fixture.
  296. app_with_1000_components: The app harness.
  297. """
  298. with chdir(app_with_1000_components.app_path):
  299. app_with_1000_components._initialize_app()
  300. build.setup_frontend(app_with_1000_components.app_path)
  301. def benchmark_fn():
  302. with chdir(app_with_1000_components.app_path):
  303. app_with_1000_components.app_instance._compile()
  304. benchmark(benchmark_fn)