1+ from collections import defaultdict
12from dataclasses import dataclass
23import time
34from typing import Optional
4- from sqlalchemy import create_engine , Column , Integer , String
5+ from sqlalchemy import create_engine , Column , Integer , String , LargeBinary
56from sqlalchemy .ext .declarative import declarative_base
67from sqlalchemy .orm import sessionmaker
78
9+ from core import logger , scheduler
10+
811@dataclass
912class StorageStatistics :
1013 storage : Optional [str ] = None
@@ -16,6 +19,22 @@ class Statistics:
1619 cluster_id : str
1720 storages : list [StorageStatistics ]
1821
22+ @dataclass
23+ class FileStatisticsKey :
24+ hour : int
25+ cluster_id : str
26+ storage_id : Optional [str ]
27+
28+ def __hash__ (self ):
29+ return hash ((self .hour , self .cluster_id , self .storage_id ))
30+
31+ @dataclass
32+ class FileStatistics :
33+ hits : int = 0
34+ bytes : int = 0
35+
36+
37+
1938engine = create_engine ('sqlite:///database.db' )
2039Base = declarative_base ()
2140
@@ -35,6 +54,18 @@ class StorageStatisticsTable(Base):
3554 hits = Column (String , nullable = False )
3655 bytes = Column (String , nullable = False )
3756
57+ class ResponseTable (Base ):
58+ __tablename__ = 'Responses'
59+ id = Column (Integer , primary_key = True )
60+ hour = Column (Integer , nullable = False )
61+ cluster = Column (String , nullable = False )
62+ storage = Column (String , nullable = False )
63+ success = Column (String , nullable = False )
64+ not_found = Column (String , nullable = False )
65+ error = Column (String , nullable = False )
66+ redirect = Column (String , nullable = False )
67+ ip_tables = Column (LargeBinary , nullable = False )
68+
3869class Session :
3970 def __init__ (self ):
4071 self .session = sessionmaker (bind = engine )()
@@ -47,8 +78,11 @@ def get_session(self):
4778 return self .session
4879
4980SESSION = Session ()
81+ FILE_CACHE : defaultdict [FileStatisticsKey , FileStatistics ] = defaultdict (lambda : FileStatistics ())
5082
83+ @DeprecationWarning
5184def add_statistics (data : Statistics ):
85+ return
5286 session = SESSION .get_session ()
5387 hits , bytes = 0 , 0
5488 hour = get_hour ()
@@ -78,10 +112,87 @@ def add_statistics(data: Statistics):
78112 }
79113 )
80114 session .commit ()
81-
82115
116+ def add_file (cluster : str , storage : Optional [str ], bytes : int ):
117+ global FILE_CACHE
118+ try :
119+ key = FileStatisticsKey (get_hour (), cluster , storage )
120+ FILE_CACHE [key ].bytes += bytes
121+ FILE_CACHE [key ].hits += 1
122+ except :
123+ logger .traceback ()
83124
84125def get_hour ():
85126 return int (time .time () // 3600 )
86127
87- Base .metadata .create_all (engine )
128+ def _commit_storage (hour : int , storage : Optional [str ], hits : int , bytes : int ):
129+ if hits == bytes == 0 :
130+ return False
131+ session = SESSION .get_session ()
132+ q = session .query (StorageStatisticsTable ).filter_by (hour = hour , storage = storage )
133+ r = q .first () or StorageStatisticsTable (hour = hour , storage = storage , hits = str (0 ), bytes = str (0 ))
134+ if q .count () == 0 :
135+ session .add (r )
136+ q .update (
137+ {
138+ 'hits' : str (int (r .hits ) + hits ), # type: ignore
139+ 'bytes' : str (int (r .bytes ) + bytes ) # type: ignore
140+ }
141+ )
142+ return True
143+
144+ def _commit_cluster (hour : int , cluster : str , hits : int , bytes : int ):
145+ if hits == bytes == 0 :
146+ return False
147+ session = SESSION .get_session ()
148+ q = session .query (ClusterStatisticsTable ).filter_by (hour = hour , cluster = cluster )
149+ r = q .first () or ClusterStatisticsTable (hour = hour , cluster = cluster , hits = str (0 ), bytes = str (0 ))
150+ if q .count () == 0 :
151+ session .add (r )
152+ q .update (
153+ {
154+ 'hits' : str (int (r .hits ) + hits ), # type: ignore
155+ 'bytes' : str (int (r .bytes ) + bytes ) # type: ignore
156+ }
157+ )
158+ return True
159+
160+ def commit ():
161+ global FILE_CACHE
162+ total_hits = 0
163+ total_bytes = 0
164+ total_storages = 0
165+ cache = FILE_CACHE .copy ()
166+ session = SESSION .get_session ()
167+ clusters : defaultdict [tuple [int , str ], FileStatistics ] = defaultdict (lambda : FileStatistics (0 , 0 ))
168+ for key , value in FILE_CACHE .items ():
169+ hour = key .hour
170+ cluster = key .cluster_id
171+ storage = key .storage_id
172+ hits = value .hits
173+ bytes = value .bytes
174+ if _commit_storage (hour , storage , hits , bytes ):
175+ total_hits += hits
176+ total_bytes += bytes
177+ total_storages += 1
178+ clusters [(hour , cluster )].hits += hits
179+ clusters [(hour , cluster )].bytes += bytes
180+ for cluster , value in clusters .items ():
181+ _commit_cluster (cluster [0 ], cluster [1 ], value .hits , value .bytes )
182+
183+ logger .success (f'Committing { total_hits } hits and { total_bytes } bytes to database. { total_storages } storages updated' )
184+
185+ session .commit ()
186+ for key , value in cache .items ():
187+ FILE_CACHE [key ].hits -= value .hits
188+ FILE_CACHE [key ].bytes -= value .bytes
189+ if FILE_CACHE [key ].hits == FILE_CACHE [key ].bytes == 0 :
190+ del FILE_CACHE [key ]
191+ ...
192+
193+ async def init ():
194+ Base .metadata .create_all (engine )
195+ scheduler .run_repeat_later (commit , 5 , 10 )
196+
197+ async def unload ():
198+ commit ()
0 commit comments