LoadTest v2: добавлена проверка приёма СООБЩЕНИЙ (/messages, случайное содержимое заданного размера)
This commit is contained in:
+48
@@ -86,6 +86,54 @@ def upload():
|
|||||||
return jsonify(result)
|
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__":
|
if __name__ == "__main__":
|
||||||
# ОБЯЗАТЕЛЬНО для платформы: `python site/app.py` должен стартовать сервер.
|
# ОБЯЗАТЕЛЬНО для платформы: `python site/app.py` должен стартовать сервер.
|
||||||
# debug=False - прод-режим, без reloader (иначе поднимается второй процесс).
|
# debug=False - прод-режим, без reloader (иначе поднимается второй процесс).
|
||||||
|
|||||||
@@ -39,8 +39,200 @@
|
|||||||
|
|
||||||
<!-- Максимум статистики (клиент + сервер) -->
|
<!-- Максимум статистики (клиент + сервер) -->
|
||||||
<pre id="result"></pre>
|
<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>
|
</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>
|
<script>
|
||||||
// ---------- Загрузка одного файла через XMLHttpRequest ----------
|
// ---------- Загрузка одного файла через XMLHttpRequest ----------
|
||||||
// XHR даёт реальный прогресс отправки (fetch - не даёт), поэтому берём XHR.
|
// XHR даёт реальный прогресс отправки (fetch - не даёт), поэтому берём XHR.
|
||||||
|
|||||||
Reference in New Issue
Block a user