1
0

test_benchmark_compile_components.py 12 KB

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