mirror of
https://github.com/codeflash-ai/codeflash-agent.git
synced 2026-05-04 18:25:19 +00:00
Refine engagement report and case study for executive review
- Hero metrics: -89% cost, -52% peak memory, flat scaling, -12.9% latency - Add lightspeed canvas animation via assets/lightspeed.js for Plotly Cloud - Add platform-libs CI/CD migration to timeline (Phase 1b) with PR links - Update next-engagement card with POC branch and PR references - Replace RSS with peak memory in user-facing copy - Add flat memory scaling to case study results table
This commit is contained in:
parent
380bd59503
commit
a4276d658a
4 changed files with 429 additions and 44 deletions
|
|
@ -22,7 +22,7 @@ Over 7 weeks, Codeflash profiled the pipeline end-to-end under production-equiva
|
|||
|
||||
The optimization touched **5 repositories** across the Unstructured ecosystem (core-product, unstructured, unstructured-inference, unstructured-od-models, github-workflows). Each round of optimization shifted the performance profile, revealing issues that had been masked by larger problems upstream:
|
||||
|
||||
**Memory: eliminating redundant model loading and per-request growth.** The largest single win came from making OCR worker allocation CPU-aware. Using `os.sched_getaffinity(0)` (which respects cgroup limits and taskset masks), the pipeline now detects when it's running on a single-CPU pod and skips the worker pool entirely — eliminating 4 idle workers that each loaded ~500 MB of duplicate ONNX models into private memory. Combined with model loading optimizations across the inference stack (replacing HuggingFace image processors with torchvision, reading ONNX metadata from session objects instead of full protobuf loads, and switching to jemalloc to reduce Python heap fragmentation), peak memory dropped from **4,651 MB to 2,227 MB** and per-request RSS growth fell from **24 MB to 17 MB** — eliminating the slow memory creep that eventually triggered OOM kills under sustained load.
|
||||
**Memory: eliminating redundant model loading and per-request growth.** The largest single win came from making OCR worker allocation CPU-aware. Using `os.sched_getaffinity(0)` (which respects cgroup limits and taskset masks), the pipeline now detects when it's running on a single-CPU pod and skips the worker pool entirely — eliminating 4 idle workers that each loaded ~500 MB of duplicate ONNX models into private memory. Combined with model loading optimizations across the inference stack (replacing HuggingFace image processors with torchvision, reading ONNX metadata from session objects instead of full protobuf loads, and switching to jemalloc to reduce Python heap fragmentation), peak memory dropped from **4,651 MB to 2,227 MB**, per-request RSS growth fell from **24 MB to 17 MB**, and memory is now **constant regardless of document count** — ~1,400 MB whether processing 10 or 400 pages, eliminating the slow memory creep that eventually triggered OOM kills under sustained load.
|
||||
|
||||
**Latency: removing unnecessary serialization and fixing algorithmic complexity.** PDF pages rendered by pdfium were being compressed to PNG for inter-process transfer — then decompressed on the other side. Since these images only exist in shared memory between processes and are never stored or transmitted, the compression was pure overhead: ~90ms per page, adding up across multi-page documents. A text extraction function (`_patch_current_chars_with_render_mode`) had **O(N²) complexity** — re-scanning the full character list on every patch operation, causing processing time to grow quadratically on text-heavy documents. Switching to uncompressed BMP, passing file paths directly to tesseract (instead of PIL → numpy → PIL → temp-file round-trips), and fixing the quadratic scan reduced end-to-end latency from **50.8s to 44.3s** on a 10-page production workload.
|
||||
|
||||
|
|
@ -36,6 +36,7 @@ Every change passed through Unstructured's existing CI pipeline and code review
|
|||
|---|---:|---:|---|
|
||||
| K8s pod memory allocation | 32 GB | 4 GB | **87.5% reduction** |
|
||||
| Peak memory per pod | 4,651 MB | 2,227 MB | **52% reduction** |
|
||||
| Memory scaling | ~1,400 MB at 10p | ~1,400 MB at 400p | **Flat** (constant regardless of document count) |
|
||||
| RSS growth per request | 24 MB | 17 MB | **29% reduction** (eliminated OOM creep) |
|
||||
| End-to-end latency (10p scan) | 50.8s | 44.3s | **12.9% faster** |
|
||||
| Text extraction scaling | O(N²) | O(N) | **Linear on long documents** |
|
||||
|
|
|
|||
118
reports/unstructured-security/assets/lightspeed.js
Normal file
118
reports/unstructured-security/assets/lightspeed.js
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
(function() {
|
||||
if (document.getElementById('lightspeed-canvas')) return;
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.id = 'lightspeed-canvas';
|
||||
canvas.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999;';
|
||||
document.body.insertBefore(canvas, document.body.firstChild);
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
var dpr = window.devicePixelRatio || 1;
|
||||
var W, H;
|
||||
|
||||
function resize() {
|
||||
W = window.innerWidth;
|
||||
H = window.innerHeight;
|
||||
canvas.width = W * dpr;
|
||||
canvas.height = H * dpr;
|
||||
canvas.style.width = W + 'px';
|
||||
canvas.style.height = H + 'px';
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
}
|
||||
resize();
|
||||
window.addEventListener('resize', resize);
|
||||
|
||||
var palette = [
|
||||
{r:70, g:90, b:130, w:5},
|
||||
{r:90, g:110, b:155, w:4},
|
||||
{r:110, g:135, b:180, w:3},
|
||||
{r:60, g:75, b:110, w:3},
|
||||
{r:130, g:155, b:200, w:2},
|
||||
{r:255, g:210, b:39, w:1},
|
||||
];
|
||||
var wc = [];
|
||||
for (var p = 0; p < palette.length; p++)
|
||||
for (var w = 0; w < palette[p].w; w++) wc.push(palette[p]);
|
||||
|
||||
var NUM = 80;
|
||||
var bars = [];
|
||||
|
||||
function makeBar(scatter) {
|
||||
var c = wc[Math.floor(Math.random() * wc.length)];
|
||||
var angleDeg = 8 + Math.random() * 14;
|
||||
var angle = angleDeg * Math.PI / 180;
|
||||
var len = 120 + Math.random() * 500;
|
||||
var thickness = 4 + Math.random() * 14;
|
||||
var speed = 0.15 + Math.random() * 0.6;
|
||||
var x = -len - Math.random() * 400;
|
||||
var y = Math.random() * H * 1.4 - H * 0.2;
|
||||
|
||||
if (scatter) {
|
||||
x = Math.random() * (W + len) - len * 0.5;
|
||||
y = Math.random() * H;
|
||||
}
|
||||
|
||||
return {
|
||||
x: x, y: y,
|
||||
angle: angle,
|
||||
speed: speed,
|
||||
len: len,
|
||||
thickness: thickness,
|
||||
color: c,
|
||||
alpha: 0.12 + Math.random() * 0.22,
|
||||
life: 0,
|
||||
maxLife: 800 + Math.random() * 2000
|
||||
};
|
||||
}
|
||||
|
||||
for (var i = 0; i < NUM; i++) bars.push(makeBar(true));
|
||||
|
||||
function draw() {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
|
||||
for (var i = 0; i < NUM; i++) {
|
||||
var b = bars[i];
|
||||
b.x += Math.cos(b.angle) * b.speed;
|
||||
b.y += Math.sin(b.angle) * b.speed;
|
||||
b.life++;
|
||||
|
||||
if (b.x > W + b.len * 2 || b.life > b.maxLife) {
|
||||
bars[i] = makeBar(false);
|
||||
b = bars[i];
|
||||
}
|
||||
|
||||
var lp = b.life / b.maxLife;
|
||||
var fade = lp < 0.15 ? lp / 0.15 :
|
||||
lp > 0.7 ? (1 - lp) / 0.3 : 1;
|
||||
var a = b.alpha * Math.max(0, fade);
|
||||
|
||||
var cos = Math.cos(b.angle);
|
||||
var sin = Math.sin(b.angle);
|
||||
var px = -sin * b.thickness * 0.5;
|
||||
var py = cos * b.thickness * 0.5;
|
||||
|
||||
var x0 = b.x;
|
||||
var y0 = b.y;
|
||||
var x1 = b.x + cos * b.len;
|
||||
var y1 = b.y + sin * b.len;
|
||||
|
||||
var grad = ctx.createLinearGradient(x0, y0, x1, y1);
|
||||
var cr = b.color.r, cg = b.color.g, cb = b.color.b;
|
||||
grad.addColorStop(0, 'rgba(' + cr + ',' + cg + ',' + cb + ',0)');
|
||||
grad.addColorStop(0.2, 'rgba(' + cr + ',' + cg + ',' + cb + ',' + a + ')');
|
||||
grad.addColorStop(0.8, 'rgba(' + cr + ',' + cg + ',' + cb + ',' + a + ')');
|
||||
grad.addColorStop(1, 'rgba(' + cr + ',' + cg + ',' + cb + ',0)');
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x0 + px, y0 + py);
|
||||
ctx.lineTo(x1 + px, y1 + py);
|
||||
ctx.lineTo(x1 - px, y1 - py);
|
||||
ctx.lineTo(x0 - px, y0 - py);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fill();
|
||||
}
|
||||
requestAnimationFrame(draw);
|
||||
}
|
||||
requestAnimationFrame(draw);
|
||||
})();
|
||||
118
reports/unstructured/assets/lightspeed.js
Normal file
118
reports/unstructured/assets/lightspeed.js
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
(function() {
|
||||
if (document.getElementById('lightspeed-canvas')) return;
|
||||
|
||||
var canvas = document.createElement('canvas');
|
||||
canvas.id = 'lightspeed-canvas';
|
||||
canvas.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999;';
|
||||
document.body.insertBefore(canvas, document.body.firstChild);
|
||||
|
||||
var ctx = canvas.getContext('2d');
|
||||
var dpr = window.devicePixelRatio || 1;
|
||||
var W, H;
|
||||
|
||||
function resize() {
|
||||
W = window.innerWidth;
|
||||
H = window.innerHeight;
|
||||
canvas.width = W * dpr;
|
||||
canvas.height = H * dpr;
|
||||
canvas.style.width = W + 'px';
|
||||
canvas.style.height = H + 'px';
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
}
|
||||
resize();
|
||||
window.addEventListener('resize', resize);
|
||||
|
||||
var palette = [
|
||||
{r:70, g:90, b:130, w:5},
|
||||
{r:90, g:110, b:155, w:4},
|
||||
{r:110, g:135, b:180, w:3},
|
||||
{r:60, g:75, b:110, w:3},
|
||||
{r:130, g:155, b:200, w:2},
|
||||
{r:255, g:210, b:39, w:1},
|
||||
];
|
||||
var wc = [];
|
||||
for (var p = 0; p < palette.length; p++)
|
||||
for (var w = 0; w < palette[p].w; w++) wc.push(palette[p]);
|
||||
|
||||
var NUM = 80;
|
||||
var bars = [];
|
||||
|
||||
function makeBar(scatter) {
|
||||
var c = wc[Math.floor(Math.random() * wc.length)];
|
||||
var angleDeg = 8 + Math.random() * 14;
|
||||
var angle = angleDeg * Math.PI / 180;
|
||||
var len = 120 + Math.random() * 500;
|
||||
var thickness = 4 + Math.random() * 14;
|
||||
var speed = 0.15 + Math.random() * 0.6;
|
||||
var x = -len - Math.random() * 400;
|
||||
var y = Math.random() * H * 1.4 - H * 0.2;
|
||||
|
||||
if (scatter) {
|
||||
x = Math.random() * (W + len) - len * 0.5;
|
||||
y = Math.random() * H;
|
||||
}
|
||||
|
||||
return {
|
||||
x: x, y: y,
|
||||
angle: angle,
|
||||
speed: speed,
|
||||
len: len,
|
||||
thickness: thickness,
|
||||
color: c,
|
||||
alpha: 0.12 + Math.random() * 0.22,
|
||||
life: 0,
|
||||
maxLife: 800 + Math.random() * 2000
|
||||
};
|
||||
}
|
||||
|
||||
for (var i = 0; i < NUM; i++) bars.push(makeBar(true));
|
||||
|
||||
function draw() {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
|
||||
for (var i = 0; i < NUM; i++) {
|
||||
var b = bars[i];
|
||||
b.x += Math.cos(b.angle) * b.speed;
|
||||
b.y += Math.sin(b.angle) * b.speed;
|
||||
b.life++;
|
||||
|
||||
if (b.x > W + b.len * 2 || b.life > b.maxLife) {
|
||||
bars[i] = makeBar(false);
|
||||
b = bars[i];
|
||||
}
|
||||
|
||||
var lp = b.life / b.maxLife;
|
||||
var fade = lp < 0.15 ? lp / 0.15 :
|
||||
lp > 0.7 ? (1 - lp) / 0.3 : 1;
|
||||
var a = b.alpha * Math.max(0, fade);
|
||||
|
||||
var cos = Math.cos(b.angle);
|
||||
var sin = Math.sin(b.angle);
|
||||
var px = -sin * b.thickness * 0.5;
|
||||
var py = cos * b.thickness * 0.5;
|
||||
|
||||
var x0 = b.x;
|
||||
var y0 = b.y;
|
||||
var x1 = b.x + cos * b.len;
|
||||
var y1 = b.y + sin * b.len;
|
||||
|
||||
var grad = ctx.createLinearGradient(x0, y0, x1, y1);
|
||||
var cr = b.color.r, cg = b.color.g, cb = b.color.b;
|
||||
grad.addColorStop(0, 'rgba(' + cr + ',' + cg + ',' + cb + ',0)');
|
||||
grad.addColorStop(0.2, 'rgba(' + cr + ',' + cg + ',' + cb + ',' + a + ')');
|
||||
grad.addColorStop(0.8, 'rgba(' + cr + ',' + cg + ',' + cb + ',' + a + ')');
|
||||
grad.addColorStop(1, 'rgba(' + cr + ',' + cg + ',' + cb + ',0)');
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x0 + px, y0 + py);
|
||||
ctx.lineTo(x1 + px, y1 + py);
|
||||
ctx.lineTo(x1 - px, y1 - py);
|
||||
ctx.lineTo(x0 - px, y0 - py);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fill();
|
||||
}
|
||||
requestAnimationFrame(draw);
|
||||
}
|
||||
requestAnimationFrame(draw);
|
||||
})();
|
||||
|
|
@ -773,7 +773,7 @@ def build_team_view():
|
|||
# ── What Changed ──────────────────────────────────────────
|
||||
section(
|
||||
"What Changed: Memory",
|
||||
"Three root causes fixed, per-request RSS creep reduced (24 MB \u2192 17 MB/req), one allocator optimization added.",
|
||||
"Three root causes fixed, per-request memory creep reduced (24 MB \u2192 17 MB/req), one allocator optimization added.",
|
||||
),
|
||||
html.Div(
|
||||
[
|
||||
|
|
@ -1424,19 +1424,52 @@ def _method_row(label, text):
|
|||
|
||||
|
||||
def _above_fold_content(negative_margin=False):
|
||||
"""Infrastructure Cost Impact + Broader Context + Hero Metrics.
|
||||
"""Hero Metrics + Infrastructure Cost Impact + Broader Context.
|
||||
|
||||
Used above the tab toggle on the main page and at the top of /jpc.
|
||||
"""
|
||||
top_margin = "-40px" if negative_margin else "0"
|
||||
return [
|
||||
html.Div(
|
||||
style={
|
||||
"display": "flex",
|
||||
"gap": "20px",
|
||||
"flexWrap": "wrap",
|
||||
"marginTop": top_margin,
|
||||
"position": "relative",
|
||||
"zIndex": "1",
|
||||
},
|
||||
children=[
|
||||
hero_metric(
|
||||
"-89%",
|
||||
"Core-Product Cost",
|
||||
"$10,000/mo \u2192 ~$1,100/mo",
|
||||
ACCENT,
|
||||
),
|
||||
hero_metric(
|
||||
"-52%",
|
||||
"Peak Memory Usage",
|
||||
"4,651 MB \u2192 2,227 MB per pod",
|
||||
GREEN,
|
||||
),
|
||||
hero_metric(
|
||||
"Flat",
|
||||
"Memory Scaling",
|
||||
"Constant peak memory regardless of document count",
|
||||
GREEN,
|
||||
),
|
||||
hero_metric(
|
||||
"-12.9%",
|
||||
"Latency",
|
||||
"50.8s \u2192 44.3s via production FastAPI path",
|
||||
ACCENT,
|
||||
),
|
||||
],
|
||||
),
|
||||
section(
|
||||
"Infrastructure Cost Impact",
|
||||
"AKS node packing analysis based on current production topology.",
|
||||
),
|
||||
style={"marginTop": top_margin, "position": "relative", "zIndex": "1"},
|
||||
),
|
||||
card(
|
||||
[
|
||||
html.P(
|
||||
|
|
@ -1744,34 +1777,6 @@ def _above_fold_content(negative_margin=False):
|
|||
"borderLeft": f"4px solid {ACCENT}",
|
||||
},
|
||||
),
|
||||
html.Div(
|
||||
style={
|
||||
"display": "flex",
|
||||
"gap": "20px",
|
||||
"flexWrap": "wrap",
|
||||
"marginTop": "24px",
|
||||
},
|
||||
children=[
|
||||
hero_metric(
|
||||
"32 \u2192 4 GB",
|
||||
"K8s Pod Memory",
|
||||
"87.5% infrastructure reduction",
|
||||
GREEN,
|
||||
),
|
||||
hero_metric(
|
||||
"-52%",
|
||||
"Peak Memory Usage",
|
||||
"4,651 MB \u2192 2,227 MB per pod",
|
||||
GREEN,
|
||||
),
|
||||
hero_metric(
|
||||
"-12.9%",
|
||||
"Latency",
|
||||
"50.8s \u2192 44.3s via production FastAPI path",
|
||||
ACCENT,
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -1817,7 +1822,7 @@ def _jpc_content():
|
|||
html.P(
|
||||
"Over 7 weeks, we profiled the pipeline end-to-end — and each optimization "
|
||||
"peeled back a layer, revealing issues that had been masked by larger problems "
|
||||
"upstream. Fixing the worker pool exposed per-request RSS creep (24 MB/req from "
|
||||
"upstream. Fixing the worker pool exposed per-request memory creep (24 MB/req from "
|
||||
"PIL image churn). Reducing memory noise surfaced an O(N\u00b2) text extraction "
|
||||
"bottleneck and unnecessary PNG serialization between processes. These weren't "
|
||||
"problems anyone had reason to look for — they only became visible as earlier "
|
||||
|
|
@ -2139,14 +2144,20 @@ def _jpc_content():
|
|||
_next_card(
|
||||
"2",
|
||||
"Developer Experience & CI/CD",
|
||||
"Simplify the dependency installation process (should be a single "
|
||||
"lockfile, not multiple steps) and modernize CI pipelines. Our "
|
||||
"platform-libs POC already demonstrates the path forward.",
|
||||
"Collapse the complex GHA workflow permutations into a streamlined "
|
||||
"uv workspace \u2014 same GitHub Actions, same repo structure, just fewer "
|
||||
"moving parts. We've already delivered the foundation: uv workspace "
|
||||
"POC live in the ci-unified-workflows branch and platform-libs#667.",
|
||||
notes=[
|
||||
"POC complete on platform-libs (28 packages \u2192 uv workspace)",
|
||||
"Wall time: ~4 min \u2192 ~1 min, billed minutes -85%",
|
||||
"Single lockfile replaces fragmented install steps",
|
||||
"Faster CI = less context switching, quicker regression feedback",
|
||||
[
|
||||
"POC live in ",
|
||||
html.A("ci-unified-workflows", href="https://github.com/Unstructured-IO/github-workflows/tree/ci-unified-workflows", target="_blank", style={"color": BLUE, "textDecoration": "none"}),
|
||||
" branch and ",
|
||||
html.A("platform-libs#667", href="https://github.com/Unstructured-IO/platform-libs/pull/667", target="_blank", style={"color": BLUE, "textDecoration": "none"}),
|
||||
],
|
||||
"Eliminates per-package workflow permutations \u2014 one matrix, one lockfile",
|
||||
"No migration off GitHub Actions \u2014 same CI/CD platform, simplified configuration",
|
||||
"Same approach ready for core-product workspace migration",
|
||||
],
|
||||
),
|
||||
_next_card(
|
||||
|
|
@ -2707,15 +2718,137 @@ app.index_string = """<!DOCTYPE html>
|
|||
.dash-spreadsheet a { color: #60a5fa !important; text-decoration: none !important; }
|
||||
.dash-table-container a:hover,
|
||||
.dash-spreadsheet a:hover { text-decoration: underline !important; }
|
||||
#lightspeed-canvas {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="lightspeed-canvas"></canvas>
|
||||
{%app_entry%}
|
||||
<footer>
|
||||
{%config%}
|
||||
{%scripts%}
|
||||
{%renderer%}
|
||||
</footer>
|
||||
<script>
|
||||
(function() {
|
||||
var canvas = document.getElementById('lightspeed-canvas');
|
||||
var ctx = canvas.getContext('2d');
|
||||
var dpr = window.devicePixelRatio || 1;
|
||||
var W, H;
|
||||
|
||||
function resize() {
|
||||
W = window.innerWidth;
|
||||
H = window.innerHeight;
|
||||
canvas.width = W * dpr;
|
||||
canvas.height = H * dpr;
|
||||
canvas.style.width = W + 'px';
|
||||
canvas.style.height = H + 'px';
|
||||
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
||||
}
|
||||
resize();
|
||||
window.addEventListener('resize', resize);
|
||||
|
||||
var palette = [
|
||||
{r:70, g:90, b:130, w:5},
|
||||
{r:90, g:110, b:155, w:4},
|
||||
{r:110, g:135, b:180, w:3},
|
||||
{r:60, g:75, b:110, w:3},
|
||||
{r:130, g:155, b:200, w:2},
|
||||
{r:255, g:210, b:39, w:1},
|
||||
];
|
||||
var wc = [];
|
||||
for (var p = 0; p < palette.length; p++)
|
||||
for (var w = 0; w < palette[p].w; w++) wc.push(palette[p]);
|
||||
|
||||
var NUM = 80;
|
||||
var bars = [];
|
||||
|
||||
function makeBar(scatter) {
|
||||
var c = wc[Math.floor(Math.random() * wc.length)];
|
||||
var angleDeg = 8 + Math.random() * 14;
|
||||
var angle = angleDeg * Math.PI / 180;
|
||||
var len = 120 + Math.random() * 500;
|
||||
var thickness = 4 + Math.random() * 14;
|
||||
var speed = 0.15 + Math.random() * 0.6;
|
||||
var x = -len - Math.random() * 400;
|
||||
var y = Math.random() * H * 1.4 - H * 0.2;
|
||||
|
||||
if (scatter) {
|
||||
x = Math.random() * (W + len) - len * 0.5;
|
||||
y = Math.random() * H;
|
||||
}
|
||||
|
||||
return {
|
||||
x: x, y: y,
|
||||
angle: angle,
|
||||
speed: speed,
|
||||
len: len,
|
||||
thickness: thickness,
|
||||
color: c,
|
||||
alpha: 0.12 + Math.random() * 0.22,
|
||||
life: 0,
|
||||
maxLife: 800 + Math.random() * 2000
|
||||
};
|
||||
}
|
||||
|
||||
for (var i = 0; i < NUM; i++) bars.push(makeBar(true));
|
||||
|
||||
function draw() {
|
||||
ctx.clearRect(0, 0, W, H);
|
||||
|
||||
for (var i = 0; i < NUM; i++) {
|
||||
var b = bars[i];
|
||||
b.x += Math.cos(b.angle) * b.speed;
|
||||
b.y += Math.sin(b.angle) * b.speed;
|
||||
b.life++;
|
||||
|
||||
if (b.x > W + b.len * 2 || b.life > b.maxLife) {
|
||||
bars[i] = makeBar(false);
|
||||
b = bars[i];
|
||||
}
|
||||
|
||||
var lp = b.life / b.maxLife;
|
||||
var fade = lp < 0.15 ? lp / 0.15 :
|
||||
lp > 0.7 ? (1 - lp) / 0.3 : 1;
|
||||
var a = b.alpha * Math.max(0, fade);
|
||||
|
||||
var cos = Math.cos(b.angle);
|
||||
var sin = Math.sin(b.angle);
|
||||
var px = -sin * b.thickness * 0.5;
|
||||
var py = cos * b.thickness * 0.5;
|
||||
|
||||
var x0 = b.x;
|
||||
var y0 = b.y;
|
||||
var x1 = b.x + cos * b.len;
|
||||
var y1 = b.y + sin * b.len;
|
||||
|
||||
var grad = ctx.createLinearGradient(x0, y0, x1, y1);
|
||||
var cr = b.color.r, cg = b.color.g, cb = b.color.b;
|
||||
grad.addColorStop(0, 'rgba(' + cr + ',' + cg + ',' + cb + ',0)');
|
||||
grad.addColorStop(0.2, 'rgba(' + cr + ',' + cg + ',' + cb + ',' + a + ')');
|
||||
grad.addColorStop(0.8, 'rgba(' + cr + ',' + cg + ',' + cb + ',' + a + ')');
|
||||
grad.addColorStop(1, 'rgba(' + cr + ',' + cg + ',' + cb + ',0)');
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x0 + px, y0 + py);
|
||||
ctx.lineTo(x1 + px, y1 + py);
|
||||
ctx.lineTo(x1 - px, y1 - py);
|
||||
ctx.lineTo(x0 - px, y0 - py);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = grad;
|
||||
ctx.fill();
|
||||
}
|
||||
requestAnimationFrame(draw);
|
||||
}
|
||||
requestAnimationFrame(draw);
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
|
|
@ -2957,6 +3090,21 @@ def _timeline_content():
|
|||
],
|
||||
color=GREEN,
|
||||
),
|
||||
_tl_node(
|
||||
"1b", "Platform-Libs CI/CD Migration",
|
||||
"Apr 9 \u2192 Apr 14", "1 week", "Ready to Start",
|
||||
deliverables=[
|
||||
[
|
||||
"POC live in ",
|
||||
html.A("ci-unified-workflows", href="https://github.com/Unstructured-IO/github-workflows/tree/ci-unified-workflows", target="_blank", style={"color": BLUE, "textDecoration": "none"}),
|
||||
" branch and ",
|
||||
html.A("platform-libs#667", href="https://github.com/Unstructured-IO/platform-libs/pull/667", target="_blank", style={"color": BLUE, "textDecoration": "none"}),
|
||||
],
|
||||
"CI runners: ~189 \u2192 ~27 per PR (\u221285% billed minutes)",
|
||||
"Same GitHub Actions \u2014 fewer workflow permutations, not a platform migration",
|
||||
],
|
||||
color=GREEN,
|
||||
),
|
||||
_tl_gap("1 week buffer"),
|
||||
_tl_node(
|
||||
"2", "Developer Experience & CI/CD",
|
||||
|
|
|
|||
Loading…
Reference in a new issue