Skip to content

Commit cd25347

Browse files
committed
feat: 统计节点带宽
1 parent 4eedf0a commit cd25347

File tree

8 files changed

+187
-24
lines changed

8 files changed

+187
-24
lines changed

assets/js/index.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,6 +1203,9 @@ class Tools {
12031203
static formatDate(d) {
12041204
return `${d.getFullYear().toString().padStart(4, "0")}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d.getDate().toString().padStart(2, "0")}`
12051205
}
1206+
static formatDateHourMinute(d) {
1207+
return `${d.getFullYear().toString().padStart(4, "0")}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d.getDate().toString().padStart(2, "0")} ${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}`
1208+
}
12061209
}
12071210
class UserAuth {
12081211
constructor() {
@@ -1812,12 +1815,29 @@ async function load() {
18121815
var type = $dashboard_locals.advanced_time_switch.current.button
18131816
var day = +type.slice(type.lastIndexOf(".") + 1)
18141817
var ip_access = await $channel.send("response_ip_access", day)
1818+
var resp = {}
1819+
var server_time = $dashboard_locals.info_runtime
1820+
var datetime = (server_time.current_time - server_time.diff / 1000.0 + (+new Date() - server_time.resp_timestamp) / 1000.0);
1821+
var previous;
1822+
if (day == 30) {
1823+
previous = (datetime - ((datetime + UTC_OFFSET) % 86400) - 86400 * day);
1824+
for (var i = 0; i <= day; i++) {
1825+
let key = Tools.formatDate(new Date((previous + 86400 * i) * 1000.0))
1826+
resp[key] = ip_access[key] || 0
1827+
}
1828+
} else {
1829+
previous = (datetime - ((datetime + UTC_OFFSET) % 3600) - 3600 * (day * 24));
1830+
for (var i = 0; i <= day * 24; i++) {
1831+
let key = Tools.formatDateHourMinute(new Date((previous + i * 3600) * 1000.0))
1832+
resp[Tools.formatDateHourMinute(new Date((previous + i * 3600) * 1000.0))] = ip_access[key] || 0
1833+
}
1834+
}
18151835
$dashboard_locals.advanced_ip_access_echarts.base.setOption({
18161836
xAxis: {
1817-
data: Object.keys(ip_access),
1837+
data: Object.keys(resp),
18181838
},
18191839
series: [{
1820-
data: Object.values(ip_access),
1840+
data: Object.values(resp),
18211841
}]
18221842
})
18231843
waitTaskResponse.ip_access = false;

core/cluster.py

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import abc
22
import asyncio
33
from collections import defaultdict, deque
4-
from dataclasses import asdict, dataclass
4+
from dataclasses import asdict, dataclass, field
55
import datetime
66
import enum
77
import hashlib
@@ -943,6 +943,7 @@ def __init__(self, id: str, secret: str):
943943
self.want_enable: bool = False
944944
self.enabled = False
945945
self.enable_count: int = 0
946+
self.keepalive_error: int = 0
946947
self.keepalive_task: Optional[int] = None
947948
self.delay_enable_task: Optional[int] = None
948949
self.counter: defaultdict[storages.iStorage, ClusterCounter] = defaultdict(ClusterCounter)
@@ -1052,6 +1053,7 @@ async def enable(self):
10521053
self.enabled = True
10531054
clusters.status.enter()
10541055
self.enable_count = 0
1056+
self.keepalive_error = 0
10551057
scheduler.cancel(self.keepalive_task)
10561058
self.keepalive_task = scheduler.run_repeat_later(self.keepalive, 1, interval=60)
10571059
logger.tsuccess("cluster.success.enabled", cluster=self.id)
@@ -1083,9 +1085,14 @@ async def keepalive(self):
10831085
}
10841086
)
10851087
if result.err or not result.ack:
1086-
logger.twarning("cluster.warning.kicked_by_remote")
1087-
await self.disable()
1088+
if self.keepalive_error >= 3:
1089+
logger.twarning("cluster.warning.kicked_by_remote", cluster=self.id)
1090+
await self.disable()
1091+
else:
1092+
self.keepalive_error += 1
1093+
logger.twarning("cluster.warning.keepalive", cluster=self.id, count=self.keepalive_error)
10881094
return
1095+
self.keepalive_error = 0
10891096
self.no_storage_counter -= commit_no_storage_counter
10901097
for storage, counter in commit_counter.items():
10911098
self.counter[storage] -= counter
@@ -1290,6 +1297,53 @@ def __init__(self, hash: str, size: int, mtime: float, data: bytes, storage: Opt
12901297
super().__init__(hash=hash, size=size, mtime=mtime, storage=storage)
12911298
self.data = data
12921299

1300+
@dataclass
1301+
class Bandwidth:
1302+
cluster_id: str
1303+
bytes: int
1304+
1305+
@dataclass
1306+
class BandwidthList:
1307+
cluster_id: str
1308+
bytes: deque[int] = field(default_factory=deque)
1309+
1310+
class BandwidthCounter:
1311+
def __init__(
1312+
self
1313+
):
1314+
self.bandwidths: defaultdict[str, defaultdict[int, int]] = defaultdict(lambda: defaultdict(int))
1315+
scheduler.run_repeat_later(self.gc, 600, 5)
1316+
1317+
def hit(self, cluster_id: str, bytes: int):
1318+
current = int(time.monotonic())
1319+
self.bandwidths[cluster_id][current] += bytes
1320+
1321+
1322+
def gc(self):
1323+
current = int(time.monotonic())
1324+
for cluster_id in self.bandwidths:
1325+
for timestamp in list(self.bandwidths[cluster_id]):
1326+
if current - timestamp > 600:
1327+
del self.bandwidths[cluster_id][timestamp]
1328+
1329+
def total(self, interval: int = 1) -> list[Bandwidth]:
1330+
res: list[Bandwidth] = []
1331+
for b in self.get(interval):
1332+
res.append(Bandwidth(b.cluster_id, sum(b.bytes)))
1333+
return res
1334+
1335+
def get(self, interval: int = 1) -> list[BandwidthList]:
1336+
current = int(time.monotonic()) - 1
1337+
bandwidths: list[BandwidthList] = []
1338+
for cluster_id, b in self.bandwidths.items():
1339+
obj = BandwidthList(cluster_id)
1340+
for timestamp in range(current - interval, current + 1):
1341+
if current - timestamp > interval:
1342+
continue
1343+
obj.bytes.append(b[timestamp])
1344+
bandwidths.append(obj)
1345+
return bandwidths
1346+
12931347
ROOT = Path(__file__).parent.parent
12941348

12951349
CHECK_FILE_CONTENT = "Python OpenBMCLAPI"
@@ -1307,7 +1361,7 @@ def __init__(self, hash: str, size: int, mtime: float, data: bytes, storage: Opt
13071361
DEFAULT_MEASURES = [
13081362
10
13091363
]
1310-
1364+
BANDWIDTH_COUNTER = BandwidthCounter()
13111365
routes = web.routes
13121366
aweb = web.web
13131367
clusters = ClusterManager()
@@ -1525,6 +1579,7 @@ async def _(request: aweb.Request):
15251579
size = end - start + 1
15261580

15271581
cluster.hit(file.storage, size)
1582+
BANDWIDTH_COUNTER.hit(cluster.id, size)
15281583
# add database
15291584
# stats
15301585
name = query.get("name")

core/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def disallow_public_dashboard(self):
190190

191191
const = Const()
192192

193-
VERSION = "3.4.2"
193+
VERSION = "3.5.0"
194194
API_VERSION = "1.13.1"
195195
USER_AGENT = f"openbmclapi/{API_VERSION} python-openbmclapi/{VERSION}"
196196
PYTHON_VERSION = ".".join(map(str, (sys.version_info.major, sys.version_info.minor, sys.version_info.micro)))

core/dashboard.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -689,13 +689,13 @@ def _(req_data: Any) -> Any:
689689
if day > 7:
690690
for hour, data in query_data.items():
691691
date = datetime.datetime.fromtimestamp(hour * 3600)
692-
key = f"{date.year}-{date.month}-{date.day}"
692+
key = f"{date.year:04d}-{date.month:02d}-{date.day:02d}"
693693
temp_data[key] |= data.keys()
694694
else:
695695
for hour, data in query_data.items():
696696
date = datetime.datetime.fromtimestamp(hour * 3600)
697697
hour = date.hour
698-
key = f"{date.year}-{date.month}-{date.day} {date.hour:02d}:{date.minute:02d}"
698+
key = f"{date.year:04d}-{date.month:02d}-{date.day:02d} {date.hour:02d}:{date.minute:02d}"
699699
temp_data[key] |= data.keys()
700700
resp_data: defaultdict[str, int] = defaultdict(int)
701701
for key, data in temp_data.items():
@@ -797,6 +797,10 @@ def _(req_data: Any) -> Any:
797797
days_data[storage_id].append(APIStatistics(units.format_date(day * 86400), item.bytes, item.hits))
798798
return days_data
799799

800+
@API.on("clusters_bandwidth")
801+
def _(req_data: Any) -> Any:
802+
return cluster.BANDWIDTH_COUNTER.get(max(1, req_data) if isinstance(req_data, int) else 1)
803+
800804
@DeprecationWarning
801805
async def handle_api(
802806
event: str,

core/storages/alist.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from core.logger import logger
1313
from core.utils import WrapperTQDM
1414

15-
from .base import MeasureFile, iNetworkStorage, Range, DOWNLOAD_DIR, File, CollectionFile
15+
from .base import FileInfo, MeasureFile, iNetworkStorage, Range, DOWNLOAD_DIR, File, CollectionFile
1616

1717

1818
ALIST_TOKEN_DEAULT_EXPIRY = 86400 * 2
@@ -58,8 +58,7 @@ def __init__(
5858
cache_timeout: float = 60,
5959
retries: int = 3,
6060
public_webdav_endpoint: str = "",
61-
public_webdav_username: Optional[str] = None,
62-
public_webdav_password: Optional[str] = None
61+
s3_custom_host: str = ""
6362
):
6463
super().__init__(path, username, password, endpoint, weight, list_concurrent, name, cache_timeout)
6564
self.retries = retries
@@ -70,6 +69,10 @@ def __init__(
7069
}
7170
)
7271
self._public_webdav_endpoint = ""
72+
self._s3_custom_host = s3_custom_host
73+
if s3_custom_host:
74+
urlobject = urlparse.urlparse(s3_custom_host)
75+
self._s3_custom_host = f"{urlobject.scheme}://{urlobject.netloc}{urlobject.path}"
7376
if public_webdav_endpoint:
7477
urlobject = urlparse.urlparse(public_webdav_endpoint)
7578
self._public_webdav_endpoint = f"{urlobject.scheme}://{urlparse.quote(self.username)}:{urlparse.quote(self.password)}@{urlobject.netloc}{urlobject.path}"
@@ -165,11 +168,18 @@ async def get_files(root_id: int):
165168

166169
sem = asyncio.Semaphore(self.list_concurrent)
167170
results = set()
168-
for result in await asyncio.gather(*(
169-
get_files(root_id)
170-
for root_id in Range()
171+
for root_id, result in zip(
172+
Range(),
173+
await asyncio.gather(*(
174+
get_files(root_id)
175+
for root_id in Range()
176+
)
171177
)):
172178
for file in result:
179+
self.filelist[str(self.path / DOWNLOAD_DIR / f"{root_id:02x}" / file["name"])] = FileInfo(
180+
file["size"],
181+
utils.parse_isotime_to_timestamp(file["modified"]),
182+
)
173183
results.add(File(
174184
file["name"],
175185
file["size"],
@@ -219,20 +229,28 @@ async def read_file(self, file: File) -> io.BytesIO:
219229
return io.BytesIO(await resp.read())
220230

221231
async def get_url(self, file: CollectionFile) -> str:
232+
if self._s3_custom_host:
233+
return f"{self._s3_custom_host}{str(self.get_path(file))}"
222234
if self._public_webdav_endpoint:
223235
return f"{self._public_webdav_endpoint}{str(self.get_path(file))}"
224236
info = await self.__info_file(file)
225237
return '' if info.size == -1 else info.raw_url
226238

227239
async def write_file(self, file: MeasureFile | File, content: io.BytesIO):
240+
path = str(self.get_path(file))
228241
result = await self.__action_data(
229242
"put",
230243
"/api/fs/put",
231244
content.getvalue(),
232245
{
233-
"File-Path": urlparse.quote(str(self.get_path(file))),
246+
"File-Path": urlparse.quote(path),
234247
}
235248
)
249+
if result.code == 200:
250+
self.filelist[path] = FileInfo(
251+
result.data["size"],
252+
utils.parse_isotime_to_timestamp(result.data["modified"])
253+
)
236254
return result.code == 200
237255

238256
async def close(self):
@@ -256,6 +274,13 @@ async def get_mtime(self, file: MeasureFile | File) -> float:
256274
return (await self.__info_file(file)).modified
257275

258276
async def get_size(self, file: MeasureFile | File) -> int:
277+
path = str(self.get_path(file))
278+
if path in self.filelist:
279+
return self.filelist[path].size
259280
info = await self.__info_file(file)
281+
self.filelist[path] = FileInfo(
282+
info.size,
283+
info.modified
284+
)
260285
return max(0, info.size)
261286

core/storages/base.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import abc
2+
import collections
23
from dataclasses import dataclass
34
import hashlib
45
import io
@@ -28,6 +29,11 @@ class MeasureFile:
2829
def __hash__(self) -> int:
2930
return hash(self.size)
3031

32+
@dataclass
33+
class FileInfo:
34+
size: int
35+
mtime: float
36+
3137

3238
class FilePath(object):
3339
def __init__(self, path: str) -> None:
@@ -90,6 +96,36 @@ def __rtruediv__(self, __o: Any) -> 'FilePath':
9096
CollectionFile = MeasureFile | File
9197
Range = lambda: range(0, 256)
9298

99+
class FileList:
100+
def __init__(
101+
self,
102+
):
103+
self._data: collections.OrderedDict[str, FileInfo] = collections.OrderedDict()
104+
105+
def __contains__(self, key: str):
106+
return key in self._data
107+
108+
def __getitem__(self, key: str):
109+
return self._data[key]
110+
111+
def __setitem__(self, key: str, value: FileInfo):
112+
self._data[key] = value
113+
114+
def __delitem__(self, key: str):
115+
del self._data[key]
116+
117+
def __iter__(self):
118+
return iter(self._data)
119+
120+
def __len__(self):
121+
return len(self._data)
122+
123+
def __repr__(self):
124+
return f"FileList({self._data})"
125+
126+
def __str__(self):
127+
return f"FileList({self._data})"
128+
93129

94130
class iStorage(metaclass=abc.ABCMeta):
95131
type: str = "_interface"
@@ -105,6 +141,7 @@ def __init__(
105141
self.list_concurrent = list_concurrent
106142
self.current_weight = 0
107143
self._name = name
144+
self.filelist = FileList()
108145

109146
@property
110147
@abc.abstractmethod

0 commit comments

Comments
 (0)