33import clickhouse_connect
44
55from logging import getLogger
6+ from dataclasses import dataclass , field
7+ from collections import defaultdict
68
79from .config import ClickhouseSettings
810from .table_structure import TableStructure , TableField
2830'''
2931
3032
33+ @dataclass
34+ class SingleStats :
35+ duration : float = 0.0
36+ events : int = 0
37+ records : int = 0
38+
39+ def to_dict (self ):
40+ return self .__dict__
41+
42+
43+ @dataclass
44+ class InsertEraseStats :
45+ inserts : SingleStats = field (default_factory = SingleStats )
46+ erases : SingleStats = field (default_factory = SingleStats )
47+
48+ def to_dict (self ):
49+ return {
50+ 'inserts' : self .inserts .to_dict (),
51+ 'erases' : self .erases .to_dict (),
52+ }
53+
54+
55+ @dataclass
56+ class GeneralStats :
57+ general : InsertEraseStats = field (default_factory = InsertEraseStats )
58+ table_stats : dict [str , InsertEraseStats ] = field (default_factory = lambda : defaultdict (InsertEraseStats ))
59+
60+ def on_event (self , table_name : str , is_insert : bool , duration : float , records : int ):
61+ targets = []
62+ if is_insert :
63+ targets .append (self .general .inserts )
64+ targets .append (self .table_stats [table_name ].inserts )
65+
66+ for target in targets :
67+ target .duration += duration
68+ target .events += 1
69+ target .records += records
70+
71+ def to_dict (self ):
72+ results = {'total' : self .general .to_dict ()}
73+ for table_name , table_stats in self .table_stats .items ():
74+ results [table_name ] = table_stats .to_dict ()
75+ return results
76+
77+
3178class ClickhouseApi :
3279 MAX_RETRIES = 5
3380 RETRY_INTERVAL = 30
@@ -44,8 +91,14 @@ def __init__(self, database: str | None, clickhouse_settings: ClickhouseSettings
4491 send_receive_timeout = clickhouse_settings .send_receive_timeout ,
4592 )
4693 self .tables_last_record_version = {} # table_name => last used row version
94+ self .stats = GeneralStats ()
4795 self .execute_command ('SET final = 1;' )
4896
97+ def get_stats (self ):
98+ stats = self .stats .to_dict ()
99+ self .stats = GeneralStats ()
100+ return stats
101+
49102 def get_tables (self ):
50103 result = self .client .query ('SHOW TABLES' )
51104 tables = result .result_rows
@@ -160,16 +213,27 @@ def insert(self, table_name, records, table_structure: TableStructure = None):
160213 if '.' not in full_table_name :
161214 full_table_name = f'{ self .database } .{ table_name } '
162215
216+ duration = 0.0
163217 for attempt in range (ClickhouseApi .MAX_RETRIES ):
164218 try :
219+ t1 = time .time ()
165220 self .client .insert (table = full_table_name , data = records_to_insert )
221+ t2 = time .time ()
222+ duration += (t2 - t1 )
166223 break
167224 except clickhouse_connect .driver .exceptions .OperationalError as e :
168225 logger .error (f'error inserting data: { e } ' , exc_info = e )
169226 if attempt == ClickhouseApi .MAX_RETRIES - 1 :
170227 raise e
171228 time .sleep (ClickhouseApi .RETRY_INTERVAL )
172229
230+ self .stats .on_event (
231+ table_name = table_name ,
232+ duration = duration ,
233+ is_insert = True ,
234+ records = len (records_to_insert ),
235+ )
236+
173237 self .set_last_used_version (table_name , current_version )
174238
175239 def erase (self , table_name , field_name , field_values ):
@@ -181,7 +245,16 @@ def erase(self, table_name, field_name, field_values):
181245 'field_name' : field_name ,
182246 'field_values' : field_values ,
183247 })
248+ t1 = time .time ()
184249 self .execute_command (query )
250+ t2 = time .time ()
251+ duration = t2 - t1
252+ self .stats .on_event (
253+ table_name = table_name ,
254+ duration = duration ,
255+ is_insert = True ,
256+ records = len (field_values ),
257+ )
185258
186259 def drop_database (self , db_name ):
187260 self .execute_command (f'DROP DATABASE IF EXISTS { db_name } ' )
0 commit comments