| commit a9bae04df9fddcb340455735eff71223c92e98a7 parent 0f378562efa1880f57c9960971f3ec6a6af21421 Author: sqzer <110329244+sqzer-x@users.noreply.github.com> Date: Tue, 28 Jul 2026 15:41:36 +0900 build --profile 로 페이즈별 시간을 stderr 에 낸다 느린 빌드가 콘텐츠 탓인지 이 도구 탓인지 구분할 수단이 없었다. discover / assets / feeds / templates / render / search / generate / write 여덟 구간의 벽시계를 한 줄씩 찍는다. stderr 전용이다 — stdout의 "N pages → …" 계약과 --json 출력, 빌드 산출물은 건드리지 않으므로 결정성과 무관하다. 실패하는 빌드에서도 찍힌 구간까지는 그대로 나오고, 에러 태그와 종료 코드는 변하지 않는다. Diffstat:
|
| 6 files changed, 70 insertion(+), 2 deletion(-) |
diff --git a/docs/content/start/cli.ko.md b/docs/content/start/cli.ko.md @@ -7,7 +7,7 @@ toc = true ``` sqzass init [DIR] -sqzass build [-i DIR] [-o DIR] [--drafts] [--base-url URL] +sqzass build [-i DIR] [-o DIR] [--drafts] [--base-url URL] [--profile] sqzass serve [-i DIR] [-b ADDR] [-p PORT] [--drafts] [--base-url URL] sqzass doctor [-i DIR] [--fail-on note|warn] [--drafts] ``` @@ -28,10 +28,22 @@ sqzass doctor [-i DIR] [--fail-on note|warn] [--drafts] | `-o`, `--output` | `<input>/public` | 사이트 루트가 아니라 **셸의 현재 디렉터리** 기준으로 해석합니다. | | `--drafts` | | `draft = true`인 페이지도 포함합니다. | | `--base-url` | 설정값 | 프리뷰 배포에 씁니다. | +| `--profile` | | 페이즈별 소요 시간을 stderr로 냅니다. stdout은 그대로입니다. | 출력 디렉터리는 매번 비우고 시작합니다. 지운 페이지가 빌드된 사이트에 유령으로 남지 않습니다. +`--profile`은 페이즈마다 한 줄씩 찍습니다 — discover, assets, feeds, templates, +render, search, generate, write. 느린 빌드의 원인이 콘텐츠인지 이 도구인지를 +여기서 알 수 있습니다. + +``` + discover 750.8µs + assets 1.4ms + render 198.9ms + write 1.3ms +``` + ## serve 메모리에서 서빙하며 <http://127.0.0.1:3000>에 라이브 리로드를 붙입니다. diff --git a/docs/content/start/cli.md b/docs/content/start/cli.md @@ -7,7 +7,7 @@ toc = true ``` sqzass init [DIR] -sqzass build [-i DIR] [-o DIR] [--drafts] [--base-url URL] +sqzass build [-i DIR] [-o DIR] [--drafts] [--base-url URL] [--profile] sqzass serve [-i DIR] [-b ADDR] [-p PORT] [--drafts] [--base-url URL] sqzass doctor [-i DIR] [--fail-on note|warn] [--drafts] ``` @@ -28,10 +28,22 @@ run where a `sqzass.toml` already exists. See | `-o`, `--output` | `<input>/public` | Resolved against your shell's directory, not the site root. | | `--drafts` | | Include pages marked `draft = true`. | | `--base-url` | from config | Useful for preview deployments. | +| `--profile` | | Per-phase timings on stderr; stdout stays as is. | The output directory is emptied first, so a page you deleted does not linger as a ghost in the built site. +`--profile` prints one line per phase — discover, assets, feeds, templates, +render, search, generate, write — which is how you learn whether a slow build +is your content or this tool: + +``` + discover 750.8µs + assets 1.4ms + render 198.9ms + write 1.3ms +``` + ## serve Serves from memory with live reload on <http://127.0.0.1:3000>. See diff --git a/src/init.rs b/src/init.rs @@ -119,6 +119,7 @@ mod tests { output: None, drafts: false, base_url: None, + profile: false, }) .unwrap(); assert_eq!(out.pages_written, 1); diff --git a/src/lib.rs b/src/lib.rs @@ -39,6 +39,32 @@ pub struct BuildOptions { pub drafts: bool, /// 설정의 `base_url`을 덮어쓴다. pub base_url: Option<String>, + /// 페이즈별 소요 시간을 stderr로 낸다 (`build --profile`). + pub profile: bool, +} + +/// `--profile`의 페이즈 시계. stderr로만 말한다 — stdout의 "N pages → …" 계약과 +/// 빌드 산출물은 건드리지 않으므로 결정성과 무관하다. +struct Profiler { + enabled: bool, + last: std::time::Instant, +} + +impl Profiler { + fn new(enabled: bool) -> Self { + Self { + enabled, + last: std::time::Instant::now(), + } + } + + /// 직전 구간의 경과를 찍고 시계를 되돌린다. + fn lap(&mut self, name: &str) { + if self.enabled { + eprintln!("{name:>9} {:.1?}", self.last.elapsed()); + } + self.last = std::time::Instant::now(); + } } /// 생성된 하이라이트 스타일시트의 논리 경로. 다른 에셋과 같은 규칙으로 해시가 붙는다. @@ -86,9 +112,11 @@ fn load_config(opts: &BuildOptions) -> Result<Config> { fn render_site(opts: &BuildOptions, cfg: Config) -> Result<BuildOutput> { let root = &opts.input; let drafts = opts.drafts || cfg.build.drafts; + let mut prof = Profiler::new(opts.profile); let pages = content::discover(root, &cfg, drafts).map_err(Kind::Content.tag())?; let site = Site::build(&pages, &cfg); + prof.lap("discover"); // 에셋을 먼저 처리해야 템플릿이 해시가 붙은 최종 URL을 조회할 수 있다. let mut assets = @@ -106,6 +134,7 @@ fn render_site(opts: &BuildOptions, cfg: Config) -> Result<BuildOutput> { } else { None }; + prof.lap("assets"); // 피드를 먼저 만든다. 페이지가 <head>에 자동 발견 링크를 넣으려면 자기 언어에 // 피드가 있는지를 렌더 시점에 알아야 한다. @@ -134,6 +163,7 @@ fn render_site(opts: &BuildOptions, cfg: Config) -> Result<BuildOutput> { } } } + prof.lap("feeds"); // 사이트가 실제로 내보내는 URL 전체. 이게 있어야 `[설치](/start/install/)` 같은 // 평범한 절대 경로 링크도 빌드가 검증할 수 있다 — 지금까지는 `@/`만 봤다. @@ -194,6 +224,7 @@ fn render_site(opts: &BuildOptions, cfg: Config) -> Result<BuildOutput> { (lang, minijinja::Value::from_serialize(&ctx)) }) .collect(); + prof.lap("templates"); let mut out = BuildOutput { pages: pages.len(), @@ -227,12 +258,14 @@ fn render_site(opts: &BuildOptions, cfg: Config) -> Result<BuildOutput> { ); index.entry(page.language.clone()).or_default().push(entry); } + prof.lap("render"); for (language, entries) in index { let json = serde_json::to_vec(&entries) .with_context(|| format!("{language} 검색 색인을 만들 수 없습니다"))?; out.files.insert(search::path(&language), json); } + prof.lap("search"); // 옛 URL을 새 URL로 보내는 스텁. GitHub Pages에는 리다이렉트 규칙이 없으므로 // 이걸 빌드가 만들지 않으면 아무도 만들어 주지 않는다. @@ -299,6 +332,7 @@ fn render_site(opts: &BuildOptions, cfg: Config) -> Result<BuildOutput> { out.files.insert(".nojekyll".into(), Vec::new()); check_output_collisions(&out).map_err(Kind::Content.tag())?; + prof.lap("generate"); Ok(out) } @@ -317,6 +351,7 @@ pub fn build(opts: &BuildOptions) -> Result<BuildStats> { let cfg = load_config(opts)?; let out_dir = resolve_output_dir(opts, &cfg); let output = render_site(opts, cfg)?; + let mut prof = Profiler::new(opts.profile); // 출력 디렉터리를 매번 새로 만든다. 지운 페이지가 유령으로 남는 걸 막는다. if out_dir.exists() { @@ -335,6 +370,7 @@ pub fn build(opts: &BuildOptions) -> Result<BuildStats> { .with_context(|| format!("{}을(를) 쓸 수 없습니다", dest.display())) .map_err(Kind::Io.tag())?; } + prof.lap("write"); Ok(BuildStats { pages_written: output.pages, diff --git a/src/main.rs b/src/main.rs @@ -103,6 +103,10 @@ struct BuildArgs { /// 설정의 base_url을 덮어쓴다 #[arg(long, value_name = "URL")] base_url: Option<String>, + + /// 페이즈별 소요 시간을 stderr로 낸다 + #[arg(long)] + profile: bool, } fn main() { @@ -137,6 +141,7 @@ fn run(cli: Cli) -> Result<()> { output: args.output, drafts: args.drafts, base_url: args.base_url, + profile: args.profile, })?; if json { println!( @@ -181,6 +186,7 @@ fn run(cli: Cli) -> Result<()> { output: None, drafts: args.drafts, base_url: None, + profile: false, })?; let gated = findings .iter() diff --git a/src/serve.rs b/src/serve.rs @@ -70,6 +70,7 @@ pub async fn serve(opts: ServeOptions) -> Result<()> { output: None, drafts: opts.drafts, base_url: opts.base_url.clone(), + profile: false, }; let initial = build_to_memory(&build_opts) |