1
0

test_benchmark_compile_components.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  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, render_component=render_component # type: ignore
  140. ),
  141. ) # type: ignore
  142. @pytest.fixture(scope="session")
  143. def app_with_100_components(
  144. tmp_path_factory,
  145. ) -> Generator[AppHarness, None, None]:
  146. """Start Blank Template app at tmp_path via AppHarness.
  147. Args:
  148. tmp_path_factory: pytest tmp_path_factory fixture
  149. Yields:
  150. running AppHarness instance
  151. """
  152. root = tmp_path_factory.mktemp("app100components")
  153. yield AppHarness.create(
  154. root=root,
  155. app_source=functools.partial(
  156. AppWithHundredComponentOnePage, render_component=render_component # type: ignore
  157. ),
  158. ) # type: ignore
  159. @pytest.fixture(scope="session")
  160. def app_with_1000_components(
  161. tmp_path_factory,
  162. ) -> Generator[AppHarness, None, None]:
  163. """Create an app with 1000 components at tmp_path via AppHarness.
  164. Args:
  165. tmp_path_factory: pytest tmp_path_factory fixture
  166. Yields:
  167. an AppHarness instance
  168. """
  169. root = tmp_path_factory.mktemp("app1000components")
  170. yield AppHarness.create(
  171. root=root,
  172. app_source=functools.partial(
  173. AppWithThousandComponentsOnePage, render_component=render_component # type: ignore
  174. ),
  175. ) # type: ignore
  176. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  177. @pytest.mark.benchmark(
  178. group="Compile time of varying component numbers",
  179. timer=time.perf_counter,
  180. disable_gc=True,
  181. warmup=False,
  182. )
  183. def test_app_10_compile_time_cold(benchmark, app_with_10_components):
  184. """Test the compile time on a cold start for an app with roughly 10 components.
  185. Args:
  186. benchmark: The benchmark fixture.
  187. app_with_10_components: The app harness.
  188. """
  189. def setup():
  190. with chdir(app_with_10_components.app_path):
  191. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  192. app_with_10_components._initialize_app()
  193. build.setup_frontend(app_with_10_components.app_path)
  194. def benchmark_fn():
  195. with chdir(app_with_10_components.app_path):
  196. app_with_10_components.app_instance.compile_()
  197. benchmark.pedantic(benchmark_fn, setup=setup, rounds=10)
  198. @pytest.mark.benchmark(
  199. group="Compile time of varying component numbers",
  200. min_rounds=5,
  201. timer=time.perf_counter,
  202. disable_gc=True,
  203. warmup=False,
  204. )
  205. def test_app_10_compile_time_warm(benchmark, app_with_10_components):
  206. """Test the compile time on a warm start for an app with roughly 10 components.
  207. Args:
  208. benchmark: The benchmark fixture.
  209. app_with_10_components: The app harness.
  210. """
  211. with chdir(app_with_10_components.app_path):
  212. app_with_10_components._initialize_app()
  213. build.setup_frontend(app_with_10_components.app_path)
  214. def benchmark_fn():
  215. with chdir(app_with_10_components.app_path):
  216. app_with_10_components.app_instance.compile_()
  217. benchmark(benchmark_fn)
  218. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  219. @pytest.mark.benchmark(
  220. group="Compile time of varying component numbers",
  221. timer=time.perf_counter,
  222. disable_gc=True,
  223. warmup=False,
  224. )
  225. def test_app_100_compile_time_cold(benchmark, app_with_100_components):
  226. """Test the compile time on a cold start for an app with roughly 100 components.
  227. Args:
  228. benchmark: The benchmark fixture.
  229. app_with_100_components: The app harness.
  230. """
  231. def setup():
  232. with chdir(app_with_100_components.app_path):
  233. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  234. app_with_100_components._initialize_app()
  235. build.setup_frontend(app_with_100_components.app_path)
  236. def benchmark_fn():
  237. with chdir(app_with_100_components.app_path):
  238. app_with_100_components.app_instance.compile_()
  239. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  240. @pytest.mark.benchmark(
  241. group="Compile time of varying component numbers",
  242. min_rounds=5,
  243. timer=time.perf_counter,
  244. disable_gc=True,
  245. warmup=False,
  246. )
  247. def test_app_100_compile_time_warm(benchmark, app_with_100_components):
  248. """Test the compile time on a warm start for an app with roughly 100 components.
  249. Args:
  250. benchmark: The benchmark fixture.
  251. app_with_100_components: The app harness.
  252. """
  253. with chdir(app_with_100_components.app_path):
  254. app_with_100_components._initialize_app()
  255. build.setup_frontend(app_with_100_components.app_path)
  256. def benchmark_fn():
  257. with chdir(app_with_100_components.app_path):
  258. app_with_100_components.app_instance.compile_()
  259. benchmark(benchmark_fn)
  260. @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON)
  261. @pytest.mark.benchmark(
  262. group="Compile time of varying component numbers",
  263. timer=time.perf_counter,
  264. disable_gc=True,
  265. warmup=False,
  266. )
  267. def test_app_1000_compile_time_cold(benchmark, app_with_1000_components):
  268. """Test the compile time on a cold start for an app with roughly 1000 components.
  269. Args:
  270. benchmark: The benchmark fixture.
  271. app_with_1000_components: The app harness.
  272. """
  273. def setup():
  274. with chdir(app_with_1000_components.app_path):
  275. utils.empty_dir(constants.Dirs.WEB_PAGES, keep_files=["_app.js"])
  276. app_with_1000_components._initialize_app()
  277. build.setup_frontend(app_with_1000_components.app_path)
  278. def benchmark_fn():
  279. with chdir(app_with_1000_components.app_path):
  280. app_with_1000_components.app_instance.compile_()
  281. benchmark.pedantic(benchmark_fn, setup=setup, rounds=5)
  282. @pytest.mark.benchmark(
  283. group="Compile time of varying component numbers",
  284. min_rounds=5,
  285. timer=time.perf_counter,
  286. disable_gc=True,
  287. warmup=False,
  288. )
  289. def test_app_1000_compile_time_warm(benchmark, app_with_1000_components):
  290. """Test the compile time on a warm start for an app with roughly 1000 components.
  291. Args:
  292. benchmark: The benchmark fixture.
  293. app_with_1000_components: The app harness.
  294. """
  295. with chdir(app_with_1000_components.app_path):
  296. app_with_1000_components._initialize_app()
  297. build.setup_frontend(app_with_1000_components.app_path)
  298. def benchmark_fn():
  299. with chdir(app_with_1000_components.app_path):
  300. app_with_1000_components.app_instance.compile_()
  301. benchmark(benchmark_fn)