1-
2- from dataclasses import dataclass , asdict
3- import os
4- from pathlib import Path
5- import sqlite3
6- import time
7- from typing import Any
8-
9- from utils import FileDataInputStream , FileDataOutputStream
10- from timer import Timer # type: ignore
11-
12-
13- @dataclass
14- class Counters :
15- hit : int = 0
16- bytes : int = 0
17- qps : int = 0
18- bandwidth : int = 0
19-
20- cache : Path = Path ("./cache" )
21- cache .mkdir (exist_ok = True , parents = True )
22-
23- db : sqlite3 .Connection = sqlite3 .Connection ("./cache/stats.db" )
24- db .execute ("create table if not exists `Stats`(Time numeric not null, hits numeric default 0, bytes numeric default 0, qps numeric default 0, bandwidth numeric default 0)" )
25- db .commit ()
26- counter = Counters ()
27- last_counter = Counters ()
28- last_time : int = 0
29- def write ():
30- global counter , last_counter
31- with open ("./cache/stats_count.bin" , "wb" ) as w :
32- f = FileDataOutputStream (w )
33- f .writeVarInt (counter .hit )
34- f .writeVarInt (counter .bytes )
35- f .writeVarInt (counter .qps )
36- f .writeVarInt (counter .bandwidth )
37- f .writeVarInt (last_counter .hit )
38- f .writeVarInt (last_counter .bytes )
39-
40- def read ():
41- global counter , last_counter
42- if Path ("./cache/stats_count.bin" ).exists ():
43- with open ("./cache/stats_count.bin" , "rb" ) as r :
44- hit = 0
45- bytes = 0
46- qps = 0
47- bandwidth = 0
48- last_hit = 0
49- last_bytes = 0
50- try :
51- f = FileDataInputStream (r )
52- hit += f .readVarInt ()
53- bytes += f .readVarInt ()
54- qps += f .readVarInt ()
55- bandwidth += f .readVarInt ()
56- last_hit += f .readVarInt ()
57- last_bytes += f .readVarInt ()
58- counter .hit += hit
59- counter .bytes += bytes
60- counter .qps += qps
61- counter .bandwidth += bandwidth
62- last_counter .hit += last_hit
63- last_counter .bytes += last_bytes
64- except :
65- ...
66-
67- def execute (cmd : str , * params ) -> None :
68- global db
69- db .execute (cmd , params )
70- db .commit ()
71-
72- def executemany (* cmds : tuple [str , tuple [Any , ...]]) -> None :
73- global db
74- for cmd in cmds :
75- db .execute (* cmd )
76- db .commit ()
77-
78- def query (cmd : str , * params ) -> list [Any ]:
79- global db
80- cur = db .execute (cmd , params )
81- return cur .fetchone () or []
82-
83- def queryAllData (cmd : str , * params ) -> list [tuple ]:
84- global db
85- cur = db .execute (cmd , params )
86- return cur .fetchall () or []
87-
88- def exists (cmd : str , * params ) -> bool :
89- return len (query (cmd , * params )) != 0
90-
91- def columns (table ):
92- return [q [0 ] for q in queryAllData (f'SHOW COLUMNS FROM { table } ' )]
93-
94- async def addColumns (table , params , data , default = None ):
95- if params not in columns (table ):
96- execute (f'ALTER TABLE { table } ADD COLUMN { params } { data } ' )
97- if default is not None :
98- execute (f'UPDATE { table } SET { params } ={ default } ' )
99-
100- def write_database ():
101- global last_time , counter , extend_counter
102- hits = counter .hit
103- bytes = counter .bytes
104- qps = counter .qps
105- bandwidth = counter .bandwidth
106- if hits == bytes == qps == bandwidth == 0 :
107- return
108- t = int (time .time () // 3600 )
109- if last_time != t and not exists ("select `Time` from `Stats` where `Time` = ?" , t ):
110- execute ("insert into `Stats`(`Time`) values (?)" , t )
111- executemany (("update `Stats` set `hits` = ?, `bytes` = ?, `qps` = ? where `Time` = ?" , (hits , bytes , qps , t )),
112- ("update `Stats` set `bandwidth` = ? where `Time` = ? and `bandwidth` < ?" , (bandwidth , t , bandwidth )))
113- counter .bandwidth = 0
114- if last_time != t :
115- counter .hit = 0
116- counter .bytes = 0
117- counter .bandwidth = 0
118- counter .qps = 0
119- last_counter .hit = 0
120- last_counter .bytes = 0
121- last_time = t
122-
123- def hourly ():
124- t = int (time .time () // 86400 ) * 24
125- data = []
126- for r in queryAllData ("select `Time`, `hits`, `bytes`, `qps`, `bandwidth` from `Stats` where `Time` >= ?" , t ):
127- hour = r [0 ] - t + int (os .environ ["UTC" ])
128- data .append (
129- {"_hour" : hour ,
130- "hits" : r [1 ],
131- "bytes" : r [2 ],
132- "qps" : r [3 ],
133- "bandwidth" : r [4 ]
134- }
135- )
136- return data
137-
138- def days ():
139- t = (int (time .time () // 86400 ) - 30 ) * 24
140- r = queryAllData ("select `Time`, `hits`, `bytes`, `qps`, `bandwidth` from `Stats` where `Time` >= ?" , t )
141- data = []
142- days : dict [int , Counters ] = {}
143- for r in queryAllData ("select `Time`, `hits`, `bytes`, `qps`, `bandwidth` from `Stats` where `Time` >= ?" , t ):
144- hour = (r [0 ] - t + int (os .environ ["UTC" ])) // 24
145- if hour not in days :
146- days [hour ] = Counters ()
147- days [hour ].hit += r [1 ]
148- days [hour ].bytes += r [2 ]
149- days [hour ].qps += r [3 ]
150- days [hour ].bandwidth += r [4 ]
151- for day in sorted (days .keys ()):
152- data .append ({
153- "_day" : day ,
154- ** asdict (days [day ])
155- })
156- return data
157- read ( )
158- Timer .repeat (write , (), 0.01 , 0. 1 )
159- Timer . repeat ( write_database , (), 1 , 1 )
1+
2+ from dataclasses import dataclass , asdict
3+ import os
4+ from pathlib import Path
5+ import sqlite3
6+ import time
7+ from typing import Any
8+
9+ from utils import FileDataInputStream , FileDataOutputStream
10+ from timer import Timer # type: ignore
11+
12+
13+ @dataclass
14+ class Counters :
15+ hit : int = 0
16+ bytes : int = 0
17+ qps : int = 0
18+ bandwidth : int = 0
19+
20+ cache : Path = Path ("./cache" )
21+ cache .mkdir (exist_ok = True , parents = True )
22+
23+ db : sqlite3 .Connection = sqlite3 .Connection ("./cache/stats.db" )
24+ db .execute ("create table if not exists `Stats`(Time numeric not null, hits numeric default 0, bytes numeric default 0, qps numeric default 0, bandwidth numeric default 0)" )
25+ db .commit ()
26+ counter = Counters ()
27+ last_counter = Counters ()
28+ last_time : int = 0
29+ def write ():
30+ global counter , last_counter
31+ with open ("./cache/stats_count.bin" , "wb" ) as w :
32+ f = FileDataOutputStream (w )
33+ f .writeVarInt (counter .hit )
34+ f .writeVarInt (counter .bytes )
35+ f .writeVarInt (counter .qps )
36+ f .writeVarInt (counter .bandwidth )
37+ f .writeVarInt (last_counter .hit )
38+ f .writeVarInt (last_counter .bytes )
39+
40+ def read ():
41+ global counter , last_counter
42+ if Path ("./cache/stats_count.bin" ).exists ():
43+ with open ("./cache/stats_count.bin" , "rb" ) as r :
44+ hit = 0
45+ bytes = 0
46+ qps = 0
47+ bandwidth = 0
48+ last_hit = 0
49+ last_bytes = 0
50+ try :
51+ f = FileDataInputStream (r )
52+ hit += f .readVarInt ()
53+ bytes += f .readVarInt ()
54+ qps += f .readVarInt ()
55+ bandwidth += f .readVarInt ()
56+ last_hit += f .readVarInt ()
57+ last_bytes += f .readVarInt ()
58+ counter .hit += hit
59+ counter .bytes += bytes
60+ counter .qps += qps
61+ counter .bandwidth += bandwidth
62+ last_counter .hit += last_hit
63+ last_counter .bytes += last_bytes
64+ except :
65+ ...
66+
67+ def execute (cmd : str , * params ) -> None :
68+ global db
69+ db .execute (cmd , params )
70+ db .commit ()
71+
72+ def executemany (* cmds : tuple [str , tuple [Any , ...]]) -> None :
73+ global db
74+ for cmd in cmds :
75+ db .execute (* cmd )
76+ db .commit ()
77+
78+ def query (cmd : str , * params ) -> list [Any ]:
79+ global db
80+ cur = db .execute (cmd , params )
81+ return cur .fetchone () or []
82+
83+ def queryAllData (cmd : str , * params ) -> list [tuple ]:
84+ global db
85+ cur = db .execute (cmd , params )
86+ return cur .fetchall () or []
87+
88+ def exists (cmd : str , * params ) -> bool :
89+ return len (query (cmd , * params )) != 0
90+
91+ def columns (table ):
92+ return [q [0 ] for q in queryAllData (f'SHOW COLUMNS FROM { table } ' )]
93+
94+ async def addColumns (table , params , data , default = None ):
95+ if params not in columns (table ):
96+ execute (f'ALTER TABLE { table } ADD COLUMN { params } { data } ' )
97+ if default is not None :
98+ execute (f'UPDATE { table } SET { params } ={ default } ' )
99+
100+ def write_database ():
101+ global last_time , counter
102+ hits = counter .hit
103+ bytes = counter .bytes
104+ qps = counter .qps
105+ bandwidth = counter .bandwidth
106+ if hits == bytes == qps == bandwidth == 0 :
107+ return
108+ t = int (time .time () // 3600 )
109+ if last_time != t and not exists ("select `Time` from `Stats` where `Time` = ?" , t ):
110+ execute ("insert into `Stats`(`Time`) values (?)" , t )
111+ executemany (("update `Stats` set `hits` = ?, `bytes` = ?, `qps` = ? where `Time` = ?" , (hits , bytes , qps , t )),
112+ ("update `Stats` set `bandwidth` = ? where `Time` = ? and `bandwidth` < ?" , (bandwidth , t , bandwidth )))
113+ counter .bandwidth = 0
114+ if last_time != 0 and last_time != t :
115+ counter .hit = 0
116+ counter .bytes = 0
117+ counter .bandwidth = 0
118+ counter .qps = 0
119+ last_counter .hit = 0
120+ last_counter .bytes = 0
121+ last_time = t
122+
123+ def hourly ():
124+ t = int (time .time () // 86400 ) * 24
125+ data = []
126+ for r in queryAllData ("select `Time`, `hits`, `bytes`, `qps`, `bandwidth` from `Stats` where `Time` >= ?" , t ):
127+ hour = r [0 ] - t + int (os .environ ["UTC" ])
128+ data .append (
129+ {"_hour" : hour ,
130+ "hits" : r [1 ],
131+ "bytes" : r [2 ],
132+ "qps" : r [3 ],
133+ "bandwidth" : r [4 ]
134+ }
135+ )
136+ return data
137+
138+ def days ():
139+ t = (int (time .time () // 86400 ) - 30 ) * 24
140+ r = queryAllData ("select `Time`, `hits`, `bytes`, `qps`, `bandwidth` from `Stats` where `Time` >= ?" , t )
141+ data = []
142+ days : dict [int , Counters ] = {}
143+ for r in queryAllData ("select `Time`, `hits`, `bytes`, `qps`, `bandwidth` from `Stats` where `Time` >= ?" , t ):
144+ hour = (r [0 ] - t + int (os .environ ["UTC" ])) // 24
145+ if hour not in days :
146+ days [hour ] = Counters ()
147+ days [hour ].hit += r [1 ]
148+ days [hour ].bytes += r [2 ]
149+ days [hour ].qps += r [3 ]
150+ days [hour ].bandwidth += r [4 ]
151+ for day in sorted (days .keys ()):
152+ data .append ({
153+ "_day" : day ,
154+ ** asdict (days [day ])
155+ })
156+ return data
157+ Timer . repeat ( write , (), 0.01 , 0.1 )
158+ Timer .repeat (write_database , (), 1 , 1 )
159+ read ( )
0 commit comments