{"id":191,"date":"2024-12-09T22:21:55","date_gmt":"2024-12-09T22:21:55","guid":{"rendered":"https:\/\/lectenna.com\/?page_id=191"},"modified":"2025-11-05T19:35:45","modified_gmt":"2025-11-05T19:35:45","slug":"letu","status":"publish","type":"page","link":"https:\/\/lectenna.com\/index.php\/letu\/","title":{"rendered":"\ud83d\udedc2.4 GHz"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>LEctenna Tester Active<\/title>\n    <style>\n        body {\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            height: 100vh;\n            margin: 0;\n            background-color: black;\n            color: #fff;\n            font-family: Arial, sans-serif;\n            position: relative;\n        }\n\n        h1, p {\n            text-align: center;\n        }\n\n        #statusMessage {\n            margin-top: 20px;\n            text-align: center;\n            font-size: 18px;\n            color: #00ff00;\n        }\n\n        #toggleButton {\n            position: absolute;\n            top: 1200px;\n            left: 10px;\n            padding: 10px 20px;\n            background-color: #fff;\n            color: #000;\n            border: none;\n            border-radius: 5px;\n            cursor: pointer;\n            font-size: 16px;\n            transition: background-color 0.3s ease, color 0.3s ease;\n        }\n\n        #toggleButton.dark {\n            background-color: #000;\n            color: #fff;\n        }\n    <\/style>\n<\/head>\n<body>\n    <button id=\"toggleButton\">Switch to Light Mode<\/button>\n\n    <div>\n        <h1>LEctenna Tester Active<\/h1>\n        <p>Uploading real data packets to a testing endpoint.<\/p>\n        <p id=\"statusMessage\">Status: Preparing&#8230;<\/p>\n    <\/div>\n\n    <script>\n        \/\/ -------- Konfiguration (ohne UI-\u00c4nderung) --------\n        const TARGET_URL = \"https:\/\/httpbin.org\/post\";   \/\/ Dummy Endpoint\n        const TOTAL_BYTES_PER_CYCLE = 500 * 1024 * 1024; \/\/ 500 MB je Zyklus\n        const MAX_CONCURRENCY = 4;                       \/\/ parallele Streams\n        const START_CHUNK = 4 * 1024 * 1024;             \/\/ 4 MB\n        const MIN_CHUNK = 256 * 1024;                    \/\/ 256 KB\n        const MAX_CHUNK = 16 * 1024 * 1024;              \/\/ 16 MB\n        const MAX_RETRIES = 6;                           \/\/ pro Chunk\n        const BASE_BACKOFF_MS = 500;                     \/\/ Start-Backoff\n        const HARD_TIMEOUT_MS = 20000;                   \/\/ 20 s Timeout je Chunk\n        const EMA_ALPHA = 0.2;                           \/\/ Gl\u00e4ttung Speed\n\n        const statusMessage = document.getElementById('statusMessage');\n        const toggleButton = document.getElementById('toggleButton');\n\n        \/\/ ---- UI Toggle (unver\u00e4ndert) ----\n        toggleButton.addEventListener('click', () => {\n            const isDark = document.body.style.backgroundColor === 'black';\n            document.body.style.backgroundColor = isDark ? 'white' : 'black';\n            document.body.style.color = isDark ? 'black' : 'white';\n            toggleButton.textContent = isDark ? 'Switch to Dark Mode' : 'Switch to Light Mode';\n            toggleButton.classList.toggle('dark', !isDark);\n        });\n\n        \/\/ ---- Utils ----\n        function sleep(ms){ return new Promise(r => setTimeout(r, ms)); }\n        function human(bytes){\n            if (bytes >= 1024**3) return (bytes\/1024**3).toFixed(2) + \" GB\";\n            if (bytes >= 1024**2) return (bytes\/1024**2).toFixed(2) + \" MB\";\n            if (bytes >= 1024)    return (bytes\/1024).toFixed(2) + \" KB\";\n            return bytes + \" B\";\n        }\n\n        \/\/ ---- Dynamik \/ globale Zust\u00e4nde ----\n        let chunkSize = START_CHUNK;\n        try {\n            const c = navigator.connection;\n            if (c && (c.effectiveType?.includes('5g') || (c.downlink||0) > 50)) {\n                chunkSize = 8 * 1024 * 1024; \/\/ 8 MB Start bei gutem 5G\n            }\n        } catch(_) {}\n\n        let emaMbps = 0;              \/\/ gegl\u00e4ttete Gesamt-Rate\n        let cycleBytesUploaded = 0;   \/\/ fortschritt im Zyklus\n        let cycleTargetBytes = TOTAL_BYTES_PER_CYCLE;\n        let errorsInWindow = 0;\n\n        \/\/ Gemeinsamer Puffer (wird je nach aktuellem chunkSize neu erzeugt)\n        function makePayload(bytes){ return new Uint8Array(bytes).fill(65); }\n\n        async function uploadChunk(bytes){\n            const controller = new AbortController();\n            const signal = controller.signal;\n            const payload = makePayload(bytes);\n\n            let attempt = 0;\n            while (true) {\n                const started = performance.now();\n                const timeout = setTimeout(() => controller.abort('timeout'), HARD_TIMEOUT_MS);\n                try {\n                    await fetch(TARGET_URL, {\n                        method: \"POST\",\n                        body: payload,\n                        headers: { \"Content-Type\": \"application\/octet-stream\", \"Cache-Control\":\"no-store\" },\n                        signal\n                    });\n                    clearTimeout(timeout);\n\n                    const dt = (performance.now() - started) \/ 1000; \/\/ s\n                    const mbps = (bytes * 8) \/ (dt * 1e6);\n                    emaMbps = emaMbps === 0 ? mbps : (EMA_ALPHA * mbps + (1 - EMA_ALPHA) * emaMbps);\n\n                    \/\/ Chunk-Tuning\n                    if (mbps > 80 && chunkSize < MAX_CHUNK) {\n                        chunkSize = Math.min(MAX_CHUNK, Math.round(chunkSize * 1.5));\n                    } else if ((mbps < 10 || dt > HARD_TIMEOUT_MS \/ 2000) && chunkSize > MIN_CHUNK) {\n                        chunkSize = Math.max(MIN_CHUNK, Math.round(chunkSize \/ 2));\n                    }\n\n                    return { mbps, dt };\n                } catch (err) {\n                    clearTimeout(timeout);\n                    attempt += 1;\n                    errorsInWindow += 1;\n\n                    if (attempt > MAX_RETRIES) {\n                        \/\/ konservativ verkleinern bei harten Problemen\n                        chunkSize = Math.max(MIN_CHUNK, Math.round(chunkSize \/ 2));\n                        throw err;\n                    }\n\n                    \/\/ Exponential Backoff + Jitter\n                    const jitter = Math.random() * 0.4 + 0.8; \/\/ 0.8x\u20131.2x\n                    const wait = Math.min(30000, BASE_BACKOFF_MS * (2 ** (attempt - 1))) * jitter;\n                    await sleep(wait);\n                }\n            }\n        }\n\n        \/\/ Reserviert den n\u00e4chsten Arbeitsumfang f\u00fcr einen Stream (Single-Thread JS => safe)\n        function reserveNextBytes(){\n            if (cycleBytesUploaded >= cycleTargetBytes) return 0;\n            const remaining = cycleTargetBytes - cycleBytesUploaded;\n            const take = Math.min(chunkSize, remaining);\n            cycleBytesUploaded += take;\n            return take;\n        }\n\n        async function streamWorker(id){\n            while (true) {\n                const bytes = reserveNextBytes();\n                if (bytes === 0) return; \/\/ Zyklus fertig\n                try {\n                    const { mbps } = await uploadChunk(bytes);\n                    \/\/ optional: pro-Stream-Log in Konsole\n                    \/\/ console.debug(`stream ${id}: ${human(bytes)} @ ${mbps.toFixed(1)} Mbps`);\n                } catch(_) {\n                    \/\/ Fehlermeldung kommt nur in Statuszeile (UI bleibt gleich)\n                    \/\/ kleine Pause, Bytes bleiben als \"verbraucht\" -> n\u00e4chste Runde schiebt weiter\n                    await sleep(1500);\n                }\n            }\n        }\n\n        function updateStatus(){\n            statusMessage.textContent =\n                `Status: Uploading\u2026 ${human(cycleBytesUploaded)}\/${human(cycleTargetBytes)} `\n                + `| ${emaMbps.toFixed(1)} Mbps avg | chunk ${human(chunkSize)} `\n                + `| streams ${MAX_CONCURRENCY}`;\n        }\n\n        async function uploadCycle(){\n            cycleBytesUploaded = 0;\n            cycleTargetBytes = TOTAL_BYTES_PER_CYCLE;\n            emaMbps = 0;\n            errorsInWindow = 0;\n\n            statusMessage.textContent = \"Status: Uploading...\";\n\n            \/\/ Starte parallele Streams\n            const workers = [];\n            for (let i = 0; i < MAX_CONCURRENCY; i++) {\n                workers.push(streamWorker(i+1));\n            }\n\n            \/\/ UI-Progress-Ticker\n            const uiTimer = setInterval(updateStatus, 300);\n\n            \/\/ Warten bis alle Streams fertig sind\n            await Promise.allSettled(workers);\n            clearInterval(uiTimer);\n            updateStatus();\n\n            statusMessage.textContent = \"Status: Preparing for next upload...\";\n            await sleep(20);\n        }\n\n        async function uploadLoop(){\n            while (true) {\n                try {\n                    await uploadCycle();\n                } catch (e) {\n                    statusMessage.textContent = \"Status: Netzproblem \u2013 retry\u2026\";\n                    await sleep(2000);\n                }\n            }\n        }\n\n        window.onload = uploadLoop;\n    <\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>LEctenna Tester Active Switch to Light Mode LEctenna Tester Active Uploading real data packets to a testing endpoint. Status: Preparing&#8230;<\/p>\n","protected":false},"author":1,"featured_media":294,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"footnotes":""},"class_list":["post-191","page","type-page","status-publish","has-post-thumbnail","hentry","entry"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/lectenna.com\/index.php\/wp-json\/wp\/v2\/pages\/191","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lectenna.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/lectenna.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/lectenna.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/lectenna.com\/index.php\/wp-json\/wp\/v2\/comments?post=191"}],"version-history":[{"count":75,"href":"https:\/\/lectenna.com\/index.php\/wp-json\/wp\/v2\/pages\/191\/revisions"}],"predecessor-version":[{"id":402,"href":"https:\/\/lectenna.com\/index.php\/wp-json\/wp\/v2\/pages\/191\/revisions\/402"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/lectenna.com\/index.php\/wp-json\/wp\/v2\/media\/294"}],"wp:attachment":[{"href":"https:\/\/lectenna.com\/index.php\/wp-json\/wp\/v2\/media?parent=191"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}