From c022806bf2227f6fcf5367802d61f2366e887195 Mon Sep 17 00:00:00 2001 From: dakocha3 Date: Wed, 23 Sep 2026 23:51:07 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=BC=D0=B5=D1=82=D1=80=D0=B8=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=B0=D0=B6=D0=B4=D0=BE=D0=B9?= =?UTF-8?q?=20NER-=D0=BC=D0=BE=D0=B4=D0=B5=D0=BB=D0=B8=20=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=B0=D0=BD=D0=B5=D0=BB=D0=B8=20=D0=B4=D0=B0=D1=88=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Добавлены счётчики pdguard.ner.model.requests и таймеры pdguard.ner.model.duration с тегом model (name/address/legal). - Гистограммы для таймеров моделей включены в MetricsConfiguration. - Дашборд дополнен панелями «Обращения к моделям» и «Время моделей». - Prometheus собирает метрики с обеих нод кластера. - Целевые значения лимитера: min-concurrent 100, target-latency 900. - Подавление S2925 (Thread.sleep) в тесте лимитера. --- monitoring/grafana/dashboards/pd-guard.json | 150 ++++++++++++++++-- monitoring/prometheus.yml | 4 +- .../ru/pdguard/core/MetricsConfiguration.java | 1 + .../java/ru/pdguard/detect/NameCascade.java | 49 +++++- src/main/resources/application.yml | 4 +- .../core/AdaptiveConcurrencyLimiterTest.java | 1 + 6 files changed, 192 insertions(+), 17 deletions(-) diff --git a/monitoring/grafana/dashboards/pd-guard.json b/monitoring/grafana/dashboards/pd-guard.json index 730e236..30239bd 100644 --- a/monitoring/grafana/dashboards/pd-guard.json +++ b/monitoring/grafana/dashboards/pd-guard.json @@ -2149,6 +2149,136 @@ } } }, + { + "type": "timeseries", + "title": "Обращения к моделям", + "id": 49, + "datasource": { + "type": "prometheus", + "uid": "PDGUARD_PROM" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 74 + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PDGUARD_PROM" + }, + "expr": "sum by (model) (rate(pdguard_ner_model_requests_total[$__rate_interval]))", + "refId": "A", + "instant": false, + "legendFormat": "{{model}}" + } + ], + "fieldConfig": { + "defaults": { + "unit": "reqps", + "custom": { + "fillOpacity": 45, + "lineWidth": 2, + "showPoints": "never", + "stacking": { + "mode": "normal" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "table", + "placement": "bottom", + "calcs": [ + "mean", + "max" + ], + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + }, + { + "type": "timeseries", + "title": "Время моделей", + "id": 50, + "datasource": { + "type": "prometheus", + "uid": "PDGUARD_PROM" + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 74 + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PDGUARD_PROM" + }, + "expr": "histogram_quantile(0.95, sum by (le, model) (rate(pdguard_ner_model_duration_seconds_bucket[$__rate_interval])))", + "refId": "A", + "instant": false, + "legendFormat": "{{model}} p95" + } + ], + "fieldConfig": { + "defaults": { + "unit": "s", + "custom": { + "fillOpacity": 12, + "lineWidth": 2, + "showPoints": "never", + "stacking": { + "mode": "none" + } + }, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + } + ] + } + }, + "overrides": [] + }, + "options": { + "legend": { + "displayMode": "table", + "placement": "bottom", + "calcs": [ + "mean", + "max" + ], + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "desc" + } + } + }, { "type": "row", "title": "Ресурсы узла", @@ -2157,7 +2287,7 @@ "h": 1, "w": 24, "x": 0, - "y": 74 + "y": 90 }, "collapsed": false }, @@ -2173,7 +2303,7 @@ "h": 5, "w": 4, "x": 0, - "y": 75 + "y": 91 }, "targets": [ { @@ -2236,7 +2366,7 @@ "h": 5, "w": 4, "x": 4, - "y": 75 + "y": 91 }, "targets": [ { @@ -2291,7 +2421,7 @@ "h": 5, "w": 4, "x": 8, - "y": 75 + "y": 91 }, "targets": [ { @@ -2346,7 +2476,7 @@ "h": 5, "w": 4, "x": 12, - "y": 75 + "y": 91 }, "targets": [ { @@ -2409,7 +2539,7 @@ "h": 5, "w": 4, "x": 16, - "y": 75 + "y": 91 }, "targets": [ { @@ -2464,7 +2594,7 @@ "h": 5, "w": 4, "x": 20, - "y": 75 + "y": 91 }, "targets": [ { @@ -2523,7 +2653,7 @@ "h": 8, "w": 12, "x": 0, - "y": 80 + "y": 96 }, "targets": [ { @@ -2588,7 +2718,7 @@ "h": 8, "w": 12, "x": 12, - "y": 80 + "y": 96 }, "targets": [ { @@ -2643,4 +2773,4 @@ } } ] -} +} \ No newline at end of file diff --git a/monitoring/prometheus.yml b/monitoring/prometheus.yml index 162c918..d0237c3 100644 --- a/monitoring/prometheus.yml +++ b/monitoring/prometheus.yml @@ -8,6 +8,6 @@ scrape_configs: - job_name: pd-guard metrics_path: /actuator/prometheus static_configs: - - targets: ["pd-guard:8080"] + - targets: ["node-a:8080", "node-b:8080"] labels: - instance: "один узел" \ No newline at end of file + instance: "кластер из двух узлов" \ No newline at end of file diff --git a/src/main/java/ru/pdguard/core/MetricsConfiguration.java b/src/main/java/ru/pdguard/core/MetricsConfiguration.java index 0dfd242..ee06cbb 100644 --- a/src/main/java/ru/pdguard/core/MetricsConfiguration.java +++ b/src/main/java/ru/pdguard/core/MetricsConfiguration.java @@ -58,6 +58,7 @@ public class MetricsConfiguration { private static boolean needsHistogram(String name) { return "pdguard.process".equals(name) || "pdguard.ner.duration".equals(name) + || "pdguard.ner.model.duration".equals(name) || "http.server.requests".equals(name); } } diff --git a/src/main/java/ru/pdguard/detect/NameCascade.java b/src/main/java/ru/pdguard/detect/NameCascade.java index 786fdfb..5ce2bbd 100644 --- a/src/main/java/ru/pdguard/detect/NameCascade.java +++ b/src/main/java/ru/pdguard/detect/NameCascade.java @@ -7,6 +7,7 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import jakarta.annotation.PreDestroy; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; @@ -127,6 +128,12 @@ public class NameCascade { private final Counter candidates; private final Timer duration; + /** Счётчики обращений к каждой модели второй ступени: тег {@code model} — имя движка. */ + private final Map modelRequests; + + /** Время работы каждой модели: тег {@code model} — имя движка. */ + private final Map modelDuration; + @Autowired public NameCascade( @Value("${pdguard.ner.name-engine:off}") String nameEngine, @@ -166,6 +173,36 @@ public class NameCascade { Timer.builder("pdguard.ner.duration") .description("Время работы второй ступени") .register(meters); + this.modelRequests = modelCounters(meters); + this.modelDuration = modelTimers(meters); + } + + /** Счётчики обращений к каждой модели: тег {@code model} — имя движка. */ + private static Map modelCounters(MeterRegistry meters) { + Map counters = new HashMap<>(); + for (String model : new String[] {"name", "address", "legal"}) { + counters.put( + model, + Counter.builder("pdguard.ner.model.requests") + .description("Обращения к модели второй ступени") + .tag("model", model) + .register(meters)); + } + return counters; + } + + /** Таймеры времени работы каждой модели: тег {@code model} — имя движка. */ + private static Map modelTimers(MeterRegistry meters) { + Map timers = new HashMap<>(); + for (String model : new String[] {"name", "address", "legal"}) { + timers.put( + model, + Timer.builder("pdguard.ner.model.duration") + .description("Время работы модели второй ступени") + .tag("model", model) + .register(meters)); + } + return timers; } /** Конструктор для тестов: движки задаются конфигом, метрики — реестром. */ @@ -255,6 +292,8 @@ public class NameCascade { this.busy = meters.counter(NER_REQUESTS_METRIC, OUTCOME_TAG, "busy"); this.candidates = meters.counter("pdguard.ner.candidates"); this.duration = Timer.builder("pdguard.ner.duration").register(meters); + this.modelRequests = modelCounters(meters); + this.modelDuration = modelTimers(meters); } public static NameCascade disabled() { @@ -315,8 +354,8 @@ public class NameCascade { continue; } examined++; - collect(text, m.start(), m.end(), found, nameRecogniser); - collect(text, m.start(), m.end(), found, addressRecogniser); + collect(text, m.start(), m.end(), found, "name", nameRecogniser); + collect(text, m.start(), m.end(), found, "address", addressRecogniser); } // LLAIM Legal NER ищет реквизиты (ИНН, СНИЛС, паспорт), которые не // являются словами с заглавной буквы, — отдельный проход по своим @@ -328,7 +367,7 @@ public class NameCascade { continue; } examined++; - collect(text, lm.start(), lm.end(), found, legalRecogniser); + collect(text, lm.start(), lm.end(), found, "legal", legalRecogniser); } } candidates.increment(examined); @@ -349,6 +388,7 @@ public class NameCascade { int candidateStart, int candidateEnd, List sink, + String modelName, RuBertRecogniser recogniser) { if (recogniser == null) { return; @@ -356,6 +396,7 @@ public class NameCascade { int from = Math.max(0, candidateStart - CONTEXT_CHARS); int to = Math.min(text.length(), candidateEnd + CONTEXT_CHARS); boolean nameFound = false; + long started = System.nanoTime(); for (Span span : recogniser.recognise(text, from, to, PRIORITY)) { if (isAccepted(text, candidateStart, candidateEnd, span)) { sink.add(span); @@ -364,6 +405,8 @@ public class NameCascade { } } } + modelRequests.get(modelName).increment(); + modelDuration.get(modelName).record(System.nanoTime() - started, TimeUnit.NANOSECONDS); // Модель распознала имя в кандидате, но правила могли найти лишь его часть // («Жан» вместо «Жан-Поль Дюваль») с более высоким приоритетом и заблокировать // полное имя при разрешении перекрытий. Убираем такие частичные находки правил, diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 2f963ff..1a2caba 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -40,9 +40,9 @@ pdguard: ttl-minutes: 30 # 32 байта в hex; AES-256 ключ шифрования хранилища encryption-key: "46a38b200c6df557a5fd2c8a57ad3fec6b710b9f3e1fef1451d121a094f63573" - min-concurrent: 8 + min-concurrent: 100 max-concurrent: 2000 - target-latency-ms: 200 + target-latency-ms: 900 warmup-iterations: 2000 ner: name-engine: off diff --git a/src/test/java/ru/pdguard/core/AdaptiveConcurrencyLimiterTest.java b/src/test/java/ru/pdguard/core/AdaptiveConcurrencyLimiterTest.java index 5bc329b..7f69b59 100644 --- a/src/test/java/ru/pdguard/core/AdaptiveConcurrencyLimiterTest.java +++ b/src/test/java/ru/pdguard/core/AdaptiveConcurrencyLimiterTest.java @@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.concurrent.TimeUnit; import org.junit.jupiter.api.Test; +@SuppressWarnings("java:S2925") // Thread.sleep ждёт прохождения окна регулировки лимитера class AdaptiveConcurrencyLimiterTest { @Test