404 lines
18 KiB
HTML
404 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<!-- LoadTest - страница проверки загрузки файлов. -->
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>LoadTest - проверка загрузки файлов</title>
|
||
<link rel="stylesheet" href="/static/style.css">
|
||
</head>
|
||
<body>
|
||
<div class="wrap">
|
||
<h1>LoadTest - проверка загрузки файлов</h1>
|
||
<p class="hint">
|
||
Выберите ОДИН файл (в т.ч. большой) и нажмите «Загрузить».
|
||
Файл читается в память Flask (на диск не пишется). Результат: файл дошёл или нет + статистика.
|
||
</p>
|
||
|
||
<!-- Панель выбора файла и кнопка загрузки -->
|
||
<div class="panel">
|
||
<label for="file">Файл:</label>
|
||
<input type="file" id="file">
|
||
<button id="btn" onclick="startUpload()">Загрузить</button>
|
||
</div>
|
||
|
||
<!-- Индикатор прогресса: полоса, % и таймер -->
|
||
<div class="progress-wrap" id="progressWrap" style="display:none">
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" id="progressFill"></div>
|
||
</div>
|
||
<div class="progress-info">
|
||
<span id="progressPct">0%</span>
|
||
<span id="progressTimer">0с</span>
|
||
<span id="progressSpeed"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Итоговый статус: УСПЕХ / ОШИБКА -->
|
||
<div id="status"></div>
|
||
|
||
<!-- Максимум статистики (клиент + сервер) -->
|
||
<pre id="result"></pre>
|
||
|
||
<!-- ============================================================ -->
|
||
<!-- СЕКЦИЯ СООБЩЕНИЙ (проверка очередей/ИоТ) -->
|
||
<!-- ============================================================ -->
|
||
<hr style="margin:24px 0;border:none;border-top:2px dashed #ccc;">
|
||
|
||
<h2>Сообщение (очередь / ИоТ)</h2>
|
||
<p class="hint">
|
||
Задайте размер сообщения (в байтах). Содержимое - случайное.
|
||
Проверяется, ДОХОДИТ ли тело сообщения нужного размера до бэкенда (без multipart, как в очереди).
|
||
</p>
|
||
|
||
<div class="panel">
|
||
<label for="msgSize">Размер (байт):</label>
|
||
<input type="number" id="msgSize" value="1024" min="1" step="1" style="width:180px;">
|
||
<button id="btnMsg" onclick="startMessage()">Отправить сообщение</button>
|
||
</div>
|
||
|
||
<!-- Прогресс отправки сообщения -->
|
||
<div class="progress-wrap" id="msgProgressWrap" style="display:none">
|
||
<div class="progress-bar">
|
||
<div class="progress-fill" id="msgProgressFill"></div>
|
||
</div>
|
||
<div class="progress-info">
|
||
<span id="msgProgressPct">0%</span>
|
||
<span id="msgProgressTimer">0с</span>
|
||
<span id="msgProgressSpeed"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Итоговый статус сообщения -->
|
||
<div id="msgStatus"></div>
|
||
|
||
<!-- Статистика сообщения -->
|
||
<pre id="msgResult"></pre>
|
||
</div>
|
||
|
||
<script>
|
||
// ---------- Проверка приёма СООБЩЕНИЯ (очередь/ИоТ) ----------
|
||
// Тело - сырое (raw), без multipart, со случайным содержимым заданного размера.
|
||
|
||
function startMessage() {
|
||
var sizeEl = document.getElementById('msgSize');
|
||
var size = parseInt(sizeEl.value, 10);
|
||
var status = document.getElementById('msgStatus');
|
||
var result = document.getElementById('msgResult');
|
||
var wrap = document.getElementById('msgProgressWrap');
|
||
result.textContent = '';
|
||
status.textContent = '';
|
||
|
||
if (!size || size < 1) {
|
||
status.textContent = 'ОШИБКА: укажите размер';
|
||
status.className = 'status error';
|
||
return;
|
||
}
|
||
|
||
// ---- Генерируем случайное содержимое нужного размера ----
|
||
var startMs = Date.now();
|
||
var startLocal = new Date().toLocaleTimeString();
|
||
var randomStr = randomBytes(size); // строка случайных символов длиной size
|
||
|
||
// ---- Прогресс ----
|
||
wrap.style.display = 'block';
|
||
var fill = document.getElementById('msgProgressFill');
|
||
var pctEl = document.getElementById('msgProgressPct');
|
||
var timerEl = document.getElementById('msgProgressTimer');
|
||
var speedEl = document.getElementById('msgProgressSpeed');
|
||
fill.style.width = '0%';
|
||
pctEl.textContent = '0%';
|
||
timerEl.textContent = '0с';
|
||
speedEl.textContent = '';
|
||
|
||
var timer = setInterval(function () {
|
||
var sec = Math.floor((Date.now() - startMs) / 1000);
|
||
timerEl.textContent = sec + 'с';
|
||
}, 1000);
|
||
|
||
var xhr = new XMLHttpRequest();
|
||
xhr.open('POST', '/messages');
|
||
xhr.timeout = 600000;
|
||
// Шлём как текстовое тело (как очередь обычно шлёт payload).
|
||
xhr.setRequestHeader('Content-Type', 'text/plain');
|
||
|
||
xhr.upload.onprogress = function (e) {
|
||
if (!e.lengthComputable) { pctEl.textContent = '?%'; return; }
|
||
var pct = Math.round(e.loaded / e.total * 100);
|
||
fill.style.width = pct + '%';
|
||
pctEl.textContent = pct + '%';
|
||
var sec = (Date.now() - startMs) / 1000;
|
||
if (sec > 0) speedEl.textContent = ((e.loaded / (1024 * 1024)) / sec).toFixed(2) + ' МБ/с';
|
||
};
|
||
|
||
xhr.onload = function () {
|
||
clearInterval(timer);
|
||
var sec = ((Date.now() - startMs) / 1000).toFixed(2);
|
||
var httpStatus = xhr.status;
|
||
var ok = xhr.status >= 200 && xhr.status < 300;
|
||
var data = null;
|
||
try { data = JSON.parse(xhr.responseText); } catch (e) { data = null; }
|
||
|
||
if (ok && data && data.ok) {
|
||
status.textContent = '✅ СООБЩЕНИЕ ПРИНЯТО (' + size + ' байт)';
|
||
status.className = 'status ok';
|
||
renderMsgStats(size, startLocal, sec, httpStatus, data);
|
||
} else {
|
||
status.textContent = '❌ СООБЩЕНИЕ НЕ ПРИНЯТО (HTTP ' + httpStatus + ')';
|
||
status.className = 'status error';
|
||
var errBody = data && data.error ? 'ошибка: ' + data.error : xhr.responseText;
|
||
result.textContent =
|
||
'HTTP статус: ' + httpStatus + '\n' +
|
||
'Ответ сервера: ' + (errBody || '(пусто)') + '\n' +
|
||
msgClientStats(size, startLocal, sec);
|
||
}
|
||
};
|
||
|
||
xhr.onerror = function () {
|
||
clearInterval(timer);
|
||
var sec = ((Date.now() - startMs) / 1000).toFixed(2);
|
||
status.textContent = '❌ СЕТЕВАЯ ОШИБКА - ответа нет';
|
||
status.className = 'status error';
|
||
result.textContent = msgClientStats(size, startLocal, sec) +
|
||
'\n(соединение прервано, прокси/платформа не ответила)';
|
||
};
|
||
|
||
xhr.ontimeout = function () {
|
||
clearInterval(timer);
|
||
var sec = ((Date.now() - startMs) / 1000).toFixed(2);
|
||
status.textContent = '❌ ТАЙМАУТ (10 мин) - сообщение не принято';
|
||
status.className = 'status error';
|
||
result.textContent = msgClientStats(size, startLocal, sec);
|
||
};
|
||
|
||
xhr.send(randomStr);
|
||
}
|
||
|
||
// ---- Генерация случайной строки заданной длины ----
|
||
function randomBytes(n) {
|
||
var chars = 'ABCDEF0123456789abcdefghijklmnopqrstuvwxyz';
|
||
var out = '';
|
||
// Генерируем частями (по 8192) - для очень больших n без зависания строки.
|
||
var chunk = 8192;
|
||
while (out.length < n) {
|
||
var need = Math.min(chunk, n - out.length);
|
||
var s = '';
|
||
for (var i = 0; i < need; i++) s += chars[Math.floor(Math.random() * chars.length)];
|
||
out += s;
|
||
}
|
||
return out;
|
||
}
|
||
|
||
// ---- Клиентская статистика сообщения ----
|
||
function msgClientStats(size, startLocal, sec) {
|
||
var avg = sec > 0 ? (size / (1024 * 1024) / sec).toFixed(2) : '0';
|
||
return '-- КЛИЕНТ (сообщение) --\n' +
|
||
'Размер: ' + size + ' байт (' + (size / 1024).toFixed(3) + ' КБ)\n' +
|
||
'Начало: ' + startLocal + '\n' +
|
||
'Длительность: ' + sec + ' с\n' +
|
||
'Средняя скорость: ' + avg + ' МБ/с\n';
|
||
}
|
||
|
||
// ---- Полная статистика при успехе ----
|
||
function renderMsgStats(size, startLocal, sec, httpStatus, data) {
|
||
var m = data.message;
|
||
var s = data.server;
|
||
var meta = data.meta;
|
||
var avg = sec > 0 ? (size / (1024 * 1024) / sec).toFixed(2) : '0';
|
||
var out = '';
|
||
|
||
out += '-- КЛИЕНТ (сообщение) --\n' +
|
||
'Размер: ' + size + ' байт (' + (size / 1024).toFixed(3) + ' КБ)\n' +
|
||
'Начало: ' + startLocal + '\n' +
|
||
'Длительность: ' + sec + ' с\n' +
|
||
'Средняя скорость: ' + avg + ' МБ/с\n' +
|
||
'HTTP статус: ' + httpStatus + '\n\n';
|
||
|
||
out += '-- СЕРВЕР (что реально дошло) --\n' +
|
||
'Принято байт: ' + m.size_bytes + ' (' + m.size_kb + ' КБ)\n' +
|
||
'Чтение в память: ' + s.read_ms + ' мс\n' +
|
||
'Обработка всего: ' + s.total_ms + ' мс\n' +
|
||
'Скорость (сервер): ' + s.speed_mb_s + ' МБ/с\n' +
|
||
'MD5 принятых байт: ' + data.md5 + '\n' +
|
||
'Префикс тела: ' + m.prefix + '\n\n';
|
||
|
||
out += '-- ЗАПРОС --\n' +
|
||
'Время сервера: ' + meta.server_start + '\n' +
|
||
'Remote addr: ' + meta.remote_addr + '\n' +
|
||
'Content-Length (заголовок): ' + meta.content_length_header + '\n' +
|
||
'Content-Type (заголовок): ' + meta.content_type_header + '\n' +
|
||
'User-Agent: ' + meta.user_agent;
|
||
|
||
result.textContent = out;
|
||
}
|
||
</script>
|
||
|
||
<script>
|
||
// ---------- Загрузка одного файла через XMLHttpRequest ----------
|
||
// XHR даёт реальный прогресс отправки (fetch - не даёт), поэтому берём XHR.
|
||
|
||
function startUpload() {
|
||
var input = document.getElementById('file');
|
||
var file = input.files[0];
|
||
|
||
// Сброс старых данных.
|
||
var result = document.getElementById('result');
|
||
var status = document.getElementById('status');
|
||
var wrap = document.getElementById('progressWrap');
|
||
result.textContent = '';
|
||
status.textContent = '';
|
||
|
||
if (!file) {
|
||
status.textContent = 'ОШИБКА: выберите файл';
|
||
status.className = 'status error';
|
||
return;
|
||
}
|
||
|
||
// ---- Статистика клиента ----
|
||
var startMs = Date.now(); // время начала загрузки (клиент)
|
||
var fileSize = file.size; // размер файла (байт)
|
||
var fileSizeMB = fileSize / (1024 * 1024);
|
||
var startLocal = new Date().toLocaleTimeString();
|
||
|
||
// ---- Прогресс: полоса + % ----
|
||
wrap.style.display = 'block';
|
||
var fill = document.getElementById('progressFill');
|
||
var pctEl = document.getElementById('progressPct');
|
||
var timerEl = document.getElementById('progressTimer');
|
||
var speedEl = document.getElementById('progressSpeed');
|
||
fill.style.width = '0%';
|
||
pctEl.textContent = '0%';
|
||
timerEl.textContent = '0с';
|
||
speedEl.textContent = '';
|
||
|
||
// Таймер (второстепенно): обновляем прошедшие секунды каждую 1с.
|
||
var timer = setInterval(function () {
|
||
var sec = Math.floor((Date.now() - startMs) / 1000);
|
||
timerEl.textContent = sec + 'с';
|
||
}, 1000);
|
||
|
||
// ---- Формируем multipart-запрос ----
|
||
var fd = new FormData();
|
||
fd.append('file', file, file.name); // поле должно называться 'file' (см. app.py)
|
||
|
||
var xhr = new XMLHttpRequest();
|
||
xhr.open('POST', '/upload');
|
||
// Таймаут 10 минут - большие файлы могут ехать долго.
|
||
xhr.timeout = 600000;
|
||
|
||
// Реальный прогресс отправки тела запроса (loaded - отправлено байт).
|
||
xhr.upload.onprogress = function (e) {
|
||
if (!e.lengthComputable) {
|
||
pctEl.textContent = '?%';
|
||
return;
|
||
}
|
||
var pct = Math.round(e.loaded / e.total * 100);
|
||
fill.style.width = pct + '%';
|
||
pctEl.textContent = pct + '%';
|
||
// Мгновенная средняя скорость клиента.
|
||
var sec = (Date.now() - startMs) / 1000;
|
||
if (sec > 0) {
|
||
var mbps = (e.loaded / (1024 * 1024)) / sec;
|
||
speedEl.textContent = mbps.toFixed(2) + ' МБ/с';
|
||
}
|
||
};
|
||
|
||
// ---- Ответ сервера получен ----
|
||
xhr.onload = function () {
|
||
clearInterval(timer);
|
||
var sec = ((Date.now() - startMs) / 1000).toFixed(2);
|
||
var httpStatus = xhr.status;
|
||
var ok = xhr.status >= 200 && xhr.status < 300;
|
||
|
||
// Сервер может ответить и НЕ-JSON (413, HTML ошибки прокси) - показываем текст.
|
||
var data = null;
|
||
try { data = JSON.parse(xhr.responseText); } catch (e) { data = null; }
|
||
|
||
if (ok && data && data.ok) {
|
||
// УСПЕХ - файл дошёл до бэкенда.
|
||
status.textContent = '✅ ФАЙЛ ЗАГРУЖЕН';
|
||
status.className = 'status ok';
|
||
renderStats(file, fileSize, fileSizeMB, startLocal, sec, httpStatus, data);
|
||
} else {
|
||
// ОШИБКА - показываем статус, текст сервера и клиентскую статистику.
|
||
status.textContent = '❌ ФАЙЛ НЕ ЗАГРУЖЕН (HTTP ' + httpStatus + ')';
|
||
status.className = 'status error';
|
||
var errBody = data && data.error ? 'ошибка: ' + data.error : xhr.responseText;
|
||
result.textContent =
|
||
'HTTP статус: ' + httpStatus + '\n' +
|
||
'Ответ сервера: ' + (errBody || '(пусто)') + '\n' +
|
||
clientStats(file, fileSize, fileSizeMB, startLocal, sec);
|
||
}
|
||
};
|
||
|
||
// ---- Сеть: соединение оборвано, нет ответа ----
|
||
xhr.onerror = function () {
|
||
clearInterval(timer);
|
||
var sec = ((Date.now() - startMs) / 1000).toFixed(2);
|
||
status.textContent = '❌ СЕТЕВАЯ ОШИБКА - ответа нет';
|
||
status.className = 'status error';
|
||
result.textContent = clientStats(file, fileSize, fileSizeMB, startLocal, sec) +
|
||
'\n(соединение прервано, прокси/платформа не ответила)';
|
||
};
|
||
|
||
// ---- Таймаут ----
|
||
xhr.ontimeout = function () {
|
||
clearInterval(timer);
|
||
var sec = ((Date.now() - startMs) / 1000).toFixed(2);
|
||
status.textContent = '❌ ТАЙМАУТ (10 мин) - файл не загружен';
|
||
status.className = 'status error';
|
||
result.textContent = clientStats(file, fileSize, fileSizeMB, startLocal, sec);
|
||
};
|
||
|
||
xhr.send(fd);
|
||
}
|
||
|
||
// ---- Клиентская статистика (общая для всех исходов) ----
|
||
function clientStats(file, size, sizeMB, startLocal, sec) {
|
||
var avg = sec > 0 ? (size / (1024 * 1024) / sec).toFixed(2) : '0';
|
||
return '-- КЛИЕНТ --\n' +
|
||
'Файл: ' + file.name + '\n' +
|
||
'Размер: ' + size + ' байт (' + sizeMB.toFixed(3) + ' МБ)\n' +
|
||
'Начало: ' + startLocal + '\n' +
|
||
'Длительность: ' + sec + ' с\n' +
|
||
'Средняя скорость: ' + avg + ' МБ/с\n';
|
||
}
|
||
|
||
// ---- Полная статистика при успехе: клиент + сервер ----
|
||
function renderStats(file, size, sizeMB, startLocal, sec, httpStatus, data) {
|
||
var f = data.file;
|
||
var s = data.server;
|
||
var m = data.meta;
|
||
var avg = sec > 0 ? (size / (1024 * 1024) / sec).toFixed(2) : '0';
|
||
var out = '';
|
||
|
||
out += '-- КЛИЕНТ --\n' +
|
||
'Файл: ' + file.name + '\n' +
|
||
'Размер: ' + size + ' байт (' + sizeMB.toFixed(3) + ' МБ)\n' +
|
||
'Начало: ' + startLocal + '\n' +
|
||
'Длительность: ' + sec + ' с\n' +
|
||
'Средняя скорость (клиент): ' + avg + ' МБ/с\n' +
|
||
'HTTP статус: ' + httpStatus + '\n\n';
|
||
|
||
out += '-- СЕРВЕР (что реально дошло) --\n' +
|
||
'Имя файла: ' + f.name + '\n' +
|
||
'MIME: ' + f.mime + '\n' +
|
||
'Принято байт: ' + f.size_bytes + ' (' + f.size_mb + ' МБ)\n' +
|
||
'Чтение в память: ' + s.read_ms + ' мс\n' +
|
||
'Обработка всего: ' + s.total_ms + ' мс\n' +
|
||
'Скорость (сервер): ' + s.speed_mb_s + ' МБ/с\n' +
|
||
'MD5 принятых байт: ' + data.md5 + '\n\n';
|
||
|
||
out += '-- ЗАПРОС --\n' +
|
||
'Время сервера: ' + m.server_start + '\n' +
|
||
'Remote addr: ' + m.remote_addr + '\n' +
|
||
'Content-Length (заголовок): ' + m.content_length_header + '\n' +
|
||
'Content-Type (заголовок): ' + m.content_type_header + '\n' +
|
||
'User-Agent: ' + m.user_agent;
|
||
|
||
result.textContent = out;
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|