|
| 1 | +# ============================================================================= |
| 2 | +# Web log analyzer — a non-trivial OMC program |
| 3 | +# ============================================================================= |
| 4 | +# Reads a CSV access log, computes standard summary statistics, then runs |
| 5 | +# OMC-distinctive harmonic analyses (Fibonacci-attractor partitioning of |
| 6 | +# latencies, harmonic_sort by HIM, attractor-grouped outlier detection). |
| 7 | +# |
| 8 | +# Demonstrates: file I/O, CSV parsing, first-class functions (arr_map / |
| 9 | +# arr_filter / arr_reduce), HOF composition, mutable closures for stats |
| 10 | +# accumulators, and the harmonic_* builtins as the OMC-native pivot. |
| 11 | +# |
| 12 | +# Run: |
| 13 | +# ./target/release/omnimcode-standalone examples/log_analyzer.omc |
| 14 | +# OMC_VM=1 ./target/release/omnimcode-standalone examples/log_analyzer.omc |
| 15 | +# ============================================================================= |
| 16 | + |
| 17 | +# ---- 1. Generate sample log ------------------------------------------------ |
| 18 | +# We embed the sample directly so the demo is self-contained — no |
| 19 | +# external file needed. 24 fake requests across four endpoints. The |
| 20 | +# `/api/search` endpoint deliberately has heavier-tailed latencies so |
| 21 | +# the harmonic-partition step has something interesting to show. |
| 22 | + |
| 23 | +h log_lines = [ |
| 24 | + "1715000001,/api/users,200,12", |
| 25 | + "1715000002,/api/users,200,8", |
| 26 | + "1715000003,/api/users,200,15", |
| 27 | + "1715000004,/api/orders,200,22", |
| 28 | + "1715000005,/api/orders,200,19", |
| 29 | + "1715000006,/api/orders,500,2100", |
| 30 | + "1715000007,/api/orders,200,21", |
| 31 | + "1715000008,/api/search,200,89", |
| 32 | + "1715000009,/api/search,200,144", |
| 33 | + "1715000010,/api/search,200,55", |
| 34 | + "1715000011,/api/search,200,233", |
| 35 | + "1715000012,/api/search,200,90", |
| 36 | + "1715000013,/api/search,500,3400", |
| 37 | + "1715000014,/api/login,200,34", |
| 38 | + "1715000015,/api/login,401,55", |
| 39 | + "1715000016,/api/login,200,21", |
| 40 | + "1715000017,/api/login,200,13", |
| 41 | + "1715000018,/api/orders,200,17", |
| 42 | + "1715000019,/api/users,200,9", |
| 43 | + "1715000020,/api/users,200,11", |
| 44 | + "1715000021,/api/search,200,87", |
| 45 | + "1715000022,/api/search,500,2900", |
| 46 | + "1715000023,/api/login,200,28", |
| 47 | + "1715000024,/api/orders,200,18" |
| 48 | +]; |
| 49 | +h sample = str_join(log_lines, "\n"); |
| 50 | + |
| 51 | +write_file("/tmp/access.log", sample); |
| 52 | + |
| 53 | +# ---- 2. Read + parse ------------------------------------------------------- |
| 54 | + |
| 55 | +h raw = read_file("/tmp/access.log"); |
| 56 | +h lines = str_split(raw, "\n"); |
| 57 | + |
| 58 | +# Each line → array [ts, endpoint, status, latency_ms]. We carry latency |
| 59 | +# as HInt so resonance / HIM math works on it downstream. |
| 60 | +fn parse_line(ln) { |
| 61 | + h parts = str_split(ln, ","); |
| 62 | + return [ |
| 63 | + to_int(arr_get(parts, 0)), |
| 64 | + arr_get(parts, 1), |
| 65 | + to_int(arr_get(parts, 2)), |
| 66 | + to_int(arr_get(parts, 3)) |
| 67 | + ]; |
| 68 | +} |
| 69 | + |
| 70 | +h rows = arr_map(lines, parse_line); |
| 71 | + |
| 72 | +# ---- 3. Standard summary stats -------------------------------------------- |
| 73 | + |
| 74 | +fn get_status(row) { return arr_get(row, 2); } |
| 75 | +fn get_latency(row) { return arr_get(row, 3); } |
| 76 | +fn get_endpoint(row) { return arr_get(row, 1); } |
| 77 | + |
| 78 | +fn is_error(row) { |
| 79 | + return get_status(row) >= 400; |
| 80 | +} |
| 81 | + |
| 82 | +h total = arr_len(rows); |
| 83 | +h errors = arr_filter(rows, is_error); |
| 84 | +h error_count = arr_len(errors); |
| 85 | + |
| 86 | +# Sum-of-latencies via reduce — exercises first-class fn closure |
| 87 | +fn add_latency(acc, row) { return acc + get_latency(row); } |
| 88 | +h total_latency = arr_reduce(rows, add_latency, 0); |
| 89 | +h mean_latency = total_latency / total; |
| 90 | + |
| 91 | +# Max via reduce |
| 92 | +fn max_latency(acc, row) { |
| 93 | + h l = get_latency(row); |
| 94 | + if l > acc { return l; } |
| 95 | + return acc; |
| 96 | +} |
| 97 | +h peak_latency = arr_reduce(rows, max_latency, 0); |
| 98 | + |
| 99 | +println("=== Standard summary ====================================="); |
| 100 | +println(concat_many(" total requests: ", total)); |
| 101 | +println(concat_many(" errors (>=400): ", error_count)); |
| 102 | +println(concat_many(" mean latency: ", mean_latency, " ms")); |
| 103 | +println(concat_many(" peak latency: ", peak_latency, " ms")); |
| 104 | +println(""); |
| 105 | + |
| 106 | +# ---- 4. Per-endpoint breakdown via mutable-closure accumulator ------------ |
| 107 | +# Build an endpoint→count map by hand (OMC has no dict yet, so we use two |
| 108 | +# parallel arrays and a small helper). The accumulator is a closure that |
| 109 | +# captures the parallel arrays — sibling closures share state, which is |
| 110 | +# exactly the mutable-closure case our recent VM work fixed. |
| 111 | + |
| 112 | +fn make_counter_table() { |
| 113 | + h keys = []; |
| 114 | + h vals = []; |
| 115 | + |
| 116 | + h bump = fn(k) { |
| 117 | + h i = 0; |
| 118 | + while i < arr_len(keys) { |
| 119 | + if arr_get(keys, i) == k { |
| 120 | + arr_set(vals, i, arr_get(vals, i) + 1); |
| 121 | + return 0; |
| 122 | + } |
| 123 | + i = i + 1; |
| 124 | + } |
| 125 | + # First time seeing this key — append. |
| 126 | + arr_push(keys, k); |
| 127 | + arr_push(vals, 1); |
| 128 | + return 0; |
| 129 | + }; |
| 130 | + |
| 131 | + h dump = fn() { |
| 132 | + return [keys, vals]; |
| 133 | + }; |
| 134 | + |
| 135 | + return [bump, dump]; |
| 136 | +} |
| 137 | + |
| 138 | +h tbl = make_counter_table(); |
| 139 | +h bump = arr_get(tbl, 0); |
| 140 | +h dump = arr_get(tbl, 1); |
| 141 | + |
| 142 | +fn count_endpoint(row) { |
| 143 | + bump(get_endpoint(row)); |
| 144 | + return 0; |
| 145 | +} |
| 146 | +arr_map(rows, count_endpoint); |
| 147 | + |
| 148 | +h pair = dump(); |
| 149 | +h endpoints = arr_get(pair, 0); |
| 150 | +h counts = arr_get(pair, 1); |
| 151 | + |
| 152 | +println("=== Per-endpoint counts (via mutable closures) ============"); |
| 153 | +h i = 0; |
| 154 | +while i < arr_len(endpoints) { |
| 155 | + println(concat_many(" ", arr_get(endpoints, i), " : ", arr_get(counts, i))); |
| 156 | + i = i + 1; |
| 157 | +} |
| 158 | +println(""); |
| 159 | + |
| 160 | +# ---- 5. Harmonic statistics ------------------------------------------------ |
| 161 | +# This is the OMC-distinctive part. We pull out just the latencies and |
| 162 | +# feed them through the harmonic_* builtins. The Fibonacci-attractor |
| 163 | +# partitioning gives us a free, semantically-meaningful histogram — |
| 164 | +# /api/search clumps near 89/144/233 (its natural latency band), while |
| 165 | +# the 500-error rows land in the 2300+ outlier bucket all by themselves. |
| 166 | + |
| 167 | +h latencies = arr_map(rows, get_latency); |
| 168 | +h harmonic_sorted = harmonic_sort(latencies); |
| 169 | + |
| 170 | +println("=== Harmonic-sort (by HIM score) =========================="); |
| 171 | +println(" Top 5 by harmonic dominance:"); |
| 172 | +h j = 0; |
| 173 | +while j < 5 { |
| 174 | + println(concat_many(" ", arr_get(harmonic_sorted, j), " ms")); |
| 175 | + j = j + 1; |
| 176 | +} |
| 177 | +println(""); |
| 178 | + |
| 179 | +h partitioned = harmonic_partition(latencies); |
| 180 | + |
| 181 | +println("=== Latency clustering on Fibonacci attractors ============"); |
| 182 | +h k = 0; |
| 183 | +while k < arr_len(partitioned) { |
| 184 | + h bucket = arr_get(partitioned, k); |
| 185 | + h bucket_len = arr_len(bucket); |
| 186 | + # Attractor-id = fold(first element) — cheap label. |
| 187 | + h label = fold(arr_get(bucket, 0)); |
| 188 | + println(concat_many(" attractor ~", label, " : ", bucket_len, " request(s)")); |
| 189 | + k = k + 1; |
| 190 | +} |
| 191 | +println(""); |
| 192 | + |
| 193 | +# ---- 6. Outlier detection: high-attractor latencies ----------------------- |
| 194 | +# An outlier is any latency landing on a Fibonacci attractor >= 377 |
| 195 | +# (one φ-octave above the natural search-endpoint band). The 500-error |
| 196 | +# rows at 2100 / 2900 / 3400 ms all fold onto the 610 attractor, so |
| 197 | +# this surfaces them cleanly without arbitrary z-score thresholds. |
| 198 | + |
| 199 | +fn is_outlier(lat) { |
| 200 | + if fold(lat) >= 377 { return 1; } |
| 201 | + return 0; |
| 202 | +} |
| 203 | + |
| 204 | +h spikes = arr_filter(latencies, is_outlier); |
| 205 | + |
| 206 | +println("=== Outliers (latency >= 377-attractor) ==================="); |
| 207 | +h m = 0; |
| 208 | +while m < arr_len(spikes) { |
| 209 | + h s = arr_get(spikes, m); |
| 210 | + println(concat_many(" spike: ", s, " ms (folds to attractor ", fold(s), ")")); |
| 211 | + m = m + 1; |
| 212 | +} |
| 213 | + |
| 214 | +println(""); |
| 215 | +println("=== Done =================================================="); |
0 commit comments