NDT7 single-stream + optional multi-stream peak. Includes loaded latency, VPN grading, use-case readiness, ISP contacts, and exportable logs. October 2025
This mirrors speed.measurementlab.net methodology. For best fidelity: wired Ethernet, VPN off, pause sync tools, close heavy tabs.
Configure your CDN base URL below. This test uses parallel Range GETs with moving-window throughput, similar to consumer “peak” tests.
Expected objects at: {base}/obj_16.bin, obj_64.bin, obj_256.bin, obj_1024.bin. Required headers:
Access-Control-Allow-Origin: *, Accept-Ranges: bytes, Cache-Control: public, max-age=31536000, immutable.
| Scenario | Recommended | Based on your results |
|---|---|---|
| 4K Streaming | ≥50 Mbps down, bloat <50 ms | — |
| HD Video Calls | ≥10 Mbps up/down, bloat <30 ms | — |
| Online Gaming | Idle RTT <40 ms, bloat <20 ms | — |
| Cloud Gaming | ≥100 Mbps down, idle <30 ms | — |
| Large File Transfers | ≥300 Mbps up/down | — |
| Remote Work / VDI | ≥25 Mbps down, bloat <40 ms | — |
Transfer time at 1 Gbps (ideal): 10 MB ≈ 0.1 s, 25 MB ≈ 0.2 s, 50 MB ≈ 0.4 s. Real paths add protocol / queueing overhead.
// Elements const fields = { ndtStatus: $('#ndtStatus'), ndtDown: $('#ndtDown'), ndtUp: $('#ndtUp'), ndtDLLoaded: $('#ndtDLLoaded'), ndtULLoaded: $('#ndtULLoaded'), ndtDLJitter: $('#ndtDLJitter'), ndtULJitter: $('#ndtULJitter'), idleRtt: $('#idleRtt'), bloatDown: $('#bloatDown'), bloatUp: $('#bloatUp'), peakStatus: $('#peakStatus'), peakMbps: $('#peakMbps'), peakSustain: $('#peakSustain'), peakBytes: $('#peakBytes'), peakDur: $('#peakDur'), peakMeter: $('#peakMeter'), vpnThroughput: $('#vpnThroughput'), vpnGrade: $('#vpnGrade'), vpnMeter: $('#vpnMeter'), vpnNote: $('#vpnNote'), ispName: $('#ispName'), wanIp: $('#wanIp') }; const ispBox = $('#ispContacts'); const tipsEl = $('#tips');
// Controls const btnStartNDT = $('#btnStartNDT'); const btnStartNDTLong = $('#btnStartNDTLong'); const btnCancelNDT = $('#btnCancelNDT'); const btnStartPeak = $('#btnStartPeak'); const btnCancelPeak = $('#btnCancelPeak'); const btnCopyLog = $('#btnCopyLog'); const btnExport = $('#btnExport'); const btnClearLog = $('#btnClearLog');
const peakStreamsSel = $('#peakStreams'); const peakObjectSel = $('#peakObject'); const cdnBaseInput = $('#cdnBase'); const vpnPresetSel = $('#vpnPreset'); const ispSelect = $('#ispSelect');
// ISP directory (US-centric; extend as needed) const ISP_BOOK = { verizon: {name:'Verizon Fios', billing:'1-800-VERIZON (1-800-837-4966)', support:'1-800-VERIZON (1-800-837-4966)'}, optimum: {name:'Optimum (Altice)', billing:'1-866-200-7273', support:'1-888-276-5255'}, xfinity: {name:'Xfinity', billing:'1-800-934-6489', support:'1-800-934-6489'}, spectrum: {name:'Spectrum', billing:'1-855-707-7328', support:'1-833-267-6094'}, att: {name:'AT&T Fiber', billing:'1-800-288-2020', support:'1-800-288-2020'}, other: {name:'Other / Unknown', billing:'Check your statement', support:'Check your statement'} }; function renderISPBox(key){ const rec = ISP_BOOK[key]; if(!rec){ ispBox.innerHTML=''; return; } ispBox.innerHTML = `
`; } ispSelect.addEventListener('change', e=> renderISPBox(e.target.value||''));
// Detect IP/ISP (best-effort) (async function detect(){ try{ const r = await fetch('https://api.ipify.org?format=json',{cache:'no-store'}); const ip = (await r.json()).ip; fields.wanIp.textContent = ip; log('Public IP: '+ip); }catch(e){} try{ const r2 = await fetch('https://ipinfo.io/json?token=',{cache:'no-store'}); if(r2.ok){ const j=await r2.json(); fields.ispName.textContent = j.org || (j.asn && j.asn.name) || 'Unknown'; const n=(fields.ispName.textContent||'').toLowerCase(); if(n.includes('verizon')) ispSelect.value='verizon'; else if(n.includes('altice')||n.includes('optimum')) ispSelect.value='optimum'; else if(n.includes('comcast')||n.includes('xfinity')) ispSelect.value='xfinity'; else if(n.includes('charter')||n.includes('spectrum')) ispSelect.value='spectrum'; else if(n.includes('at&t')||n.includes('att')) ispSelect.value='att'; renderISPBox(ispSelect.value||''); log('ISP: '+fields.ispName.textContent); } else { fields.ispName.textContent='Unknown'; } }catch(e){ fields.ispName.textContent='Unknown'; } })();
// Helpers const fmtMbps = v => (v==null||isNaN(v))?'—':`${v.toFixed(1)} Mbps`; const fmtMs = v => (v==null||isNaN(v))?'—':`${Math.round(v)} ms`; function classifyBloat(ms){ if(ms==null) return {cls:'',label:'—'}; if(ms=600) return {g:'A (Excellent)',pct:95,note:'Headroom for multi-user remote work & large transfers.'}; if(tp>=300) return {g:'B (Strong)',pct:75,note:'Smooth HD calls, 4K streaming, big syncs.'}; if(tp>=100) return {g:'C (Adequate)',pct:50,note:'Most tasks fine; large uploads slower.'}; if(tp>=50) return {g:'D (Limited)',pct:25,note:'OK for mail/Docs; media moves slowly.'}; return {g:'E (Poor)',pct:10,note:'Expect lag; check Wi-Fi/CPE/ISP congestion.'}; }
// Global state let ndtAbort = null, peakAbort=false; const state = { idleRtt:null, ndt:{dl:{mbps:null,rtts:[]}, ul:{mbps:null,rtts:[]}}, peak:{bpsInstant:0,bpsPeak:0,bpsWin:[],bytes:0,start:0,end:0} };
// Idle RTT probe async function probeIdle(){ try{ const t0=performance.now(); await fetch('https://www.gstatic.com/generate_204',{mode:'no-cors',cache:'no-store'}); state.idleRtt=Math.max(1,performance.now()-t0); fields.idleRtt.textContent = fmtMs(state.idleRtt); log('Idle RTT probe: '+Math.round(state.idleRtt)+' ms'); }catch(e){ state.idleRtt=null; fields.idleRtt.textContent='—'; } }
// NDT7 (single-stream) — balanced (~20s) and long (~40–60s) async function runNDT(durationSec){ if(!window.ndt7){ fields.ndtStatus.textContent='Error: NDT7 not loaded'; log('NDT7 script missing'); return; } await probeIdle(); btnStartNDT.disabled=true; btnStartNDTLong.disabled=true; btnCancelNDT.disabled=false; fields.ndtStatus.textContent='Running…'; state.ndt = {dl:{mbps:null,rtts:[]}, ul:{mbps:null,rtts:[]}}; const workerRoot='https://unpkg.com/@m-lab/ndt7/dist'; const opts={userAcceptedDataPolicy:true,downloadworkerfile:`${workerRoot}/ndt7-download-worker.min.js`,uploadworkerfile:`${workerRoot}/ndt7-upload-worker.min.js`,signal:undefined}; const ctrl=new AbortController(); ndtAbort=ctrl; opts.signal=ctrl.signal; const cutoff=performance.now()+durationSec*1000;
// Download log('NDT7 download start'); try{ await new Promise((resolve,reject)=>{ let last=0; ndt7.download({ ...opts, onprogress:m=>{ if(performance.now()>cutoff) return; const mbps = m.Mbps ?? ((m.NumBytes*8)/(m.ElapsedTime/1e9)/1e6); if(mbps!=null){ state.ndt.dl.mbps=mbps; fields.ndtDown.textContent=fmtMbps(mbps); } if(m.TCPInfo && m.TCPInfo.MinRTT){ state.ndt.dl.rtts.push(m.TCPInfo.MinRTT*1000); } if(performance.now()-last>600){ log(`DL ~ ${fmtMbps(state.ndt.dl.mbps)}`); last=performance.now(); } }, oncomplete:()=>resolve(), onerror:e=>reject(e) }); }); }catch(e){ log('NDT7 download error'); }
// Upload log('NDT7 upload start'); try{ await new Promise((resolve,reject)=>{ let last=0; ndt7.upload({ ...opts, onprogress:m=>{ if(performance.now()>cutoff) return; const mbps = m.Mbps ?? ((m.NumBytes*8)/(m.ElapsedTime/1e9)/1e6); if(mbps!=null){ state.ndt.ul.mbps=mbps; fields.ndtUp.textContent=fmtMbps(mbps); } if(m.TCPInfo && m.TCPInfo.MinRTT){ state.ndt.ul.rtts.push(m.TCPInfo.MinRTT*1000); } if(performance.now()-last>600){ log(`UL ~ ${fmtMbps(state.ndt.ul.mbps)}`); last=performance.now(); } }, oncomplete:()=>resolve(), onerror:e=>reject(e) }); }); }catch(e){ log('NDT7 upload error'); }
// Summaries function pct(arr,p){ if(!arr.length) return null; const s=arr.slice().sort((a,b)=>a-b); return s[Math.min(s.length-1,Math.floor(s.length*p))]; } const p90DL=pct(state.ndt.dl.rtts,.90), p10DL=pct(state.ndt.dl.rtts,.10); const p90UL=pct(state.ndt.ul.rtts,.90), p10UL=pct(state.ndt.ul.rtts,.10); fields.ndtDLLoaded.textContent=fmtMs(p90DL); fields.ndtULLoaded.textContent=fmtMs(p90UL); fields.ndtDLJitter.textContent=fmtMs(p90DL!=null&&p10DL!=null?Math.max(0,p90DL-p10DL):null); fields.ndtULJitter.textContent=fmtMs(p90UL!=null&&p10UL!=null?Math.max(0,p90UL-p10UL):null);
const bd=(p90DL!=null && state.idleRtt!=null)?(p90DL-state.idleRtt):null; const bu=(p90UL!=null && state.idleRtt!=null)?(p90UL-state.idleRtt):null; const BDL=classifyBloat(bd), BUL=classifyBloat(bu); fields.bloatDown.innerHTML=`${BDL.label}`; fields.bloatUp.innerHTML =`${BUL.label}`;
fields.ndtStatus.textContent='Finished'; btnStartNDT.disabled=false; btnStartNDTLong.disabled=false; btnCancelNDT.disabled=true; ndtAbort=null;
computeVPN(); evalUseCases(); buildTips(); log('NDT7 finished.'); }
// Multi-stream peak (parallel HTTP Range) async function runPeak(){ const base=(cdnBaseInput.value||'').replace(/\/+$/,''); if(!base){ fields.peakStatus.textContent='Set CDN base URL first'; log('Peak test blocked: CDN base missing'); return; } const objMb=parseInt(peakObjectSel.value,10); const streams=parseInt(peakStreamsSel.value,10); const url=`${base}/obj_${objMb}.bin`; fields.peakStatus.textContent='Running…'; btnStartPeak.disabled=true; btnCancelPeak.disabled=false; state.peak={bpsInstant:0,bpsPeak:0,bpsWin:[],bytes:0,start:performance.now(),end:0}; peakAbort=false;
// Sliding window for last 5s const winSamples=[]; function updateMeters(){ const mbpsPeak=state.peak.bpsPeak/1e6; const mbpsWin=(state.peak.bpsInstant)/1e6; fields.peakMbps.textContent = isFinite(mbpsPeak)? `${mbpsPeak.toFixed(1)} Mbps`:'—'; fields.peakSustain.textContent = isFinite(mbpsWin)? `${mbpsWin.toFixed(1)} Mbps`:'—'; fields.peakBytes.textContent = `${(state.peak.bytes/1e6).toFixed(1)} MB`; const dur = (state.peak.end?state.peak.end:performance.now())-state.peak.start; fields.peakDur.textContent = `${(dur/1000).toFixed(1)} s`; const pct = Math.min(100, (mbpsWin/1000)*100); // scale meter assuming 1 Gbps = 100% fields.peakMeter.style.width = `${Math.max(0,Math.min(100,pct))}%`; }
// Worker: repeatedly range-GET random slices async function streamWorker(){ const CHUNK=15000){ winSamples.shift(); } const winBytes = winSamples.reduce((a,s)=>a+s.bytes,0); state.peak.bpsInstant = (winBytes*8)/5; const totalDur=(nowt-state.peak.start)/1000; const instBps = (buf.byteLength*8)/((CHUNK/1e6)/1000); // rough; we rely on window instead state.peak.bpsPeak = Math.max(state.peak.bpsPeak, state.peak.bpsInstant); updateMeters(); offset += CHUNK; if(totalDur>30) break; // ~30s target }catch(e){ throw e; } } }
// Launch N workers try{ const workers = Array.from({length:streams},()=>streamWorker()); await Promise.all(workers); state.peak.end=performance.now(); updateMeters(); fields.peakStatus.textContent='Finished'; log('Peak test finished.'); }catch(e){ fields.peakStatus.textContent='Error (check CORS/Range)'; log('Peak test error: '+(e?.message||e)); }finally{ btnStartPeak.disabled=false; btnCancelPeak.disabled=true; peakAbort=false; } }
// VPN estimate & use-cases function computeVPN(){ const eff = (parseInt(vpnPresetSel.value,10)||70)/100; const d = state.ndt.dl.mbps||0, u=state.ndt.ul.mbps||0; const est = Math.min(d,u)*eff; if(!est){ fields.vpnThroughput.textContent='—'; fields.vpnGrade.textContent='—'; fields.vpnMeter.style.width='0%'; fields.vpnNote.textContent='Run tests to estimate.'; return; } fields.vpnThroughput.textContent = fmtMbps(est); const g=gradeVPN(est); fields.vpnGrade.textContent = g.g; fields.vpnMeter.style.width = Math.min(100,g.pct)+'%'; fields.vpnNote.textContent = g.note; } vpnPresetSel.addEventListener('change', computeVPN);
function evalUseCases(){ const d = state.ndt.dl.mbps||0, u=state.ndt.ul.mbps||0; const idle = state.idleRtt||999; const p90DL = fields.ndtDLLoaded.textContent.includes('ms') ? parseInt(fields.ndtDLLoaded.textContent) : 999; const p90UL = fields.ndtULLoaded.textContent.includes('ms') ? parseInt(fields.ndtULLoaded.textContent) : 999; const bD = (state.idleRtt!=null && !isNaN(p90DL)) ? (p90DL - state.idleRtt) : 999; const bU = (state.idleRtt!=null && !isNaN(p90UL)) ? (p90UL - state.idleRtt) : 999;
$('#uc4k').innerHTML = (d>=50 && bD${d`; $('#ucHDVC').innerHTML = (d>=10 && u>=10 && Math.max(bD,bU)Check UL/latency`; $('#ucGaming').innerHTML = (idleLatency/Bloat`; $('#ucCloud').innerHTML = (d>=100 && idleCheck DL/RTT`; $('#ucFile').innerHTML = (d>=300 && u>=300) ? '✅ Fast' : `Throughput limited`; $('#ucVDI').innerHTML = (d>=25 && Math.max(bD,bU)Latency sensitivity`; }
function buildTips(){ const tips=[]; const d=state.ndt.dl.mbps||0, u=state.ndt.ul.mbps||0; const idle=state.idleRtt; const p90DL=fields.ndtDLLoaded.textContent.includes('ms')?parseInt(fields.ndtDLLoaded.textContent):null; const p90UL=fields.ndtULLoaded.textContent.includes('ms')?parseInt(fields.ndtULLoaded.textContent):null; const bd=(p90DL!=null && idle!=null)?(p90DL-idle):null; const bu=(p90UL!=null && idle!=null)?(p90UL-idle):null;
if(d80) || (bu!=null && bu>80)){ tips.push('High bufferbloat under load. Enable Smart Queue (CAKE/FQ-CoDel), set WAN shaper to 90–95% of actual line rate, retest.'); }else if((bd!=null && bd>20) || (bu!=null && bu>20)){ tips.push('Moderate latency rise under load. Consider SQM and avoid heavy uploads during calls.'); }else{ tips.push('Latency controlled under load—good for calls, VDI, and gaming.'); } if(u{ const li=document.createElement('li'); li.innerHTML=t; tipsEl.appendChild(li); }); }
// Buttons btnStartNDT.addEventListener('click', ()=> runNDT(22)); // balanced btnStartNDTLong.addEventListener('click', ()=> runNDT(40));// long for gigabit btnCancelNDT.addEventListener('click', ()=>{ if(ndtAbort){ ndtAbort.abort(); fields.ndtStatus.textContent='Cancelled'; log('NDT7 cancelled'); } btnStartNDT.disabled=false; btnStartNDTLong.disabled=false; btnCancelNDT.disabled=true; });
btnStartPeak.addEventListener('click', runPeak); btnCancelPeak.addEventListener('click', ()=>{ peakAbort=true; fields.peakStatus.textContent='Cancelling…'; log('Peak test cancelling…'); });
btnCopyLog.addEventListener('click', ()=> navigator.clipboard.writeText(logEl.textContent||'').then(()=>log('Log copied to clipboard.'))); btnClearLog.addEventListener('click', ()=>{ logEl.textContent=''; }); btnExport.addEventListener('click', ()=>{ const payload = { ts: new Date().toISOString(), idleRtt: state.idleRtt, ndt:{downMbps:state.ndt.dl.mbps, upMbps:state.ndt.ul.mbps}, peak:{bpsPeak:state.peak.bpsPeak, bpsWin:state.peak.bpsInstant, bytes:state.peak.bytes, durMs:(state.peak.end||performance.now())-state.peak.start} }; const blob=new Blob([JSON.stringify(payload,null,2)],{type:'application/json'}); const url=URL.createObjectURL(blob); const a=document.createElement('a'); a.href=url; a.download=`macworks360_inet_diag_${Date.now()}.json`; a.click(); URL.revokeObjectURL(url); log('Exported JSON.'); }); })();