| 1 | module main |
| 2 | |
| 3 | import os |
| 4 | import strings |
| 5 | import time |
| 6 | import x.templating.dtm2 |
| 7 | |
| 8 | const bench_root_name = 'dtm2_benchmark' |
| 9 | |
| 10 | struct BenchConfig { |
| 11 | case_name string |
| 12 | iterations int |
| 13 | cold_iterations int |
| 14 | placeholder_count int |
| 15 | compress_html bool |
| 16 | reload_modified_templates bool |
| 17 | validate_each_iteration bool |
| 18 | } |
| 19 | |
| 20 | fn main() { |
| 21 | config := BenchConfig{ |
| 22 | case_name: os.getenv('DTM2_BENCH_CASE') |
| 23 | iterations: env_int('DTM2_BENCH_ITERATIONS', 50000) |
| 24 | cold_iterations: env_int('DTM2_BENCH_COLD_ITERATIONS', 500) |
| 25 | placeholder_count: env_int('DTM2_BENCH_PLACEHOLDERS', 50) |
| 26 | compress_html: env_bool('DTM2_BENCH_COMPRESS_HTML', true) |
| 27 | reload_modified_templates: env_bool('DTM2_BENCH_RELOAD_MODIFIED_TEMPLATES', false) |
| 28 | validate_each_iteration: env_bool('DTM2_BENCH_VALIDATE_EACH_ITERATION', false) |
| 29 | } |
| 30 | root := os.join_path(os.vtmp_dir(), '${bench_root_name}_${os.getpid()}') |
| 31 | os.rmdir_all(root) or {} |
| 32 | setup_files(root, config)! |
| 33 | defer { |
| 34 | os.rmdir_all(root) or {} |
| 35 | } |
| 36 | |
| 37 | print_config(root, config) |
| 38 | small := small_placeholders() |
| 39 | many := many_placeholders(config.placeholder_count) |
| 40 | include := include_placeholders() |
| 41 | |
| 42 | if should_run(config, 'small_hot') { |
| 43 | bench_hot('small_hot', root, 'small.html', small, config.iterations, config) |
| 44 | } |
| 45 | if should_run(config, 'small_cold') { |
| 46 | bench_cold('small_cold', root, 'small.html', small, config.cold_iterations, config) |
| 47 | } |
| 48 | if should_run(config, 'many_hot') { |
| 49 | bench_hot('many_hot', root, 'many.html', many, config.iterations, config) |
| 50 | } |
| 51 | if should_run(config, 'many_cold') { |
| 52 | bench_cold('many_cold', root, 'many.html', many, config.cold_iterations, config) |
| 53 | } |
| 54 | if should_run(config, 'include_hot') { |
| 55 | bench_hot('include_hot', root, 'with_include.html', include, config.iterations, config) |
| 56 | } |
| 57 | if should_run(config, 'include_cold') { |
| 58 | bench_cold('include_cold', root, 'with_include.html', include, config.cold_iterations, |
| 59 | config) |
| 60 | } |
| 61 | if should_run(config, 'xml_hot') { |
| 62 | bench_hot('xml_hot', root, 'feed.xml', small, config.iterations, config) |
| 63 | } |
| 64 | if should_run(config, 'xml_cold') { |
| 65 | bench_cold('xml_cold', root, 'feed.xml', small, config.cold_iterations, config) |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | fn print_config(root string, config BenchConfig) { |
| 70 | println('DTM2 benchmark') |
| 71 | println('root: ${root}') |
| 72 | println('case_name: ${if config.case_name == '' { 'all' } else { config.case_name }}') |
| 73 | println('iterations: ${config.iterations}') |
| 74 | println('cold_iterations: ${config.cold_iterations}') |
| 75 | println('placeholder_count: ${config.placeholder_count}') |
| 76 | println('compress_html: ${config.compress_html}') |
| 77 | println('reload_modified_templates: ${config.reload_modified_templates}') |
| 78 | println('validate_each_iteration: ${config.validate_each_iteration}') |
| 79 | println('') |
| 80 | } |
| 81 | |
| 82 | fn env_int(name string, default_value int) int { |
| 83 | raw := os.getenv(name) |
| 84 | if raw == '' { |
| 85 | return default_value |
| 86 | } |
| 87 | value := raw.int() |
| 88 | if value <= 0 { |
| 89 | return default_value |
| 90 | } |
| 91 | return value |
| 92 | } |
| 93 | |
| 94 | fn env_bool(name string, default_value bool) bool { |
| 95 | raw := os.getenv(name).to_lower() |
| 96 | if raw == '' { |
| 97 | return default_value |
| 98 | } |
| 99 | if raw in ['1', 'true', 'yes', 'on'] { |
| 100 | return true |
| 101 | } |
| 102 | if raw in ['0', 'false', 'no', 'off'] { |
| 103 | return false |
| 104 | } |
| 105 | return default_value |
| 106 | } |
| 107 | |
| 108 | fn should_run(config BenchConfig, label string) bool { |
| 109 | return config.case_name == '' || config.case_name == 'all' || config.case_name == label |
| 110 | } |
| 111 | |
| 112 | fn setup_files(root string, config BenchConfig) ! { |
| 113 | os.mkdir_all(os.join_path(root, 'partials'))! |
| 114 | os.write_file(os.join_path(root, 'small.html'), |
| 115 | '<main>@title <p>@body</p><span>@count</span></main>')! |
| 116 | mut many := strings.new_builder(config.placeholder_count * 30) |
| 117 | many.writeln('<dl>') |
| 118 | for i := 0; i < config.placeholder_count; i++ { |
| 119 | many.writeln('<dt>${i}</dt><dd>@p_${i}</dd>') |
| 120 | } |
| 121 | many.writeln('</dl>') |
| 122 | os.write_file(os.join_path(root, 'many.html'), many.str())! |
| 123 | os.write_file(os.join_path(root, 'partials', 'nav.html'), '<nav>@title</nav>')! |
| 124 | os.write_file(os.join_path(root, 'with_include.html'), |
| 125 | '<header>@include "partials/nav"</header><main>@body</main>')! |
| 126 | os.write_file(os.join_path(root, 'feed.xml'), |
| 127 | '<feed><title>@title</title><entry>@body</entry><count>@count</count></feed>')! |
| 128 | } |
| 129 | |
| 130 | fn small_placeholders() map[string]string { |
| 131 | mut placeholders := map[string]string{} |
| 132 | placeholders['title'] = 'Small'.clone() |
| 133 | placeholders['body'] = 'Body <escaped>'.clone() |
| 134 | placeholders['count'] = '42' |
| 135 | return placeholders |
| 136 | } |
| 137 | |
| 138 | fn many_placeholders(count int) map[string]string { |
| 139 | mut placeholders := map[string]string{} |
| 140 | for i := 0; i < count; i++ { |
| 141 | placeholders['p_${i}'] = 'value ${i}'.clone() |
| 142 | } |
| 143 | return placeholders |
| 144 | } |
| 145 | |
| 146 | fn include_placeholders() map[string]string { |
| 147 | mut placeholders := map[string]string{} |
| 148 | placeholders['title'] = 'Menu'.clone() |
| 149 | placeholders['body'] = 'Body <escaped>'.clone() |
| 150 | return placeholders |
| 151 | } |
| 152 | |
| 153 | fn bench_hot(label string, root string, template_path string, placeholders map[string]string, iterations int, config BenchConfig) { |
| 154 | mut manager := new_manager(root, config) |
| 155 | expected := manager.expand(template_path, placeholders: &placeholders).clone() |
| 156 | mut last_len := expected.len |
| 157 | sw := time.new_stopwatch() |
| 158 | for i in 0 .. iterations { |
| 159 | rendered := manager.expand(template_path, placeholders: &placeholders) |
| 160 | if config.validate_each_iteration { |
| 161 | validate_rendered(label, i, expected, rendered) |
| 162 | } |
| 163 | last_len = rendered.len |
| 164 | } |
| 165 | elapsed := sw.elapsed() |
| 166 | validate_rendered(label, iterations, expected, manager.expand(template_path, |
| 167 | placeholders: &placeholders |
| 168 | )) |
| 169 | print_result(label, iterations, elapsed, last_len, manager.compiled_template_count()) |
| 170 | } |
| 171 | |
| 172 | fn bench_cold(label string, root string, template_path string, placeholders map[string]string, iterations int, config BenchConfig) { |
| 173 | mut expected_manager := new_manager(root, config) |
| 174 | expected := expected_manager.expand(template_path, placeholders: &placeholders).clone() |
| 175 | mut last_len := expected.len |
| 176 | sw := time.new_stopwatch() |
| 177 | for i in 0 .. iterations { |
| 178 | mut manager := new_manager(root, config) |
| 179 | rendered := manager.expand(template_path, placeholders: &placeholders) |
| 180 | if config.validate_each_iteration { |
| 181 | validate_rendered(label, i, expected, rendered) |
| 182 | } |
| 183 | last_len = rendered.len |
| 184 | } |
| 185 | elapsed := sw.elapsed() |
| 186 | mut final_manager := new_manager(root, config) |
| 187 | validate_rendered(label, iterations, expected, final_manager.expand(template_path, |
| 188 | placeholders: &placeholders |
| 189 | )) |
| 190 | print_result(label, iterations, elapsed, last_len, final_manager.compiled_template_count()) |
| 191 | } |
| 192 | |
| 193 | fn new_manager(root string, config BenchConfig) &dtm2.Manager { |
| 194 | return dtm2.initialize( |
| 195 | template_dir: root |
| 196 | compress_html: config.compress_html |
| 197 | reload_modified_templates: config.reload_modified_templates |
| 198 | ) |
| 199 | } |
| 200 | |
| 201 | fn validate_rendered(label string, iteration int, expected string, rendered string) { |
| 202 | if rendered != expected { |
| 203 | eprintln('${label} invalid output at iteration ${iteration}: expected_len=${expected.len} actual_len=${rendered.len}') |
| 204 | exit(1) |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | fn print_result(label string, iterations int, elapsed time.Duration, last_len int, compiled_count int) { |
| 209 | ns_per_op := if iterations > 0 { elapsed.nanoseconds() / i64(iterations) } else { i64(0) } |
| 210 | ops_per_sec := if ns_per_op > 0 { 1_000_000_000.0 / f64(ns_per_op) } else { 0.0 } |
| 211 | println('${label:-16} iterations=${iterations:8} total_ms=${elapsed.milliseconds():8} ns_per_op=${ns_per_op:10} ops_per_sec=${ops_per_sec:10.1f} last_len=${last_len:6} compiled_templates=${compiled_count}') |
| 212 | } |
| 213 | |