3
3
import clickhouse_connect
4
4
5
5
from logging import getLogger
6
+ from dataclasses import dataclass , field
7
+ from collections import defaultdict
6
8
7
9
from .config import ClickhouseSettings
8
10
from .table_structure import TableStructure , TableField
28
30
'''
29
31
30
32
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
+
31
78
class ClickhouseApi :
32
79
MAX_RETRIES = 5
33
80
RETRY_INTERVAL = 30
@@ -44,8 +91,14 @@ def __init__(self, database: str | None, clickhouse_settings: ClickhouseSettings
44
91
send_receive_timeout = clickhouse_settings .send_receive_timeout ,
45
92
)
46
93
self .tables_last_record_version = {} # table_name => last used row version
94
+ self .stats = GeneralStats ()
47
95
self .execute_command ('SET final = 1;' )
48
96
97
+ def get_stats (self ):
98
+ stats = self .stats .to_dict ()
99
+ self .stats = GeneralStats ()
100
+ return stats
101
+
49
102
def get_tables (self ):
50
103
result = self .client .query ('SHOW TABLES' )
51
104
tables = result .result_rows
@@ -160,16 +213,27 @@ def insert(self, table_name, records, table_structure: TableStructure = None):
160
213
if '.' not in full_table_name :
161
214
full_table_name = f'{ self .database } .{ table_name } '
162
215
216
+ duration = 0.0
163
217
for attempt in range (ClickhouseApi .MAX_RETRIES ):
164
218
try :
219
+ t1 = time .time ()
165
220
self .client .insert (table = full_table_name , data = records_to_insert )
221
+ t2 = time .time ()
222
+ duration += (t2 - t1 )
166
223
break
167
224
except clickhouse_connect .driver .exceptions .OperationalError as e :
168
225
logger .error (f'error inserting data: { e } ' , exc_info = e )
169
226
if attempt == ClickhouseApi .MAX_RETRIES - 1 :
170
227
raise e
171
228
time .sleep (ClickhouseApi .RETRY_INTERVAL )
172
229
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
+
173
237
self .set_last_used_version (table_name , current_version )
174
238
175
239
def erase (self , table_name , field_name , field_values ):
@@ -181,7 +245,16 @@ def erase(self, table_name, field_name, field_values):
181
245
'field_name' : field_name ,
182
246
'field_values' : field_values ,
183
247
})
248
+ t1 = time .time ()
184
249
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
+ )
185
258
186
259
def drop_database (self , db_name ):
187
260
self .execute_command (f'DROP DATABASE IF EXISTS { db_name } ' )
0 commit comments