Ping Test Tool
HTTP(S) Ping
Note: This tool measures HTTP(S) request latency, not a true ICMP ping.
Host
Sent
0
Lost
0
Avg. Latency
0 ms
Live Results
Ping History
Your ping history will appear here.
Your ping history will appear here.
'; return; } ui.logList.innerHTML = lookupLog.map(log => `
${log.timestamp}
`).join('');
}
function logEntry(host, sent, avg) {
const timestamp = new Date().toLocaleString('en-US');
pingLog.unshift({ timestamp, host, sent, avg });
if (pingLog.length > 50) pingLog.pop(); // Limit log size
saveState();
renderLog();
}
function showMessage(message, isError = true) {
ui.errorMsg.textContent = message;
ui.errorMsg.style.display = 'block';
}
// --- PING LOGIC ---
function sanitizeHost(host) {
return host.trim().replace(/^(https?:\/\/)/, '').replace(/\/.*$/, '');
}
async function executePing(host, timeout) {
const controller = new AbortController();
const signal = controller.signal;
const timeoutId = setTimeout(() => controller.abort(), timeout);
const startTime = performance.now();
try {
// We use 'no-cors' to measure latency to any domain without a CORS error.
// We can't read the response, but we can time the round-trip.
await fetch(`https://${host}`, { mode: 'no-cors', signal, cache: 'no-store' });
const endTime = performance.now();
clearTimeout(timeoutId);
return { status: 'success', time: (endTime - startTime) };
} catch (e) {
clearTimeout(timeoutId);
if (e.name === 'AbortError') {
return { status: 'timeout', time: null };
}
return { status: 'error', time: null };
}
}
async function startPingSequence() {
const host = sanitizeHost(ui.hostInput.value);
if (!host) {
showMessage("Please enter a valid host or IP address.");
return;
}
isPinging = true;
ui.startBtn.disabled = true;
ui.stopBtn.style.display = 'inline-block';
ui.errorMsg.style.display = 'none';
ui.resultsContainer.style.display = 'block';
ui.resultsLog.innerHTML = '';
currentResults = { host: host, pings: [] };
const count = ui.countSelect.value === 'Infinity' ? Infinity : parseInt(ui.countSelect.value);
const timeout = parseInt(ui.timeoutInput.value) || 2000;
for (let i = 0; i < count; i++) {
if (!isPinging) break;
const result = await executePing(host, timeout);
result.seq = i + 1;
currentResults.pings.push(result);
renderPingLine(result);
updateSummary();
// Wait 1 second between pings
if (i < count - 1) await new Promise(resolve => setTimeout(resolve, 1000));
}
stopPingSequence();
}
function stopPingSequence() {
isPinging = false;
ui.startBtn.disabled = false;
ui.stopBtn.style.display = 'none';
if (currentResults.pings.length > 0) {
const { sent, lost, avg } = getSummaryStats();
logEntry(currentResults.host, sent, avg.toFixed(2));
}
}
function getSummaryStats() {
const sent = currentResults.pings.length;
const successfulPings = currentResults.pings.filter(p => p.status === 'success');
const lost = sent - successfulPings.length;
const avg = successfulPings.length > 0
? successfulPings.reduce((acc, p) => acc + p.time, 0) / successfulPings.length
: 0;
return { sent, lost, avg };
}
function updateSummary() {
const { sent, lost, avg } = getSummaryStats();
ui.summaryHost.textContent = currentResults.host;
ui.summarySent.textContent = sent;
ui.summaryLost.textContent = lost;
ui.summaryAvg.textContent = `${avg.toFixed(2)} ms`;
}
function renderPingLine(ping) {
const line = document.createElement('div');
line.className = 'ptt-log-line';
let statusText = '';
switch (ping.status) {
case 'success':
statusText = `Reply from ${escapeHTML(currentResults.host)}: seq=${ping.seq} time=${ping.time.toFixed(2)} ms`;
if (ping.time > 500) line.classList.add('warn');
else if (ping.time > 1000) line.classList.add('error');
else line.classList.add('success');
break;
case 'timeout':
statusText = `Request timed out for seq=${ping.seq}`;
line.classList.add('error');
break;
case 'error':
statusText = `General error for seq=${ping.seq} (Host may be unreachable)`;
line.classList.add('error');
break;
}
line.textContent = statusText;
ui.resultsLog.appendChild(line);
ui.resultsLog.scrollTop = ui.resultsLog.scrollHeight;
}
// --- PDF LOGIC ---
function downloadReportPDF(data, title, filename) {
const { jsPDF } = window.jspdf;
if (!jsPDF || !window.jspdf.autoTable) { alert("PDF library not loaded!"); return; }
const doc = new jsPDF();
doc.setFontSize(18);
doc.text(title, 14, 22);
if (data.pings) { // Single Report
const { sent, lost, avg } = getSummaryStats();
doc.setFontSize(11);
doc.setTextColor(100);
doc.text(`Report for: ${data.host}`, 14, 28);
doc.autoTable({
startY: 35,
theme: 'grid',
head: [['Metric', 'Value']],
body: [
['Packets Sent', sent],
['Packets Lost', lost],
['Avg. Latency', `${avg.toFixed(2)} ms`],
]
});
doc.autoTable({
startY: doc.autoTable.previous.finalY + 10,
head: [['Sequence', 'Status', 'Time (ms)']],
body: data.pings.map(p => [p.seq, p.status, p.time ? p.time.toFixed(2) : 'N/A']),
theme: 'striped',
headStyles: { fillColor: [22, 163, 74] } // var(--primary-color)
});
} else { // Log Report
doc.autoTable({
startY: 30,
head: [['Timestamp', 'Host', 'Packets Sent', 'Avg. Latency (ms)']],
body: data.map(log => [log.timestamp, log.host, log.sent, log.avg]),
theme: 'striped',
headStyles: { fillColor: [22, 163, 74] }
});
}
doc.save(filename);
}
// --- EVENT LISTENERS ---
ui.startBtn.addEventListener('click', startPingSequence);
ui.stopBtn.addEventListener('click', stopPingSequence);
ui.downloadReportBtn.addEventListener('click', () => {
if (currentResults.pings.length === 0) {
alert("Please run a test before downloading.");
return;
}
downloadReportPDF(currentResults, "Ping Test Report", `Ping_Report_${currentResults.host}.pdf`);
});
ui.downloadLogBtn.addEventListener('click', () => {
if (pingLog.length === 0) {
alert("Log is empty.");
return;
}
downloadReportPDF(pingLog, "Ping History Log", "Ping_Log.pdf");
});
ui.clearLogBtn.addEventListener('click', () => {
pingLog = [];
saveState();
renderLog();
});
// --- TABBING LOGIC ---
function switchTab(targetTabId) {
ui.tabContents.forEach(c => c.classList.remove('active'));
ui.tabButtons.forEach(b => b.classList.remove('active'));
document.getElementById(`ptt-tab-${targetTabId}`).classList.add('active');
document.querySelector(`.ptt-tab-button[data-tab="${targetTabId}"]`).classList.add('active');
}
function navigateTabs(dir) {
const tabs = Array.from(ui.tabButtons);
const active = tabs.find(t => t.classList.contains('active'));
let index = tabs.indexOf(active) + dir;
if (index < 0) index = 0; if (index >= tabs.length) index = tabs.length - 1;
switchTab(tabs[index].dataset.tab);
}
ui.tabButtons.forEach(button => button.addEventListener('click', () => switchTab(button.dataset.tab)));
ui.nextTabBtn.addEventListener('click', () => navigateTabs(1));
ui.prevTabBtn.addEventListener('click', () => navigateTabs(-1));
// --- UTILITY ---
function escapeHTML(str) { const p = document.createElement('p'); p.textContent = str; return p.innerHTML; }
// --- INITIALIZATION ---
loadState();
renderLog();
});
Pinged ${escapeHTML(log.host)} (${log.sent} packets, ${log.avg} ms avg)
