From f9ef9261218179737c7a92b5cc76f70bc73d088b Mon Sep 17 00:00:00 2001 From: symstu Date: Mon, 16 Feb 2026 13:03:01 +0100 Subject: [PATCH] Replaced TFtAccumulativeTimeDetector with TFtAvgTimeDetector Closes #8 --- app.py | 7 ++-- detectors/tft.py | 35 ++++++++++++++------ tests/test_detector_tft_based.py | 55 +++++++++++++++++++++++++------- 3 files changed, 72 insertions(+), 25 deletions(-) diff --git a/app.py b/app.py index bce0b15..97312c0 100755 --- a/app.py +++ b/app.py @@ -15,7 +15,7 @@ IPRPSDetector, ) from detectors.tft import ( - TFtAccumulativeTimeDetector, + TFtAvgTimeDetector, TFtErrorRequestDetector, TFtRPSDetector, ) @@ -33,9 +33,6 @@ __copyright__ = "Copyright (C) 2023-2025 Tempesta Technologies, Inc." __license__ = "GPL2" -import asyncio -import signal - shutdown_task = None @@ -126,7 +123,7 @@ async def main(app_config): intersection_percent=app_config.detector_tft_rps_intersection_percent, block_users_per_iteration=app_config.detector_tft_rps_block_users_per_iteration, ), - TFtAccumulativeTimeDetector.name(): TFtAccumulativeTimeDetector( + TFtAvgTimeDetector.name(): TFtAvgTimeDetector( access_log=clickhouse_client, default_threshold=app_config.detector_tft_time_default_threshold, intersection_percent=app_config.detector_tft_time_intersection_percent, diff --git a/detectors/tft.py b/detectors/tft.py index 67ec1b6..01cdb2f 100644 --- a/detectors/tft.py +++ b/detectors/tft.py @@ -62,23 +62,40 @@ def get_request(self, start_at, finish_at): ) -class TFtAccumulativeTimeDetector(TFtRPSDetector): +class TFtAvgTimeDetector(TFtRPSDetector): @staticmethod def name() -> str: - return "tft_time" + return "tft_avg_time" def get_request(self, start_at, finish_at): return self.shared_filter( f""" + , top_accumulative_response_time as ( + SELECT + tft, + groupUniqArray(tfh) tfh, + groupUniqArray(address) address, + sum(response_time) value + FROM prepared_users + GROUP by tft + ), + top_rps as ( + SELECT + tft, + count() value + FROM prepared_users + GROUP by tft + ) SELECT - array(tft) tft, - groupUniqArray(tfh) tfh, - groupUniqArray(address) address, - sum(response_time) value - FROM prepared_users - GROUP by tft - HAVING + array(tart.tft) tft, + tart.tfh, + tart.address, + tart.value/tr.value value + FROM top_accumulative_response_time tart + JOIN top_rps tr + ON tart.tft = tr.tft + WHERE value >= {self.threshold} LIMIT {self.block_limit_per_check} """, diff --git a/tests/test_detector_tft_based.py b/tests/test_detector_tft_based.py index 87cd3bb..3ff21b0 100644 --- a/tests/test_detector_tft_based.py +++ b/tests/test_detector_tft_based.py @@ -4,7 +4,7 @@ import pytest from detectors.tft import ( - TFtAccumulativeTimeDetector, + TFtAvgTimeDetector, TFtErrorRequestDetector, TFtRPSDetector, ) @@ -28,6 +28,15 @@ async def data(access_log): """ ) +@pytest.fixture +async def time_data(access_log): + await access_log.conn.query( + """ + insert into access_log values + (cast('1751535007' as DateTime64(3, 'UTC')), '127.0.0.4', 0, 1, 200, 0, 60, 'default', '/', '/', 'UserAgent4', 13, 23, 0) + """ + ) + async def test_rps(access_log): detector = TFtRPSDetector( @@ -152,10 +161,16 @@ async def test_errors_forbidden_statuses(access_log): assert users_after == [] -async def test_time(access_log): - detector = TFtAccumulativeTimeDetector( +async def test_time(access_log, time_data): + """ + UserAgent2/3/4 with TFT 13 generates + 1. most requests, TFT COUNT = 4 + 2. top accum response time = TFT SUM(TIME) = 90 + 3. TFT 13 is in TOP of COUNT and SUM(TIME). AVG TIME = SUM(TIME)/COUNT = 90/4 = 22.5 + """ + detector = TFtAvgTimeDetector( access_log=access_log, - default_threshold=Decimal("15"), + default_threshold=Decimal("20"), intersection_percent=Decimal("10"), ) users_before, users_after = await detector.find_users( @@ -170,11 +185,18 @@ async def test_time(access_log): } -async def test_time_with_user_agents(access_log): +async def test_time_with_user_agents(access_log, time_data): + """ + UserAgent2/4 with TFT 13 generates + 1. most requests, TFT COUNT = 3 + 2. top accum response time = TFT SUM(TIME) = 80 + 3. TFT 13 is in TOP of COUNT and SUM(TIME). AVG TIME = SUM(TIME)/COUNT = 80/3 = 26 + """ + await access_log.user_agents_table_insert([["UserAgent"], ["UserAgent3"]]) - detector = TFtAccumulativeTimeDetector( + detector = TFtAvgTimeDetector( access_log=access_log, - default_threshold=Decimal("15"), + default_threshold=Decimal("20"), intersection_percent=Decimal("10"), ) users_before, users_after = await detector.find_users( @@ -189,19 +211,30 @@ async def test_time_with_user_agents(access_log): } -async def test_time_with_persistent_users(access_log): +async def test_time_with_persistent_users(access_log, time_data): + """ + UserAgent4 with TFT 13 generates + 1. most requests, TFT COUNT = 2 + 2. top accum response time = TFT SUM(TIME) = 70 + 3. TFT 13 is in TOP of COUNT and SUM(TIME). AVG TIME = SUM(TIME)/COUNT = 70/2 = 35 + """ + await access_log.persistent_users_table_insert( [ ["127.0.0.3"], ] ) - detector = TFtAccumulativeTimeDetector( + detector = TFtAvgTimeDetector( access_log=access_log, - default_threshold=Decimal("15"), + default_threshold=Decimal("20"), intersection_percent=Decimal("10"), ) users_before, users_after = await detector.find_users( current_time=1751535010, interval=5 ) assert users_before == [] - assert users_after == [] + assert len(users_after) == 1 + assert users_after[0].tft == ['d'] + assert set(users_after[0].ip) == { + IPv4Address("127.0.0.4"), + }