LoadTest v2: добавлена проверка приёма СООБЩЕНИЙ (/messages, случайное содержимое заданного размера)
This commit is contained in:
+48
@@ -86,6 +86,54 @@ def upload():
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
@app.route("/messages", methods=["POST"])
|
||||
def messages():
|
||||
"""Приём СООБЩЕНИЯ в память (для проверки очередей/ИоТ).
|
||||
|
||||
От клиента приходит сырое тело запроса (raw body) нужного размера.
|
||||
Задача - проверить, ДОХОДИТ ли сообщение нужного размера до бэкенда
|
||||
(в отличие от /upload здесь нет multipart - только тело, как в очередях).
|
||||
"""
|
||||
t0 = time.time()
|
||||
|
||||
meta = {
|
||||
"server_start": time.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"remote_addr": request.remote_addr or "",
|
||||
# Заголовки, которые обычно шлёт очередь/ИоТ-клиент.
|
||||
"content_length_header": request.headers.get("Content-Length", ""),
|
||||
"content_type_header": request.headers.get("Content-Type", ""),
|
||||
"user_agent": request.headers.get("User-Agent", ""),
|
||||
"x_amz_sns_message_type": request.headers.get("X-Amz-Sns-Message-Type", ""),
|
||||
"ce_specversion": request.headers.get("Ce-Specversion", ""),
|
||||
}
|
||||
|
||||
# Читаем ВСЁ сырое тело сообщения в память и замеряем время.
|
||||
t_read = time.time()
|
||||
data = request.get_data() # сырое тело без разбора (как в SQS/IoT)
|
||||
read_s = time.time() - t_read
|
||||
|
||||
size = len(data) # сколько байт РЕАЛЬНО дошло до бэкенда
|
||||
total_s = time.time() - t0
|
||||
|
||||
result = {
|
||||
"ok": True,
|
||||
"message": {
|
||||
"size_bytes": size,
|
||||
"size_kb": round(size / 1024, 3),
|
||||
"size_mb": round(size / (1024 * 1024), 3),
|
||||
"prefix": data[:64].decode("utf-8", "replace"), # первые 64 байта для наглядности
|
||||
},
|
||||
"server": {
|
||||
"read_ms": round(read_s * 1000, 2),
|
||||
"total_ms": round(total_s * 1000, 2),
|
||||
"speed_mb_s": round(size / (1024 * 1024) / read_s, 3) if read_s > 0 else 0,
|
||||
},
|
||||
"md5": hashlib.md5(data).hexdigest(), # контрольная сумма принятых байт
|
||||
"meta": meta,
|
||||
}
|
||||
return jsonify(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# ОБЯЗАТЕЛЬНО для платформы: `python site/app.py` должен стартовать сервер.
|
||||
# debug=False - прод-режим, без reloader (иначе поднимается второй процесс).
|
||||
|
||||
@@ -39,8 +39,200 @@
|
||||
|
||||
<!-- Максимум статистики (клиент + сервер) -->
|
||||
<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.
|
||||
|
||||
Reference in New Issue
Block a user