add: собираем агрегированную информацию по настройкам типографа, процессорному времени и выводим процессорное время на фронет.
All checks were successful
Build ETPGRF-site / build (push) Successful in 1m35s

This commit is contained in:
2026-01-22 00:26:58 +03:00
parent bc237a6552
commit 2d09aef79d
3 changed files with 65 additions and 14 deletions

View File

@@ -274,10 +274,13 @@
<label class="form-label fw-bold small text-muted ls-1 mb-0"> <label class="form-label fw-bold small text-muted ls-1 mb-0">
<i class="bi bi-code-slash me-1"></i> Результат обработки: <i class="bi bi-code-slash me-1"></i> Результат обработки:
</label> </label>
<div class="d-flex align-items-center">
<span id="processing-time" class="small text-muted me-3 nowrap"></span>
<button id="btn-copy" class="btn btn-sm btn-outline-secondary d-none" title="Копировать в буфер обмена"> <button id="btn-copy" class="btn btn-sm btn-outline-secondary d-none" title="Копировать в буфер обмена">
<i class="bi bi-clipboard me-1"></i> Копировать в&nbsp;буфер обмена <i class="bi bi-clipboard me-1"></i> Копировать в&nbsp;буфер обмена
</button> </button>
</div> </div>
</div>
<div id="cm-result-wrapper" class="result-box p-0"></div> <div id="cm-result-wrapper" class="result-box p-0"></div>
<div id="result-area" style="display: none;"></div> <div id="result-area" style="display: none;"></div>
</div> </div>

View File

@@ -7,6 +7,7 @@ from etpgrf.typograph import Typographer
from etpgrf.layout import LayoutProcessor from etpgrf.layout import LayoutProcessor
from etpgrf.hyphenation import Hyphenator from etpgrf.hyphenation import Hyphenator
from .models import DailyStat from .models import DailyStat
import time
def index(request): def index(request):
@@ -131,39 +132,71 @@ def process_text(request):
'sanitizer': sanitizer_option, 'sanitizer': sanitizer_option,
} }
# --- ДИАГНОСТИКА --- # Обрабатываем текст с замером времени
# print("Typographer options:", options) start_time = time.perf_counter()
# ------------------- # Создаем экземпляр типографа и передаем настройки в него
# Создаем экземпляр типографа
typo = Typographer(**options) typo = Typographer(**options)
# Обрабатываем текст в Типографе
# Обрабатываем текст
processed = typo.process(text) processed = typo.process(text)
end_time = time.perf_counter()
duration_ms = (end_time - start_time) * 1000
# --- СБОР СТАТИСТИКИ --- # --- СБОР СТАТИСТИКИ ---
try: try:
today = timezone.now().date() today = timezone.now().date()
stat, created = DailyStat.objects.get_or_create(date=today) stat, created = DailyStat.objects.get_or_create(date=today)
# Обновляем атомарные поля # 1. Атомарное обновление счетчиков
DailyStat.objects.filter(pk=stat.pk).update( DailyStat.objects.filter(pk=stat.pk).update(
process_requests=F('process_requests') + 1, process_requests=F('process_requests') + 1,
chars_in=F('chars_in') + len(text), chars_in=F('chars_in') + len(text),
chars_out=F('chars_out') + len(processed), chars_out=F('chars_out') + len(processed),
# total_processing_time_ms мы пока не считаем, чтобы не усложнять total_processing_time_ms=F('total_processing_time_ms') + duration_ms
) )
# JSON с настройками пока не пишем, чтобы не усложнять (как договаривались) # 2. Обновление JSON с настройками (не атомарно, а значит при высокой нагрузке возможны потери данных)
# Перечитываем объект, чтобы получить актуальный JSON
stat.refresh_from_db()
current_stats = stat.settings_stats
def inc_stat(key, value):
k = f"{key}:{value}"
current_stats[k] = current_stats.get(k, 0) + 1
# Собираем статистику по опциям
# langs может быть строкой или списком
lang_val = options['langs']
if isinstance(lang_val, list):
lang_val = lang_val[0] if lang_val else 'ru'
inc_stat('lang', lang_val)
inc_stat('mode', options['mode'])
if options['quotes']: inc_stat('feature', 'quotes')
if layout_enabled: inc_stat('feature', 'layout')
if options['unbreakables']: inc_stat('feature', 'unbreakables')
if hyphenation_enabled: inc_stat('feature', 'hyphenation')
if options['symbols']: inc_stat('feature', 'symbols')
if hanging_enabled: inc_stat('feature', 'hanging')
if sanitizer_enabled: inc_stat('feature', 'sanitizer')
stat.settings_stats = current_stats
stat.save(update_fields=['settings_stats'])
except Exception as e: except Exception as e:
print(f"Stat error: {e}") print(f"Stat error: {e}")
# ----------------------- # -----------------------
return render( response = render(
request, request,
template_name='typograph/result_fragment.html', template_name='typograph/result_fragment.html',
context={'processed_text': processed} context={'processed_text': processed}
) )
# Добавляем заголовок с временем обработки (с запятой вместо точки)
response['X-Processing-Time'] = f"{duration_ms:.4f}".replace('.', ',')
return response
return HttpResponse(status=405) return HttpResponse(status=405)

View File

@@ -21,6 +21,7 @@ import {
const resultWrapper = document.getElementById('cm-result-wrapper'); const resultWrapper = document.getElementById('cm-result-wrapper');
const btnCopy = document.getElementById('btn-copy'); const btnCopy = document.getElementById('btn-copy');
const sourceTextarea = document.querySelector('textarea[name="text"]'); const sourceTextarea = document.querySelector('textarea[name="text"]');
const processingTimeSpan = document.getElementById('processing-time');
const themeCompartment = new Compartment(); const themeCompartment = new Compartment();
function getTheme() { function getTheme() {
@@ -100,6 +101,17 @@ document.body.addEventListener('htmx:afterSwap', function (evt) {
if (btnCopy) { if (btnCopy) {
btnCopy.classList.remove('d-none'); btnCopy.classList.remove('d-none');
} }
// Показываем время обработки из заголовка
if (processingTimeSpan) {
const time = evt.detail.xhr.getResponseHeader('X-Processing-Time');
if (time) {
processingTimeSpan.innerHTML = `<i class="bi bi-cpu me-1"></i>${time}&thinsp;ms`;
processingTimeSpan.style.display = 'inline';
} else {
processingTimeSpan.style.display = 'none';
}
}
} }
}); });
@@ -109,6 +121,9 @@ if (sourceTextarea) {
if (btnCopy) { if (btnCopy) {
btnCopy.classList.add('d-none'); btnCopy.classList.add('d-none');
} }
if (processingTimeSpan) {
processingTimeSpan.innerText = '';
}
// Сбрасываем редактор на плейсхолдер // Сбрасываем редактор на плейсхолдер
if (resultView.state.doc.toString() !== PLACEHOLDER_TEXT) { if (resultView.state.doc.toString() !== PLACEHOLDER_TEXT) {
resultView.dispatch({ resultView.dispatch({