diff --git a/bench-func/bench_full.sh b/bench-func/bench_full.sh new file mode 100644 index 0000000..0df3967 --- /dev/null +++ b/bench-func/bench_full.sh @@ -0,0 +1,1032 @@ +#!/usr/bin/env bash +# bench_full.sh — полный бенчмарк: все языки × все размеры + архивы +# Языки: Python, Node.js, Ruby, PHP +# Размеры: tiny, small, medium, large, archive (ZIP + доп. файл) +# Метрики: upload_ms, cold_ms, hot_avg_ms (3 invoke) +# +# Запуск: bash bench-func/bench_full.sh +# На VM: bash ~/terra/fission/bench-func/bench_full.sh + +set -uo pipefail + +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +ROOT_DIR=$(cd "${SCRIPT_DIR}/.." && pwd) +source "${ROOT_DIR}/scripts/lib.sh" + +TIMESTAMP=$(date +%Y-%m-%d_%H-%M-%S) +RESULTS_DIR="${ROOT_DIR}/test-results/bench-func" +LOGFILE="${RESULTS_DIR}/bench_full_${TIMESTAMP}.log" +mkdir -p "$RESULTS_DIR" + +SUB="bench@bench.local" +RUNID=$(date +%s | tail -c 8) +TMP=$(mktemp -d) + +echo "" | tee -a "$LOGFILE" +echo "════════════════════════════════════════════════════════════════" | tee -a "$LOGFILE" +echo " bench_full.sh run=${RUNID} $(date)" | tee -a "$LOGFILE" +echo " API: ${BASE}" | tee -a "$LOGFILE" +echo " SUB: ${SUB}" | tee -a "$LOGFILE" +echo "════════════════════════════════════════════════════════════════" | tee -a "$LOGFILE" + +declare -a ALL_FNS=() +declare -a SUMMARY_ROWS=() +PASS_CNT=0 +FAIL_CNT=0 + +# ── Cleanup ─────────────────────────────────────────────────────────────── +cleanup() { + rm -rf "$TMP" 2>/dev/null || true + for fn in "${ALL_FNS[@]:-}"; do + curl -s -X DELETE "${BASE}/functions/${fn}" \ + -H "X-Auth-Token: ${SUB}" >/dev/null 2>&1 || true + done +} +trap cleanup EXIT + +# ── Helpers ─────────────────────────────────────────────────────────────── +log() { echo "$@" | tee -a "$LOGFILE"; } + +escape_code() { + python3 -c 'import json, sys; print(json.dumps(sys.stdin.read()))' +} + +get_status() { + python3 -c "import json,sys +try: + d=json.load(sys.stdin) + print(d.get('status','')) +except: + print('')" 2>/dev/null +} + +get_field() { + local field="$1" + python3 -c "import json,sys +try: + d=json.load(sys.stdin) + print(d.get('$field','')) +except: + print('')" 2>/dev/null +} + +# bench_code LANG SIZE FN CODEFILE EXPECTED_TAG +bench_code() { + local lang="$1" size="$2" fn="$3" codefile="$4" expected="${5:-}" + ALL_FNS+=("$fn") + + local code_esc + code_esc=$(cat "$codefile" | escape_code) + + # Upload + local t0 t1 R CODE BODY + t0=$(date +%s%3N) + R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \ + -H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \ + -d "{\"name\":\"${fn}\",\"language\":\"${lang}\",\"code\":${code_esc}}" 2>/dev/null) + t1=$(date +%s%3N) + CODE=$(echo "$R" | tail -1) + BODY=$(echo "$R" | head -n -1) + local UPLOAD_MS=$((t1 - t0)) + + if [ "$CODE" != "201" ]; then + log " FAIL [${lang}/${size}] upload HTTP=$CODE | $(printf '%s' "$BODY" | head -c 150)" + SUMMARY_ROWS+=("$(printf '%-8s %-8s %8s %10s %10s %s' "$lang" "$size" "-" "-" "-" "UPLOAD-FAIL-${CODE}")") + FAIL_CNT=$((FAIL_CNT+1)) + return 1 + fi + + # Cold start (retry 20×5s = 100s max) + local tc0 COLD_MS=0 cold_ok=0 + tc0=$(date +%s%3N) + for i in $(seq 1 20); do + local resp s raw + resp=$(curl -s --max-time 35 -X POST "${BASE}/functions/${fn}/invoke" \ + -H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}' 2>/dev/null) + s=$(echo "$resp" | get_status) + raw=$(echo "$resp" | get_field "response_raw") + if [ "$s" = "200" ]; then + if [ -z "$expected" ] || echo "$raw" | grep -qF "$expected"; then + local tc1 + tc1=$(date +%s%3N) + COLD_MS=$((tc1 - tc0)) + cold_ok=1 + break + fi + fi + sleep 5 + done + + if [ $cold_ok -eq 0 ]; then + log " FAIL [${lang}/${size}] cold start timeout" + SUMMARY_ROWS+=("$(printf '%-8s %-8s %8s %10s %10s %s' "$lang" "$size" "$UPLOAD_MS" "-" "-" "COLD-TIMEOUT")") + FAIL_CNT=$((FAIL_CNT+1)) + return 1 + fi + + # Hot start ×3 + local HOT_TOTAL=0 hot_ok=0 + for i in 1 2 3; do + local th0 th1 resp s + th0=$(date +%s%3N) + resp=$(curl -s --max-time 35 -X POST "${BASE}/functions/${fn}/invoke" \ + -H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}' 2>/dev/null) + th1=$(date +%s%3N) + s=$(echo "$resp" | get_status) + if [ "$s" = "200" ]; then + HOT_TOTAL=$((HOT_TOTAL + th1 - th0)) + hot_ok=$((hot_ok+1)) + fi + done + + local HOT_AVG_MS=0 + [ $hot_ok -gt 0 ] && HOT_AVG_MS=$((HOT_TOTAL / hot_ok)) + + log " OK [${lang}/${size}] upload=${UPLOAD_MS}ms cold=${COLD_MS}ms hot_avg=${HOT_AVG_MS}ms (${hot_ok}/3)" + SUMMARY_ROWS+=("$(printf '%-8s %-8s %8s %10s %10s %s' "$lang" "$size" "${UPLOAD_MS}" "${COLD_MS}" "${HOT_AVG_MS}" "OK(hot=${hot_ok}/3)")") + PASS_CNT=$((PASS_CNT+1)) +} + +# bench_archive LANG_SHORT LANG FN ZIP EXPECTED_TAG +bench_archive() { + local lang_short="$1" lang="$2" fn="$3" zipfile="$4" expected="$5" + ALL_FNS+=("$fn") + + local t0 t1 R CODE BODY + t0=$(date +%s%3N) + R=$(curl -s -w "\n%{http_code}" -X POST "${BASE}/functions" \ + -H "X-Auth-Token: ${SUB}" \ + -F "name=${fn}" \ + -F "language=${lang}" \ + -F "archive=@${zipfile};type=application/zip" 2>/dev/null) + t1=$(date +%s%3N) + CODE=$(echo "$R" | tail -1) + BODY=$(echo "$R" | head -n -1) + local UPLOAD_MS=$((t1 - t0)) + + if [ "$CODE" != "201" ]; then + log " FAIL [${lang}/archive] upload HTTP=$CODE | $(printf '%s' "$BODY" | head -c 150)" + SUMMARY_ROWS+=("$(printf '%-8s %-8s %8s %10s %10s %s' "$lang" "archive" "-" "-" "-" "UPLOAD-FAIL-${CODE}")") + FAIL_CNT=$((FAIL_CNT+1)) + return 1 + fi + + # Cold start (retry 24×5s = 120s — архивам нужна сборка) + local tc0 COLD_MS=0 cold_ok=0 + tc0=$(date +%s%3N) + for i in $(seq 1 24); do + local resp s raw + resp=$(curl -s --max-time 35 -X POST "${BASE}/functions/${fn}/invoke" \ + -H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}' 2>/dev/null) + s=$(echo "$resp" | get_status) + raw=$(echo "$resp" | get_field "response_raw") + if [ "$s" = "200" ] && echo "$raw" | grep -qF "$expected"; then + local tc1 + tc1=$(date +%s%3N) + COLD_MS=$((tc1 - tc0)) + cold_ok=1 + break + fi + sleep 5 + done + + if [ $cold_ok -eq 0 ]; then + log " FAIL [${lang}/archive] cold start timeout" + SUMMARY_ROWS+=("$(printf '%-8s %-8s %8s %10s %10s %s' "$lang" "archive" "$UPLOAD_MS" "-" "-" "COLD-TIMEOUT")") + FAIL_CNT=$((FAIL_CNT+1)) + return 1 + fi + + # Hot ×3 + local HOT_TOTAL=0 hot_ok=0 + for i in 1 2 3; do + local th0 th1 resp s + th0=$(date +%s%3N) + resp=$(curl -s --max-time 35 -X POST "${BASE}/functions/${fn}/invoke" \ + -H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" -d '{}' 2>/dev/null) + th1=$(date +%s%3N) + s=$(echo "$resp" | get_status) + [ "$s" = "200" ] && { HOT_TOTAL=$((HOT_TOTAL + th1 - th0)); hot_ok=$((hot_ok+1)); } + done + + local HOT_AVG_MS=0 + [ $hot_ok -gt 0 ] && HOT_AVG_MS=$((HOT_TOTAL / hot_ok)) + + log " OK [${lang}/archive] upload=${UPLOAD_MS}ms cold=${COLD_MS}ms hot_avg=${HOT_AVG_MS}ms (${hot_ok}/3)" + SUMMARY_ROWS+=("$(printf '%-8s %-8s %8s %10s %10s %s' "$lang" "archive" "${UPLOAD_MS}" "${COLD_MS}" "${HOT_AVG_MS}" "OK(hot=${hot_ok}/3)")") + PASS_CNT=$((PASS_CNT+1)) +} + +# ═══════════════════════════════════════════════════════════════════════════ +# ГЕНЕРАЦИЯ КОДА ФУНКЦИЙ +# ═══════════════════════════════════════════════════════════════════════════ + +# ── PYTHON ──────────────────────────────────────────────────────────────── +cat > "${TMP}/py_tiny.py" << 'PYEOF' +def main(event, context): + return {"statusCode": 200, "body": "bench-py-tiny-ok"} +PYEOF + +cat > "${TMP}/py_small.py" << 'PYEOF' +import math, time + +def fib(n): + a, b = 0, 1 + for _ in range(n): a, b = b, a + b + return a + +def sieve(n): + s = [True] * (n + 1); s[0] = s[1] = False + for i in range(2, int(math.sqrt(n)) + 1): + if s[i]: + for j in range(i*i, n+1, i): s[j] = False + return [i for i in range(n+1) if s[i]] + +def sqrt_sum(n): + return sum(math.sqrt(i) for i in range(1, n + 1)) + +def main(event, context): + t0 = time.time() + f = fib(30) + primes = sieve(200) + s = sqrt_sum(500) + elapsed = time.time() - t0 + return { + "statusCode": 200, + "body": f"bench-py-small-ok:fib30={f}:primes={len(primes)}:sqrt={s:.1f}:t={elapsed:.4f}s" + } +PYEOF + +cat > "${TMP}/py_medium.py" << 'PYEOF' +import math, time, json, hashlib + +LOOKUP = {i: i*i for i in range(100)} +WORDS = ["alpha","beta","gamma","delta","epsilon","zeta","eta","theta", + "iota","kappa","lambda","mu","nu","xi","omicron","pi", + "rho","sigma","tau","upsilon","phi","chi","psi","omega"] + +def fib(n): + a, b = 0, 1 + for _ in range(n): a, b = b, a + b + return a + +def sieve(n): + s = [True] * (n + 1); s[0] = s[1] = False + for i in range(2, int(math.sqrt(n)) + 1): + if s[i]: + for j in range(i*i, n+1, i): s[j] = False + return [i for i in range(n+1) if s[i]] + +def sqrt_sum(n): + return sum(math.sqrt(i) for i in range(1, n + 1)) + +def bubble_sort(arr): + arr = arr[:] + n = len(arr) + for i in range(n): + for j in range(0, n-i-1): + if arr[j] > arr[j+1]: arr[j], arr[j+1] = arr[j+1], arr[j] + return arr + +def mat_mul(a, b): + ra, ca, cb = len(a), len(a[0]), len(b[0]) + c = [[0]*cb for _ in range(ra)] + for i in range(ra): + for j in range(cb): + for k in range(ca): c[i][j] += a[i][k] * b[k][j] + return c + +def process_words(words): + return [w.upper()[::-1] for w in words if len(w) > 3] + +def get_stats(lst): + n = len(lst); m = sum(lst) / n + v = sum((x - m)**2 for x in lst) / n + return {"n": n, "mean": round(m, 4), "std": round(math.sqrt(v), 4)} + +def main(event, context): + t0 = time.time() + f = fib(35) + primes = sieve(500) + s = sqrt_sum(1000) + nums = list(range(50, 0, -1)) + sorted_nums = bubble_sort(nums) + a = [[1,2,3],[4,5,6]]; b = [[7,8],[9,10],[11,12]] + c = mat_mul(a, b) + pw = process_words(WORDS) + st = get_stats(primes) + h = hashlib.sha256(b"bench-medium-python").hexdigest()[:16] + elapsed = time.time() - t0 + return {"statusCode": 200, "body": json.dumps({ + "tag": "bench-py-medium-ok", "fib35": f, "primes": len(primes), + "sqrt": round(s, 1), "sorted0": sorted_nums[0], "mat_c00": c[0][0], + "words": len(pw), "stats": st, "hash": h, "elapsed": round(elapsed, 5) + })} +PYEOF + +cat > "${TMP}/py_large.py" << 'PYEOF' +import math, time, json, hashlib, re, base64 + +ALPHABET = "abcdefghijklmnopqrstuvwxyz" +LOOKUP_SQ = {i: i*i for i in range(200)} +LOOKUP_CUBE = {i: i**3 for i in range(200)} +WORDS = ["alpha","beta","gamma","delta","epsilon","zeta","eta","theta", + "iota","kappa","lambda","mu","nu","xi","omicron","pi", + "rho","sigma","tau","upsilon","phi","chi","psi","omega", + "one","two","three","four","five","six","seven","eight", + "nine","ten","eleven","twelve","thirteen","fourteen","fifteen"] +CONSTANTS = {"pi": math.pi, "e": math.e, "phi": (1+math.sqrt(5))/2, + "sqrt2": math.sqrt(2), "ln2": math.log(2)} + +def fib(n): + a, b = 0, 1 + for _ in range(n): a, b = b, a + b + return a + +def fib_list(n): + r = []; a, b = 0, 1 + for _ in range(n): r.append(a); a, b = b, a + b + return r + +def sieve(n): + s = [True] * (n + 1); s[0] = s[1] = False + for i in range(2, int(math.sqrt(n)) + 1): + if s[i]: + for j in range(i*i, n+1, i): s[j] = False + return [i for i in range(n+1) if s[i]] + +def sqrt_sum(n): return sum(math.sqrt(i) for i in range(1, n + 1)) +def log_sum(n): return sum(math.log(i) for i in range(1, n + 1)) +def trig_sum(n): return sum(math.sin(i) * math.cos(i) for i in range(n)) + +def bubble_sort(arr): + arr = arr[:] + n = len(arr) + for i in range(n): + for j in range(0, n-i-1): + if arr[j] > arr[j+1]: arr[j], arr[j+1] = arr[j+1], arr[j] + return arr + +def insertion_sort(arr): + arr = arr[:] + for i in range(1, len(arr)): + key = arr[i]; j = i - 1 + while j >= 0 and arr[j] > key: arr[j+1] = arr[j]; j -= 1 + arr[j+1] = key + return arr + +def mat_mul(a, b): + ra, ca, cb = len(a), len(a[0]), len(b[0]) + c = [[0]*cb for _ in range(ra)] + for i in range(ra): + for j in range(cb): + for k in range(ca): c[i][j] += a[i][k] * b[k][j] + return c + +def transpose(m): + return [[m[j][i] for j in range(len(m))] for i in range(len(m[0]))] + +def process_words(words): + return [{"original": w, "upper": w.upper(), "reversed": w[::-1], + "len": len(w), "vowels": sum(1 for c in w if c in "aeiou")} + for w in words if len(w) >= 3] + +def get_stats(lst): + n = len(lst); m = sum(lst) / n + v = sum((x - m)**2 for x in lst) / n + s = sorted(lst) + median = s[n//2] if n % 2 else (s[n//2-1] + s[n//2]) / 2 + return {"n": n, "min": min(lst), "max": max(lst), + "mean": round(m, 4), "median": median, "std": round(math.sqrt(v), 4)} + +def caesar(text, shift): + r = [] + for c in text: + if c.isalpha(): + base = ord('a') if c.islower() else ord('A') + r.append(chr((ord(c) - base + shift) % 26 + base)) + else: r.append(c) + return ''.join(r) + +def run_regex(text): + return {p: len(re.findall(p, text)) for p in [r'\d+', r'[a-z]+', r'[A-Z]+']} + +def lookup_ops(n): + return sum(LOOKUP_SQ.get(i % 200, 0) + LOOKUP_CUBE.get(i % 200, 0) for i in range(n)) + +def main(event, context): + t0 = time.time() + sq = sqrt_sum(2000); lg = log_sum(1000); tr = trig_sum(500) + primes = sieve(1000); f40 = fib(40); fibs = fib_list(20) + nums = list(range(100, 0, -1)) + bs = bubble_sort(nums[:50]); ins = insertion_sort(nums[50:]) + h256 = hashlib.sha256(b"bench-large-python-2026").hexdigest() + md5 = hashlib.md5(b"bench-large-python-2026").hexdigest() + b64 = base64.b64encode(b"hello from bench-py-large").decode() + ma = [[i+j for j in range(4)] for i in range(4)] + mb = [[i*j+1 for j in range(4)] for i in range(4)] + mc = mat_mul(ma, mb); mt = transpose(mc) + pw = process_words(WORDS); pst = get_stats(primes[:50]) + cipher = caesar("hello bench world", 13) + rx = run_regex("Hello World 42 bench-py-large TEST 2026") + lookup = lookup_ops(500) + elapsed = time.time() - t0 + return {"statusCode": 200, "body": json.dumps({ + "tag": "bench-py-large-ok", + "sqrt": round(sq, 2), "log": round(lg, 2), "trig": round(tr, 4), + "primes": len(primes), "fib40": f40, "fibs_sum": sum(fibs), + "h256": h256[:16], "md5": md5[:16], "b64_len": len(b64), + "mat_trace": sum(mc[i][i] for i in range(len(mc))), + "words": len(pw), "stats": pst, "cipher": cipher, + "regex": rx, "lookup": lookup, "elapsed": round(elapsed, 5) + })} +PYEOF + +# ── NODE.JS ─────────────────────────────────────────────────────────────── +cat > "${TMP}/node_tiny.js" << 'JSEOF' +module.exports = async function(ctx) { + return { status: 200, body: "bench-node-tiny-ok" }; +}; +JSEOF + +cat > "${TMP}/node_small.js" << 'JSEOF' +function fib(n) { let a=0,b=1; for(let i=0;i{if(v)a.push(i);return a;},[]); +} +function sqrtSum(n) { let s=0; for(let i=1;i<=n;i++) s+=Math.sqrt(i); return s; } + +module.exports = async function(ctx) { + const t0=Date.now(); + const f=fib(30), primes=sieve(200), s=sqrtSum(500); + const elapsed=Date.now()-t0; + return { + status: 200, + body: `bench-node-small-ok:fib30=${f}:primes=${primes.length}:sqrt=${s.toFixed(1)}:t=${elapsed}ms` + }; +}; +JSEOF + +cat > "${TMP}/node_medium.js" << 'JSEOF' +const crypto = require('crypto'); +const WORDS = ["alpha","beta","gamma","delta","epsilon","zeta","eta","theta", + "iota","kappa","lambda","mu","nu","xi","omicron","pi", + "rho","sigma","tau","upsilon","phi","chi","psi","omega"]; + +function fib(n) { let a=0,b=1; for(let i=0;i{if(v)a.push(i);return a;},[]); +} +function sqrtSum(n) { let s=0; for(let i=1;i<=n;i++) s+=Math.sqrt(i); return s; } +function bubbleSort(arr) { + arr=[...arr]; const n=arr.length; + for(let i=0;iarr[j+1]) [arr[j],arr[j+1]]=[arr[j+1],arr[j]]; + return arr; +} +function matMul(a,b) { + const ra=a.length,ca=a[0].length,cb=b[0].length; + const c=Array.from({length:ra},()=>new Array(cb).fill(0)); + for(let i=0;is.length>3).map(s=>s.toUpperCase().split('').reverse().join('')); } +function stats(arr) { + const n=arr.length, m=arr.reduce((a,b)=>a+b,0)/n; + const v=arr.reduce((a,b)=>a+(b-m)**2,0)/n; + return {n, mean:+m.toFixed(4), std:+Math.sqrt(v).toFixed(4)}; +} +function sha256(s) { return crypto.createHash('sha256').update(s).digest('hex').slice(0,16); } + +module.exports = async function(ctx) { + const t0=Date.now(); + const f=fib(35), primes=sieve(500), s=sqrtSum(1000); + const nums=Array.from({length:50},(_,i)=>50-i); + const sorted=bubbleSort(nums); + const a=[[1,2,3],[4,5,6]], b=[[7,8],[9,10],[11,12]], c=matMul(a,b); + const pw=processWords(WORDS), st=stats(primes), h=sha256('bench-medium-node'); + const elapsed=Date.now()-t0; + return {status:200, body:JSON.stringify({ + tag:"bench-node-medium-ok", fib35:f, primes:primes.length, + sqrt:+s.toFixed(1), sorted0:sorted[0], mat_c00:c[0][0], + words:pw.length, stats:st, hash:h, elapsed + })}; +}; +JSEOF + +cat > "${TMP}/node_large.js" << 'JSEOF' +const crypto = require('crypto'); +const WORDS = ["alpha","beta","gamma","delta","epsilon","zeta","eta","theta", + "iota","kappa","lambda","mu","nu","xi","omicron","pi", + "rho","sigma","tau","upsilon","phi","chi","psi","omega", + "one","two","three","four","five","six","seven","eight", + "nine","ten","eleven","twelve","thirteen","fourteen","fifteen"]; +const LOOKUP_SQ = Object.fromEntries(Array.from({length:200},(_,i)=>[i,i*i])); +const LOOKUP_CUBE = Object.fromEntries(Array.from({length:200},(_,i)=>[i,i**3])); +const CONSTANTS = {pi:Math.PI, e:Math.E, phi:(1+Math.sqrt(5))/2, sqrt2:Math.SQRT2, ln2:Math.LN2}; + +function fib(n) { let a=0,b=1; for(let i=0;i{if(v)a.push(i);return a;},[]); +} +function sqrtSum(n) { let s=0; for(let i=1;i<=n;i++) s+=Math.sqrt(i); return s; } +function logSum(n) { let s=0; for(let i=1;i<=n;i++) s+=Math.log(i); return s; } +function trigSum(n) { let s=0; for(let i=0;iarr[j+1]) [arr[j],arr[j+1]]=[arr[j+1],arr[j]]; + return arr; +} +function insertionSort(arr) { + arr=[...arr]; + for(let i=1;i=0&&arr[j]>k){arr[j+1]=arr[j];j--;} arr[j+1]=k; + } + return arr; +} +function matMul(a,b) { + const ra=a.length,ca=a[0].length,cb=b[0].length; + const c=Array.from({length:ra},()=>new Array(cb).fill(0)); + for(let i=0;im.map(r=>r[i])); } +function processWords(w) { + return w.filter(s=>s.length>=3).map(s=>({ + original:s, upper:s.toUpperCase(), reversed:s.split('').reverse().join(''), + len:s.length, vowels:[...s].filter(c=>"aeiou".includes(c)).length + })); +} +function stats(arr) { + const n=arr.length, m=arr.reduce((a,b)=>a+b,0)/n; + const v=arr.reduce((a,b)=>a+(b-m)**2,0)/n; + const sorted=[...arr].sort((a,b)=>a-b); + return {n, min:sorted[0], max:sorted[n-1], mean:+m.toFixed(4), std:+Math.sqrt(v).toFixed(4)}; +} +function sha256(s) { return crypto.createHash('sha256').update(s).digest('hex'); } +function md5(s) { return crypto.createHash('md5').update(s).digest('hex'); } +function caesar(t,sh) { + return [...t].map(c=>{ + const b=c.match(/[a-z]/)?97:c.match(/[A-Z]/)?65:null; + return b===null?c:String.fromCharCode((c.charCodeAt(0)-b+sh)%26+b); + }).join(''); +} +function lookupOps(n) { let r=0; for(let i=0;i100-i); + const bs=bubbleSort(nums.slice(0,50)), ins=insertionSort(nums.slice(50)); + const h256=sha256('bench-large-node-2026'), md=md5('bench-large-node-2026'); + const ma=Array.from({length:4},(_,i)=>Array.from({length:4},(_,j)=>i+j)); + const mb=Array.from({length:4},(_,i)=>Array.from({length:4},(_,j)=>i*j+1)); + const mc=matMul(ma,mb), mt=transpose(mc); + const pw=processWords(WORDS), pst=stats(primes.slice(0,50)); + const cipher=caesar('hello bench world',13); + const lookup=lookupOps(500); + const csum=Object.values(CONSTANTS).reduce((a,b)=>a+b,0); + const elapsed=Date.now()-t0; + return {status:200, body:JSON.stringify({ + tag:"bench-node-large-ok", + sqrt:+sq.toFixed(2), log:+lg.toFixed(2), trig:+tr.toFixed(4), + primes:primes.length, fib40:f40.toString(), fibsSum:fibs.reduce((a,b)=>a+b,0), + h256:h256.slice(0,16), md:md.slice(0,16), + matTrace:mc.reduce((s,r,i)=>s+r[i],0), + words:pw.length, stats:pst, cipher, lookup, csum:+csum.toFixed(6), elapsed + })}; +}; +JSEOF + +# ── RUBY ────────────────────────────────────────────────────────────────── +cat > "${TMP}/ruby_tiny.rb" << 'RBEOF' +def handler(ctx) + { statusCode: 200, body: "bench-ruby-tiny-ok" } +end +RBEOF + +cat > "${TMP}/ruby_small.rb" << 'RBEOF' +require 'json' + +def fib(n); a,b=0,1; n.times{a,b=b,a+b}; a; end +def sieve(n) + s=Array.new(n+1,true); s[0]=s[1]=false + (2..Math.sqrt(n).to_i).each{|i|(i*i..n).step(i){|j|s[j]=false} if s[i]} + (0..n).select{|i|s[i]} +end +def sqrt_sum(n); (1..n).sum{|i|Math.sqrt(i)}; end + +def handler(ctx) + t0=Time.now + f=fib(30); primes=sieve(200); s=sqrt_sum(500) + elapsed=Time.now-t0 + { statusCode: 200, body: "bench-ruby-small-ok:fib30=#{f}:primes=#{primes.length}:sqrt=#{s.round(1)}:t=#{elapsed.round(4)}s" } +end +RBEOF + +cat > "${TMP}/ruby_medium.rb" << 'RBEOF' +require 'json' +require 'digest' + +WORDS=%w[alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi omicron pi rho sigma tau upsilon phi chi psi omega] + +def fib(n); a,b=0,1; n.times{a,b=b,a+b}; a; end +def sieve(n) + s=Array.new(n+1,true); s[0]=s[1]=false + (2..Math.sqrt(n).to_i).each{|i|(i*i..n).step(i){|j|s[j]=false} if s[i]} + (0..n).select{|i|s[i]} +end +def sqrt_sum(n); (1..n).sum{|i|Math.sqrt(i)}; end +def bubble_sort(arr) + arr=arr.dup; n=arr.length + n.times{(n-1).times{|j|arr[j],arr[j+1]=arr[j+1],arr[j] if arr[j]>arr[j+1]}} + arr +end +def mat_mul(a,b) + ra,ca,cb=a.length,a[0].length,b[0].length + Array.new(ra){|i|Array.new(cb){|j|(0...ca).sum{|k|a[i][k]*b[k][j]}}} +end +def process_words(w); w.select{|s|s.length>3}.map{|s|s.upcase.reverse}; end +def get_stats(arr) + n=arr.length; m=arr.sum.to_f/n; v=arr.sum{|x|(x-m)**2}/n + {n:n, mean:m.round(4), std:Math.sqrt(v).round(4)} +end + +def handler(ctx) + t0=Time.now + f=fib(35); primes=sieve(500); s=sqrt_sum(1000) + nums=(1..50).to_a.reverse; sorted=bubble_sort(nums) + a=[[1,2,3],[4,5,6]]; b=[[7,8],[9,10],[11,12]]; c=mat_mul(a,b) + pw=process_words(WORDS); st=get_stats(primes) + h=Digest::SHA256.hexdigest('bench-medium-ruby')[0,16] + elapsed=Time.now-t0 + { statusCode: 200, body: JSON.generate({tag:"bench-ruby-medium-ok", fib35:f, primes:primes.length, + sqrt:s.round(1), sorted0:sorted[0], mat_c00:c[0][0], + words:pw.length, stats:st, hash:h, elapsed:elapsed.round(5)}) } +end +RBEOF + +cat > "${TMP}/ruby_large.rb" << 'RBEOF' +require 'json' +require 'digest' +require 'base64' + +WORDS=%w[alpha beta gamma delta epsilon zeta eta theta iota kappa lambda mu nu xi omicron pi rho sigma tau upsilon phi chi psi omega one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen] +LOOKUP_SQ=(0...200).each_with_object({}){|i,h|h[i]=i*i} +LOOKUP_CUBE=(0...200).each_with_object({}){|i,h|h[i]=i**3} +CONSTANTS={pi:Math::PI, e:Math::E, phi:(1+Math.sqrt(5))/2, sqrt2:Math.sqrt(2), ln2:Math.log(2)} + +def fib(n); a,b=0,1; n.times{a,b=b,a+b}; a; end +def fib_list(n); r=[]; a,b=0,1; n.times{r<arr[j+1]}} + arr +end +def insertion_sort(arr) + arr=arr.dup + (1...arr.length).each{|i|k=arr[i];j=i-1;while j>=0&&arr[j]>k;arr[j+1]=arr[j];j-=1;end;arr[j+1]=k} + arr +end +def mat_mul(a,b) + ra,ca,cb=a.length,a[0].length,b[0].length + Array.new(ra){|i|Array.new(cb){|j|(0...ca).sum{|k|a[i][k]*b[k][j]}}} +end +def transpose(m); m[0].length.times.map{|j|m.map{|r|r[j]}}; end +def process_words(w) + w.select{|s|s.length>=3}.map{|s|{original:s,upper:s.upcase,reversed:s.reverse,len:s.length,vowels:s.count("aeiou")}} +end +def get_stats(arr) + n=arr.length; m=arr.sum.to_f/n; v=arr.sum{|x|(x-m)**2}/n + {n:n, min:arr.min, max:arr.max, mean:m.round(4), std:Math.sqrt(v).round(4)} +end +def caesar(t,sh) + t.chars.map{|c| + if c=~/[a-z]/; (('a'.ord+(c.ord-'a'.ord+sh)%26).chr) + elsif c=~/[A-Z]/; (('A'.ord+(c.ord-'A'.ord+sh)%26).chr) + else c end + }.join +end +def lookup_ops(n); (0...n).sum{|i|(LOOKUP_SQ[i%200]||0)+(LOOKUP_CUBE[i%200]||0)}; end + +def handler(ctx) + t0=Time.now + sq=sqrt_sum(2000); lg=log_sum(1000); tr=trig_sum(500) + primes=sieve(1000); f40=fib(40); fibs=fib_list(20) + nums=(1..100).to_a.reverse + bs=bubble_sort(nums[0,50]); ins=insertion_sort(nums[50,50]) + h256=Digest::SHA256.hexdigest('bench-large-ruby-2026') + md5=Digest::MD5.hexdigest('bench-large-ruby-2026') + b64=Base64.strict_encode64('hello from bench-ruby-large') + ma=Array.new(4){|i|Array.new(4){|j|i+j}} + mb=Array.new(4){|i|Array.new(4){|j|i*j+1}} + mc=mat_mul(ma,mb); mt=transpose(mc) + pw=process_words(WORDS); pst=get_stats(primes[0,50]) + cipher=caesar('hello bench world',13) + lookup=lookup_ops(500) + csum=CONSTANTS.values.sum + elapsed=Time.now-t0 + { statusCode: 200, body: JSON.generate({tag:"bench-ruby-large-ok", + sqrt:sq.round(2), log:lg.round(2), trig:tr.round(4), + primes:primes.length, fib40:f40, fibs_sum:fibs.sum, + h256:h256[0,16], md5:md5[0,16], b64_len:b64.length, + mat_trace:mc.each_with_index.sum{|r,i|r[i]}, + words:pw.length, stats:pst, cipher:cipher, + lookup:lookup, csum:csum.round(6), elapsed:elapsed.round(5)}) } +end +RBEOF + +# ── PHP ─────────────────────────────────────────────────────────────────── +cat > "${TMP}/php_tiny.php" << 'PHPEOF' + "${TMP}/php_small.php" << 'PHPEOF' + "${TMP}/php_medium.php" << 'PHPEOF' +$arr[$j+1])[$arr[$j],$arr[$j+1]]=[$arr[$j+1],$arr[$j]];return $arr;} +function matMul($a,$b){$ra=count($a);$ca=count($a[0]);$cb=count($b[0]);$c=array_fill(0,$ra,array_fill(0,$cb,0));for($i=0;$i<$ra;$i++)for($j=0;$j<$cb;$j++)for($k=0;$k<$ca;$k++)$c[$i][$j]+=$a[$i][$k]*$b[$k][$j];return $c;} +function processWords($w){return array_values(array_map(fn($s)=>strrev(strtoupper($s)),array_filter($w,fn($s)=>strlen($s)>3)));} +function statsArr($arr){$n=count($arr);$m=array_sum($arr)/$n;$v=array_sum(array_map(fn($x)=>($x-$m)**2,$arr))/$n;return['n'=>$n,'mean'=>round($m,4),'std'=>round(sqrt($v),4)];} + +function main($event,$context){ + $t0=microtime(true); + $words=["alpha","beta","gamma","delta","epsilon","zeta","eta","theta","iota","kappa","lambda","mu"]; + $f=fib(35);$primes=sieve(500);$s=sqrtSum(1000); + $nums=range(50,1);$sorted=bubbleSort($nums); + $a=[[1,2,3],[4,5,6]];$b=[[7,8],[9,10],[11,12]];$c=matMul($a,$b); + $pw=processWords($words);$st=statsArr($primes); + $h=substr(hash('sha256','bench-medium-php'),0,16); + $elapsed=microtime(true)-$t0; + return json_encode(['tag'=>'bench-php-medium-ok','fib35'=>$f,'primes'=>count($primes), + 'sqrt'=>round($s,1),'sorted0'=>$sorted[0],'mat_c00'=>$c[0][0], + 'words'=>count($pw),'stats'=>$st,'hash'=>$h,'elapsed'=>round($elapsed,5)]); +} +PHPEOF + +cat > "${TMP}/php_large.php" << 'PHPEOF' +$arr[$j+1])[$arr[$j],$arr[$j+1]]=[$arr[$j+1],$arr[$j]];return $arr;} +function insertionSort($arr){for($i=1;$i=0&&$arr[$j]>$k){$arr[$j+1]=$arr[$j];$j--;}$arr[$j+1]=$k;}return $arr;} +function matMul($a,$b){$ra=count($a);$ca=count($a[0]);$cb=count($b[0]);$c=array_fill(0,$ra,array_fill(0,$cb,0));for($i=0;$i<$ra;$i++)for($j=0;$j<$cb;$j++)for($k=0;$k<$ca;$k++)$c[$i][$j]+=$a[$i][$k]*$b[$k][$j];return $c;} +function processWords($w){return array_values(array_map(fn($s)=>['original'=>$s,'upper'=>strtoupper($s),'reversed'=>strrev($s),'len'=>strlen($s),'vowels'=>preg_match_all('/[aeiou]/',$s)],array_filter($w,fn($s)=>strlen($s)>=3)));} +function statsArr($arr){$n=count($arr);$m=array_sum($arr)/$n;$v=array_sum(array_map(fn($x)=>($x-$m)**2,$arr))/$n;return['n'=>$n,'min'=>min($arr),'max'=>max($arr),'mean'=>round($m,4),'std'=>round(sqrt($v),4)];} +function caesar($t,$sh){return preg_replace_callback('/[a-zA-Z]/',fn($m)=>chr((ord($m[0])-(ctype_lower($m[0])?97:65)+$sh)%26+(ctype_lower($m[0])?97:65)),$t);} +function lookupOps($n){$sq=array_combine(range(0,199),array_map(fn($i)=>$i*$i,range(0,199)));$cu=array_combine(range(0,199),array_map(fn($i)=>$i**3,range(0,199)));$r=0;for($i=0;$i<$n;$i++)$r+=($sq[$i%200]??0)+($cu[$i%200]??0);return $r;} + +function main($event,$context){ + $words=$GLOBALS['WORDS']; + $t0=microtime(true); + $sq=sqrtSum(2000);$lg=logSum(1000);$tr=trigSum(500); + $primes=sieve(1000);$f40=fib(40);$fibs=fibList(20); + $nums=range(100,1); + $bs=bubbleSort(array_slice($nums,0,50));$ins=insertionSort(array_slice($nums,50,50)); + $h256=substr(hash('sha256','bench-large-php-2026'),0,16); + $md5=substr(hash('md5','bench-large-php-2026'),0,16); + $b64=base64_encode('hello from bench-php-large'); + $ma=array_map(fn($i)=>array_map(fn($j)=>$i+$j,range(0,3)),range(0,3)); + $mb=array_map(fn($i)=>array_map(fn($j)=>$i*$j+1,range(0,3)),range(0,3)); + $mc=matMul($ma,$mb); + $pw=processWords($words);$pst=statsArr(array_slice($primes,0,50)); + $cipher=caesar('hello bench world',13); + $lookup=lookupOps(500); + $csum=M_PI+M_E+(1+sqrt(5))/2+sqrt(2)+log(2); + $elapsed=microtime(true)-$t0; + return json_encode(['tag'=>'bench-php-large-ok', + 'sqrt'=>round($sq,2),'log'=>round($lg,2),'trig'=>round($tr,4), + 'primes'=>count($primes),'fib40'=>$f40,'fibs_sum'=>array_sum($fibs), + 'h256'=>$h256,'md5'=>$md5,'b64_len'=>strlen($b64), + 'mat_trace'=>array_sum(array_map(fn($i)=>$mc[$i][$i],range(0,3))), + 'words'=>count($pw),'stats'=>$pst,'cipher'=>$cipher, + 'lookup'=>$lookup,'csum'=>round($csum,6),'elapsed'=>round($elapsed,5)]); +} +PHPEOF + +# ═══════════════════════════════════════════════════════════════════════════ +# ГЕНЕРАЦИЯ ZIP-АРХИВОВ +# ═══════════════════════════════════════════════════════════════════════════ +log "" +log ">>> Генерация ZIP-архивов..." + +# Python archive: main.py + utils.py +python3 - > "${TMP}/arc-py.zip" << 'PYARCEOF' +import zipfile, io, sys +buf = io.BytesIO() +main_code = """import json +from utils import compute +def main(event, context): + result = compute() + return json.dumps({"tag": "bench-arc-py-ok", "result": result}), 200 +""" +utils_code = """import math, hashlib +def compute(): + primes = sieve(200) + sq = sum(math.sqrt(i) for i in range(1, 500)) + h = hashlib.sha256(b'bench-arc-python').hexdigest()[:12] + return {"primes": len(primes), "sqrt_sum": round(sq, 2), "hash": h} +def sieve(n): + s = [True]*(n+1); s[0]=s[1]=False + for i in range(2,int(math.sqrt(n))+1): + if s[i]: + for j in range(i*i,n+1,i): s[j]=False + return [i for i in range(n+1) if s[i]] +""" +with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf: + zf.writestr('main.py', main_code) + zf.writestr('utils.py', utils_code) +sys.stdout.buffer.write(buf.getvalue()) +PYARCEOF + +# Node.js archive: main.js + utils.js + package.json +python3 - > "${TMP}/arc-node.zip" << 'NODEARCEOF' +import zipfile, io, sys +buf = io.BytesIO() +main_code = """const { compute } = require('./utils'); +module.exports = async function(ctx) { + const result = compute(); + return { status: 200, body: JSON.stringify({ tag: "bench-arc-node-ok", result }) }; +}; +""" +utils_code = """const crypto = require('crypto'); +function sieve(n) { + const s=new Array(n+1).fill(true); s[0]=s[1]=false; + for(let i=2;i*i<=n;i++) if(s[i]) for(let j=i*i;j<=n;j+=i) s[j]=false; + return s.reduce((a,v,i)=>{if(v)a.push(i);return a;},[]); +} +function compute() { + const primes=sieve(200); + let sq=0; for(let i=1;i<=500;i++) sq+=Math.sqrt(i); + const h=crypto.createHash('sha256').update('bench-arc-node').digest('hex').slice(0,12); + return { primes: primes.length, sqrt_sum: +sq.toFixed(2), hash: h }; +} +module.exports = { compute }; +""" +pkg = '{"name":"bench-arc-node","version":"1.0.0","main":"main.js"}' +with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf: + zf.writestr('main.js', main_code) + zf.writestr('utils.js', utils_code) + zf.writestr('package.json', pkg) +sys.stdout.buffer.write(buf.getvalue()) +NODEARCEOF + +# Ruby archive: handler.rb + utils.rb +python3 - > "${TMP}/arc-ruby.zip" << 'RUBYARCEOF' +import zipfile, io, sys +buf = io.BytesIO() +main_code = """require 'json' +require_relative 'utils' +def handler(ctx) + result = compute() + { statusCode: 200, body: JSON.generate({ tag: "bench-arc-ruby-ok", result: result }) } +end +""" +utils_code = """require 'digest' +def sieve(n) + s=Array.new(n+1,true); s[0]=s[1]=false + (2..Math.sqrt(n).to_i).each{|i|(i*i..n).step(i){|j|s[j]=false} if s[i]} + (0..n).select{|i|s[i]} +end +def compute + primes=sieve(200) + sq=(1..500).sum{|i|Math.sqrt(i)} + h=Digest::SHA256.hexdigest('bench-arc-ruby')[0,12] + {primes: primes.length, sqrt_sum: sq.round(2), hash: h} +end +""" +with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf: + zf.writestr('handler.rb', main_code) + zf.writestr('utils.rb', utils_code) +sys.stdout.buffer.write(buf.getvalue()) +RUBYARCEOF + +# PHP archive: main.php + utils.php +python3 - > "${TMP}/arc-php.zip" << 'PHPARCEOF' +import zipfile, io, sys +buf = io.BytesIO() +main_code = """getBody()->write(json_encode(['tag' => 'bench-arc-php-ok', 'result' => $result])); +} +""" +utils_code = """count($primes),'sqrt_sum'=>round($sq,2),'hash'=>$h]; +} +""" +with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf: + zf.writestr('main.php', main_code) + zf.writestr('utils.php', utils_code) +sys.stdout.buffer.write(buf.getvalue()) +PHPARCEOF + +# ═══════════════════════════════════════════════════════════════════════════ +# ЗАПУСК БЕНЧМАРКОВ +# ═══════════════════════════════════════════════════════════════════════════ +log "" +log "────────────────────────────────────────────────────────────────────────" +log " LANG SIZE UPLOAD(ms) COLD(ms) HOT_AVG(ms) STATUS" +log "────────────────────────────────────────────────────────────────────────" + +declare -A LANG_SHORT=([python]=py [nodejs]=node [ruby]=ruby [php]=php) +declare -A CODE_EXT=([python]=py [nodejs]=js [ruby]=rb [php]=php) +declare -A CODE_PREFIX=([python]=py [nodejs]=node [ruby]=ruby [php]=php) + +for lang in python nodejs ruby php; do + short="${LANG_SHORT[$lang]}" + prefix="${CODE_PREFIX[$lang]}" + ext="${CODE_EXT[$lang]}" + log "" + log " === ${lang^^} ===" + for size in tiny small medium large; do + fn="bench-${short}-${size}-${RUNID}" + codefile="${TMP}/${prefix}_${size}.${ext}" + bench_code "$lang" "$size" "$fn" "$codefile" + done +done + +log "" +log "────────────────────────────────────────────────────────────────────────" +log " АРХИВЫ (ZIP с несколькими файлами)" +log "────────────────────────────────────────────────────────────────────────" + +bench_archive "py" "python" "bench-arc-py-${RUNID}" "${TMP}/arc-py.zip" "bench-arc-py-ok" +bench_archive "node" "nodejs" "bench-arc-node-${RUNID}" "${TMP}/arc-node.zip" "bench-arc-node-ok" +bench_archive "ruby" "ruby" "bench-arc-ruby-${RUNID}" "${TMP}/arc-ruby.zip" "bench-arc-ruby-ok" +bench_archive "php" "php" "bench-arc-php-${RUNID}" "${TMP}/arc-php.zip" "bench-arc-php-ok" + +# ═══════════════════════════════════════════════════════════════════════════ +# ИТОГОВАЯ ТАБЛИЦА +# ═══════════════════════════════════════════════════════════════════════════ +log "" +log "════════════════════════════════════════════════════════════════════════" +log " ИТОГОВАЯ ТАБЛИЦА" +log " LANG SIZE UPLOAD(ms) COLD(ms) HOT_AVG(ms) STATUS" +log "════════════════════════════════════════════════════════════════════════" +for row in "${SUMMARY_ROWS[@]}"; do + log " $row" +done +log "════════════════════════════════════════════════════════════════════════" +log " PASS: ${PASS_CNT} FAIL: ${FAIL_CNT} TOTAL: $((PASS_CNT+FAIL_CNT))" +log "════════════════════════════════════════════════════════════════════════" +log "" +log " Лог: ${LOGFILE}" +log " Готово: $(date)" diff --git a/console/deploy/console.yaml b/console/deploy/console.yaml index 22aefbd..673c18e 100644 --- a/console/deploy/console.yaml +++ b/console/deploy/console.yaml @@ -58,7 +58,7 @@ spec: serviceAccountName: fission-console containers: - name: console - image: naeel/fission-console:v1.3.95 + image: naeel/fission-console:v1.3.96 imagePullPolicy: Always ports: - containerPort: 8090 diff --git a/console/internal/cloud/tenant.go b/console/internal/cloud/tenant.go index 42ba5cc..aceda13 100644 --- a/console/internal/cloud/tenant.go +++ b/console/internal/cloud/tenant.go @@ -411,6 +411,11 @@ func (m *NSManager) reapExpiredFunctionsInNS(ctx context.Context, ns string, now if !strings.HasSuffix(pkgName, "-pkg") { continue } + // Пропускаем свежесозданные пакеты: Function CRD может ещё не успеть + // создаться после загрузки Package (race condition при быстром ExpiryReaper). + if age := time.Since(pkg.GetCreationTimestamp().Time); age < 2*time.Minute { + continue + } fnName := strings.TrimSuffix(pkgName, "-pkg") if _, exists := freshFunctionSet[fnName]; !exists { log.Printf("cloud.ExpiryReaper: deleting orphan package %s/%s", ns, pkgName) diff --git a/console/ui/index.html b/console/ui/index.html index 3760974..ab0dbea 100644 --- a/console/ui/index.html +++ b/console/ui/index.html @@ -103,7 +103,7 @@
NUBES
FISSION CONSOLE
-
v1.3.95
+
v1.3.96
@@ -626,7 +626,7 @@ psycopg2-binary" style="width:100%; box-sizing:border-box; font-family:monospace
- v1.3.95 + v1.3.96