Compare commits

...

154 Commits

Author SHA1 Message Date
刘祥超
c7d60e01b8 在请求上下文中停用统计的时候也停用对应的指标统计 2024-04-30 00:05:32 +08:00
刘祥超
bbb4b7d31a 版本修改为1.3.7 2024-04-29 23:12:12 +08:00
刘祥超
aa2467c7cf 增加读写线程限制相关测试用例 2024-04-29 23:10:02 +08:00
刘祥超
fa02713ab5 限制读写线程最小值为4,最大值为32 2024-04-29 22:51:51 +08:00
刘祥超
973324ae8f 写入和删除缓存文件时增加线程数限制 2024-04-29 22:36:26 +08:00
刘祥超
7febc6aaf3 读取文件时增加线程数限制 2024-04-29 22:01:55 +08:00
刘祥超
1cede74db3 bfs:修复并发读提示ErrClosed都问题 2024-04-28 19:02:59 +08:00
刘祥超
0dbbc12eb7 bfs:实现对FileHeader中Block数据的compaction 2024-04-28 15:38:13 +08:00
刘祥超
b272de2122 bfs:对FileHeader的压缩和解压使用Pool管理 2024-04-28 10:06:29 +08:00
刘祥超
962cbde4e9 bfs:弹出BFile的时候确保没有正在被使用 2024-04-27 20:55:04 +08:00
刘祥超
6226e31cdc bfs:实现FileHeader的lazy load 2024-04-27 20:11:50 +08:00
刘祥超
fb8c78553a bfs:同一个Hash同时只能有一个Writer,避免多线程读冲突 2024-04-27 18:27:49 +08:00
刘祥超
801f5d4525 bfs:实现maxOpenFiles 2024-04-27 17:29:12 +08:00
刘祥超
04007bf8f1 bfs: 增加读写线程限制 2024-04-27 07:09:14 +08:00
刘祥超
8c044faace bfs: bfs.FS增加锁 (experimental) 2024-04-26 19:26:22 +08:00
刘祥超
f262e76f96 bfs commit (exprimental) 2024-04-26 18:44:29 +08:00
刘祥超
ef057b106e 初步实现bfs原型(仅用于实验) 2024-04-26 17:16:32 +08:00
刘祥超
508f8cbae0 调整KV相关选项尺寸 2024-04-26 17:15:55 +08:00
刘祥超
d8e4ff3d01 增强KV字节编码安全性 2024-04-24 14:33:15 +08:00
刘祥超
f247e57e76 优化KV相关日志 2024-04-24 09:03:15 +08:00
刘祥超
3581ce3763 优化KV相关错误提示/可以从路径直接加载数据库 2024-04-23 11:56:00 +08:00
刘祥超
b66c70de66 ${requestMethod}变量增加默认值GET 2024-04-22 21:32:53 +08:00
刘祥超
d3a4f14999 版本号修改为1.3.6.1 2024-04-22 18:46:29 +08:00
刘祥超
00a7324640 增加缓存索引相关测试用例 2024-04-22 18:46:21 +08:00
刘祥超
d8ea1809dc 优化代码 2024-04-22 12:38:18 +08:00
刘祥超
7f5088958e 版本号修改为1.3.6 2024-04-22 10:51:19 +08:00
刘祥超
eff928d988 修复非80/443端口回源跟随无法正确识别地址的问题 2024-04-22 10:51:11 +08:00
刘祥超
b1e1c3ebbf 启动时判断是否处于空闲时间决定是否执行fstrim 2024-04-21 23:04:51 +08:00
刘祥超
c8f440e24c 优化测试用例 2024-04-21 19:04:31 +08:00
刘祥超
6c5f4e0705 修复HTTP Client相关测试用例 2024-04-21 15:13:45 +08:00
刘祥超
73e6b04026 下载Partial Content时,本地缓存不完整时,只要结束位置为空,就从源站下载 2024-04-21 12:51:44 +08:00
刘祥超
0b09de3378 修复从本地数据库中的第一次加载的IP可能不起作用的问题 2024-04-21 10:33:12 +08:00
刘祥超
69b1355cfe 修复IP名单中“所有IP”类型的IP不起作用的问题 2024-04-21 10:32:04 +08:00
刘祥超
459f8a8215 优化测试用例 2024-04-21 08:48:33 +08:00
刘祥超
a58b9a1fda 版本号修改为1.3.5 2024-04-20 22:39:28 +08:00
刘祥超
6cdcceda86 优化ttlcache测试用例 2024-04-20 22:39:17 +08:00
刘祥超
1cf0f13f7d 修复ttlcache可能缺失回收数据的问题 2024-04-20 22:20:44 +08:00
刘祥超
b1b450bb50 写分区加载文件时,避免单次拓展的文件尺寸太大 2024-04-20 18:59:07 +08:00
刘祥超
a7ad2cea8f 提升Partial Content的范围数据(ranges)写入效率 2024-04-20 17:44:23 +08:00
刘祥超
c9dac96366 优化代码 2024-04-20 15:51:48 +08:00
刘祥超
5b9ffe1225 当使用Range: bytes=0-访问分区文件缓存时,只有已缓存完整才会允许访问
防止有些客户端软件无法根据返回的Content-Range分段读取内容
2024-04-20 15:31:20 +08:00
刘祥超
b965080bb0 忽略一些实验性代码 2024-04-20 08:54:54 +08:00
刘祥超
ed49d74874 更新依赖库 2024-04-20 08:54:04 +08:00
刘祥超
91f817d2d2 优化xxhash和fnv相关代码 2024-04-18 18:25:33 +08:00
刘祥超
96cb816200 优化${serverName}识别相关代码 2024-04-18 17:14:56 +08:00
刘祥超
bf28505d91 请求源站时支持自动gzip回源 2024-04-18 16:18:49 +08:00
刘祥超
ad558cc0e8 硬盘TRIM增加追踪统计 2024-04-18 15:10:08 +08:00
刘祥超
0065543e96 修改空闲时间数据缓存文件名 2024-04-18 12:32:02 +08:00
刘祥超
56219d5082 改进空闲时间算法 2024-04-18 10:02:09 +08:00
刘祥超
ffac717219 优化代码 2024-04-18 08:47:05 +08:00
刘祥超
8794bf5676 回源跟随限制单次请求最大跳转次数为8 2024-04-18 08:29:52 +08:00
刘祥超
7130154bc8 修复回源跟随无法跨不同协议、不同服务器地址的问题 2024-04-17 20:38:00 +08:00
刘祥超
2b01162ab4 优化HTTP客户端管理 2024-04-17 18:24:21 +08:00
刘祥超
3ce30e36bb 增加测试用例 2024-04-17 17:18:11 +08:00
刘祥超
3e183ac824 防盗链功能增加“例外URL“和“限制URL”设置 2024-04-17 15:45:24 +08:00
刘祥超
e1d9e72df0 UA名单支持批量添加关键词 2024-04-17 15:26:33 +08:00
刘祥超
bbb71db0d0 增加三个常用文件扩展名 2024-04-17 14:06:30 +08:00
刘祥超
7310b601d6 内容压缩功能增加“例外URL“和“限制URL”设置 2024-04-17 13:56:38 +08:00
刘祥超
7220c53ced 自动在空闲时间执行定时任务 2024-04-17 13:10:55 +08:00
刘祥超
234887cc1d 内容压缩增加繁忙(busy)检测 2024-04-17 08:36:14 +08:00
刘祥超
fedc4262a0 将版本号修改为1.3.4.4 2024-04-16 13:56:34 +08:00
刘祥超
267ac99d65 再次调低内容压缩Pool的内容长度 2024-04-16 12:55:18 +08:00
刘祥超
140d3bbf59 优化内容压缩
* 取消用户设置的压缩级别,现在压缩级别通过系统自动设置
* Pool中的对象命中100万次时自动销毁,避免内存泄漏
* 降低Pool中的对象数量,避免占用太多内存
* 根据系统CPU线程数自动计算压缩级别,避免消耗太多CPU
* zstd限制解码的最大Window
* zstd使用低内存模式
2024-04-16 11:32:38 +08:00
刘祥超
ae82310b92 优化代码 2024-04-15 19:37:38 +08:00
刘祥超
484b56492b 修复DAU统计导致无法提示进程在运行的问题 2024-04-15 16:06:16 +08:00
刘祥超
167bb2df29 增加 vm.max_map_count 内核参数自动调整 2024-04-15 15:18:22 +08:00
刘祥超
a4d58ffc40 增加测试用例 2024-04-15 14:15:30 +08:00
刘祥超
64a186b0e7 优化代码 2024-04-15 12:20:31 +08:00
刘祥超
e0bed33cc5 版本号修改为1.3.4.3 2024-04-15 09:26:13 +08:00
刘祥超
5e7ea9a884 优化字节缓冲区相关代码 2024-04-15 09:26:00 +08:00
刘祥超
4bdd248f99 优化代码 2024-04-15 08:42:33 +08:00
刘祥超
0789b3d0d5 golangci中禁用tagliatelle 2024-04-14 20:00:22 +08:00
刘祥超
27c61ca0d4 改进单元测试,以便于可以使用go test ../...进行执行 2024-04-14 19:59:56 +08:00
刘祥超
7141add68e 删除一直未实现的Unix协议相关内容 2024-04-14 17:12:53 +08:00
刘祥超
7e11ee98a9 TCP和UDP也支持DAU统计 2024-04-14 16:55:35 +08:00
刘祥超
cdb6134a3b 增加DAU清理指标 2024-04-14 15:47:16 +08:00
刘祥超
3a9fec5196 CC防护增加“忽略常用文件”选项 2024-04-13 20:39:21 +08:00
刘祥超
0396cae524 上传封禁IP时同时传入原始IP值 2024-04-13 16:49:45 +08:00
刘祥超
04c503d644 WAF策略中的IP名单也应用于网站单独设置的WAF规则 2024-04-13 09:58:06 +08:00
刘祥超
f526665633 执行定时任务时自动根据负载进行延后执行 2024-04-12 21:13:19 +08:00
刘祥超
fe069762bb 增加网站每日独立IP统计 2024-04-12 20:12:09 +08:00
刘祥超
62f8ed63be 统一KV存储表命名方式 2024-04-12 13:58:57 +08:00
刘祥超
4fb1e414be 增强加密密钥兼容性 2024-04-12 13:47:07 +08:00
刘祥超
4cc6a4ff31 优化代码 2024-04-12 08:41:14 +08:00
刘祥超
6026d8c3bd 根据系统可用内存调整写入缓存到内存的阈值 2024-04-12 08:17:14 +08:00
刘祥超
dc9cff541e 人机识别验证成功后记录到Cookie,以便于在重启、切换节点时仍能恢复验证状态 2024-04-11 16:14:14 +08:00
刘祥超
b1151479e0 增强加密密钥的稳定性 2024-04-11 15:11:58 +08:00
刘祥超
7bb808c08d 增强加密密钥的稳定性 2024-04-11 14:18:32 +08:00
刘祥超
200718d7dc 提高硬盘TRIM运行时负载要求 2024-04-09 11:39:16 +08:00
刘祥超
d75afa9850 版本号修改为1.3.4.2 2024-04-09 10:05:49 +08:00
刘祥超
8d72e5fdd1 优化KV存储数据表关闭后的错误提示 2024-04-07 14:55:12 +08:00
刘祥超
1fe15d4e3c 优化WAF
* 信息加密使用struct代替map,以缩短加密后内容长度
* 拦截动作、人机识别动作增加是否尝试全局封禁选项
* JSCookie识别动作增加默认设置选项
* 人机识别中传入info参数异常时,尝试跳转到来源地址,避免直接提示invalid request
2024-04-07 14:31:22 +08:00
刘祥超
e87ea9d802 从IP名单中查询IP时增加过期时间检查,防止GC速度慢时可能有1秒延迟 2024-04-07 13:57:01 +08:00
刘祥超
b15b5cfb8f 优化IP名单相关代码 2024-04-06 15:37:14 +08:00
刘祥超
c9eb577c06 更好地支持IPv6/优化IP名单内存用量 2024-04-06 10:07:39 +08:00
刘祥超
ece596d7b9 限制硬盘TRIM只在Linux下执行 2024-04-05 11:47:54 +08:00
刘祥超
58b6d7848a 缓存写入结束时检查Content-Length是否和实际内容长度一致 2024-04-05 11:45:18 +08:00
刘祥超
c4bb92433d 优化缓存从内存刷新到硬盘程序 2024-04-05 10:59:14 +08:00
刘祥超
f6e5201cc3 上传指标数据时忽略不完整数据 2024-04-05 10:05:20 +08:00
刘祥超
b645c76a07 修复修改内存缓存策略导致缓存策略实际容量越来越少的问题 2024-04-05 09:09:31 +08:00
刘祥超
27c56119ae 硬盘TRIM周期从1周改为2天 2024-04-04 19:55:15 +08:00
刘祥超
e034c31333 增加SSD硬盘自动TRIM功能 2024-04-04 17:20:13 +08:00
刘祥超
94ada46abc 删除不需要的代码 2024-04-04 09:30:41 +08:00
刘祥超
4148681bb8 优化MMAP相关功能 2024-04-04 08:28:14 +08:00
刘祥超
e5c5234be8 使用KV存储实现指标统计 2024-04-02 19:54:04 +08:00
刘祥超
750aafdea1 修复KV存储锁无法创建的问题 2024-03-31 16:03:35 +08:00
刘祥超
4d3c214d7d Agent识别库增加KV存储 2024-03-31 15:05:07 +08:00
刘祥超
a5950cc91b 优化代码 2024-03-31 12:54:30 +08:00
刘祥超
4d88629707 优化缓存索引代码 2024-03-31 11:47:50 +08:00
刘祥超
98441ccd3a 优化过期列表管理 2024-03-31 11:47:34 +08:00
刘祥超
2a44283016 优化代码 2024-03-31 10:39:18 +08:00
刘祥超
db9463bdb1 为KV存储增加文件锁 2024-03-31 10:37:06 +08:00
刘祥超
d2e9c8c10f 使用KV数据库来管理IP名单 2024-03-31 10:08:53 +08:00
刘祥超
b4995868c9 提升IP名单性能 2024-03-30 14:42:56 +08:00
刘祥超
10319ab48f 使用MMAP提升缓存读取性能 2024-03-29 19:28:16 +08:00
刘祥超
6146a829ed 版本号修改为1.3.4.1 2024-03-29 17:08:16 +08:00
刘祥超
947ef4eb78 降低默认的CPU线程数(经测试,有约20%的性能提升) 2024-03-29 09:25:53 +08:00
刘祥超
4cfd48f0c4 优化代码 2024-03-28 17:18:00 +08:00
刘祥超
f1d0984031 优化代码 2024-03-28 17:17:34 +08:00
刘祥超
f119690ab7 优化文件句柄缓存相关代码 2024-03-28 08:52:53 +08:00
刘祥超
a77134a601 降低并发写线程数 2024-03-27 20:18:32 +08:00
刘祥超
707ed03f1e 根据系统环境动态调整内容刷入磁盘线程数 2024-03-25 16:36:29 +08:00
刘祥超
07080b13ee 默认启用KV缓存数据库 2024-03-25 11:40:19 +08:00
刘祥超
f78f9136d0 调整pebble相关参数 2024-03-25 11:40:08 +08:00
刘祥超
ee4a011a62 增加KV存储测试用例 2024-03-24 21:37:21 +08:00
刘祥超
23f5503a08 更新依赖库 2024-03-24 20:09:50 +08:00
刘祥超
32a3b2db2e KV存储迭代器增加panic处理 2024-03-24 20:06:00 +08:00
刘祥超
f425b0faf6 优化索引数据库关闭速度 2024-03-24 18:20:42 +08:00
刘祥超
d9cf043cfe KV存储增加panic处理 2024-03-24 17:13:12 +08:00
刘祥超
03e8394bff 初步实验使用KV数据库(pebble)存储缓存索引 2024-03-24 11:25:35 +08:00
刘祥超
22b4a4afbc 在缓存索引中判断缓存是否存在时增加过期时间检查 2024-03-22 10:05:01 +08:00
刘祥超
5a8ee34360 优化代码 2024-03-22 09:21:45 +08:00
刘祥超
1c275e8cfb 优化代码 2024-03-22 08:23:22 +08:00
刘祥超
5d138238de 降低WAF相关日志级别 2024-03-21 18:46:10 +08:00
刘祥超
206f6c8a5d 将以往的caches.FileList修改为caches.SQLiteFileList 2024-03-21 08:37:32 +08:00
刘祥超
8f794be0c1 优化测试用例 2024-03-19 21:08:00 +08:00
刘祥超
eaf8f06b87 集群设置--网站设置中“处理未绑定域名方式”支持跳转到网址 2024-03-16 09:02:52 +08:00
刘祥超
5d5312e897 优化测试脚本 2024-03-14 10:28:12 +08:00
刘祥超
08798b6078 优化页面优化相关代码 2024-03-12 16:24:23 +08:00
刘祥超
bf08170f6d 时间可以快速获取当前小时 2024-03-12 16:20:38 +08:00
刘祥超
6d8be979db 优化systemd服务配置 2024-03-08 18:56:08 +08:00
刘祥超
94aefacba4 HTTP防火墙在启动时检测是否可用 2024-03-08 10:21:59 +08:00
刘祥超
62b971453a 支持设置HTTP防火墙 2024-03-07 19:32:02 +08:00
刘祥超
9dc08d3256 修复部分测试用例 2024-03-07 19:31:35 +08:00
刘祥超
dceb314a32 build-all.sh不编译amd64和arm64之外的架构 2024-03-07 18:46:15 +08:00
刘祥超
3028922835 版本号修改为1.3.3.1 2024-01-29 17:59:13 +08:00
刘祥超
a0a5ba8263 开启403/404重试后,仍然保留最后一次请求的源站内容 2024-01-28 09:27:50 +08:00
刘祥超
99bde764d5 优化压缩相关测试用例 2024-01-25 16:43:22 +08:00
刘祥超
c2274379f6 被设置不支持低版本时HTTP状态码从400改为505 2024-01-24 11:03:17 +08:00
刘祥超
204967f05a upgrade go.mod 2024-01-23 11:26:48 +08:00
刘祥超
8cd25e4dc2 增加若干内存缓存相关基准测试 2024-01-22 21:45:28 +08:00
刘祥超
68d4b1898d 优化测试用例 2024-01-22 11:21:19 +08:00
刘祥超
a37f984871 优化访问日志内存使用 2024-01-22 10:46:48 +08:00
刘祥超
4c143310b5 优化内存缓存写入速度 2024-01-22 10:28:23 +08:00
266 changed files with 16521 additions and 3466 deletions

View File

@@ -71,4 +71,6 @@ linters:
- forcetypeassert - forcetypeassert
- whitespace - whitespace
- noctx - noctx
- rowserrcheck - rowserrcheck
- tagliatelle
- protogetter

View File

@@ -1,9 +1,9 @@
#!/usr/bin/env bash #!/usr/bin/env bash
./build.sh linux amd64 ./build.sh linux amd64
./build.sh linux 386 #./build.sh linux 386
./build.sh linux arm64 ./build.sh linux arm64
./build.sh linux mips64 #./build.sh linux mips64
./build.sh linux mips64le #./build.sh linux mips64le
./build.sh darwin amd64 #./build.sh darwin amd64
./build.sh darwin arm64 #./build.sh darwin arm64

View File

@@ -6,6 +6,9 @@ if [ -z "$TAG" ]; then
TAG="community" TAG="community"
fi fi
# stop node
go run -tags=${TAG} ../cmd/edge-node/main.go stop
# reference: https://pkg.go.dev/cmd/go/internal/test # reference: https://pkg.go.dev/cmd/go/internal/test
go clean -testcache go clean -testcache
go test -timeout 10s -tags="${TAG}" -cover ../... go test -timeout 60s -tags="${TAG}" -cover ../...

90
go.mod
View File

@@ -1,82 +1,98 @@
module github.com/TeaOSLab/EdgeNode module github.com/TeaOSLab/EdgeNode
go 1.18 go 1.21
replace ( replace (
github.com/TeaOSLab/EdgeCommon => ../EdgeCommon github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
github.com/dchest/captcha => github.com/iwind/captcha v0.0.0-20231130092438-ae985686ed84
github.com/fsnotify/fsnotify => github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4 github.com/fsnotify/fsnotify => github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4
github.com/google/nftables => github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4
) )
require ( require (
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000 github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
github.com/andybalholm/brotli v1.0.5 github.com/andybalholm/brotli v1.0.5
github.com/aws/aws-sdk-go v1.44.279 github.com/aws/aws-sdk-go v1.44.279
github.com/baidubce/bce-sdk-go v0.9.153 github.com/baidubce/bce-sdk-go v0.9.170
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670 github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
github.com/cespare/xxhash v1.1.0 github.com/cespare/xxhash/v2 v2.2.0
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/cockroachdb/pebble v1.1.0
github.com/fsnotify/fsnotify v1.6.0 github.com/dchest/captcha v0.0.0-00010101000000-000000000000
github.com/fsnotify/fsnotify v1.7.0
github.com/go-redis/redis/v8 v8.11.5 github.com/go-redis/redis/v8 v8.11.5
github.com/google/gopacket v1.1.19 github.com/google/gopacket v1.1.19
github.com/google/nftables v0.1.0 github.com/google/nftables v0.2.0
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d github.com/iwind/TeaGo v0.0.0-20240411075713-6c1fc9aca7b6
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11 github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
github.com/iwind/gowebp v0.0.0-20231026013903-1c22b0d78cc4 github.com/iwind/gowebp v0.0.0-20240109104518-489f3429f5c5
github.com/klauspost/compress v1.17.2 github.com/klauspost/compress v1.17.8
github.com/mattn/go-sqlite3 v1.14.17 github.com/mattn/go-sqlite3 v1.14.17
github.com/mdlayher/netlink v1.7.1 github.com/mdlayher/netlink v1.7.2
github.com/miekg/dns v1.1.43 github.com/miekg/dns v1.1.43
github.com/mssola/useragent v1.0.0 github.com/mssola/useragent v1.0.0
github.com/pires/go-proxyproto v0.6.1 github.com/pires/go-proxyproto v0.6.1
github.com/qiniu/go-sdk/v7 v7.16.0 github.com/qiniu/go-sdk/v7 v7.16.0
github.com/quic-go/quic-go v0.39.2 github.com/quic-go/quic-go v0.42.0
github.com/shirou/gopsutil/v3 v3.22.2 github.com/shirou/gopsutil/v3 v3.22.2
github.com/tdewolff/minify/v2 v2.20.19
github.com/tencentyun/cos-go-sdk-v5 v0.7.41 github.com/tencentyun/cos-go-sdk-v5 v0.7.41
golang.org/x/image v0.13.0 golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
golang.org/x/net v0.17.0 golang.org/x/image v0.15.0
golang.org/x/sys v0.13.0 golang.org/x/net v0.24.0
google.golang.org/grpc v1.59.0 golang.org/x/sys v0.19.0
google.golang.org/protobuf v1.31.0 google.golang.org/grpc v1.62.1
google.golang.org/protobuf v1.33.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
require ( require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/DataDog/zstd v1.5.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/clbanning/mxj v1.8.4 // indirect github.com/clbanning/mxj v1.8.4 // indirect
github.com/cockroachdb/errors v1.11.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.0.0 // indirect github.com/google/go-querystring v1.0.0 // indirect
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v1.0.0 // indirect github.com/josharian/native v1.1.0 // indirect
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
github.com/mdlayher/socket v0.4.0 // indirect github.com/mdlayher/socket v0.5.0 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/mozillazg/go-httpheader v0.2.1 // indirect github.com/mozillazg/go-httpheader v0.2.1 // indirect
github.com/onsi/ginkgo/v2 v2.13.0 // indirect github.com/onsi/ginkgo/v2 v2.16.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.51.0 // indirect
github.com/prometheus/procfs v0.13.0 // indirect
github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/tdewolff/minify/v2 v2.12.7 // indirect github.com/tdewolff/parse/v2 v2.7.12 // indirect
github.com/tdewolff/parse/v2 v2.6.6 // indirect
github.com/tklauser/go-sysconf v0.3.9 // indirect github.com/tklauser/go-sysconf v0.3.9 // indirect
github.com/tklauser/numcpus v0.3.0 // indirect github.com/tklauser/numcpus v0.3.0 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect
go.uber.org/mock v0.3.0 // indirect go.uber.org/mock v0.4.0 // indirect
golang.org/x/crypto v0.14.0 // indirect golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect golang.org/x/mod v0.17.0 // indirect
golang.org/x/mod v0.13.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/sync v0.4.0 // indirect golang.org/x/text v0.14.0 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/time v0.5.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.20.0 // indirect
golang.org/x/tools v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
) )

223
go.sum
View File

@@ -1,34 +1,46 @@
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible h1:KpbJFXwhVeuxNtBJ74MCGbIoaBok2uZvkD7QXp2+Wis= github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/aws/aws-sdk-go v1.44.279 h1:g23dxnYjIiPlQo0gIKNR0zVPsSvo1bj5frWln+5sfhk= github.com/aws/aws-sdk-go v1.44.279 h1:g23dxnYjIiPlQo0gIKNR0zVPsSvo1bj5frWln+5sfhk=
github.com/aws/aws-sdk-go v1.44.279/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go v1.44.279/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
github.com/baidubce/bce-sdk-go v0.9.153 h1:h5l2EXehe4C4/bdlAPBaULrbnEDgIu5HOYgniN7bjGM= github.com/baidubce/bce-sdk-go v0.9.170 h1:vAr7COuhu6SEf+8f77DVRji45x7TVZtY5kbu9sX7q8g=
github.com/baidubce/bce-sdk-go v0.9.153/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/baidubce/bce-sdk-go v0.9.170/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670 h1:FQPKKjDhzG0T4ew6dm6MGrXb4PRAi8ZmTuYuxcF62BM= github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670 h1:FQPKKjDhzG0T4ew6dm6MGrXb4PRAi8ZmTuYuxcF62BM=
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670/go.mod h1:iRWAFbKXMMkVQyxZ1PfGlkBr1TjATx1zy2MRprV7A3Q= github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670/go.mod h1:iRWAFbKXMMkVQyxZ1PfGlkBr1TjATx1zy2MRprV7A3Q=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U=
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8=
github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
github.com/cockroachdb/pebble v1.1.0 h1:pcFh8CdCIt2kmEpK0OIatq67Ln9uGDYY3d5XnE0LJG4=
github.com/cockroachdb/pebble v1.1.0/go.mod h1:sEHm5NOXxyiAoKWhoFxT8xMgd/f3RA6qUqQ1BXKrh2E=
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE= github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
@@ -41,50 +53,58 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0= github.com/google/nftables v0.2.0 h1:PbJwaBmbVLzpeldoeUKGkE2RjstrjPKMl6oLrfEJ6/8=
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/google/nftables v0.2.0/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible h1:XRAk4HBDLCYEdPLWtKf5iZhOi7lfx17aY0oSO9+mcg8= github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible h1:XRAk4HBDLCYEdPLWtKf5iZhOi7lfx17aY0oSO9+mcg8=
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s= github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible/go.mod h1:l7VUhRbTKCzdOacdT4oWCwATKyvZqUOlOqr0Ous3k4s=
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d h1:XnTIj781NdSipts60fVqbgZorVAKVSRaA6nqVNfBQ1g= github.com/iwind/TeaGo v0.0.0-20240411075713-6c1fc9aca7b6 h1:dS3pTxrLlDQxdoxSUcHkHnr3LHpsBIXv8v2/xw65RN8=
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s= github.com/iwind/TeaGo v0.0.0-20240411075713-6c1fc9aca7b6/go.mod h1:SfqVbWyIPdVflyA6lMgicZzsoGS8pyeLiTRe8/CIpGI=
github.com/iwind/captcha v0.0.0-20231130092438-ae985686ed84 h1:/RtK8t22a/YFkBWiEwxS+JWcDmxAKsu+r+p00c36K0Q=
github.com/iwind/captcha v0.0.0-20231130092438-ae985686ed84/go.mod h1:7zoElIawLp7GUMLcj54K9kbw+jEyvz2K0FDdRRYhvWo=
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4 h1:PKtXlgNHJhdwl5ozio7KRV3n0SckMw+8ZC2NCpRSv8U= github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4 h1:PKtXlgNHJhdwl5ozio7KRV3n0SckMw+8ZC2NCpRSv8U=
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4/go.mod h1:DmAukmDY25inGlriLn0B2jidmvaDR1REOcPXwvzvDPI= github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4/go.mod h1:DmAukmDY25inGlriLn0B2jidmvaDR1REOcPXwvzvDPI=
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11 h1:DaQjoWZhLNxjhIXedVg4/vFEtHkZhK4IjIwsWdyzBLg= github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11 h1:DaQjoWZhLNxjhIXedVg4/vFEtHkZhK4IjIwsWdyzBLg=
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11/go.mod h1:JtbX20untAjUVjZs1ZBtq80f5rJWvwtQNRL6EnuYRnY= github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11/go.mod h1:JtbX20untAjUVjZs1ZBtq80f5rJWvwtQNRL6EnuYRnY=
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A= github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA= github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
github.com/iwind/gowebp v0.0.0-20231026013903-1c22b0d78cc4 h1:eyymORsZg0tZ0niyolYF4nao4sdNUI+Ll40s96tKHBY= github.com/iwind/gowebp v0.0.0-20240109104518-489f3429f5c5 h1:tA0HEDQJ/FM847wc7kVpSgkTfMF1LervEmd2UZQr3Po=
github.com/iwind/gowebp v0.0.0-20231026013903-1c22b0d78cc4/go.mod h1:AYyXDhbbD7q9N6rJff2jrE7pGupaiyvtv3YeyIAQLXk= github.com/iwind/gowebp v0.0.0-20240109104518-489f3429f5c5/go.mod h1:AYyXDhbbD7q9N6rJff2jrE7pGupaiyvtv3YeyIAQLXk=
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4 h1:RPAH9Sj9l/20zH5zU5/iJGszfwPq6eLjoiC/n/asulA=
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4/go.mod h1:7OLL+86wZKfBnAJxNxmdcZ0ebbgdp/A28fcagx9oJqA=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk= github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw= github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -92,13 +112,12 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg= github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ= github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw= github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc= github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
@@ -108,48 +127,60 @@ github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISe
github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o= github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o=
github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y= github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o= github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw= github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
github.com/prometheus/common v0.51.0 h1:vT5R9NAlW4V6k8Wruk7ikrHaHRsrPbduM/cKTOdQM/k=
github.com/prometheus/common v0.51.0/go.mod h1:wHFBCEVWVmHMUpg7pYcOm2QUR/ocQdYSJVQJKnHc3xQ=
github.com/prometheus/procfs v0.13.0 h1:GqzLlQyfsPbaEHaQkO7tbDlriv/4o5Hudv6OXHGKX7o=
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk= github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
github.com/qiniu/go-sdk/v7 v7.16.0 h1:Jt4YOMLuaDfgb/KdVg0O1fYLpv5MDkYe/zV+Ri7gWRs= github.com/qiniu/go-sdk/v7 v7.16.0 h1:Jt4YOMLuaDfgb/KdVg0O1fYLpv5MDkYe/zV+Ri7gWRs=
github.com/qiniu/go-sdk/v7 v7.16.0/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYXOwye868w= github.com/qiniu/go-sdk/v7 v7.16.0/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYXOwye868w=
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs= github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
github.com/quic-go/quic-go v0.39.2 h1:hmwAf8zAHlvan0Y5PXxeeBFZEW17IW99sXLry8I2kjk=
github.com/quic-go/quic-go v0.39.2/go.mod h1:T09QsDQWjLiQ74ZmacDfqZmhY/NLnw5BC40MANNNZ1Q=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks= github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks=
github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY= github.com/shirou/gopsutil/v3 v3.22.2/go.mod h1:WapW1AOOPlHyXr+yOyw3uYx36enocrtSoSBy0L5vUHY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tdewolff/minify/v2 v2.12.7 h1:pBzz2tAfz5VghOXiQIsSta6srhmTeinQPjRDHWoumCA= github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo= github.com/tdewolff/minify/v2 v2.20.19 h1:tX0SR0LUrIqGoLjXnkIzRSIbKJ7PaNnSENLD4CyH6Xo=
github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM=
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ=
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0= github.com/tdewolff/parse/v2 v2.7.12/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.194/go.mod h1:yrBKWhChnDqNz1xuXdSbWXG56XawEq0G5j1lg4VwBD4= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.194/go.mod h1:yrBKWhChnDqNz1xuXdSbWXG56XawEq0G5j1lg4VwBD4=
github.com/tencentyun/cos-go-sdk-v5 v0.7.41 h1:iU0Li/Np78H4SBna0ECQoF3mpgi6ImLXU+doGzPFXGc= github.com/tencentyun/cos-go-sdk-v5 v0.7.41 h1:iU0Li/Np78H4SBna0ECQoF3mpgi6ImLXU+doGzPFXGc=
@@ -159,42 +190,53 @@ github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ= github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8= github.com/tklauser/numcpus v0.3.0/go.mod h1:yFGUr7TUHQRAhyqBcEg0Ge34zDBAsIvJJcyE6boqnA8=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4= github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg=
github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.3.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -204,11 +246,9 @@ golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -218,35 +258,38 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -3,7 +3,6 @@ package apps
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files" "github.com/iwind/TeaGo/files"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
@@ -41,7 +40,7 @@ func (this *LogWriter) Init() {
this.c = make(chan string, 1024) this.c = make(chan string, 1024)
// 异步写入文件 // 异步写入文件
var maxFileSize = 128 * sizes.M // 文件最大尺寸,超出此尺寸则清空 var maxFileSize int64 = 128 << 20 // 文件最大尺寸,超出此尺寸则清空
if fp != nil { if fp != nil {
goman.New(func() { goman.New(func() {
var totalSize int64 = 0 var totalSize int64 = 0

View File

@@ -6,13 +6,14 @@ import "errors"
// 常用的几个错误 // 常用的几个错误
var ( var (
ErrNotFound = errors.New("cache not found") ErrNotFound = errors.New("cache not found")
ErrFileIsWriting = errors.New("the cache file is updating") ErrFileIsWriting = errors.New("the cache file is updating")
ErrInvalidRange = errors.New("invalid range") ErrInvalidRange = errors.New("invalid range")
ErrEntityTooLarge = errors.New("entity too large") ErrEntityTooLarge = errors.New("entity too large")
ErrWritingUnavailable = errors.New("writing unavailable") ErrWritingUnavailable = errors.New("writing unavailable")
ErrWritingQueueFull = errors.New("writing queue full") ErrWritingQueueFull = errors.New("writing queue full")
ErrServerIsBusy = errors.New("server is busy") ErrServerIsBusy = errors.New("server is busy")
ErrUnexpectedContentLength = errors.New("unexpected content length")
) )
// CapacityError 容量错误 // CapacityError 容量错误
@@ -45,3 +46,16 @@ func CanIgnoreErr(err error) bool {
var capacityErr *CapacityError var capacityErr *CapacityError
return errors.As(err, &capacityErr) return errors.As(err, &capacityErr)
} }
func IsCapacityError(err error) bool {
if err == nil {
return false
}
var capacityErr *CapacityError
return errors.As(err, &capacityErr)
}
func IsBusyError(err error) bool {
return err != nil && errors.Is(err, ErrServerIsBusy)
}

View File

@@ -19,20 +19,21 @@ func currentWeek() int32 {
} }
type Item struct { type Item struct {
Type ItemType `json:"type"` Type ItemType `json:"-"`
Key string `json:"key"` Key string `json:"1,omitempty"`
ExpiredAt int64 `json:"expiredAt"` ExpiresAt int64 `json:"2,omitempty"`
StaleAt int64 `json:"staleAt"` StaleAt int64 `json:"3,omitempty"`
HeaderSize int64 `json:"headerSize"` HeaderSize int64 `json:"-"`
BodySize int64 `json:"bodySize"` BodySize int64 `json:"4,omitempty"`
MetaSize int64 `json:"metaSize"` MetaSize int64 `json:"-"`
Host string `json:"host"` // 主机名 Host string `json:"-"` // 主机名
ServerId int64 `json:"serverId"` // 服务ID ServerId int64 `json:"5,omitempty"` // 服务ID
Week int32 `json:"week"` Week int32 `json:"-"`
CreatedAt int64 `json:"6,omitempty"`
} }
func (this *Item) IsExpired() bool { func (this *Item) IsExpired() bool {
return this.ExpiredAt < fasttime.Now().Unix() return this.ExpiresAt < fasttime.Now().Unix()
} }
func (this *Item) TotalSize() int64 { func (this *Item) TotalSize() int64 {

View File

@@ -3,7 +3,9 @@
package caches_test package caches_test
import ( import (
"encoding/json"
"github.com/TeaOSLab/EdgeNode/internal/caches" "github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils" "github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/TeaOSLab/EdgeNode/internal/zero" "github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
@@ -13,6 +15,33 @@ import (
"time" "time"
) )
func TestItem_Marshal(t *testing.T) {
{
var item = &caches.Item{}
data, err := json.Marshal(item)
if err != nil {
t.Fatal(err)
}
t.Log(string(data))
}
{
var item = &caches.Item{
Type: caches.ItemTypeFile,
Key: "https://example.com/index.html",
ExpiresAt: fasttime.Now().Unix(),
HeaderSize: 1 << 10,
BodySize: 1 << 20,
MetaSize: 256,
}
data, err := json.Marshal(item)
if err != nil {
t.Fatal(err)
}
t.Log(string(data))
}
}
func TestItems_Memory(t *testing.T) { func TestItems_Memory(t *testing.T) {
var stat = &runtime.MemStats{} var stat = &runtime.MemStats{}
runtime.ReadMemStats(stat) runtime.ReadMemStats(stat)

View File

@@ -7,9 +7,10 @@ import (
"fmt" "fmt"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs" "github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime" "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"net" "net"
@@ -20,13 +21,13 @@ import (
"time" "time"
) )
type FileListDB struct { type SQLiteFileListDB struct {
dbPath string dbPath string
readDB *dbs.DB readDB *dbs.DB
writeDB *dbs.DB writeDB *dbs.DB
hashMap *FileListHashMap hashMap *SQLiteFileListHashMap
itemsTableName string itemsTableName string
@@ -53,18 +54,18 @@ type FileListDB struct {
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存 listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
} }
func NewFileListDB() *FileListDB { func NewSQLiteFileListDB() *SQLiteFileListDB {
return &FileListDB{ return &SQLiteFileListDB{
hashMap: NewFileListHashMap(), hashMap: NewSQLiteFileListHashMap(),
} }
} }
func (this *FileListDB) Open(dbPath string) error { func (this *SQLiteFileListDB) Open(dbPath string) error {
this.dbPath = dbPath this.dbPath = dbPath
// 动态调整Cache值 // 动态调整Cache值
var cacheSize = 512 var cacheSize = 512
var memoryGB = utils.SystemMemoryGB() var memoryGB = memutils.SystemMemoryGB()
if memoryGB >= 1 { if memoryGB >= 1 {
cacheSize = 256 * memoryGB cacheSize = 256 * memoryGB
} }
@@ -119,7 +120,7 @@ func (this *FileListDB) Open(dbPath string) error {
return nil return nil
} }
func (this *FileListDB) Init() error { func (this *SQLiteFileListDB) Init() error {
this.itemsTableName = "cacheItems" this.itemsTableName = "cacheItems"
// 创建 // 创建
@@ -184,11 +185,11 @@ func (this *FileListDB) Init() error {
return nil return nil
} }
func (this *FileListDB) IsReady() bool { func (this *SQLiteFileListDB) IsReady() bool {
return this.isReady return this.isReady
} }
func (this *FileListDB) Total() (int64, error) { func (this *SQLiteFileListDB) Total() (int64, error) {
// 读取总数量 // 读取总数量
var row = this.readDB.QueryRow(`SELECT COUNT(*) FROM "` + this.itemsTableName + `"`) var row = this.readDB.QueryRow(`SELECT COUNT(*) FROM "` + this.itemsTableName + `"`)
if row.Err() != nil { if row.Err() != nil {
@@ -199,14 +200,14 @@ func (this *FileListDB) Total() (int64, error) {
return total, err return total, err
} }
func (this *FileListDB) AddSync(hash string, item *Item) error { func (this *SQLiteFileListDB) AddSync(hash string, item *Item) error {
this.hashMap.Add(hash) this.hashMap.Add(hash)
if item.StaleAt == 0 { if item.StaleAt == 0 {
item.StaleAt = item.ExpiredAt item.StaleAt = item.ExpiresAt
} }
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix()) _, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiresAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix())
if err != nil { if err != nil {
return this.WrapError(err) return this.WrapError(err)
} }
@@ -214,7 +215,7 @@ func (this *FileListDB) AddSync(hash string, item *Item) error {
return nil return nil
} }
func (this *FileListDB) DeleteSync(hash string) error { func (this *SQLiteFileListDB) DeleteSync(hash string) error {
this.hashMap.Delete(hash) this.hashMap.Delete(hash)
_, err := this.deleteByHashStmt.Exec(hash) _, err := this.deleteByHashStmt.Exec(hash)
@@ -224,7 +225,7 @@ func (this *FileListDB) DeleteSync(hash string) error {
return nil return nil
} }
func (this *FileListDB) ListExpiredItems(count int) (hashList []string, err error) { func (this *SQLiteFileListDB) ListExpiredItems(count int) (hashList []string, err error) {
if !this.isReady { if !this.isReady {
return nil, nil return nil, nil
} }
@@ -252,7 +253,7 @@ func (this *FileListDB) ListExpiredItems(count int) (hashList []string, err erro
return hashList, nil return hashList, nil
} }
func (this *FileListDB) ListLFUItems(count int) (hashList []string, err error) { func (this *SQLiteFileListDB) ListLFUItems(count int) (hashList []string, err error) {
if !this.isReady { if !this.isReady {
return nil, nil return nil, nil
} }
@@ -280,7 +281,7 @@ func (this *FileListDB) ListLFUItems(count int) (hashList []string, err error) {
return hashList, nil return hashList, nil
} }
func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64, err error) { func (this *SQLiteFileListDB) ListHashes(lastId int64) (hashList []string, maxId int64, err error) {
rows, err := this.selectHashListStmt.Query(lastId) rows, err := this.selectHashListStmt.Query(lastId)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
@@ -301,12 +302,12 @@ func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64
return return
} }
func (this *FileListDB) IncreaseHitAsync(hash string) error { func (this *SQLiteFileListDB) IncreaseHitAsync(hash string) error {
// do nothing // do nothing
return nil return nil
} }
func (this *FileListDB) CleanPrefix(prefix string) error { func (this *SQLiteFileListDB) CleanPrefix(prefix string) error {
if !this.isReady { if !this.isReady {
return nil return nil
} }
@@ -327,7 +328,7 @@ func (this *FileListDB) CleanPrefix(prefix string) error {
} }
} }
func (this *FileListDB) CleanMatchKey(key string) error { func (this *SQLiteFileListDB) CleanMatchKey(key string) error {
if !this.isReady { if !this.isReady {
return nil return nil
} }
@@ -372,7 +373,7 @@ func (this *FileListDB) CleanMatchKey(key string) error {
return nil return nil
} }
func (this *FileListDB) CleanMatchPrefix(prefix string) error { func (this *SQLiteFileListDB) CleanMatchPrefix(prefix string) error {
if !this.isReady { if !this.isReady {
return nil return nil
} }
@@ -404,7 +405,7 @@ func (this *FileListDB) CleanMatchPrefix(prefix string) error {
return err return err
} }
func (this *FileListDB) CleanAll() error { func (this *SQLiteFileListDB) CleanAll() error {
if !this.isReady { if !this.isReady {
return nil return nil
} }
@@ -419,7 +420,7 @@ func (this *FileListDB) CleanAll() error {
return nil return nil
} }
func (this *FileListDB) Close() error { func (this *SQLiteFileListDB) Close() error {
if this.isClosed { if this.isClosed {
return nil return nil
} }
@@ -477,19 +478,19 @@ func (this *FileListDB) Close() error {
return errors.New("close database failed: " + strings.Join(errStrings, ", ")) return errors.New("close database failed: " + strings.Join(errStrings, ", "))
} }
func (this *FileListDB) WrapError(err error) error { func (this *SQLiteFileListDB) WrapError(err error) error {
if err == nil { if err == nil {
return nil return nil
} }
return fmt.Errorf("%w (file: %s)", err, this.dbPath) return fmt.Errorf("%w (file: %s)", err, this.dbPath)
} }
func (this *FileListDB) HashMapIsLoaded() bool { func (this *SQLiteFileListDB) HashMapIsLoaded() bool {
return this.hashMapIsLoaded return this.hashMapIsLoaded
} }
// 初始化 // 初始化
func (this *FileListDB) initTables(times int) error { func (this *SQLiteFileListDB) initTables(times int) error {
{ {
// expiredAt - 过期时间,用来判断有无过期 // expiredAt - 过期时间,用来判断有无过期
// staleAt - 过时缓存最大时间,用来清理缓存 // staleAt - 过时缓存最大时间,用来清理缓存
@@ -553,7 +554,7 @@ ON "` + this.itemsTableName + `" (
return nil return nil
} }
func (this *FileListDB) listOlderItems(count int) (hashList []string, err error) { func (this *SQLiteFileListDB) listOlderItems(count int) (hashList []string, err error) {
rows, err := this.listOlderItemsStmt.Query(count) rows, err := this.listOlderItemsStmt.Query(count)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -574,7 +575,7 @@ func (this *FileListDB) listOlderItems(count int) (hashList []string, err error)
return hashList, nil return hashList, nil
} }
func (this *FileListDB) shouldRecover() bool { func (this *SQLiteFileListDB) shouldRecover() bool {
result, err := this.writeDB.Query("pragma integrity_check;") result, err := this.writeDB.Query("pragma integrity_check;")
if err != nil { if err != nil {
logs.Println(result) logs.Println(result)
@@ -592,14 +593,14 @@ func (this *FileListDB) shouldRecover() bool {
} }
// 删除数据库文件 // 删除数据库文件
func (this *FileListDB) deleteDB() { func (this *SQLiteFileListDB) deleteDB() {
_ = os.Remove(this.dbPath) _ = fsutils.Remove(this.dbPath)
_ = os.Remove(this.dbPath + "-shm") _ = fsutils.Remove(this.dbPath + "-shm")
_ = os.Remove(this.dbPath + "-wal") _ = fsutils.Remove(this.dbPath + "-wal")
} }
// 加载Hash列表 // 加载Hash列表
func (this *FileListDB) loadHashMap() { func (this *SQLiteFileListDB) loadHashMap() {
this.hashMapIsLoaded = false this.hashMapIsLoaded = false
err := this.hashMap.Load(this) err := this.hashMap.Load(this)

View File

@@ -18,7 +18,7 @@ func TestFileListDB_ListLFUItems(t *testing.T) {
return return
} }
var db = caches.NewFileListDB() var db = caches.NewSQLiteFileListDB()
defer func() { defer func() {
_ = db.Close() _ = db.Close()
@@ -46,7 +46,7 @@ func TestFileListDB_CleanMatchKey(t *testing.T) {
return return
} }
var db = caches.NewFileListDB() var db = caches.NewSQLiteFileListDB()
defer func() { defer func() {
_ = db.Close() _ = db.Close()
@@ -78,7 +78,7 @@ func TestFileListDB_CleanMatchPrefix(t *testing.T) {
return return
} }
var db = caches.NewFileListDB() var db = caches.NewSQLiteFileListDB()
defer func() { defer func() {
_ = db.Close() _ = db.Close()
@@ -110,7 +110,7 @@ func TestFileListDB_Memory(t *testing.T) {
return return
} }
var db = caches.NewFileListDB() var db = caches.NewSQLiteFileListDB()
defer func() { defer func() {
_ = db.Close() _ = db.Close()

View File

@@ -3,7 +3,7 @@
package caches package caches
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/utils" memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
"github.com/TeaOSLab/EdgeNode/internal/zero" "github.com/TeaOSLab/EdgeNode/internal/zero"
"math/big" "math/big"
"sync" "sync"
@@ -17,8 +17,8 @@ var bigIntPool = sync.Pool{
}, },
} }
// FileListHashMap 文件Hash列表 // SQLiteFileListHashMap 文件Hash列表
type FileListHashMap struct { type SQLiteFileListHashMap struct {
m []map[uint64]zero.Zero m []map[uint64]zero.Zero
lockers []*sync.RWMutex lockers []*sync.RWMutex
@@ -27,7 +27,7 @@ type FileListHashMap struct {
isReady bool isReady bool
} }
func NewFileListHashMap() *FileListHashMap { func NewSQLiteFileListHashMap() *SQLiteFileListHashMap {
var m = make([]map[uint64]zero.Zero, HashMapSharding) var m = make([]map[uint64]zero.Zero, HashMapSharding)
var lockers = make([]*sync.RWMutex, HashMapSharding) var lockers = make([]*sync.RWMutex, HashMapSharding)
@@ -36,7 +36,7 @@ func NewFileListHashMap() *FileListHashMap {
lockers[i] = &sync.RWMutex{} lockers[i] = &sync.RWMutex{}
} }
return &FileListHashMap{ return &SQLiteFileListHashMap{
m: m, m: m,
lockers: lockers, lockers: lockers,
isAvailable: false, isAvailable: false,
@@ -44,9 +44,9 @@ func NewFileListHashMap() *FileListHashMap {
} }
} }
func (this *FileListHashMap) Load(db *FileListDB) error { func (this *SQLiteFileListHashMap) Load(db *SQLiteFileListDB) error {
// 如果系统内存过小,我们不缓存 // 如果系统内存过小,我们不缓存
if utils.SystemMemoryGB() < 3 { if memutils.SystemMemoryGB() < 3 {
return nil return nil
} }
@@ -75,7 +75,7 @@ func (this *FileListHashMap) Load(db *FileListDB) error {
return nil return nil
} }
func (this *FileListHashMap) Add(hash string) { func (this *SQLiteFileListHashMap) Add(hash string) {
if !this.isAvailable { if !this.isAvailable {
return return
} }
@@ -87,7 +87,7 @@ func (this *FileListHashMap) Add(hash string) {
this.lockers[index].Unlock() this.lockers[index].Unlock()
} }
func (this *FileListHashMap) AddHashes(hashes []string) { func (this *SQLiteFileListHashMap) AddHashes(hashes []string) {
if !this.isAvailable { if !this.isAvailable {
return return
} }
@@ -100,7 +100,7 @@ func (this *FileListHashMap) AddHashes(hashes []string) {
} }
} }
func (this *FileListHashMap) Delete(hash string) { func (this *SQLiteFileListHashMap) Delete(hash string) {
if !this.isAvailable { if !this.isAvailable {
return return
} }
@@ -111,7 +111,7 @@ func (this *FileListHashMap) Delete(hash string) {
this.lockers[index].Unlock() this.lockers[index].Unlock()
} }
func (this *FileListHashMap) Exist(hash string) bool { func (this *SQLiteFileListHashMap) Exist(hash string) bool {
if !this.isAvailable { if !this.isAvailable {
return true return true
} }
@@ -128,7 +128,7 @@ func (this *FileListHashMap) Exist(hash string) bool {
return ok return ok
} }
func (this *FileListHashMap) Clean() { func (this *SQLiteFileListHashMap) Clean() {
for i := 0; i < HashMapSharding; i++ { for i := 0; i < HashMapSharding; i++ {
this.lockers[i].Lock() this.lockers[i].Lock()
} }
@@ -143,11 +143,11 @@ func (this *FileListHashMap) Clean() {
} }
} }
func (this *FileListHashMap) IsReady() bool { func (this *SQLiteFileListHashMap) IsReady() bool {
return this.isReady return this.isReady
} }
func (this *FileListHashMap) Len() int { func (this *SQLiteFileListHashMap) Len() int {
for i := 0; i < HashMapSharding; i++ { for i := 0; i < HashMapSharding; i++ {
this.lockers[i].Lock() this.lockers[i].Lock()
} }
@@ -164,15 +164,15 @@ func (this *FileListHashMap) Len() int {
return count return count
} }
func (this *FileListHashMap) SetIsAvailable(isAvailable bool) { func (this *SQLiteFileListHashMap) SetIsAvailable(isAvailable bool) {
this.isAvailable = isAvailable this.isAvailable = isAvailable
} }
func (this *FileListHashMap) SetIsReady(isReady bool) { func (this *SQLiteFileListHashMap) SetIsReady(isReady bool) {
this.isReady = isReady this.isReady = isReady
} }
func (this *FileListHashMap) bigInt(hash string) (hashInt uint64, index int) { func (this *SQLiteFileListHashMap) bigInt(hash string) (hashInt uint64, index int) {
var bigInt = bigIntPool.Get().(*big.Int) var bigInt = bigIntPool.Get().(*big.Int)
bigInt.SetString(hash, 16) bigInt.SetString(hash, 16)
hashInt = bigInt.Uint64() hashInt = bigInt.Uint64()

View File

@@ -22,7 +22,7 @@ func TestFileListHashMap_Memory(t *testing.T) {
var stat1 = &runtime.MemStats{} var stat1 = &runtime.MemStats{}
runtime.ReadMemStats(stat1) runtime.ReadMemStats(stat1)
var m = caches.NewFileListHashMap() var m = caches.NewSQLiteFileListHashMap()
m.SetIsAvailable(true) m.SetIsAvailable(true)
for i := 0; i < 1_000_000; i++ { for i := 0; i < 1_000_000; i++ {
@@ -91,7 +91,7 @@ func TestFileListHashMap_Load(t *testing.T) {
return return
} }
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList) var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1").(*caches.SQLiteFileList)
defer func() { defer func() {
_ = list.Close() _ = list.Close()
@@ -102,7 +102,7 @@ func TestFileListHashMap_Load(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
var m = caches.NewFileListHashMap() var m = caches.NewSQLiteFileListHashMap()
var before = time.Now() var before = time.Now()
var db = list.GetDB("abc") var db = list.GetDB("abc")
err = m.Load(db) err = m.Load(db)
@@ -121,7 +121,7 @@ func TestFileListHashMap_Load(t *testing.T) {
func TestFileListHashMap_Delete(t *testing.T) { func TestFileListHashMap_Delete(t *testing.T) {
var a = assert.NewAssertion(t) var a = assert.NewAssertion(t)
var m = caches.NewFileListHashMap() var m = caches.NewSQLiteFileListHashMap()
m.SetIsReady(true) m.SetIsReady(true)
m.SetIsAvailable(true) m.SetIsAvailable(true)
m.Add("a") m.Add("a")
@@ -131,7 +131,7 @@ func TestFileListHashMap_Delete(t *testing.T) {
} }
func TestFileListHashMap_Clean(t *testing.T) { func TestFileListHashMap_Clean(t *testing.T) {
var m = caches.NewFileListHashMap() var m = caches.NewSQLiteFileListHashMap()
m.SetIsAvailable(true) m.SetIsAvailable(true)
m.Clean() m.Clean()
m.Add("a") m.Add("a")
@@ -149,7 +149,7 @@ func Benchmark_BigInt(b *testing.B) {
} }
func BenchmarkFileListHashMap_Exist(b *testing.B) { func BenchmarkFileListHashMap_Exist(b *testing.B) {
var m = caches.NewFileListHashMap() var m = caches.NewSQLiteFileListHashMap()
m.SetIsAvailable(true) m.SetIsAvailable(true)
m.SetIsReady(true) m.SetIsReady(true)

View File

@@ -0,0 +1,311 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches
import (
"fmt"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
"github.com/iwind/TeaGo/types"
"strings"
"testing"
)
const countKVStores = 10
type KVFileList struct {
dir string
stores [countKVStores]*KVListFileStore
onAdd func(item *Item)
onRemove func(item *Item)
}
func NewKVFileList(dir string) *KVFileList {
dir = strings.TrimSuffix(dir, "/")
var stores = [countKVStores]*KVListFileStore{}
for i := 0; i < countKVStores; i++ {
stores[i] = NewKVListFileStore(dir + "/db-" + types.String(i) + ".store")
}
return &KVFileList{
dir: dir,
stores: stores,
}
}
// Init 初始化
func (this *KVFileList) Init() error {
remotelogs.Println("CACHE", "loading database from '"+this.dir+"' ...")
var group = goman.NewTaskGroup()
var lastErr error
for _, store := range this.stores {
var storeCopy = store
group.Run(func() {
err := storeCopy.Open()
if err != nil {
lastErr = fmt.Errorf("open store '"+storeCopy.Path()+"' failed: %w", err)
}
})
}
group.Wait()
return lastErr
}
// Reset 重置数据
func (this *KVFileList) Reset() error {
// do nothing
return nil
}
// Add 添加内容
func (this *KVFileList) Add(hash string, item *Item) error {
err := this.getStore(hash).AddItem(hash, item)
if err != nil {
return err
}
if this.onAdd != nil {
this.onAdd(item)
}
return nil
}
// Exist 检查内容是否存在
func (this *KVFileList) Exist(hash string) (bool, int64, error) {
return this.getStore(hash).ExistItem(hash)
}
// ExistQuick 快速检查内容是否存在
func (this *KVFileList) ExistQuick(hash string) (bool, error) {
return this.getStore(hash).ExistQuickItem(hash)
}
// CleanPrefix 清除某个前缀的缓存
func (this *KVFileList) CleanPrefix(prefix string) error {
var group = goman.NewTaskGroup()
var lastErr error
for _, store := range this.stores {
var storeCopy = store
group.Run(func() {
err := storeCopy.CleanItemsWithPrefix(prefix)
if err != nil {
lastErr = err
}
})
}
group.Wait()
return lastErr
}
// CleanMatchKey 清除通配符匹配的Key
func (this *KVFileList) CleanMatchKey(key string) error {
var group = goman.NewTaskGroup()
var lastErr error
for _, store := range this.stores {
var storeCopy = store
group.Run(func() {
err := storeCopy.CleanItemsWithWildcardKey(key)
if err != nil {
lastErr = err
}
})
}
group.Wait()
return lastErr
}
// CleanMatchPrefix 清除通配符匹配的前缀
func (this *KVFileList) CleanMatchPrefix(prefix string) error {
var group = goman.NewTaskGroup()
var lastErr error
for _, store := range this.stores {
var storeCopy = store
group.Run(func() {
err := storeCopy.CleanItemsWithWildcardPrefix(prefix)
if err != nil {
lastErr = err
}
})
}
group.Wait()
return lastErr
}
// Remove 删除内容
func (this *KVFileList) Remove(hash string) error {
err := this.getStore(hash).RemoveItem(hash)
if err != nil {
return err
}
if this.onRemove != nil {
// when remove file item, no any extra information needed
this.onRemove(nil)
}
return nil
}
// Purge 清理过期数据
func (this *KVFileList) Purge(count int, callback func(hash string) error) (int, error) {
count /= countKVStores
if count <= 0 {
count = 100
}
var countFound = 0
var lastErr error
for _, store := range this.stores {
purgeCount, err := store.PurgeItems(count, callback)
countFound += purgeCount
if err != nil {
lastErr = err
}
}
return countFound, lastErr
}
// PurgeLFU 清理LFU数据
func (this *KVFileList) PurgeLFU(count int, callback func(hash string) error) error {
count /= countKVStores
if count <= 0 {
count = 100
}
var lastErr error
for _, store := range this.stores {
err := store.PurgeLFUItems(count, callback)
if err != nil {
lastErr = err
}
}
return lastErr
}
// CleanAll 清除所有缓存
func (this *KVFileList) CleanAll() error {
var group = goman.NewTaskGroup()
var lastErr error
for _, store := range this.stores {
var storeCopy = store
group.Run(func() {
err := storeCopy.RemoveAllItems()
if err != nil {
lastErr = err
}
})
}
group.Wait()
return lastErr
}
// Stat 统计
func (this *KVFileList) Stat(check func(hash string) bool) (*Stat, error) {
var stat = &Stat{}
var group = goman.NewTaskGroup()
var lastErr error
for _, store := range this.stores {
var storeCopy = store
group.Run(func() {
storeStat, err := storeCopy.StatItems()
if err != nil {
lastErr = err
return
}
group.Lock()
stat.Size += storeStat.Size
stat.ValueSize += storeStat.ValueSize
stat.Count += storeStat.Count
group.Unlock()
})
}
group.Wait()
return stat, lastErr
}
// Count 总数量
func (this *KVFileList) Count() (int64, error) {
var count int64
var group = goman.NewTaskGroup()
var lastErr error
for _, store := range this.stores {
var storeCopy = store
group.Run(func() {
countStoreItems, err := storeCopy.CountItems()
if err != nil {
lastErr = err
return
}
group.Lock()
count += countStoreItems
group.Unlock()
})
}
group.Wait()
return count, lastErr
}
// OnAdd 添加事件
func (this *KVFileList) OnAdd(fn func(item *Item)) {
this.onAdd = fn
}
// OnRemove 删除事件
func (this *KVFileList) OnRemove(fn func(item *Item)) {
this.onRemove = fn
}
// Close 关闭
func (this *KVFileList) Close() error {
var lastErr error
var group = goman.NewTaskGroup()
for _, store := range this.stores {
var storeCopy = store
group.Run(func() {
err := storeCopy.Close()
if err != nil {
lastErr = err
}
})
}
group.Wait()
return lastErr
}
// IncreaseHit 增加点击量
func (this *KVFileList) IncreaseHit(hash string) error {
// do nothing
return nil
}
func (this *KVFileList) TestInspect(t *testing.T) error {
for _, store := range this.stores {
err := store.TestInspect(t)
if err != nil {
return err
}
}
return nil
}
func (this *KVFileList) getStore(hash string) *KVListFileStore {
return this.stores[fnv.HashString(hash)%countKVStores]
}

View File

@@ -0,0 +1,66 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches
import (
"encoding/binary"
"encoding/json"
"strings"
)
// ItemKVEncoder item encoder
type ItemKVEncoder[T interface{ *Item }] struct {
}
func NewItemKVEncoder[T interface{ *Item }]() *ItemKVEncoder[T] {
return &ItemKVEncoder[T]{}
}
func (this *ItemKVEncoder[T]) Encode(value T) ([]byte, error) {
return json.Marshal(value)
}
func (this *ItemKVEncoder[T]) EncodeField(value T, fieldName string) ([]byte, error) {
switch fieldName {
case "createdAt":
var b = make([]byte, 4)
var createdAt = any(value).(*Item).CreatedAt
binary.BigEndian.PutUint32(b, uint32(createdAt))
return b, nil
case "staleAt":
var b = make([]byte, 4)
var staleAt = any(value).(*Item).StaleAt
if staleAt < 0 {
staleAt = 0
}
binary.BigEndian.PutUint32(b, uint32(staleAt))
return b, nil
case "serverId":
var b = make([]byte, 4)
var serverId = any(value).(*Item).ServerId
if serverId < 0 {
serverId = 0
}
binary.BigEndian.PutUint32(b, uint32(serverId))
return b, nil
case "key":
return []byte(any(value).(*Item).Key), nil
case "wildKey":
var key = any(value).(*Item).Key
var dotIndex = strings.Index(key, ".")
if dotIndex > 0 {
var slashIndex = strings.LastIndex(key[:dotIndex], "/")
if slashIndex > 0 {
key = key[:dotIndex][:slashIndex+1] + "*" + key[dotIndex:]
}
}
return []byte(key), nil
}
return nil, nil
}
func (this *ItemKVEncoder[T]) Decode(valueBytes []byte) (value T, err error) {
err = json.Unmarshal(valueBytes, &value)
return
}

View File

@@ -0,0 +1,69 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches_test
import (
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestItemKVEncoder_EncodeField(t *testing.T) {
var a = assert.NewAssertion(t)
var encoder = caches.NewItemKVEncoder[*caches.Item]()
{
key, err := encoder.EncodeField(&caches.Item{
Key: "https://example.com/index.html",
}, "key")
if err != nil {
t.Fatal(err)
}
t.Log("key:", string(key))
a.IsTrue(string(key) == "https://example.com/index.html")
}
{
key, err := encoder.EncodeField(&caches.Item{
Key: "",
}, "wildKey")
if err != nil {
t.Fatal(err)
}
t.Log("key:", string(key))
a.IsTrue(string(key) == "")
}
{
key, err := encoder.EncodeField(&caches.Item{
Key: "example.com/index.html",
}, "wildKey")
if err != nil {
t.Fatal(err)
}
t.Log("key:", string(key))
a.IsTrue(string(key) == "example.com/index.html")
}
{
key, err := encoder.EncodeField(&caches.Item{
Key: "https://example.com/index.html",
}, "wildKey")
if err != nil {
t.Fatal(err)
}
t.Log("key:", string(key))
a.IsTrue(string(key) == "https://*.com/index.html")
}
{
key, err := encoder.EncodeField(&caches.Item{
Key: "https://www.example.com/index.html",
}, "wildKey")
if err != nil {
t.Fatal(err)
}
t.Log("key:", string(key))
a.IsTrue(string(key) == "https://*.example.com/index.html")
}
}

View File

@@ -0,0 +1,481 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches
import (
"errors"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/utils/kvstore"
"github.com/cockroachdb/pebble"
"regexp"
"strings"
"testing"
)
type KVListFileStore struct {
path string
rawStore *kvstore.Store
// tables
itemsTable *kvstore.Table[*Item]
rawIsReady bool
}
func NewKVListFileStore(path string) *KVListFileStore {
return &KVListFileStore{
path: path,
}
}
func (this *KVListFileStore) Open() error {
var reg = regexp.MustCompile(`^(.+)/([\w-]+)(\.store)$`)
var matches = reg.FindStringSubmatch(this.path)
if len(matches) != 4 {
return errors.New("invalid path '" + this.path + "'")
}
var dir = matches[1]
var name = matches[2]
rawStore, err := kvstore.OpenStoreDir(dir, name)
if err != nil {
return err
}
this.rawStore = rawStore
db, err := rawStore.NewDB("cache")
if err != nil {
return err
}
{
table, tableErr := kvstore.NewTable[*Item]("items", NewItemKVEncoder[*Item]())
if tableErr != nil {
return tableErr
}
err = table.AddFields("staleAt", "key", "wildKey", "createdAt")
if err != nil {
return err
}
db.AddTable(table)
this.itemsTable = table
}
this.rawIsReady = true
return nil
}
func (this *KVListFileStore) Path() string {
return this.path
}
func (this *KVListFileStore) AddItem(hash string, item *Item) error {
if !this.isReady() {
return nil
}
var currentTime = fasttime.Now().Unix()
if item.ExpiresAt <= currentTime {
return nil
}
if item.CreatedAt <= 0 {
item.CreatedAt = currentTime
}
if item.StaleAt <= 0 {
item.StaleAt = item.ExpiresAt + DefaultStaleCacheSeconds
}
return this.itemsTable.Set(hash, item)
}
func (this *KVListFileStore) ExistItem(hash string) (bool, int64, error) {
if !this.isReady() {
return false, -1, nil
}
item, err := this.itemsTable.Get(hash)
if err != nil {
if kvstore.IsNotFound(err) {
return false, -1, nil
}
return false, -1, err
}
if item == nil {
return false, -1, nil
}
return item.ExpiresAt > fasttime.Now().Unix(), item.HeaderSize + item.BodySize, nil
}
func (this *KVListFileStore) ExistQuickItem(hash string) (bool, error) {
if !this.isReady() {
return false, nil
}
return this.itemsTable.Exist(hash)
}
func (this *KVListFileStore) RemoveItem(hash string) error {
if !this.isReady() {
return nil
}
return this.itemsTable.Delete(hash)
}
func (this *KVListFileStore) RemoveAllItems() error {
if !this.isReady() {
return nil
}
return this.itemsTable.Truncate()
}
func (this *KVListFileStore) PurgeItems(count int, callback func(hash string) error) (int, error) {
if !this.isReady() {
return 0, nil
}
var countFound int
var currentTime = fasttime.Now().Unix()
var hashList []string
err := this.itemsTable.
Query().
FieldAsc("staleAt").
Limit(count).
FindAll(func(tx *kvstore.Tx[*Item], item kvstore.Item[*Item]) (goNext bool, err error) {
if item.Value == nil {
return true, nil
}
if item.Value.StaleAt < currentTime {
countFound++
hashList = append(hashList, item.Key)
return true, nil
}
return false, nil
})
if err != nil {
return 0, err
}
// delete items
if len(hashList) > 0 {
txErr := this.itemsTable.WriteTx(func(tx *kvstore.Tx[*Item]) error {
for _, hash := range hashList {
deleteErr := tx.Delete(hash)
if deleteErr != nil {
return deleteErr
}
}
return nil
})
if txErr != nil {
return 0, txErr
}
for _, hash := range hashList {
callbackErr := callback(hash)
if callbackErr != nil {
return 0, callbackErr
}
}
}
return countFound, nil
}
func (this *KVListFileStore) PurgeLFUItems(count int, callback func(hash string) error) error {
if !this.isReady() {
return nil
}
var hashList []string
err := this.itemsTable.
Query().
FieldAsc("createdAt").
Limit(count).
FindAll(func(tx *kvstore.Tx[*Item], item kvstore.Item[*Item]) (goNext bool, err error) {
if item.Value != nil {
hashList = append(hashList, item.Key)
}
return true, nil
})
if err != nil {
return err
}
// delete items
if len(hashList) > 0 {
txErr := this.itemsTable.WriteTx(func(tx *kvstore.Tx[*Item]) error {
for _, hash := range hashList {
deleteErr := tx.Delete(hash)
if deleteErr != nil {
return deleteErr
}
}
return nil
})
if txErr != nil {
return txErr
}
for _, hash := range hashList {
callbackErr := callback(hash)
if callbackErr != nil {
return callbackErr
}
}
}
return nil
}
func (this *KVListFileStore) CleanItemsWithPrefix(prefix string) error {
if !this.isReady() {
return nil
}
if len(prefix) == 0 {
return nil
}
var currentTime = fasttime.Now().Unix()
var fieldOffset []byte
const size = 1000
for {
var count int
err := this.itemsTable.
Query().
FieldPrefix("key", prefix).
FieldOffset(fieldOffset).
Limit(size).
ForUpdate().
FindAll(func(tx *kvstore.Tx[*Item], item kvstore.Item[*Item]) (goNext bool, err error) {
if item.Value == nil {
return true, nil
}
count++
fieldOffset = item.FieldKey
if item.Value.CreatedAt >= currentTime {
return true, nil
}
if item.Value.ExpiresAt == 0 {
return true, nil
}
item.Value.ExpiresAt = 0
item.Value.StaleAt = 0
setErr := tx.Set(item.Key, item.Value) // TODO improve performance
if setErr != nil {
return false, setErr
}
return true, nil
})
if err != nil {
return err
}
if count < size {
break
}
}
return nil
}
func (this *KVListFileStore) CleanItemsWithWildcardPrefix(prefix string) error {
if !this.isReady() {
return nil
}
if len(prefix) == 0 {
return nil
}
var currentTime = fasttime.Now().Unix()
var fieldOffset []byte
const size = 1000
for {
var count int
err := this.itemsTable.
Query().
FieldPrefix("wildKey", prefix).
FieldOffset(fieldOffset).
Limit(size).
ForUpdate().
FindAll(func(tx *kvstore.Tx[*Item], item kvstore.Item[*Item]) (goNext bool, err error) {
if item.Value == nil {
return true, nil
}
count++
fieldOffset = item.FieldKey
if item.Value.CreatedAt >= currentTime {
return true, nil
}
if item.Value.ExpiresAt == 0 {
return true, nil
}
item.Value.ExpiresAt = 0
item.Value.StaleAt = 0
setErr := tx.Set(item.Key, item.Value) // TODO improve performance
if setErr != nil {
return false, setErr
}
return true, nil
})
if err != nil {
return err
}
if count < size {
break
}
}
return nil
}
func (this *KVListFileStore) CleanItemsWithWildcardKey(key string) error {
if !this.isReady() {
return nil
}
if len(key) == 0 {
return nil
}
var currentTime = fasttime.Now().Unix()
for _, realKey := range []string{key, key + SuffixAll} {
var fieldOffset = append(this.itemsTable.FieldKey("wildKey"), '$')
fieldOffset = append(fieldOffset, realKey...)
const size = 1000
var wildKey string
if !strings.HasSuffix(realKey, SuffixAll) {
wildKey = string(append([]byte(realKey), 0, 0))
} else {
wildKey = realKey
}
for {
var count int
err := this.itemsTable.
Query().
FieldPrefix("wildKey", wildKey).
FieldOffset(fieldOffset).
Limit(size).
ForUpdate().
FindAll(func(tx *kvstore.Tx[*Item], item kvstore.Item[*Item]) (goNext bool, err error) {
if item.Value == nil {
return true, nil
}
count++
fieldOffset = item.FieldKey
if item.Value.CreatedAt >= currentTime {
return true, nil
}
if item.Value.ExpiresAt == 0 {
return true, nil
}
item.Value.ExpiresAt = 0
item.Value.StaleAt = 0
setErr := tx.Set(item.Key, item.Value) // TODO improve performance
if setErr != nil {
return false, setErr
}
return true, nil
})
if err != nil {
return err
}
if count < size {
break
}
}
}
return nil
}
func (this *KVListFileStore) CountItems() (int64, error) {
if !this.isReady() {
return 0, nil
}
return this.itemsTable.Count()
}
func (this *KVListFileStore) StatItems() (*Stat, error) {
if !this.isReady() {
return &Stat{}, nil
}
var stat = &Stat{}
err := this.itemsTable.
Query().
FindAll(func(tx *kvstore.Tx[*Item], item kvstore.Item[*Item]) (goNext bool, err error) {
if item.Value != nil {
stat.Size += item.Value.Size()
stat.ValueSize += item.Value.BodySize
stat.Count++
}
return true, nil
})
return stat, err
}
func (this *KVListFileStore) TestInspect(t *testing.T) error {
if !this.isReady() {
return nil
}
it, err := this.rawStore.RawDB().NewIter(&pebble.IterOptions{})
if err != nil {
return err
}
defer func() {
_ = it.Close()
}()
for it.First(); it.Valid(); it.Next() {
valueBytes, valueErr := it.ValueAndErr()
if valueErr != nil {
return valueErr
}
t.Log(string(it.Key()), "=>", string(valueBytes))
}
return nil
}
func (this *KVListFileStore) Close() error {
if this.rawStore != nil {
return this.rawStore.Close()
}
return nil
}
func (this *KVListFileStore) isReady() bool {
return this.rawIsReady && !this.rawStore.IsClosed()
}

View File

@@ -0,0 +1,432 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches_test
import (
"fmt"
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
stringutil "github.com/iwind/TeaGo/utils/string"
"math/rand"
"strconv"
"sync"
"testing"
"time"
)
var testingKVList *caches.KVFileList
func testOpenKVFileList(t *testing.T) *caches.KVFileList {
var list = caches.NewKVFileList(Tea.Root + "/data/stores/cache-stores")
err := list.Init()
if err != nil {
t.Fatal(err)
}
testingKVList = list
return list
}
func TestNewKVFileList(t *testing.T) {
var list = testOpenKVFileList(t)
err := list.Close()
if err != nil {
t.Fatal(err)
}
}
func TestKVFileList_Add(t *testing.T) {
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
err := list.Add(stringutil.Md5("123456"), &caches.Item{
Type: caches.ItemTypeFile,
Key: "https://example.com/index.html",
ExpiresAt: time.Now().Unix() + 60,
StaleAt: 0,
HeaderSize: 0,
BodySize: 4096,
MetaSize: 0,
Host: "",
ServerId: 1,
Week: 0,
})
if err != nil {
t.Fatal(err)
}
}
func TestKVFileList_Add_Many(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
const start = 0
const count = 1_000_000
const concurrent = 100
var before = time.Now()
defer func() {
var costSeconds = time.Since(before).Seconds()
t.Log("cost:", fmt.Sprintf("%.2fs", costSeconds), "qps:", fmt.Sprintf("%.2fK/s", float64(count)/1000/costSeconds))
}()
var wg = &sync.WaitGroup{}
wg.Add(concurrent)
for c := 0; c < concurrent; c++ {
go func(c int) {
defer wg.Done()
var segmentStart = start + count/concurrent*c
for i := segmentStart; i < segmentStart+count/concurrent; i++ {
err := list.Add(stringutil.Md5(strconv.Itoa(i)), &caches.Item{
Type: caches.ItemTypeFile,
Key: "https://www.example.com/index.html" + strconv.Itoa(i),
ExpiresAt: time.Now().Unix() + 3600,
StaleAt: 0,
HeaderSize: 0,
BodySize: int64(rand.Int() % 1_000_000),
MetaSize: 0,
Host: "",
ServerId: 1,
Week: 0,
})
if err != nil {
t.Log(err)
}
}
}(c)
}
wg.Wait()
}
func TestKVFileList_Add_Many_Suffix(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
const start = 0
const count = 1000
const concurrent = 100
var before = time.Now()
defer func() {
var costSeconds = time.Since(before).Seconds()
t.Log("cost:", fmt.Sprintf("%.2fs", costSeconds), "qps:", fmt.Sprintf("%.2fK/s", float64(count)/1000/costSeconds))
}()
var wg = &sync.WaitGroup{}
wg.Add(concurrent)
for c := 0; c < concurrent; c++ {
go func(c int) {
defer wg.Done()
var segmentStart = start + count/concurrent*c
for i := segmentStart; i < segmentStart+count/concurrent; i++ {
err := list.Add(stringutil.Md5(strconv.Itoa(i)+caches.SuffixAll), &caches.Item{
Type: caches.ItemTypeFile,
Key: "https://www.example.com/index.html" + strconv.Itoa(i) + caches.SuffixAll + "zip",
ExpiresAt: time.Now().Unix() + 60,
StaleAt: 0,
HeaderSize: 0,
BodySize: int64(rand.Int() % 1_000_000),
MetaSize: 0,
Host: "",
ServerId: 1,
Week: 0,
})
if err != nil {
t.Log(err)
}
}
}(c)
}
wg.Wait()
}
func TestKVFileList_Exist(t *testing.T) {
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
for _, hash := range []string{
stringutil.Md5("123456"),
stringutil.Md5("654321"),
} {
b, _, err := list.Exist(hash)
if err != nil {
t.Fatal(err)
}
t.Log(hash, "=>", b)
}
}
func TestKVFileList_ExistMany(t *testing.T) {
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
var countFound int
var count = 10
if testutils.IsSingleTesting() {
count = 2_000_000
}
var before = time.Now()
for i := 0; i < count; i++ {
ok, _, err := list.Exist(stringutil.Md5(strconv.Itoa(i)))
if err != nil {
t.Fatal(err)
}
if ok {
countFound++
}
}
var costSeconds = time.Since(before).Seconds()
t.Log("total:", costSeconds*1000, "ms", "found:", countFound, "qps:", fmt.Sprintf("%.2fK/s", float64(count)/costSeconds/1000), "per read:", fmt.Sprintf("%.4fms", costSeconds*1000/float64(count)))
}
func TestKVFileList_ExistQuick(t *testing.T) {
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
for _, hash := range []string{
stringutil.Md5("123456"),
stringutil.Md5("654321"),
} {
b, err := list.ExistQuick(hash)
if err != nil {
t.Fatal(err)
}
t.Log(hash, "=>", b)
}
}
func TestKVFileList_Remove(t *testing.T) {
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
for _, hash := range []string{
stringutil.Md5("123456"),
stringutil.Md5("654321"),
} {
err := list.Remove(hash)
if err != nil {
t.Fatal(err)
}
}
}
func TestKVFileList_RemoveMany(t *testing.T) {
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
var count = 10
if testutils.IsSingleTesting() {
count = 2_000_000
}
var before = time.Now()
for i := 0; i < count; i++ {
err := list.Remove(stringutil.Md5(strconv.Itoa(i)))
if err != nil {
t.Fatal(err)
}
}
var costSeconds = time.Since(before).Seconds()
t.Log("total:", costSeconds*1000, "ms", "qps:", fmt.Sprintf("%.2fK/s", float64(count)/costSeconds/1000), "per delete:", fmt.Sprintf("%.4fms", costSeconds*1000/float64(count)))
}
func TestKVFileList_CleanAll(t *testing.T) {
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
err := list.CleanAll()
if err != nil {
t.Fatal(err)
}
}
func TestKVFileList_Inspect(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
err := list.TestInspect(t)
if err != nil {
t.Fatal(err)
}
}
func TestKVFileList_Purge(t *testing.T) {
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
var before = time.Now()
count, err := list.Purge(4_000, func(hash string) error {
//t.Log("hash:", hash)
return nil
})
if err != nil {
t.Fatal(err)
}
t.Log("cost:", fmt.Sprintf("%.2fms", time.Since(before).Seconds()*1000), "count:", count)
}
func TestKVFileList_PurgeLFU(t *testing.T) {
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
var before = time.Now()
err := list.PurgeLFU(20000, func(hash string) error {
t.Log("hash:", hash)
return nil
})
if err != nil {
t.Fatal(err)
}
t.Log("cost:", fmt.Sprintf("%.2fms", time.Since(before).Seconds()*1000))
}
func TestKVFileList_Count(t *testing.T) {
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
var before = time.Now()
count, err := list.Count()
if err != nil {
t.Fatal(err)
}
t.Log("cost:", fmt.Sprintf("%.2fms", time.Since(before).Seconds()*1000), "count:", count)
}
func TestKVFileList_Stat(t *testing.T) {
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
var before = time.Now()
stat, err := list.Stat(func(hash string) bool {
return true
})
if err != nil {
t.Fatal(err)
}
t.Log("cost:", fmt.Sprintf("%.2fms", time.Since(before).Seconds()*1000), "stat:", fmt.Sprintf("%+v", stat))
}
func TestKVFileList_CleanPrefix(t *testing.T) {
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
var before = time.Now()
defer func() {
var costSeconds = time.Since(before).Seconds()
t.Log("cost:", fmt.Sprintf("%.2fms", costSeconds*1000))
}()
err := list.CleanPrefix("https://www.example.com/index.html")
if err != nil {
t.Fatal(err)
}
}
func TestKVFileList_CleanMatchPrefix(t *testing.T) {
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
var before = time.Now()
defer func() {
var costSeconds = time.Since(before).Seconds()
t.Log("cost:", fmt.Sprintf("%.2fms", costSeconds*1000))
}()
err := list.CleanMatchPrefix("https://*.example.com/index.html")
if err != nil {
t.Fatal(err)
}
}
func TestKVFileList_CleanMatchKey(t *testing.T) {
var list = testOpenKVFileList(t)
defer func() {
_ = list.Close()
}()
var before = time.Now()
defer func() {
var costSeconds = time.Since(before).Seconds()
t.Log("cost:", fmt.Sprintf("%.2fms", costSeconds*1000))
}()
err := list.CleanMatchKey("https://*.example.com/index.html123")
if err != nil {
t.Fatal(err)
}
}
func BenchmarkKVFileList_Exist(b *testing.B) {
var list = caches.NewKVFileList(Tea.Root + "/data/stores/cache-stores")
err := list.Init()
if err != nil {
b.Fatal(err)
}
defer func() {
_ = list.Close()
}()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, _, existErr := list.Exist(stringutil.Md5(strconv.Itoa(rand.Int() % 2_000_000)))
if existErr != nil {
b.Fatal(existErr)
}
}
})
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs" "github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime" "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv" "github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
"github.com/TeaOSLab/EdgeNode/internal/zero" "github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"os" "os"
@@ -21,10 +22,10 @@ import (
const CountFileDB = 20 const CountFileDB = 20
// FileList 文件缓存列表管理 // SQLiteFileList 文件缓存列表管理
type FileList struct { type SQLiteFileList struct {
dir string dir string
dbList [CountFileDB]*FileListDB dbList [CountFileDB]*SQLiteFileListDB
onAdd func(item *Item) onAdd func(item *Item)
onRemove func(item *Item) onRemove func(item *Item)
@@ -35,18 +36,18 @@ type FileList struct {
oldDir string oldDir string
} }
func NewFileList(dir string) ListInterface { func NewSQLiteFileList(dir string) ListInterface {
return &FileList{ return &SQLiteFileList{
dir: dir, dir: dir,
memoryCache: ttlcache.NewCache[zero.Zero](), memoryCache: ttlcache.NewCache[zero.Zero](),
} }
} }
func (this *FileList) SetOldDir(oldDir string) { func (this *SQLiteFileList) SetOldDir(oldDir string) {
this.oldDir = oldDir this.oldDir = oldDir
} }
func (this *FileList) Init() error { func (this *SQLiteFileList) Init() error {
// 检查目录是否存在 // 检查目录是否存在
_, err := os.Stat(this.dir) _, err := os.Stat(this.dir)
if err != nil { if err != nil {
@@ -73,7 +74,7 @@ func (this *FileList) Init() error {
go func(i int) { go func(i int) {
defer wg.Done() defer wg.Done()
var db = NewFileListDB() var db = NewSQLiteFileListDB()
dbErr := db.Open(dir + "/db-" + types.String(i) + ".db") dbErr := db.Open(dir + "/db-" + types.String(i) + ".db")
if dbErr != nil { if dbErr != nil {
lastErr = dbErr lastErr = dbErr
@@ -105,12 +106,12 @@ func (this *FileList) Init() error {
return nil return nil
} }
func (this *FileList) Reset() error { func (this *SQLiteFileList) Reset() error {
// 不做任何事情 // 不做任何事情
return nil return nil
} }
func (this *FileList) Add(hash string, item *Item) error { func (this *SQLiteFileList) Add(hash string, item *Item) error {
var db = this.GetDB(hash) var db = this.GetDB(hash)
if !db.IsReady() { if !db.IsReady() {
@@ -122,7 +123,7 @@ func (this *FileList) Add(hash string, item *Item) error {
return err return err
} }
this.memoryCache.Write(hash, zero.Zero{}, this.maxExpiresAtForMemoryCache(item.ExpiredAt)) this.memoryCache.Write(hash, zero.Zero{}, this.maxExpiresAtForMemoryCache(item.ExpiresAt))
if this.onAdd != nil { if this.onAdd != nil {
this.onAdd(item) this.onAdd(item)
@@ -130,26 +131,26 @@ func (this *FileList) Add(hash string, item *Item) error {
return nil return nil
} }
func (this *FileList) Exist(hash string) (bool, error) { func (this *SQLiteFileList) Exist(hash string) (bool, int64, error) {
var db = this.GetDB(hash) var db = this.GetDB(hash)
if !db.IsReady() { if !db.IsReady() {
return false, nil return false, -1, nil
} }
// 如果Hash列表里不存在那么必然不存在 // 如果Hash列表里不存在那么必然不存在
if !db.hashMap.Exist(hash) { if !db.hashMap.Exist(hash) {
return false, nil return false, -1, nil
} }
var item = this.memoryCache.Read(hash) var item = this.memoryCache.Read(hash)
if item != nil { if item != nil {
return true, nil return true, -1, nil
} }
var row = db.existsByHashStmt.QueryRow(hash, time.Now().Unix()) var row = db.existsByHashStmt.QueryRow(hash, time.Now().Unix())
if row.Err() != nil { if row.Err() != nil {
return false, nil return false, -1, nil
} }
var expiredAt int64 var expiredAt int64
@@ -158,13 +159,18 @@ func (this *FileList) Exist(hash string) (bool, error) {
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
err = nil err = nil
} }
return false, err return false, -1, err
} }
if expiredAt <= fasttime.Now().Unix() {
return false, -1, nil
}
this.memoryCache.Write(hash, zero.Zero{}, this.maxExpiresAtForMemoryCache(expiredAt)) this.memoryCache.Write(hash, zero.Zero{}, this.maxExpiresAtForMemoryCache(expiredAt))
return true, nil return true, -1, nil
} }
func (this *FileList) ExistQuick(hash string) (isReady bool, found bool) { func (this *SQLiteFileList) ExistQuick(hash string) (isReady bool, found bool) {
var db = this.GetDB(hash) var db = this.GetDB(hash)
if !db.IsReady() || !db.HashMapIsLoaded() { if !db.IsReady() || !db.HashMapIsLoaded() {
@@ -177,7 +183,7 @@ func (this *FileList) ExistQuick(hash string) (isReady bool, found bool) {
} }
// CleanPrefix 清理某个前缀的缓存数据 // CleanPrefix 清理某个前缀的缓存数据
func (this *FileList) CleanPrefix(prefix string) error { func (this *SQLiteFileList) CleanPrefix(prefix string) error {
if len(prefix) == 0 { if len(prefix) == 0 {
return nil return nil
} }
@@ -197,7 +203,7 @@ func (this *FileList) CleanPrefix(prefix string) error {
} }
// CleanMatchKey 清理通配符匹配的缓存数据,类似于 https://*.example.com/hello // CleanMatchKey 清理通配符匹配的缓存数据,类似于 https://*.example.com/hello
func (this *FileList) CleanMatchKey(key string) error { func (this *SQLiteFileList) CleanMatchKey(key string) error {
if len(key) == 0 { if len(key) == 0 {
return nil return nil
} }
@@ -217,7 +223,7 @@ func (this *FileList) CleanMatchKey(key string) error {
} }
// CleanMatchPrefix 清理通配符匹配的缓存数据,类似于 https://*.example.com/prefix/ // CleanMatchPrefix 清理通配符匹配的缓存数据,类似于 https://*.example.com/prefix/
func (this *FileList) CleanMatchPrefix(prefix string) error { func (this *SQLiteFileList) CleanMatchPrefix(prefix string) error {
if len(prefix) == 0 { if len(prefix) == 0 {
return nil return nil
} }
@@ -236,7 +242,7 @@ func (this *FileList) CleanMatchPrefix(prefix string) error {
return nil return nil
} }
func (this *FileList) Remove(hash string) error { func (this *SQLiteFileList) Remove(hash string) error {
_, err := this.remove(hash, false) _, err := this.remove(hash, false)
return err return err
} }
@@ -244,7 +250,7 @@ func (this *FileList) Remove(hash string) error {
// Purge 清理过期的缓存 // Purge 清理过期的缓存
// count 每次遍历的最大数量,控制此数字可以保证每次清理的时候不用花太多时间 // count 每次遍历的最大数量,控制此数字可以保证每次清理的时候不用花太多时间
// callback 每次发现过期key的调用 // callback 每次发现过期key的调用
func (this *FileList) Purge(count int, callback func(hash string) error) (int, error) { func (this *SQLiteFileList) Purge(count int, callback func(hash string) error) (int, error) {
count /= CountFileDB count /= CountFileDB
if count <= 0 { if count <= 0 {
count = 100 count = 100
@@ -285,7 +291,7 @@ func (this *FileList) Purge(count int, callback func(hash string) error) (int, e
return countFound, nil return countFound, nil
} }
func (this *FileList) PurgeLFU(count int, callback func(hash string) error) error { func (this *SQLiteFileList) PurgeLFU(count int, callback func(hash string) error) error {
count /= CountFileDB count /= CountFileDB
if count <= 0 { if count <= 0 {
count = 100 count = 100
@@ -322,7 +328,7 @@ func (this *FileList) PurgeLFU(count int, callback func(hash string) error) erro
return nil return nil
} }
func (this *FileList) CleanAll() error { func (this *SQLiteFileList) CleanAll() error {
defer this.memoryCache.Clean() defer this.memoryCache.Clean()
for _, db := range this.dbList { for _, db := range this.dbList {
@@ -335,7 +341,7 @@ func (this *FileList) CleanAll() error {
return nil return nil
} }
func (this *FileList) Stat(check func(hash string) bool) (*Stat, error) { func (this *SQLiteFileList) Stat(check func(hash string) bool) (*Stat, error) {
var result = &Stat{} var result = &Stat{}
for _, db := range this.dbList { for _, db := range this.dbList {
@@ -365,7 +371,7 @@ func (this *FileList) Stat(check func(hash string) bool) (*Stat, error) {
// Count 总数量 // Count 总数量
// 常用的方法,所以避免直接查询数据库 // 常用的方法,所以避免直接查询数据库
func (this *FileList) Count() (int64, error) { func (this *SQLiteFileList) Count() (int64, error) {
var total int64 var total int64
for _, db := range this.dbList { for _, db := range this.dbList {
count, err := db.Total() count, err := db.Total()
@@ -378,7 +384,7 @@ func (this *FileList) Count() (int64, error) {
} }
// IncreaseHit 增加点击量 // IncreaseHit 增加点击量
func (this *FileList) IncreaseHit(hash string) error { func (this *SQLiteFileList) IncreaseHit(hash string) error {
var db = this.GetDB(hash) var db = this.GetDB(hash)
if !db.IsReady() { if !db.IsReady() {
@@ -389,36 +395,41 @@ func (this *FileList) IncreaseHit(hash string) error {
} }
// OnAdd 添加事件 // OnAdd 添加事件
func (this *FileList) OnAdd(f func(item *Item)) { func (this *SQLiteFileList) OnAdd(f func(item *Item)) {
this.onAdd = f this.onAdd = f
} }
// OnRemove 删除事件 // OnRemove 删除事件
func (this *FileList) OnRemove(f func(item *Item)) { func (this *SQLiteFileList) OnRemove(f func(item *Item)) {
this.onRemove = f this.onRemove = f
} }
func (this *FileList) Close() error { func (this *SQLiteFileList) Close() error {
this.memoryCache.Destroy() this.memoryCache.Destroy()
var group = goman.NewTaskGroup()
for _, db := range this.dbList { for _, db := range this.dbList {
if db != nil { var dbCopy = db
_ = db.Close() group.Run(func() {
} if dbCopy != nil {
_ = dbCopy.Close()
}
})
} }
group.Wait()
return nil return nil
} }
func (this *FileList) GetDBIndex(hash string) uint64 { func (this *SQLiteFileList) GetDBIndex(hash string) uint64 {
return fnv.HashString(hash) % CountFileDB return fnv.HashString(hash) % CountFileDB
} }
func (this *FileList) GetDB(hash string) *FileListDB { func (this *SQLiteFileList) GetDB(hash string) *SQLiteFileListDB {
return this.dbList[fnv.HashString(hash)%CountFileDB] return this.dbList[fnv.HashString(hash)%CountFileDB]
} }
func (this *FileList) HashMapIsLoaded() bool { func (this *SQLiteFileList) HashMapIsLoaded() bool {
for _, db := range this.dbList { for _, db := range this.dbList {
if !db.HashMapIsLoaded() { if !db.HashMapIsLoaded() {
return false return false
@@ -427,7 +438,7 @@ func (this *FileList) HashMapIsLoaded() bool {
return true return true
} }
func (this *FileList) remove(hash string, isDeleted bool) (notFound bool, err error) { func (this *SQLiteFileList) remove(hash string, isDeleted bool) (notFound bool, err error) {
var db = this.GetDB(hash) var db = this.GetDB(hash)
if !db.IsReady() { if !db.IsReady() {
@@ -459,14 +470,14 @@ func (this *FileList) remove(hash string, isDeleted bool) (notFound bool, err er
} }
// 升级老版本数据库 // 升级老版本数据库
func (this *FileList) upgradeOldDB() { func (this *SQLiteFileList) upgradeOldDB() {
if len(this.oldDir) == 0 { if len(this.oldDir) == 0 {
return return
} }
_ = this.UpgradeV3(this.oldDir, false) _ = this.UpgradeV3(this.oldDir, false)
} }
func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error { func (this *SQLiteFileList) UpgradeV3(oldDir string, brokenOnError bool) error {
// index.db // index.db
var indexDBPath = oldDir + "/index.db" var indexDBPath = oldDir + "/index.db"
_, err := os.Stat(indexDBPath) _, err := os.Stat(indexDBPath)
@@ -476,7 +487,7 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
remotelogs.Println("CACHE", "upgrading local database from '"+oldDir+"' ...") remotelogs.Println("CACHE", "upgrading local database from '"+oldDir+"' ...")
defer func() { defer func() {
_ = os.Remove(indexDBPath) _ = fsutils.Remove(indexDBPath)
remotelogs.Println("CACHE", "upgrading local database finished") remotelogs.Println("CACHE", "upgrading local database finished")
}() }()
@@ -538,7 +549,7 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
err = this.Add(hash, &Item{ err = this.Add(hash, &Item{
Type: ItemTypeFile, Type: ItemTypeFile,
Key: key, Key: key,
ExpiredAt: expiredAt, ExpiresAt: expiredAt,
StaleAt: staleAt, StaleAt: staleAt,
HeaderSize: headerSize, HeaderSize: headerSize,
BodySize: bodySize, BodySize: bodySize,
@@ -565,7 +576,7 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
return nil return nil
} }
func (this *FileList) maxExpiresAtForMemoryCache(expiresAt int64) int64 { func (this *SQLiteFileList) maxExpiresAtForMemoryCache(expiresAt int64) int64 {
var maxTimestamp = fasttime.Now().Unix() + 3600 var maxTimestamp = fasttime.Now().Unix() + 3600
if expiresAt > maxTimestamp { if expiresAt > maxTimestamp {
return maxTimestamp return maxTimestamp

View File

@@ -21,7 +21,7 @@ func TestFileList_Init(t *testing.T) {
return return
} }
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1") var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1")
defer func() { defer func() {
_ = list.Close() _ = list.Close()
@@ -42,7 +42,7 @@ func TestFileList_Add(t *testing.T) {
return return
} }
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList) var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1").(*caches.SQLiteFileList)
defer func() { defer func() {
_ = list.Close() _ = list.Close()
@@ -61,7 +61,7 @@ func TestFileList_Add(t *testing.T) {
t.Log("db index:", list.GetDBIndex(hash)) t.Log("db index:", list.GetDBIndex(hash))
err = list.Add(hash, &caches.Item{ err = list.Add(hash, &caches.Item{
Key: "123456", Key: "123456",
ExpiredAt: time.Now().Unix() + 1, ExpiresAt: time.Now().Unix() + 1,
HeaderSize: 1, HeaderSize: 1,
MetaSize: 2, MetaSize: 2,
BodySize: 3, BodySize: 3,
@@ -82,7 +82,7 @@ func TestFileList_Add_Many(t *testing.T) {
return return
} }
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1") var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1")
defer func() { defer func() {
_ = list.Close() _ = list.Close()
@@ -94,11 +94,13 @@ func TestFileList_Add_Many(t *testing.T) {
} }
var before = time.Now() var before = time.Now()
for i := 0; i < 10_000_000; i++ { const offset = 0
const count = 1_000_000
for i := offset; i < offset+count; i++ {
u := "https://edge.teaos.cn/123456" + strconv.Itoa(i) u := "https://edge.teaos.cn/123456" + strconv.Itoa(i)
_ = list.Add(stringutil.Md5(u), &caches.Item{ _ = list.Add(stringutil.Md5(u), &caches.Item{
Key: u, Key: u,
ExpiredAt: time.Now().Unix() + 3600, ExpiresAt: time.Now().Unix() + 3600,
HeaderSize: 1, HeaderSize: 1,
MetaSize: 2, MetaSize: 2,
BodySize: 3, BodySize: 3,
@@ -119,7 +121,7 @@ func TestFileList_Exist(t *testing.T) {
return return
} }
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList) var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1").(*caches.SQLiteFileList)
defer func() { defer func() {
_ = list.Close() _ = list.Close()
}() }()
@@ -138,7 +140,7 @@ func TestFileList_Exist(t *testing.T) {
}() }()
{ {
var hash = stringutil.Md5("123456") var hash = stringutil.Md5("123456")
exists, err := list.Exist(hash) exists, _, err := list.Exist(hash)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -146,7 +148,7 @@ func TestFileList_Exist(t *testing.T) {
} }
{ {
var hash = stringutil.Md5("http://edge.teaos.cn/1234561") var hash = stringutil.Md5("http://edge.teaos.cn/1234561")
exists, err := list.Exist(hash) exists, _, err := list.Exist(hash)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -162,7 +164,7 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
// 测试在多个数据库下的性能 // 测试在多个数据库下的性能
var listSlice = []caches.ListInterface{} var listSlice = []caches.ListInterface{}
for i := 1; i <= 10; i++ { for i := 1; i <= 10; i++ {
var list = caches.NewFileList(Tea.Root + "/data/data" + strconv.Itoa(i)) var list = caches.NewSQLiteFileList(Tea.Root + "/data/data" + strconv.Itoa(i))
err := list.Init() err := list.Init()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -206,7 +208,7 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
countLocker.Unlock() countLocker.Unlock()
var list = listSlice[rands.Int(0, len(listSlice)-1)] var list = listSlice[rands.Int(0, len(listSlice)-1)]
_, _ = list.Exist(hash) _, _, _ = list.Exist(hash)
default: default:
return return
} }
@@ -222,7 +224,7 @@ func TestFileList_CleanPrefix(t *testing.T) {
return return
} }
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1") var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1")
defer func() { defer func() {
_ = list.Close() _ = list.Close()
@@ -246,7 +248,7 @@ func TestFileList_Remove(t *testing.T) {
return return
} }
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList) var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1").(*caches.SQLiteFileList)
defer func() { defer func() {
_ = list.Close() _ = list.Close()
}() }()
@@ -274,7 +276,7 @@ func TestFileList_Purge(t *testing.T) {
return return
} }
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1") var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1")
defer func() { defer func() {
_ = list.Close() _ = list.Close()
@@ -302,7 +304,7 @@ func TestFileList_PurgeLFU(t *testing.T) {
return return
} }
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1") var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1")
defer func() { defer func() {
_ = list.Close() _ = list.Close()
@@ -330,7 +332,7 @@ func TestFileList_Stat(t *testing.T) {
return return
} }
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1") var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1")
defer func() { defer func() {
_ = list.Close() _ = list.Close()
@@ -353,7 +355,7 @@ func TestFileList_Count(t *testing.T) {
return return
} }
var list = caches.NewFileList(Tea.Root + "/data") var list = caches.NewSQLiteFileList(Tea.Root + "/data")
defer func() { defer func() {
_ = list.Close() _ = list.Close()
@@ -377,7 +379,7 @@ func TestFileList_CleanAll(t *testing.T) {
return return
} }
var list = caches.NewFileList(Tea.Root + "/data") var list = caches.NewSQLiteFileList(Tea.Root + "/data")
defer func() { defer func() {
_ = list.Close() _ = list.Close()
@@ -400,7 +402,7 @@ func TestFileList_UpgradeV3(t *testing.T) {
return return
} }
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p43").(*caches.FileList) var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p43").(*caches.SQLiteFileList)
defer func() { defer func() {
_ = list.Close() _ = list.Close()
@@ -428,7 +430,7 @@ func BenchmarkFileList_Exist(b *testing.B) {
return return
} }
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1") var list = caches.NewSQLiteFileList(Tea.Root + "/data/cache-index/p1")
defer func() { defer func() {
_ = list.Close() _ = list.Close()
@@ -440,6 +442,6 @@ func BenchmarkFileList_Exist(b *testing.B) {
} }
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
_, _ = list.Exist("f0eb5b87e0b0041f3917002c0707475f" + types.String(i)) _, _, _ = list.Exist("f0eb5b87e0b0041f3917002c0707475f" + types.String(i))
} }
} }

View File

@@ -13,7 +13,7 @@ type ListInterface interface {
Add(hash string, item *Item) error Add(hash string, item *Item) error
// Exist 检查内容是否存在 // Exist 检查内容是否存在
Exist(hash string) (bool, error) Exist(hash string) (ok bool, size int64, err error)
// CleanPrefix 清除某个前缀的缓存 // CleanPrefix 清除某个前缀的缓存
CleanPrefix(prefix string) error CleanPrefix(prefix string) error

View File

@@ -89,21 +89,21 @@ func (this *MemoryList) Add(hash string, item *Item) error {
return nil return nil
} }
func (this *MemoryList) Exist(hash string) (bool, error) { func (this *MemoryList) Exist(hash string) (bool, int64, error) {
this.locker.RLock() this.locker.RLock()
defer this.locker.RUnlock() defer this.locker.RUnlock()
prefix := this.prefix(hash) prefix := this.prefix(hash)
itemMap, ok := this.itemMaps[prefix] itemMap, ok := this.itemMaps[prefix]
if !ok { if !ok {
return false, nil return false, -1, nil
} }
item, ok := itemMap[hash] item, ok := itemMap[hash]
if !ok { if !ok {
return false, nil return false, -1, nil
} }
return !item.IsExpired(), nil return !item.IsExpired(), -1, nil
} }
// CleanPrefix 根据前缀进行清除 // CleanPrefix 根据前缀进行清除
@@ -115,7 +115,7 @@ func (this *MemoryList) CleanPrefix(prefix string) error {
for _, itemMap := range this.itemMaps { for _, itemMap := range this.itemMaps {
for _, item := range itemMap { for _, item := range itemMap {
if strings.HasPrefix(item.Key, prefix) { if strings.HasPrefix(item.Key, prefix) {
item.ExpiredAt = 0 item.ExpiresAt = 0
} }
} }
} }
@@ -153,7 +153,7 @@ func (this *MemoryList) CleanMatchKey(key string) error {
if configutils.MatchDomain(host, item.Host) { if configutils.MatchDomain(host, item.Host) {
var itemRequestURI = item.RequestURI() var itemRequestURI = item.RequestURI()
if itemRequestURI == requestURI || strings.HasPrefix(itemRequestURI, requestURI+SuffixAll) { if itemRequestURI == requestURI || strings.HasPrefix(itemRequestURI, requestURI+SuffixAll) {
item.ExpiredAt = 0 item.ExpiresAt = 0
} }
} }
} }
@@ -189,7 +189,7 @@ func (this *MemoryList) CleanMatchPrefix(prefix string) error {
if configutils.MatchDomain(host, item.Host) { if configutils.MatchDomain(host, item.Host) {
var itemRequestURI = item.RequestURI() var itemRequestURI = item.RequestURI()
if isRootPath || strings.HasPrefix(itemRequestURI, requestURI) { if isRootPath || strings.HasPrefix(itemRequestURI, requestURI) {
item.ExpiredAt = 0 item.ExpiresAt = 0
} }
} }
} }

View File

@@ -4,7 +4,7 @@ import (
"fmt" "fmt"
"github.com/TeaOSLab/EdgeNode/internal/caches" "github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils" "github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/cespare/xxhash" "github.com/cespare/xxhash/v2"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
@@ -21,17 +21,17 @@ func TestMemoryList_Add(t *testing.T) {
_ = list.Init() _ = list.Init()
_ = list.Add("a", &caches.Item{ _ = list.Add("a", &caches.Item{
Key: "a1", Key: "a1",
ExpiredAt: time.Now().Unix() + 3600, ExpiresAt: time.Now().Unix() + 3600,
HeaderSize: 1024, HeaderSize: 1024,
}) })
_ = list.Add("b", &caches.Item{ _ = list.Add("b", &caches.Item{
Key: "b1", Key: "b1",
ExpiredAt: time.Now().Unix() + 3600, ExpiresAt: time.Now().Unix() + 3600,
HeaderSize: 1024, HeaderSize: 1024,
}) })
_ = list.Add("123456", &caches.Item{ _ = list.Add("123456", &caches.Item{
Key: "c1", Key: "c1",
ExpiredAt: time.Now().Unix() + 3600, ExpiresAt: time.Now().Unix() + 3600,
HeaderSize: 1024, HeaderSize: 1024,
}) })
t.Log(list.Prefixes()) t.Log(list.Prefixes())
@@ -44,12 +44,12 @@ func TestMemoryList_Remove(t *testing.T) {
_ = list.Init() _ = list.Init()
_ = list.Add("a", &caches.Item{ _ = list.Add("a", &caches.Item{
Key: "a1", Key: "a1",
ExpiredAt: time.Now().Unix() + 3600, ExpiresAt: time.Now().Unix() + 3600,
HeaderSize: 1024, HeaderSize: 1024,
}) })
_ = list.Add("b", &caches.Item{ _ = list.Add("b", &caches.Item{
Key: "b1", Key: "b1",
ExpiredAt: time.Now().Unix() + 3600, ExpiresAt: time.Now().Unix() + 3600,
HeaderSize: 1024, HeaderSize: 1024,
}) })
_ = list.Remove("b") _ = list.Remove("b")
@@ -62,22 +62,22 @@ func TestMemoryList_Purge(t *testing.T) {
_ = list.Init() _ = list.Init()
_ = list.Add("a", &caches.Item{ _ = list.Add("a", &caches.Item{
Key: "a1", Key: "a1",
ExpiredAt: time.Now().Unix() + 3600, ExpiresAt: time.Now().Unix() + 3600,
HeaderSize: 1024, HeaderSize: 1024,
}) })
_ = list.Add("b", &caches.Item{ _ = list.Add("b", &caches.Item{
Key: "b1", Key: "b1",
ExpiredAt: time.Now().Unix() + 3600, ExpiresAt: time.Now().Unix() + 3600,
HeaderSize: 1024, HeaderSize: 1024,
}) })
_ = list.Add("c", &caches.Item{ _ = list.Add("c", &caches.Item{
Key: "c1", Key: "c1",
ExpiredAt: time.Now().Unix() - 3600, ExpiresAt: time.Now().Unix() - 3600,
HeaderSize: 1024, HeaderSize: 1024,
}) })
_ = list.Add("d", &caches.Item{ _ = list.Add("d", &caches.Item{
Key: "d1", Key: "d1",
ExpiredAt: time.Now().Unix() - 2, ExpiresAt: time.Now().Unix() - 2,
HeaderSize: 1024, HeaderSize: 1024,
}) })
_, _ = list.Purge(100, func(hash string) error { _, _ = list.Purge(100, func(hash string) error {
@@ -109,7 +109,7 @@ func TestMemoryList_Purge_Large_List(t *testing.T) {
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
_ = list.Add("a"+strconv.Itoa(i), &caches.Item{ _ = list.Add("a"+strconv.Itoa(i), &caches.Item{
Key: "a" + strconv.Itoa(i), Key: "a" + strconv.Itoa(i),
ExpiredAt: time.Now().Unix() + int64(rands.Int(0, 24*3600)), ExpiresAt: time.Now().Unix() + int64(rands.Int(0, 24*3600)),
HeaderSize: 1024, HeaderSize: 1024,
}) })
} }
@@ -124,22 +124,22 @@ func TestMemoryList_Stat(t *testing.T) {
_ = list.Init() _ = list.Init()
_ = list.Add("a", &caches.Item{ _ = list.Add("a", &caches.Item{
Key: "a1", Key: "a1",
ExpiredAt: time.Now().Unix() + 3600, ExpiresAt: time.Now().Unix() + 3600,
HeaderSize: 1024, HeaderSize: 1024,
}) })
_ = list.Add("b", &caches.Item{ _ = list.Add("b", &caches.Item{
Key: "b1", Key: "b1",
ExpiredAt: time.Now().Unix() + 3600, ExpiresAt: time.Now().Unix() + 3600,
HeaderSize: 1024, HeaderSize: 1024,
}) })
_ = list.Add("c", &caches.Item{ _ = list.Add("c", &caches.Item{
Key: "c1", Key: "c1",
ExpiredAt: time.Now().Unix(), ExpiresAt: time.Now().Unix(),
HeaderSize: 1024, HeaderSize: 1024,
}) })
_ = list.Add("d", &caches.Item{ _ = list.Add("d", &caches.Item{
Key: "d1", Key: "d1",
ExpiredAt: time.Now().Unix() - 2, ExpiresAt: time.Now().Unix() - 2,
HeaderSize: 1024, HeaderSize: 1024,
}) })
result, _ := list.Stat(func(hash string) bool { result, _ := list.Stat(func(hash string) bool {
@@ -161,7 +161,7 @@ func TestMemoryList_CleanPrefix(t *testing.T) {
key := "https://www.teaos.cn/hello/" + strconv.Itoa(i/10000) + "/" + strconv.Itoa(i) + ".html" key := "https://www.teaos.cn/hello/" + strconv.Itoa(i/10000) + "/" + strconv.Itoa(i) + ".html"
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &caches.Item{ _ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &caches.Item{
Key: key, Key: key,
ExpiredAt: time.Now().Unix() + 3600, ExpiresAt: time.Now().Unix() + 3600,
BodySize: 0, BodySize: 0,
HeaderSize: 0, HeaderSize: 0,
}) })
@@ -278,7 +278,7 @@ func TestMemoryList_GC(t *testing.T) {
key := "https://www.teaos.cn/hello" + strconv.Itoa(i/100000) + "/" + strconv.Itoa(i) + ".html" key := "https://www.teaos.cn/hello" + strconv.Itoa(i/100000) + "/" + strconv.Itoa(i) + ".html"
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &caches.Item{ _ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &caches.Item{
Key: key, Key: key,
ExpiredAt: 0, ExpiresAt: 0,
BodySize: 0, BodySize: 0,
HeaderSize: 0, HeaderSize: 0,
}) })
@@ -308,7 +308,7 @@ func BenchmarkMemoryList(b *testing.B) {
for i := 0; i < 1_000_000; i++ { for i := 0; i < 1_000_000; i++ {
_ = list.Add(stringutil.Md5(types.String(i)), &caches.Item{ _ = list.Add(stringutil.Md5(types.String(i)), &caches.Item{
Key: "a1", Key: "a1",
ExpiredAt: time.Now().Unix() + 3600, ExpiresAt: time.Now().Unix() + 3600,
HeaderSize: 1024, HeaderSize: 1024,
}) })
} }
@@ -317,7 +317,7 @@ func BenchmarkMemoryList(b *testing.B) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
_, _ = list.Exist(types.String("a" + types.String(rands.Int(1, 10000)))) _, _, _ = list.Exist(types.String("a" + types.String(rands.Int(1, 10000))))
_ = list.Add("a"+types.String(rands.Int(1, 100000)), &caches.Item{}) _ = list.Add("a"+types.String(rands.Int(1, 100000)), &caches.Item{})
_, _ = list.Purge(1000, func(hash string) error { _, _ = list.Purge(1000, func(hash string) error {
return nil return nil

View File

@@ -7,7 +7,7 @@ import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils" memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
@@ -149,7 +149,7 @@ func (this *Manager) UpdatePolicies(newPolicies []*serverconfigs.HTTPCachePolicy
} }
this.CountFileStorages = 0 this.CountFileStorages = 0
this.CountFileStorages = 0 this.CountMemoryStorages = 0
for _, storage := range this.storageMap { for _, storage := range this.storageMap {
_, isFileStorage := storage.(*FileStorage) _, isFileStorage := storage.(*FileStorage)
this.CountMemoryStorages++ this.CountMemoryStorages++
@@ -299,7 +299,7 @@ func (this *Manager) MaxSystemMemoryBytesPerStorage() int64 {
count = 1 count = 1
} }
var resultBytes = int64(utils.SystemMemoryBytes()) / 3 / int64(count) // 1/3 of the system memory var resultBytes = int64(memutils.SystemMemoryBytes()) / 3 / int64(count) // 1/3 of the system memory
if resultBytes < 1<<30 { if resultBytes < 1<<30 {
resultBytes = 1 << 30 resultBytes = 1 << 30
} }

View File

@@ -1,375 +0,0 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches
import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/iwind/TeaGo/logs"
"os"
"sync"
"sync/atomic"
"time"
)
const (
enableFragmentPool = false
minMemoryFragmentPoolItemSize = 8 << 10
maxMemoryFragmentPoolItemSize = 128 << 20
maxItemsInMemoryFragmentPoolBucket = 1024
memoryFragmentPoolBucketSegmentSize = 512 << 10
maxMemoryFragmentPoolItemAgeSeconds = 60
)
var SharedFragmentMemoryPool *MemoryFragmentPool
func init() {
if !teaconst.IsMain {
return
}
SharedFragmentMemoryPool = NewMemoryFragmentPool()
goman.New(func() {
var ticker = time.NewTicker(200 * time.Millisecond)
for range ticker.C {
for i := 0; i < 10; i++ { // skip N empty buckets
var isEmpty = SharedFragmentMemoryPool.GCNextBucket()
if !isEmpty {
break
}
}
}
})
}
type MemoryFragmentPoolItem struct {
Bytes []byte
size int64
createdAt int64
Refs int32
}
func (this *MemoryFragmentPoolItem) IsExpired() bool {
return this.createdAt < fasttime.Now().Unix()-maxMemoryFragmentPoolItemAgeSeconds
}
func (this *MemoryFragmentPoolItem) Reset() {
this.Bytes = nil
}
func (this *MemoryFragmentPoolItem) IsAvailable() bool {
return atomic.AddInt32(&this.Refs, 1) == 1
}
// MemoryFragmentPool memory fragments management
type MemoryFragmentPool struct {
bucketMaps []map[uint64]*MemoryFragmentPoolItem // [ { id => Zero }, ... ]
countBuckets int
gcBucketIndex int
mu sync.RWMutex
id uint64
totalMemory int64
isOk bool
capacity int64
debugMode bool
countGet uint64
countNew uint64
}
// NewMemoryFragmentPool create new fragment memory pool
func NewMemoryFragmentPool() *MemoryFragmentPool {
var pool = &MemoryFragmentPool{}
pool.init()
return pool
}
func (this *MemoryFragmentPool) init() {
var capacity = int64(utils.SystemMemoryGB()) << 30 / 16
if capacity > 256<<20 {
this.isOk = true
this.capacity = capacity
this.bucketMaps = []map[uint64]*MemoryFragmentPoolItem{}
for i := 0; i < maxMemoryFragmentPoolItemSize/memoryFragmentPoolBucketSegmentSize+1; i++ {
this.bucketMaps = append(this.bucketMaps, map[uint64]*MemoryFragmentPoolItem{})
}
this.countBuckets = len(this.bucketMaps)
}
// print statistics for debug
if len(os.Getenv("GOEDGE_DEBUG_MEMORY_FRAGMENT_POOL")) > 0 {
this.debugMode = true
go func() {
var maxRounds = 10_000
var ticker = time.NewTicker(10 * time.Second)
for range ticker.C {
logs.Println("reused:", this.countGet, "created:", this.countNew, "fragments:", this.Len(), "memory:", this.totalMemory>>20, "MB")
maxRounds--
if maxRounds <= 0 {
break
}
}
}()
}
}
// Get try to get a bytes object
func (this *MemoryFragmentPool) Get(expectSize int64) (resultBytes []byte, ok bool) {
if !this.isOk {
return
}
if expectSize <= 0 {
return
}
// DO NOT check min segment size
this.mu.RLock()
var bucketIndex = this.bucketIndexForSize(expectSize)
var resultItemId uint64
const maxSearchingBuckets = 20
for i := bucketIndex; i <= bucketIndex+maxSearchingBuckets; i++ {
resultBytes, resultItemId, ok = this.findItemInMap(this.bucketMaps[i], expectSize)
if ok {
this.mu.RUnlock()
// remove from bucket
this.mu.Lock()
delete(this.bucketMaps[i], resultItemId)
this.mu.Unlock()
return
}
if i >= this.countBuckets {
break
}
}
this.mu.RUnlock()
return
}
// Put a bytes object to specified bucket
func (this *MemoryFragmentPool) Put(data []byte) (ok bool) {
if !this.isOk {
return
}
var l = int64(cap(data)) // MUST be 'cap' instead of 'len'
if l < minMemoryFragmentPoolItemSize || l > maxMemoryFragmentPoolItemSize {
return
}
if atomic.LoadInt64(&this.totalMemory) >= this.capacity {
return
}
var itemId = atomic.AddUint64(&this.id, 1)
this.mu.Lock()
defer this.mu.Unlock()
var bucketMap = this.bucketMaps[this.bucketIndexForSize(l)]
if len(bucketMap) >= maxItemsInMemoryFragmentPoolBucket {
return
}
atomic.AddInt64(&this.totalMemory, l)
bucketMap[itemId] = &MemoryFragmentPoolItem{
Bytes: data,
size: l,
createdAt: fasttime.Now().Unix(),
}
return true
}
// GC fully GC
func (this *MemoryFragmentPool) GC() {
if !this.isOk {
return
}
var totalMemory = atomic.LoadInt64(&this.totalMemory)
if totalMemory < this.capacity {
return
}
this.mu.Lock()
defer this.mu.Unlock()
var garbageSize = totalMemory * 1 / 10 // 10%
// remove expired
for _, bucketMap := range this.bucketMaps {
for itemId, item := range bucketMap {
if item.IsExpired() {
delete(bucketMap, itemId)
item.Reset()
atomic.AddInt64(&this.totalMemory, -item.size)
garbageSize -= item.size
}
}
}
// remove others
if garbageSize > 0 {
for _, bucketMap := range this.bucketMaps {
for itemId, item := range bucketMap {
delete(bucketMap, itemId)
item.Reset()
atomic.AddInt64(&this.totalMemory, -item.size)
garbageSize -= item.size
if garbageSize <= 0 {
break
}
}
}
}
}
// GCNextBucket gc one bucket
func (this *MemoryFragmentPool) GCNextBucket() (isEmpty bool) {
if !this.isOk {
return
}
var itemIds = []uint64{}
// find
this.mu.RLock()
var bucketIndex = this.gcBucketIndex
var bucketMap = this.bucketMaps[bucketIndex]
isEmpty = len(bucketMap) == 0
if isEmpty {
this.mu.RUnlock()
// move to next bucket index
bucketIndex++
if bucketIndex >= this.countBuckets {
bucketIndex = 0
}
this.gcBucketIndex = bucketIndex
return
}
for itemId, item := range bucketMap {
if item.IsExpired() {
itemIds = append(itemIds, itemId)
}
}
this.mu.RUnlock()
// remove
if len(itemIds) > 0 {
this.mu.Lock()
for _, itemId := range itemIds {
item, ok := bucketMap[itemId]
if !ok {
continue
}
if !item.IsAvailable() {
continue
}
delete(bucketMap, itemId)
item.Reset()
atomic.AddInt64(&this.totalMemory, -item.size)
}
this.mu.Unlock()
}
// move to next bucket index
bucketIndex++
if bucketIndex >= this.countBuckets {
bucketIndex = 0
}
this.gcBucketIndex = bucketIndex
return
}
func (this *MemoryFragmentPool) SetCapacity(capacity int64) {
this.capacity = capacity
}
func (this *MemoryFragmentPool) TotalSize() int64 {
return atomic.LoadInt64(&this.totalMemory)
}
func (this *MemoryFragmentPool) Len() int {
this.mu.Lock()
defer this.mu.Unlock()
var count = 0
for _, bucketMap := range this.bucketMaps {
count += len(bucketMap)
}
return count
}
func (this *MemoryFragmentPool) IncreaseNew() {
if this.isOk && this.debugMode {
atomic.AddUint64(&this.countNew, 1)
}
}
func (this *MemoryFragmentPool) bucketIndexForSize(size int64) int {
return int(size / memoryFragmentPoolBucketSegmentSize)
}
func (this *MemoryFragmentPool) findItemInMap(bucketMap map[uint64]*MemoryFragmentPoolItem, expectSize int64) (resultBytes []byte, resultItemId uint64, ok bool) {
if len(bucketMap) == 0 {
return
}
for itemId, item := range bucketMap {
if item.size >= expectSize {
// check if is referred
if !item.IsAvailable() {
continue
}
// return result
if item.size != expectSize {
resultBytes = item.Bytes[:expectSize]
} else {
resultBytes = item.Bytes
}
// reset old item
item.Reset()
atomic.AddInt64(&this.totalMemory, -item.size)
resultItemId = itemId
if this.debugMode {
atomic.AddUint64(&this.countGet, 1)
}
ok = true
return
}
}
return
}

View File

@@ -1,313 +0,0 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches_test
import (
"bytes"
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/rands"
timeutil "github.com/iwind/TeaGo/utils/time"
"runtime"
"sync"
"sync/atomic"
"testing"
"time"
)
func TestNewMemoryFragmentPool(t *testing.T) {
var a = assert.NewAssertion(t)
var pool = caches.NewMemoryFragmentPool()
for i := 0; i < 3000; i++ {
ok := pool.Put(make([]byte, 2<<20))
if !ok {
t.Log("finished at", i)
break
}
}
t.Log(pool.TotalSize()>>20, "MB", pool.Len(), "items")
{
r, ok := pool.Get(1 << 20)
a.IsTrue(ok)
a.IsTrue(len(r) == 1<<20)
}
{
r, ok := pool.Get(2 << 20)
a.IsTrue(ok)
a.IsTrue(len(r) == 2<<20)
}
{
r, ok := pool.Get(4 << 20)
a.IsFalse(ok)
a.IsTrue(len(r) == 0)
}
t.Log(pool.TotalSize()>>20, "MB", pool.Len(), "items")
}
func TestNewMemoryFragmentPool_LargeBucket(t *testing.T) {
var a = assert.NewAssertion(t)
var pool = caches.NewMemoryFragmentPool()
{
pool.Put(make([]byte, 128<<20+1))
a.IsTrue(pool.Len() == 0)
}
{
pool.Put(make([]byte, 128<<20))
a.IsTrue(pool.Len() == 1)
pool.Get(118 << 20)
a.IsTrue(pool.Len() == 0)
}
{
pool.Put(make([]byte, 128<<20))
a.IsTrue(pool.Len() == 1)
pool.Get(110 << 20)
a.IsTrue(pool.Len() == 1)
}
}
func TestMemoryFragmentPool_Get_Exactly(t *testing.T) {
var a = assert.NewAssertion(t)
var pool = caches.NewMemoryFragmentPool()
{
pool.Put(make([]byte, 129<<20))
a.IsTrue(pool.Len() == 0)
}
{
pool.Put(make([]byte, 4<<20))
a.IsTrue(pool.Len() == 1)
}
{
pool.Get(4 << 20)
a.IsTrue(pool.Len() == 0)
}
}
func TestMemoryFragmentPool_Get_Round(t *testing.T) {
var a = assert.NewAssertion(t)
var pool = caches.NewMemoryFragmentPool()
{
pool.Put(make([]byte, 8<<20))
pool.Put(make([]byte, 8<<20))
pool.Put(make([]byte, 8<<20))
a.IsTrue(pool.Len() == 3)
}
{
resultBytes, ok := pool.Get(3 << 20)
a.IsTrue(pool.Len() == 2)
if ok {
pool.Put(resultBytes)
}
}
{
pool.Get(2 << 20)
a.IsTrue(pool.Len() == 2)
}
{
pool.Get(1 << 20)
a.IsTrue(pool.Len() == 1)
}
}
func TestMemoryFragmentPool_GC(t *testing.T) {
var pool = caches.NewMemoryFragmentPool()
pool.SetCapacity(32 << 20)
for i := 0; i < 16; i++ {
pool.Put(make([]byte, 4<<20))
}
var before = time.Now()
pool.GC()
t.Log(time.Since(before).Seconds()*1000, "ms")
t.Log(pool.Len())
}
func TestMemoryFragmentPool_Memory(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var pool = caches.NewMemoryFragmentPool()
testutils.StartMemoryStats(t, func() {
t.Log(pool.Len(), "items")
})
var sampleData = bytes.Repeat([]byte{'A'}, 16<<20)
var countNew = 0
for i := 0; i < 1000; i++ {
cacheData, ok := pool.Get(16 << 20)
if ok {
copy(cacheData, sampleData)
pool.Put(cacheData)
} else {
countNew++
var data = make([]byte, 16<<20)
copy(data, sampleData)
pool.Put(data)
}
}
t.Log("count new:", countNew)
t.Log("count remains:", pool.Len())
time.Sleep(10 * time.Minute)
}
func TestMemoryFragmentPool_GCNextBucket(t *testing.T) {
if !testutils.IsSingleTesting() {
return
}
var pool = caches.NewMemoryFragmentPool()
for i := 0; i < 1000; i++ {
pool.Put(make([]byte, rands.Int(0, 100)<<20))
}
var lastLen int
for {
pool.GCNextBucket()
var currentLen = pool.Len()
if lastLen == currentLen {
continue
}
lastLen = currentLen
t.Log(currentLen, "items", pool.TotalSize(), "bytes", timeutil.Format("H:i:s"))
time.Sleep(100 * time.Millisecond)
if currentLen == 0 {
break
}
}
}
func TestMemoryFragmentPoolItem(t *testing.T) {
var a = assert.NewAssertion(t)
var m = map[int]*caches.MemoryFragmentPoolItem{}
m[1] = &caches.MemoryFragmentPoolItem{
Refs: 0,
}
var item = m[1]
a.IsTrue(item.Refs == 0)
a.IsTrue(atomic.AddInt32(&item.Refs, 1) == 1)
for _, item2 := range m {
t.Log(item2)
a.IsTrue(atomic.AddInt32(&item2.Refs, 1) == 2)
}
t.Log(m)
}
func BenchmarkMemoryFragmentPool_Get_HIT(b *testing.B) {
runtime.GOMAXPROCS(4)
var pool = caches.NewMemoryFragmentPool()
for i := 0; i < 3000; i++ {
ok := pool.Put(make([]byte, 2<<20))
if !ok {
break
}
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
data, ok := pool.Get(2 << 20)
if ok {
pool.Put(data)
}
}
})
}
func BenchmarkMemoryFragmentPool_Get_TOTALLY_MISSING(b *testing.B) {
runtime.GOMAXPROCS(4)
var pool = caches.NewMemoryFragmentPool()
for i := 0; i < 3000; i++ {
ok := pool.Put(make([]byte, 2<<20+100))
if !ok {
break
}
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
data, ok := pool.Get(2<<20 + 200)
if ok {
pool.Put(data)
}
}
})
}
func BenchmarkMemoryPool_Get_HIT_MISSING(b *testing.B) {
runtime.GOMAXPROCS(4)
var pool = caches.NewMemoryFragmentPool()
for i := 0; i < 3000; i++ {
ok := pool.Put(make([]byte, rands.Int(2, 32)<<20))
if !ok {
break
}
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
data, ok := pool.Get(4 << 20)
if ok {
pool.Put(data)
}
}
})
}
func BenchmarkMemoryFragmentPool_GC(b *testing.B) {
runtime.GOMAXPROCS(4)
var pool = caches.NewMemoryFragmentPool()
pool.SetCapacity(1 << 30)
for i := 0; i < 2_000; i++ {
pool.Put(make([]byte, 1<<20))
}
var mu = sync.Mutex{}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
mu.Lock()
for i := 0; i < 100; i++ {
pool.GCNextBucket()
}
mu.Unlock()
}
})
}

View File

@@ -5,8 +5,8 @@ package caches
import ( import (
"fmt" "fmt"
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist" "github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist"
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
@@ -43,7 +43,7 @@ func NewOpenFileCache(maxCount int) (*OpenFileCache, error) {
maxCount: maxCount, maxCount: maxCount,
poolMap: map[string]*OpenFilePool{}, poolMap: map[string]*OpenFilePool{},
poolList: linkedlist.NewList[*OpenFilePool](), poolList: linkedlist.NewList[*OpenFilePool](),
capacitySize: (int64(utils.SystemMemoryGB()) << 30) / 16, capacitySize: (int64(memutils.SystemMemoryGB()) << 30) / 16,
} }
watcher, err := fsnotify.NewWatcher() watcher, err := fsnotify.NewWatcher()
@@ -64,6 +64,8 @@ func NewOpenFileCache(maxCount int) (*OpenFileCache, error) {
} }
func (this *OpenFileCache) Get(filename string) *OpenFile { func (this *OpenFileCache) Get(filename string) *OpenFile {
filename = filepath.Clean(filename)
this.locker.RLock() this.locker.RLock()
pool, ok := this.poolMap[filename] pool, ok := this.poolMap[filename]
this.locker.RUnlock() this.locker.RUnlock()
@@ -85,6 +87,8 @@ func (this *OpenFileCache) Get(filename string) *OpenFile {
} }
func (this *OpenFileCache) Put(filename string, file *OpenFile) { func (this *OpenFileCache) Put(filename string, file *OpenFile) {
filename = filepath.Clean(filename)
if file.size > maxOpenFileSize { if file.size > maxOpenFileSize {
return return
} }
@@ -119,6 +123,8 @@ func (this *OpenFileCache) Put(filename string, file *OpenFile) {
} }
func (this *OpenFileCache) Close(filename string) { func (this *OpenFileCache) Close(filename string) {
filename = filepath.Clean(filename)
this.locker.Lock() this.locker.Lock()
pool, ok := this.poolMap[filename] pool, ok := this.poolMap[filename]

View File

@@ -6,7 +6,6 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"os"
"strconv" "strconv"
) )
@@ -70,7 +69,7 @@ func NewPartialRangesFromJSON(data []byte) (*PartialRanges, error) {
// NewPartialRangesFromFile 从文件中加载范围信息 // NewPartialRangesFromFile 从文件中加载范围信息
func NewPartialRangesFromFile(path string) (*PartialRanges, error) { func NewPartialRangesFromFile(path string) (*PartialRanges, error) {
data, err := os.ReadFile(path) data, err := SharedPartialRangesQueue.Get(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -172,7 +171,8 @@ func (this *PartialRanges) Bytes() []byte {
// WriteToFile 写入到文件中 // WriteToFile 写入到文件中
func (this *PartialRanges) WriteToFile(path string) error { func (this *PartialRanges) WriteToFile(path string) error {
return os.WriteFile(path, this.Bytes(), 0666) SharedPartialRangesQueue.Put(path, this.Bytes())
return nil
} }
// Max 获取最大位置 // Max 获取最大位置
@@ -188,6 +188,11 @@ func (this *PartialRanges) Reset() {
this.Ranges = [][2]int64{} this.Ranges = [][2]int64{}
} }
// IsCompleted 是否已下载完整
func (this *PartialRanges) IsCompleted() bool {
return len(this.Ranges) == 1 && this.Ranges[0][0] == 0 && this.Ranges[0][1] == this.BodySize-1
}
func (this *PartialRanges) merge(index int) { func (this *PartialRanges) merge(index int) {
// forward // forward
var lastIndex = index var lastIndex = index

View File

@@ -0,0 +1,144 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches
import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
"sync"
)
var SharedPartialRangesQueue = NewPartialRangesQueue()
func init() {
if !teaconst.IsMain {
return
}
SharedPartialRangesQueue.Start()
}
const partialRangesQueueSharding = 8
// PartialRangesQueue ranges file writing queue
type PartialRangesQueue struct {
m [partialRangesQueueSharding]map[string][]byte // { filename => data, ... }
c chan string // filename1, ...
mu [partialRangesQueueSharding]*sync.RWMutex
}
// NewPartialRangesQueue Create new queue
func NewPartialRangesQueue() *PartialRangesQueue {
var queueSize = 512
var memGB = memutils.SystemMemoryGB()
if memGB > 16 {
queueSize = 8 << 10
} else if memGB > 8 {
queueSize = 4 << 10
} else if memGB > 4 {
queueSize = 2 << 10
} else if memGB > 2 {
queueSize = 1 << 10
}
var m = [partialRangesQueueSharding]map[string][]byte{}
var muList = [partialRangesQueueSharding]*sync.RWMutex{}
for i := 0; i < partialRangesQueueSharding; i++ {
muList[i] = &sync.RWMutex{}
m[i] = map[string][]byte{}
}
return &PartialRangesQueue{
mu: muList,
m: m,
c: make(chan string, queueSize),
}
}
// Start the queue
func (this *PartialRangesQueue) Start() {
goman.New(func() {
this.Dump()
})
}
// Put ranges data to filename
func (this *PartialRangesQueue) Put(filename string, data []byte) {
var index = this.indexForKey(filename)
this.mu[index].Lock()
this.m[index][filename] = data
this.mu[index].Unlock()
// always wait to finish
this.c <- filename
}
// Get ranges data from filename
func (this *PartialRangesQueue) Get(filename string) ([]byte, error) {
var index = this.indexForKey(filename)
this.mu[index].RLock()
data, ok := this.m[index][filename]
this.mu[index].RUnlock()
if ok {
return data, nil
}
return fsutils.ReadFile(filename)
}
// Delete ranges filename
func (this *PartialRangesQueue) Delete(filename string) {
var index = this.indexForKey(filename)
this.mu[index].Lock()
delete(this.m[index], filename)
this.mu[index].Unlock()
}
// Dump ranges to filename from memory
func (this *PartialRangesQueue) Dump() {
for filename := range this.c {
var index = this.indexForKey(filename)
this.mu[index].Lock()
data, ok := this.m[index][filename]
if ok {
delete(this.m[index], filename)
}
this.mu[index].Unlock()
if !ok || len(data) == 0 {
continue
}
err := fsutils.WriteFile(filename, data, 0666)
if err != nil {
remotelogs.Println("PARTIAL_RANGES_QUEUE", "write file '"+filename+"' failed: "+err.Error())
}
}
}
// Len count all files
func (this *PartialRangesQueue) Len() int {
var count int
for i := 0; i < partialRangesQueueSharding; i++ {
this.mu[i].RLock()
count += len(this.m[i])
this.mu[i].RUnlock()
}
return count
}
func (this *PartialRangesQueue) indexForKey(filename string) int {
return int(fnv.HashString(filename) % partialRangesQueueSharding)
}

View File

@@ -0,0 +1,31 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches_test
import (
"github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestNewPartialRangesQueue(t *testing.T) {
var a = assert.NewAssertion(t)
var queue = caches.NewPartialRangesQueue()
queue.Put("a", []byte{1, 2, 3})
t.Log("add 'a':", queue.Len())
t.Log(queue.Get("a"))
a.IsTrue(queue.Len() == 1)
queue.Put("a", nil)
t.Log("add 'a':", queue.Len())
a.IsTrue(queue.Len() == 1)
queue.Put("b", nil)
t.Log("add 'b':", queue.Len())
a.IsTrue(queue.Len() == 2)
queue.Delete("a")
t.Log("delete 'a':", queue.Len())
a.IsTrue(queue.Len() == 1)
}

View File

@@ -3,6 +3,7 @@ package caches
import ( import (
"encoding/binary" "encoding/binary"
"errors" "errors"
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges" rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"io" "io"
@@ -174,7 +175,9 @@ func (this *FileReader) ReadHeader(buf []byte, callback ReaderFunc) error {
var headerSize = this.headerSize var headerSize = this.headerSize
for { for {
fsutils.ReaderLimiter.Ack()
n, err := this.fp.Read(buf) n, err := this.fp.Read(buf)
fsutils.ReaderLimiter.Release()
if n > 0 { if n > 0 {
if n < headerSize { if n < headerSize {
goNext, e := callback(n) goNext, e := callback(n)
@@ -236,7 +239,9 @@ func (this *FileReader) ReadBody(buf []byte, callback ReaderFunc) error {
} }
for { for {
fsutils.ReaderLimiter.Ack()
n, err := this.fp.Read(buf) n, err := this.fp.Read(buf)
fsutils.ReaderLimiter.Release()
if n > 0 { if n > 0 {
goNext, e := callback(n) goNext, e := callback(n)
if e != nil { if e != nil {
@@ -267,7 +272,9 @@ func (this *FileReader) Read(buf []byte) (n int, err error) {
return return
} }
fsutils.ReaderLimiter.Ack()
n, err = this.fp.Read(buf) n, err = this.fp.Read(buf)
fsutils.ReaderLimiter.Release()
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
_ = this.discard() _ = this.discard()
} }
@@ -299,13 +306,17 @@ func (this *FileReader) ReadBodyRange(buf []byte, start int64, end int64, callba
isOk = true isOk = true
return ErrInvalidRange return ErrInvalidRange
} }
fsutils.ReaderLimiter.Ack()
_, err := this.fp.Seek(offset, io.SeekStart) _, err := this.fp.Seek(offset, io.SeekStart)
fsutils.ReaderLimiter.Release()
if err != nil { if err != nil {
return err return err
} }
for { for {
fsutils.ReaderLimiter.Ack()
n, err := this.fp.Read(buf) n, err := this.fp.Read(buf)
fsutils.ReaderLimiter.Release()
if n > 0 { if n > 0 {
var n2 = int(end-offset) + 1 var n2 = int(end-offset) + 1
if n2 <= n { if n2 <= n {
@@ -375,7 +386,9 @@ func (this *FileReader) Close() error {
} }
func (this *FileReader) readToBuff(fp *os.File, buf []byte) (ok bool, err error) { func (this *FileReader) readToBuff(fp *os.File, buf []byte) (ok bool, err error) {
fsutils.ReaderLimiter.Ack()
n, err := fp.Read(buf) n, err := fp.Read(buf)
fsutils.ReaderLimiter.Release()
if err != nil { if err != nil {
return false, err return false, err
} }
@@ -393,5 +406,5 @@ func (this *FileReader) discard() error {
} }
// remove file // remove file
return os.Remove(this.fp.Name()) return fsutils.Remove(this.fp.Name())
} }

View File

@@ -0,0 +1,18 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package caches
import (
"errors"
"io"
)
type MMAPFileReader struct {
FileReader
}
func (this *MMAPFileReader) CopyBodyTo(writer io.Writer) (int, error) {
// stub
return 0, errors.New("not implemented")
}

View File

@@ -4,6 +4,7 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges" rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"io" "io"
@@ -34,7 +35,7 @@ func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
this.header = this.openFile.header this.header = this.openFile.header
} }
isOk := false var isOk = false
if autoDiscard { if autoDiscard {
defer func() { defer func() {
@@ -54,9 +55,9 @@ func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
var buf = this.meta var buf = this.meta
if len(buf) == 0 { if len(buf) == 0 {
buf = make([]byte, SizeMeta) buf = make([]byte, SizeMeta)
ok, err := this.readToBuff(this.fp, buf) ok, readErr := this.readToBuff(this.fp, buf)
if err != nil { if readErr != nil {
return err return readErr
} }
if !ok { if !ok {
return ErrNotFound return ErrNotFound
@@ -73,10 +74,10 @@ func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
this.status = status this.status = status
// URL // URL
urlLength := binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus : SizeExpiresAt+SizeStatus+SizeURLLength]) var urlLength = binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus : SizeExpiresAt+SizeStatus+SizeURLLength])
// header // header
headerSize := int(binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus+SizeURLLength : SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength])) var headerSize = int(binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus+SizeURLLength : SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength]))
if headerSize == 0 { if headerSize == 0 {
return nil return nil
} }
@@ -96,7 +97,7 @@ func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
if this.openFileCache != nil && len(this.header) == 0 { if this.openFileCache != nil && len(this.header) == 0 {
if headerSize > 0 && headerSize <= 512 { if headerSize > 0 && headerSize <= 512 {
this.header = make([]byte, headerSize) this.header = make([]byte, headerSize)
_, err := this.fp.Seek(this.headerOffset, io.SeekStart) _, err = this.fp.Seek(this.headerOffset, io.SeekStart)
if err != nil { if err != nil {
return err return err
} }
@@ -140,7 +141,13 @@ func (this *PartialFileReader) Ranges() *PartialRanges {
return this.ranges return this.ranges
} }
func (this *PartialFileReader) IsCompleted() bool {
return this.ranges != nil && this.ranges.IsCompleted()
}
func (this *PartialFileReader) discard() error { func (this *PartialFileReader) discard() error {
_ = os.Remove(this.rangePath) SharedPartialRangesQueue.Delete(this.rangePath)
_ = fsutils.Remove(this.rangePath)
return this.FileReader.discard() return this.FileReader.discard()
} }

View File

@@ -16,8 +16,8 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime" "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs" fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets" setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
"github.com/TeaOSLab/EdgeNode/internal/zero" "github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
@@ -54,16 +54,30 @@ const (
) )
const ( const (
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值) FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
HotItemSize = 1024 // 热点数据数量 HotItemSize = 1024 // 热点数据数量
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期 HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
FileTmpSuffix = ".tmp" FileTmpSuffix = ".tmp"
DefaultMinDiskFreeSpace uint64 = 5 << 30 // 当前磁盘最小剩余空间 DefaultMinDiskFreeSpace uint64 = 5 << 30 // 当前磁盘最小剩余空间
DefaultStaleCacheSeconds = 1200 // 过时缓存留存时间 DefaultStaleCacheSeconds = 1200 // 过时缓存留存时间
HashKeyLength = 32 HashKeyLength = 32
) )
var FileToMemoryMaxSize int64 = 32 << 20 // 可以从文件写入到内存的最大文件尺寸
func init() {
var availableMemoryGB = memutils.AvailableMemoryGB()
if availableMemoryGB > 64 {
FileToMemoryMaxSize = 512 << 20
} else if availableMemoryGB > 32 {
FileToMemoryMaxSize = 256 << 20
} else if availableMemoryGB > 16 {
FileToMemoryMaxSize = 128 << 20
} else if availableMemoryGB > 8 {
FileToMemoryMaxSize = 64 << 20
}
}
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
var sharedWritingFileKeyLocker = sync.Mutex{} var sharedWritingFileKeyLocker = sync.Mutex{}
@@ -125,7 +139,7 @@ func (this *FileStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolic
if err != nil { if err != nil {
return false return false
} }
var oldOptions = &serverconfigs.HTTPFileCacheStorage{} var oldOptions = serverconfigs.NewHTTPFileCacheStorage()
err = json.Unmarshal(oldOptionsJSON, oldOptions) err = json.Unmarshal(oldOptionsJSON, oldOptions)
if err != nil { if err != nil {
return false return false
@@ -135,7 +149,7 @@ func (this *FileStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolic
if err != nil { if err != nil {
return false return false
} }
var newOptions = &serverconfigs.HTTPFileCacheStorage{} var newOptions = serverconfigs.NewHTTPFileCacheStorage()
err = json.Unmarshal(newOptionsJSON, newOptions) err = json.Unmarshal(newOptionsJSON, newOptions)
if err != nil { if err != nil {
return false return false
@@ -158,7 +172,7 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
if err != nil { if err != nil {
return return
} }
var newOptions = &serverconfigs.HTTPFileCacheStorage{} var newOptions = serverconfigs.NewHTTPFileCacheStorage()
err = json.Unmarshal(newOptionsJSON, newOptions) err = json.Unmarshal(newOptionsJSON, newOptions)
if err != nil { if err != nil {
remotelogs.Error("CACHE", "update policy '"+types.String(this.policy.Id)+"' failed: decode options failed: "+err.Error()) remotelogs.Error("CACHE", "update policy '"+types.String(this.policy.Id)+"' failed: decode options failed: "+err.Error())
@@ -223,7 +237,7 @@ func (this *FileStorage) Init() error {
var before = time.Now() var before = time.Now()
// 配置 // 配置
var options = &serverconfigs.HTTPFileCacheStorage{} var options = serverconfigs.NewHTTPFileCacheStorage()
optionsJSON, err := json.Marshal(this.policy.Options) optionsJSON, err := json.Marshal(this.policy.Options)
if err != nil { if err != nil {
return err return err
@@ -258,12 +272,25 @@ func (this *FileStorage) Init() error {
return errors.New("[CACHE]cache storage dir can not be empty") return errors.New("[CACHE]cache storage dir can not be empty")
} }
var list = NewFileList(dir + "/p" + types.String(this.policy.Id) + "/.indexes") // read list
err = list.Init() var list ListInterface
if err != nil { var sqliteIndexesDir = dir + "/p" + types.String(this.policy.Id) + "/.indexes"
return err _, sqliteIndexesDirErr := os.Stat(sqliteIndexesDir)
if sqliteIndexesDirErr == nil || !teaconst.EnableKVCacheStore {
list = NewSQLiteFileList(sqliteIndexesDir)
err = list.Init()
if err != nil {
return err
}
list.(*SQLiteFileList).SetOldDir(dir + "/p" + types.String(this.policy.Id))
} else {
list = NewKVFileList(dir + "/p" + types.String(this.policy.Id) + "/.stores")
err = list.Init()
if err != nil {
return err
}
} }
list.(*FileList).SetOldDir(dir + "/p" + types.String(this.policy.Id))
this.list = list this.list = list
// 检查目录是否存在 // 检查目录是否存在
@@ -284,14 +311,19 @@ func (this *FileStorage) Init() error {
var totalSize = this.TotalDiskSize() var totalSize = this.TotalDiskSize()
var cost = time.Since(before).Seconds() * 1000 var cost = time.Since(before).Seconds() * 1000
var sizeMB = types.String(totalSize) + " Bytes" var sizeMB = types.String(totalSize) + " Bytes"
if totalSize > 1*sizes.G { if totalSize > (1 << 30) {
sizeMB = fmt.Sprintf("%.3f G", float64(totalSize)/float64(sizes.G)) sizeMB = fmt.Sprintf("%.3f GiB", float64(totalSize)/(1<<30))
} else if totalSize > 1*sizes.M { } else if totalSize > (1 << 20) {
sizeMB = fmt.Sprintf("%.3f M", float64(totalSize)/float64(sizes.M)) sizeMB = fmt.Sprintf("%.3f MiB", float64(totalSize)/(1<<20))
} else if totalSize > 1*sizes.K { } else if totalSize > (1 << 10) {
sizeMB = fmt.Sprintf("%.3f K", float64(totalSize)/float64(sizes.K)) sizeMB = fmt.Sprintf("%.3f KiB", float64(totalSize)/(1<<10))
} }
remotelogs.Println("CACHE", "init policy "+types.String(this.policy.Id)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, size: "+sizeMB)
var mmapTag = "disabled"
if this.options.EnableMMAP {
mmapTag = "enabled"
}
remotelogs.Println("CACHE", "init policy "+types.String(this.policy.Id)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, size: "+sizeMB+", mmap: "+mmapTag)
}() }()
// 初始化list // 初始化list
@@ -347,17 +379,33 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
hash, path, _ := this.keyPath(key) hash, path, _ := this.keyPath(key)
// 检查文件记录是否已过期 // 检查文件记录是否已过期
var estimatedSize int64
if !useStale { if !useStale {
exists, err := this.list.Exist(hash) exists, filesize, err := this.list.Exist(hash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !exists { if !exists {
return nil, ErrNotFound return nil, ErrNotFound
} }
estimatedSize = filesize
}
// 尝试通过MMAP读取
if estimatedSize > 0 {
if !fsutils.ReaderLimiter.TryAck() {
return nil, ErrServerIsBusy
}
reader, err := this.tryMMAPReader(isPartial, estimatedSize, path)
fsutils.ReaderLimiter.Release()
if err != nil {
return nil, err
}
if reader != nil {
return reader, nil
}
} }
// TODO 尝试使用mmap加快读取速度
var isOk = false var isOk = false
var openFile *OpenFile var openFile *OpenFile
var openFileCache = this.openFileCache // 因为中间可能有修改,所以先赋值再获取 var openFileCache = this.openFileCache // 因为中间可能有修改,所以先赋值再获取
@@ -365,9 +413,14 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
openFile = openFileCache.Get(path) openFile = openFileCache.Get(path)
} }
var fp *os.File var fp *os.File
var err error var err error
if openFile == nil { if openFile == nil {
if !fsutils.ReaderLimiter.TryAck() {
return nil, ErrServerIsBusy
}
fp, err = os.OpenFile(path, os.O_RDONLY, 0444) fp, err = os.OpenFile(path, os.O_RDONLY, 0444)
fsutils.ReaderLimiter.Release()
if err != nil { if err != nil {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
return nil, err return nil, err
@@ -396,6 +449,7 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
fileReader.openFileCache = openFileCache fileReader.openFileCache = openFileCache
reader = fileReader reader = fileReader
} }
err = reader.Init() err = reader.Init()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -446,7 +500,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
maxMemorySize = maxSize maxMemorySize = maxSize
} }
var memoryStorage = this.memoryStorage var memoryStorage = this.memoryStorage
if !fsutils.DiskIsExtremelyFast() && !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) { if !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) {
writer, err := memoryStorage.OpenWriter(key, expiredAt, status, headerSize, bodySize, maxMemorySize, false) writer, err := memoryStorage.OpenWriter(key, expiredAt, status, headerSize, bodySize, maxMemorySize, false)
if err == nil { if err == nil {
return writer, nil return writer, nil
@@ -456,6 +510,10 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
if errors.Is(err, ErrWritingQueueFull) { if errors.Is(err, ErrWritingQueueFull) {
return nil, err return nil, err
} }
if IsCapacityError(err) && bodySize > 0 && memoryStorage.totalDirtySize > (128<<20) {
return nil, err
}
} }
// 是否正在写入 // 是否正在写入
@@ -467,11 +525,6 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
return nil, fmt.Errorf("%w(001)", ErrFileIsWriting) return nil, fmt.Errorf("%w(001)", ErrFileIsWriting)
} }
if !isFlushing && !fsutils.WriteReady() {
sharedWritingFileKeyLocker.Unlock()
return nil, ErrServerIsBusy
}
sharedWritingFileKeyMap[key] = zero.New() sharedWritingFileKeyMap[key] = zero.New()
sharedWritingFileKeyLocker.Unlock() sharedWritingFileKeyLocker.Unlock()
defer func() { defer func() {
@@ -536,9 +589,13 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
var partialRanges *PartialRanges var partialRanges *PartialRanges
if isPartial { if isPartial {
// 数据库中是否存在 // 数据库中是否存在
existsCacheItem, _ := this.list.Exist(hash) existsCacheItem, _, _ := this.list.Exist(hash)
if existsCacheItem { if existsCacheItem {
if !fsutils.ReaderLimiter.TryAck() {
return nil, ErrServerIsBusy
}
readerFp, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444) readerFp, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444)
fsutils.ReaderLimiter.Release()
if err == nil { if err == nil {
var partialReader = NewPartialFileReader(readerFp) var partialReader = NewPartialFileReader(readerFp)
err = partialReader.Init() err = partialReader.Init()
@@ -571,16 +628,19 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
if isNewCreated && existsFile { if isNewCreated && existsFile {
flags |= os.O_TRUNC flags |= os.O_TRUNC
} }
fsutils.WriteBegin() if !fsutils.WriterLimiter.TryAck() {
return nil, ErrServerIsBusy
}
writer, err := os.OpenFile(tmpPath, flags, 0666) writer, err := os.OpenFile(tmpPath, flags, 0666)
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
if err != nil { if err != nil {
// TODO 检查在各个系统中的稳定性
if os.IsNotExist(err) { if os.IsNotExist(err) {
_ = os.MkdirAll(dir, 0777) _ = os.MkdirAll(dir, 0777)
// open file again // open file again
fsutils.WriterLimiter.Ack()
writer, err = os.OpenFile(tmpPath, flags, 0666) writer, err = os.OpenFile(tmpPath, flags, 0666)
fsutils.WriterLimiter.Release()
} }
if err != nil { if err != nil {
return nil, err return nil, err
@@ -597,7 +657,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
if !isOk { if !isOk {
_ = writer.Close() _ = writer.Close()
if removeOnFailure { if removeOnFailure {
_ = os.Remove(tmpPath) _ = fsutils.Remove(tmpPath)
} }
} }
}() }()
@@ -609,6 +669,11 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
return nil, fmt.Errorf("%w (003)", ErrFileIsWriting) return nil, fmt.Errorf("%w (003)", ErrFileIsWriting)
} }
// 关闭
if openFileCache != nil {
openFileCache.Close(cachePath)
}
var metaBodySize int64 = -1 var metaBodySize int64 = -1
var metaHeaderSize = -1 var metaHeaderSize = -1
if isNewCreated { if isNewCreated {
@@ -635,9 +700,9 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
metaBodySize = bodySize metaBodySize = bodySize
} }
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
_, err = writer.Write(metaBytes) _, err = writer.Write(metaBytes)
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1090,9 +1155,7 @@ func (this *FileStorage) purgeLoop() {
for i := 0; i < times; i++ { for i := 0; i < times; i++ {
countFound, err := this.list.Purge(purgeCount, func(hash string) error { countFound, err := this.list.Purge(purgeCount, func(hash string) error {
path, _ := this.hashPath(hash) path, _ := this.hashPath(hash)
fsutils.WriteBegin()
err := this.removeCacheFile(path) err := this.removeCacheFile(path)
fsutils.WriteEnd()
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error()) remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
} }
@@ -1149,9 +1212,7 @@ func (this *FileStorage) purgeLoop() {
var before = time.Now() var before = time.Now()
err := this.list.PurgeLFU(count, func(hash string) error { err := this.list.PurgeLFU(count, func(hash string) error {
path, _ := this.hashPath(hash) path, _ := this.hashPath(hash)
fsutils.WriteBegin()
err := this.removeCacheFile(path) err := this.removeCacheFile(path)
fsutils.WriteEnd()
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error()) remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
} }
@@ -1223,6 +1284,7 @@ func (this *FileStorage) hotLoop() {
} }
var buf = utils.BytePool16k.Get() var buf = utils.BytePool16k.Get()
defer utils.BytePool16k.Put(buf) defer utils.BytePool16k.Put(buf)
for _, item := range result[:size] { for _, item := range result[:size] {
reader, err := this.openReader(item.Key, false, false, false) reader, err := this.openReader(item.Key, false, false, false)
@@ -1264,8 +1326,8 @@ func (this *FileStorage) hotLoop() {
continue continue
} }
err = reader.ReadHeader(buf, func(n int) (goNext bool, err error) { err = reader.ReadHeader(buf.Bytes, func(n int) (goNext bool, err error) {
_, err = writer.WriteHeader(buf[:n]) _, err = writer.WriteHeader(buf.Bytes[:n])
return return
}) })
if err != nil { if err != nil {
@@ -1274,10 +1336,10 @@ func (this *FileStorage) hotLoop() {
continue continue
} }
err = reader.ReadBody(buf, func(n int) (goNext bool, err error) { err = reader.ReadBody(buf.Bytes, func(n int) (goNext bool, err error) {
goNext = true goNext = true
if n > 0 { if n > 0 {
_, err = writer.Write(buf[:n]) _, err = writer.Write(buf.Bytes[:n])
if err != nil { if err != nil {
goNext = false goNext = false
} }
@@ -1294,7 +1356,7 @@ func (this *FileStorage) hotLoop() {
Type: writer.ItemType(), Type: writer.ItemType(),
Key: item.Key, Key: item.Key,
Host: ParseHost(item.Key), Host: ParseHost(item.Key),
ExpiredAt: expiresAt, ExpiresAt: expiresAt,
HeaderSize: writer.HeaderSize(), HeaderSize: writer.HeaderSize(),
BodySize: writer.BodySize(), BodySize: writer.BodySize(),
}) })
@@ -1385,7 +1447,7 @@ func (this *FileStorage) increaseHit(key string, hash string, reader Reader) {
// 增加到热点 // 增加到热点
// 这里不收录缓存尺寸过大的文件 // 这里不收录缓存尺寸过大的文件
if memoryStorage != nil && reader.BodySize() > 0 && reader.BodySize() < 128*sizes.M { if memoryStorage != nil && reader.BodySize() > 0 && reader.BodySize() < (128<<20) {
this.hotMapLocker.Lock() this.hotMapLocker.Lock()
hotItem, ok := this.hotMap[key] hotItem, ok := this.hotMap[key]
@@ -1418,7 +1480,7 @@ func (this *FileStorage) removeCacheFile(path string) error {
openFileCache.Close(path) openFileCache.Close(path)
} }
var err = os.Remove(path) var err = fsutils.Remove(path)
if err == nil || os.IsNotExist(err) { if err == nil || os.IsNotExist(err) {
err = nil err = nil
@@ -1430,7 +1492,8 @@ func (this *FileStorage) removeCacheFile(path string) error {
_, statErr := os.Stat(partialPath) _, statErr := os.Stat(partialPath)
if statErr == nil { if statErr == nil {
_ = os.Remove(partialPath) _ = fsutils.Remove(partialPath)
SharedPartialRangesQueue.Delete(partialPath)
} }
} }
return err return err
@@ -1608,7 +1671,8 @@ func (this *FileStorage) subDir(hash string) (dirPath string, dirIsFull bool) {
// ScanGarbageCaches 清理目录中“失联”的缓存文件 // ScanGarbageCaches 清理目录中“失联”的缓存文件
// “失联”为不在HashMap中的文件 // “失联”为不在HashMap中的文件
func (this *FileStorage) ScanGarbageCaches(fileCallback func(path string) error) error { func (this *FileStorage) ScanGarbageCaches(fileCallback func(path string) error) error {
if !this.list.(*FileList).HashMapIsLoaded() { _, isSQLite := this.list.(*SQLiteFileList)
if isSQLite && !this.list.(*SQLiteFileList).HashMapIsLoaded() {
return errors.New("cache list is loading") return errors.New("cache list is loading")
} }
@@ -1641,8 +1705,8 @@ func (this *FileStorage) ScanGarbageCaches(fileCallback func(path string) error)
continue continue
} }
dir2Matches, err := filepath.Glob(dir1 + "/*") dir2Matches, globErr := filepath.Glob(dir1 + "/*")
if err != nil { if globErr != nil {
// ignore error // ignore error
continue continue
} }
@@ -1665,8 +1729,8 @@ func (this *FileStorage) ScanGarbageCaches(fileCallback func(path string) error)
} }
} }
fileMatches, err := filepath.Glob(dir2 + "/*.cache") fileMatches, globDir2Err := filepath.Glob(dir2 + "/*.cache")
if err != nil { if globDir2Err != nil {
// ignore error // ignore error
continue continue
} }
@@ -1678,7 +1742,19 @@ func (this *FileStorage) ScanGarbageCaches(fileCallback func(path string) error)
continue continue
} }
isReady, found := this.list.(*FileList).ExistQuick(hash) var isReady, found bool
switch rawList := this.list.(type) {
case *SQLiteFileList:
isReady, found = rawList.ExistQuick(hash)
case *KVFileList:
isReady = true
var checkErr error
found, checkErr = rawList.ExistQuick(hash)
if checkErr != nil {
return checkErr
}
}
if !isReady { if !isReady {
continue continue
} }
@@ -1688,8 +1764,8 @@ func (this *FileStorage) ScanGarbageCaches(fileCallback func(path string) error)
} }
// 检查文件正在被写入 // 检查文件正在被写入
stat, err := os.Stat(file) stat, statErr := os.Stat(file)
if err != nil { if statErr != nil {
continue continue
} }
if fasttime.Now().Unix()-stat.ModTime().Unix() < 300 /** 5 minutes **/ { if fasttime.Now().Unix()-stat.ModTime().Unix() < 300 /** 5 minutes **/ {
@@ -1698,9 +1774,9 @@ func (this *FileStorage) ScanGarbageCaches(fileCallback func(path string) error)
if fileCallback != nil { if fileCallback != nil {
countFound++ countFound++
err = fileCallback(file) callbackErr := fileCallback(file)
if err != nil { if callbackErr != nil {
return err return callbackErr
} }
} }
} }

View File

@@ -0,0 +1,9 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package caches
func (this *FileStorage) tryMMAPReader(isPartial bool, estimatedSize int64, path string) (Reader, error) {
// stub
return nil, nil
}

View File

@@ -47,7 +47,7 @@ func TestFileStorage_Init(t *testing.T) {
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
storage.purgeLoop() storage.purgeLoop()
t.Log(storage.list.(*FileList).Stat(func(hash string) bool { t.Log(storage.list.(*SQLiteFileList).Stat(func(hash string) bool {
return true return true
})) }))
} }
@@ -664,7 +664,7 @@ func TestFileStorage_ScanGarbageCaches(t *testing.T) {
func BenchmarkFileStorage_Read(b *testing.B) { func BenchmarkFileStorage_Read(b *testing.B) {
runtime.GOMAXPROCS(1) runtime.GOMAXPROCS(1)
_ = utils.SetRLimit(1024 * 1024) _ = utils.SetRLimit(1 << 20)
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{ var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
Id: 1, Id: 1,

View File

@@ -9,9 +9,10 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime" "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs" fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets" setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
"github.com/TeaOSLab/EdgeNode/internal/zero" "github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/cespare/xxhash" "github.com/cespare/xxhash/v2"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"math" "math"
"runtime" "runtime"
@@ -54,7 +55,9 @@ type MemoryStorage struct {
purgeTicker *utils.Ticker purgeTicker *utils.Ticker
usedSize int64 usedSize int64
totalDirtySize int64
writingKeyMap map[string]zero.Zero // key => bool writingKeyMap map[string]zero.Zero // key => bool
ignoreKeys *setutils.FixedSet ignoreKeys *setutils.FixedSet
@@ -66,7 +69,7 @@ func NewMemoryStorage(policy *serverconfigs.HTTPCachePolicy, parentStorage Stora
if parentStorage != nil { if parentStorage != nil {
if queueSize <= 0 { if queueSize <= 0 {
queueSize = utils.SystemMemoryGB() * 100_000 queueSize = memutils.SystemMemoryGB() * 100_000
} }
dirtyChan = make(chan string, queueSize) dirtyChan = make(chan string, queueSize)
@@ -99,10 +102,19 @@ func (this *MemoryStorage) Init() error {
// 启动定时Flush memory to disk任务 // 启动定时Flush memory to disk任务
if this.parentStorage != nil { if this.parentStorage != nil {
// TODO 应该根据磁盘性能决定线程数 var threads = 2
// TODO 线程数应该可以在缓存策略和节点中设定 var numCPU = runtime.NumCPU()
var threads = runtime.NumCPU() if fsutils.DiskIsExtremelyFast() {
if numCPU >= 8 {
threads = 8
} else {
threads = 4
}
} else if fsutils.DiskIsFast() {
if numCPU >= 4 {
threads = 4
}
}
for i := 0; i < threads; i++ { for i := 0; i < threads; i++ {
goman.New(func() { goman.New(func() {
this.startFlush() this.startFlush()
@@ -118,7 +130,7 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
var hash = this.hash(key) var hash = this.hash(key)
// check if exists in list // check if exists in list
exists, _ := this.list.Exist(types.String(hash)) exists, _, _ := this.list.Exist(types.String(hash))
if !exists { if !exists {
return nil, ErrNotFound return nil, ErrNotFound
} }
@@ -177,7 +189,7 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
if isDirty && if isDirty &&
this.parentStorage != nil && this.parentStorage != nil &&
this.dirtyQueueSize > 0 && this.dirtyQueueSize > 0 &&
len(this.dirtyChan) >= this.dirtyQueueSize-int(fsutils.DiskMaxWrites) /** delta **/ { // 缓存时间过长 len(this.dirtyChan) >= this.dirtyQueueSize-64 /** delta **/ { // 缓存时间过长
return nil, ErrWritingQueueFull return nil, ErrWritingQueueFull
} }
@@ -202,7 +214,7 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
item, ok := this.valuesMap[hash] item, ok := this.valuesMap[hash]
if ok && !item.IsExpired() { if ok && !item.IsExpired() {
var hashString = types.String(hash) var hashString = types.String(hash)
exists, _ := this.list.Exist(hashString) exists, _, _ := this.list.Exist(hashString)
if !exists { if !exists {
// remove from values map // remove from values map
delete(this.valuesMap, hash) delete(this.valuesMap, hash)
@@ -330,6 +342,9 @@ func (this *MemoryStorage) Stop() {
close(this.dirtyChan) close(this.dirtyChan)
} }
this.usedSize = 0
this.totalDirtySize = 0
_ = this.list.Close() _ = this.list.Close()
this.locker.Unlock() this.locker.Unlock()
@@ -496,14 +511,20 @@ func (this *MemoryStorage) startFlush() {
if fsutils.IsInExtremelyHighLoad { if fsutils.IsInExtremelyHighLoad {
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
} else if fsutils.IsInHighLoad {
time.Sleep(100 * time.Millisecond)
} }
} }
} }
// 单次Flush任务 // 单次Flush任务
func (this *MemoryStorage) flushItem(key string) { func (this *MemoryStorage) flushItem(fullKey string) {
sizeString, key, found := strings.Cut(fullKey, "@")
if !found {
return
}
defer func() {
atomic.AddInt64(&this.totalDirtySize, -types.Int64(sizeString))
}()
if this.parentStorage == nil { if this.parentStorage == nil {
return return
} }
@@ -516,11 +537,6 @@ func (this *MemoryStorage) flushItem(key string) {
// 从内存中移除,并确保无论如何都会执行 // 从内存中移除,并确保无论如何都会执行
defer func() { defer func() {
_ = this.Delete(key) _ = this.Delete(key)
// 重用内存,前提是确保内存不再被引用
if enableFragmentPool && ok && item.IsDone && !item.isReferring && len(item.BodyValue) > 0 {
SharedFragmentMemoryPool.Put(item.BodyValue)
}
}() }()
if !ok { if !ok {
@@ -536,13 +552,28 @@ func (this *MemoryStorage) flushItem(key string) {
} }
// 检查是否在列表中防止未加入列表时就开始flush // 检查是否在列表中防止未加入列表时就开始flush
isInList, err := this.list.Exist(types.String(hash)) isInList, _, err := this.list.Exist(types.String(hash))
if err != nil { if err != nil {
remotelogs.Error("CACHE", "flush items failed: "+err.Error()) remotelogs.Error("CACHE", "flush items failed: "+err.Error())
return return
} }
if !isInList { if !isInList {
time.Sleep(1 * time.Second) for i := 0; i < 1000; i++ {
isInList, _, err = this.list.Exist(types.String(hash))
if err != nil {
remotelogs.Error("CACHE", "flush items failed: "+err.Error())
time.Sleep(1 * time.Second)
continue
}
if isInList {
break
}
time.Sleep(1 * time.Millisecond)
}
if !isInList {
// discard
return
}
} }
writer, err := this.parentStorage.OpenFlushWriter(key, item.ExpiresAt, item.Status, len(item.HeaderValue), int64(len(item.BodyValue))) writer, err := this.parentStorage.OpenFlushWriter(key, item.ExpiresAt, item.Status, len(item.HeaderValue), int64(len(item.BodyValue)))
@@ -578,7 +609,7 @@ func (this *MemoryStorage) flushItem(key string) {
Type: writer.ItemType(), Type: writer.ItemType(),
Key: key, Key: key,
Host: ParseHost(key), Host: ParseHost(key),
ExpiredAt: item.ExpiresAt, ExpiresAt: item.ExpiresAt,
HeaderSize: writer.HeaderSize(), HeaderSize: writer.HeaderSize(),
BodySize: writer.BodySize(), BodySize: writer.BodySize(),
}) })

View File

@@ -6,6 +6,7 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils" "github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"math/rand"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"strconv" "strconv"
@@ -157,7 +158,7 @@ func TestMemoryStorage_Stat(t *testing.T) {
storage.AddToList(&Item{ storage.AddToList(&Item{
Key: "abc", Key: "abc",
BodySize: 5, BodySize: 5,
ExpiredAt: expiredAt, ExpiresAt: expiredAt,
}) })
} }
{ {
@@ -174,7 +175,7 @@ func TestMemoryStorage_Stat(t *testing.T) {
storage.AddToList(&Item{ storage.AddToList(&Item{
Key: "abc1", Key: "abc1",
BodySize: 5, BodySize: 5,
ExpiredAt: expiredAt, ExpiresAt: expiredAt,
}) })
} }
stat, err := storage.Stat() stat, err := storage.Stat()
@@ -201,7 +202,7 @@ func TestMemoryStorage_CleanAll(t *testing.T) {
storage.AddToList(&Item{ storage.AddToList(&Item{
Key: "abc", Key: "abc",
BodySize: 5, BodySize: 5,
ExpiredAt: expiredAt, ExpiresAt: expiredAt,
}) })
} }
{ {
@@ -217,7 +218,7 @@ func TestMemoryStorage_CleanAll(t *testing.T) {
storage.AddToList(&Item{ storage.AddToList(&Item{
Key: "abc1", Key: "abc1",
BodySize: 5, BodySize: 5,
ExpiredAt: expiredAt, ExpiresAt: expiredAt,
}) })
} }
err := storage.CleanAll() err := storage.CleanAll()
@@ -244,7 +245,7 @@ func TestMemoryStorage_Purge(t *testing.T) {
storage.AddToList(&Item{ storage.AddToList(&Item{
Key: "abc", Key: "abc",
BodySize: 5, BodySize: 5,
ExpiredAt: expiredAt, ExpiresAt: expiredAt,
}) })
} }
{ {
@@ -260,7 +261,7 @@ func TestMemoryStorage_Purge(t *testing.T) {
storage.AddToList(&Item{ storage.AddToList(&Item{
Key: "abc1", Key: "abc1",
BodySize: 5, BodySize: 5,
ExpiredAt: expiredAt, ExpiresAt: expiredAt,
}) })
} }
err := storage.Purge([]string{"abc", "abc1"}, "") err := storage.Purge([]string{"abc", "abc1"}, "")
@@ -299,7 +300,7 @@ func TestMemoryStorage_Expire(t *testing.T) {
storage.AddToList(&Item{ storage.AddToList(&Item{
Key: key, Key: key,
BodySize: 5, BodySize: 5,
ExpiredAt: expiredAt, ExpiresAt: expiredAt,
}) })
} }
time.Sleep(70 * time.Second) time.Sleep(70 * time.Second)
@@ -381,3 +382,31 @@ func BenchmarkValuesMap(b *testing.B) {
} }
}) })
} }
func BenchmarkNewMemoryStorage(b *testing.B) {
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
var data = bytes.Repeat([]byte{'A'}, 1024)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
func() {
writer, err := storage.OpenWriter("abc"+strconv.Itoa(rand.Int()), time.Now().Unix()+60, 200, -1, -1, -1, false)
if err != nil {
b.Fatal(err)
}
if err != nil {
b.Fatal(err)
}
_, _ = writer.WriteHeader([]byte("Header"))
_, _ = writer.Write(data)
err = writer.Close()
if err != nil {
b.Fatal(err)
}
}()
}
})
}

View File

@@ -5,7 +5,7 @@ package caches_test
import ( import (
"fmt" "fmt"
"github.com/TeaOSLab/EdgeNode/internal/caches" "github.com/TeaOSLab/EdgeNode/internal/caches"
"github.com/cespare/xxhash" "github.com/cespare/xxhash/v2"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"strconv" "strconv"
"testing" "testing"

View File

@@ -43,9 +43,9 @@ func NewFileWriter(storage StorageInterface, rawWriter *os.File, key string, exp
// WriteHeader 写入数据 // WriteHeader 写入数据
func (this *FileWriter) WriteHeader(data []byte) (n int, err error) { func (this *FileWriter) WriteHeader(data []byte) (n int, err error) {
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
n, err = this.rawWriter.Write(data) n, err = this.rawWriter.Write(data)
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
this.headerSize += int64(n) this.headerSize += int64(n)
if err != nil { if err != nil {
_ = this.Discard() _ = this.Discard()
@@ -136,32 +136,39 @@ func (this *FileWriter) Close() error {
var path = this.rawWriter.Name() var path = this.rawWriter.Name()
// check content length
if this.metaBodySize > 0 && this.bodySize != this.metaBodySize {
_ = this.rawWriter.Close()
_ = fsutils.Remove(path)
return ErrUnexpectedContentLength
}
err := this.WriteHeaderLength(types.Int(this.headerSize)) err := this.WriteHeaderLength(types.Int(this.headerSize))
if err != nil { if err != nil {
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
_ = this.rawWriter.Close() _ = this.rawWriter.Close()
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
_ = os.Remove(path) _ = fsutils.Remove(path)
return err return err
} }
err = this.WriteBodyLength(this.bodySize) err = this.WriteBodyLength(this.bodySize)
if err != nil { if err != nil {
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
_ = this.rawWriter.Close() _ = this.rawWriter.Close()
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
_ = os.Remove(path) _ = fsutils.Remove(path)
return err return err
} }
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
err = this.rawWriter.Close() err = this.rawWriter.Close()
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
if err != nil { if err != nil {
_ = os.Remove(path) _ = fsutils.Remove(path)
} else if strings.HasSuffix(path, FileTmpSuffix) { } else if strings.HasSuffix(path, FileTmpSuffix) {
err = os.Rename(path, strings.Replace(path, FileTmpSuffix, "", 1)) err = fsutils.Rename(path, strings.Replace(path, FileTmpSuffix, "", 1))
if err != nil { if err != nil {
_ = os.Remove(path) _ = fsutils.Remove(path)
} }
} }
@@ -174,11 +181,11 @@ func (this *FileWriter) Discard() error {
this.endFunc() this.endFunc()
}) })
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
_ = this.rawWriter.Close() _ = this.rawWriter.Close()
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
err := os.Remove(this.rawWriter.Name()) err := fsutils.Remove(this.rawWriter.Name())
return err return err
} }
@@ -204,9 +211,9 @@ func (this *FileWriter) ItemType() ItemType {
} }
func (this *FileWriter) write(data []byte) (n int, err error) { func (this *FileWriter) write(data []byte) (n int, err error) {
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
n, err = this.rawWriter.Write(data) n, err = this.rawWriter.Write(data)
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
this.bodySize += int64(n) this.bodySize += int64(n)
if this.maxSize > 0 && this.bodySize > this.maxSize { if this.maxSize > 0 && this.bodySize > this.maxSize {

View File

@@ -3,8 +3,10 @@ package caches
import ( import (
"errors" "errors"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime" "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/cespare/xxhash" "github.com/cespare/xxhash/v2"
"github.com/iwind/TeaGo/types"
"sync" "sync"
"sync/atomic"
) )
type MemoryWriter struct { type MemoryWriter struct {
@@ -32,23 +34,11 @@ func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64,
ModifiedAt: fasttime.Now().Unix(), ModifiedAt: fasttime.Now().Unix(),
Status: status, Status: status,
} }
if enableFragmentPool &&
expectedBodySize > 0 &&
expectedBodySize <= maxMemoryFragmentPoolItemSize {
bodyBytes, ok := SharedFragmentMemoryPool.Get(expectedBodySize) // try to reuse memory
if ok {
valueItem.BodyValue = bodyBytes
valueItem.IsPrepared = true
} else {
if expectedBodySize <= (16 << 20) {
var allocSize = (expectedBodySize/16384 + 1) * 16384
valueItem.BodyValue = make([]byte, allocSize)[:expectedBodySize]
valueItem.IsPrepared = true
SharedFragmentMemoryPool.IncreaseNew() if expectedBodySize > 0 {
} valueItem.BodyValue = make([]byte, 0, expectedBodySize)
}
} }
var w = &MemoryWriter{ var w = &MemoryWriter{
storage: memoryStorage, storage: memoryStorage,
key: key, key: key,
@@ -131,6 +121,14 @@ func (this *MemoryWriter) Close() error {
return nil return nil
} }
// check content length
if this.expectedBodySize > 0 && this.bodySize != this.expectedBodySize {
this.storage.locker.Lock()
delete(this.storage.valuesMap, this.hash)
this.storage.locker.Unlock()
return ErrUnexpectedContentLength
}
this.storage.locker.Lock() this.storage.locker.Lock()
this.item.IsDone = true this.item.IsDone = true
var err error var err error
@@ -139,7 +137,8 @@ func (this *MemoryWriter) Close() error {
this.storage.valuesMap[this.hash] = this.item this.storage.valuesMap[this.hash] = this.item
select { select {
case this.storage.dirtyChan <- this.key: case this.storage.dirtyChan <- types.String(this.bodySize) + "@" + this.key:
atomic.AddInt64(&this.storage.totalDirtySize, this.bodySize)
default: default:
// remove from values map // remove from values map
delete(this.storage.valuesMap, this.hash) delete(this.storage.valuesMap, this.hash)
@@ -167,14 +166,6 @@ func (this *MemoryWriter) Discard() error {
this.storage.locker.Lock() this.storage.locker.Lock()
delete(this.storage.valuesMap, this.hash) delete(this.storage.valuesMap, this.hash)
if enableFragmentPool &&
this.item != nil &&
!this.item.isReferring &&
cap(this.item.BodyValue) >= minMemoryFragmentPoolItemSize {
SharedFragmentMemoryPool.Put(this.item.BodyValue)
}
this.storage.locker.Unlock() this.storage.locker.Unlock()
return nil return nil
} }

View File

@@ -0,0 +1,143 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package caches_test
import (
"bytes"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/TeaOSLab/EdgeNode/internal/caches"
"math/rand"
"strconv"
"testing"
"time"
)
func TestNewMemoryWriter(t *testing.T) {
var storage = caches.NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
Id: 0,
IsOn: false,
Name: "",
Description: "",
Capacity: &shared.SizeCapacity{
Count: 8,
Unit: shared.SizeCapacityUnitGB,
},
}, nil)
err := storage.Init()
if err != nil {
t.Fatal(err)
}
const size = 1 << 20
const chunkSize = 16 << 10
var data = bytes.Repeat([]byte{'A'}, chunkSize)
var before = time.Now()
var writer = caches.NewMemoryWriter(storage, "a", time.Now().Unix()+3600, 200, false, size, 1<<30, func(valueItem *caches.MemoryItem) {
t.Log(len(valueItem.BodyValue), "bytes")
})
for i := 0; i < size/chunkSize; i++ {
_, err = writer.Write(data)
if err != nil {
t.Fatal(err)
}
}
err = writer.Close()
if err != nil {
t.Fatal(err)
}
t.Log("cost:", time.Since(before).Seconds()*1000, "ms")
}
func BenchmarkMemoryWriter_Capacity(b *testing.B) {
b.ReportAllocs()
var storage = caches.NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
Id: 0,
IsOn: false,
Name: "",
Description: "",
Capacity: &shared.SizeCapacity{
Count: 8,
Unit: shared.SizeCapacityUnitGB,
},
}, nil)
initErr := storage.Init()
if initErr != nil {
b.Fatal(initErr)
}
const size = 1 << 20
const chunkSize = 16 << 10
var data = bytes.Repeat([]byte{'A'}, chunkSize)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
var writer = caches.NewMemoryWriter(storage, "a"+strconv.Itoa(rand.Int()), time.Now().Unix()+3600, 200, false, size, 1<<30, func(valueItem *caches.MemoryItem) {
})
for i := 0; i < size/chunkSize; i++ {
_, err := writer.Write(data)
if err != nil {
b.Fatal(err)
}
}
err := writer.Close()
if err != nil {
b.Fatal(err)
}
}
})
}
func BenchmarkMemoryWriter_Capacity_Disabled(b *testing.B) {
b.ReportAllocs()
var storage = caches.NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
Id: 0,
IsOn: false,
Name: "",
Description: "",
Capacity: &shared.SizeCapacity{
Count: 8,
Unit: shared.SizeCapacityUnitGB,
},
}, nil)
initErr := storage.Init()
if initErr != nil {
b.Fatal(initErr)
}
const size = 1 << 20
const chunkSize = 16 << 10
var data = bytes.Repeat([]byte{'A'}, chunkSize)
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
var writer = caches.NewMemoryWriter(storage, "a"+strconv.Itoa(rand.Int()), time.Now().Unix()+3600, 200, false, 0, 1<<30, func(valueItem *caches.MemoryItem) {
})
for i := 0; i < size/chunkSize; i++ {
_, err := writer.Write(data)
if err != nil {
b.Fatal(err)
}
}
err := writer.Close()
if err != nil {
b.Fatal(err)
}
}
})
}

View File

@@ -54,9 +54,9 @@ func (this *PartialFileWriter) WriteHeader(data []byte) (n int, err error) {
if !this.isNew { if !this.isNew {
return return
} }
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
n, err = this.rawWriter.Write(data) n, err = this.rawWriter.Write(data)
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
this.headerSize += int64(n) this.headerSize += int64(n)
if err != nil { if err != nil {
_ = this.Discard() _ = this.Discard()
@@ -65,9 +65,9 @@ func (this *PartialFileWriter) WriteHeader(data []byte) (n int, err error) {
} }
func (this *PartialFileWriter) AppendHeader(data []byte) error { func (this *PartialFileWriter) AppendHeader(data []byte) error {
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
_, err := this.rawWriter.Write(data) _, err := this.rawWriter.Write(data)
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
if err != nil { if err != nil {
_ = this.Discard() _ = this.Discard()
} else { } else {
@@ -94,7 +94,9 @@ func (this *PartialFileWriter) WriteHeaderLength(headerLength int) error {
_ = this.Discard() _ = this.Discard()
return err return err
} }
fsutils.WriterLimiter.Ack()
_, err = this.rawWriter.Write(bytes4) _, err = this.rawWriter.Write(bytes4)
fsutils.WriterLimiter.Release()
if err != nil { if err != nil {
_ = this.Discard() _ = this.Discard()
return err return err
@@ -104,9 +106,9 @@ func (this *PartialFileWriter) WriteHeaderLength(headerLength int) error {
// Write 写入数据 // Write 写入数据
func (this *PartialFileWriter) Write(data []byte) (n int, err error) { func (this *PartialFileWriter) Write(data []byte) (n int, err error) {
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
n, err = this.rawWriter.Write(data) n, err = this.rawWriter.Write(data)
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
this.bodySize += int64(n) this.bodySize += int64(n)
if err != nil { if err != nil {
_ = this.Discard() _ = this.Discard()
@@ -127,6 +129,32 @@ func (this *PartialFileWriter) WriteAt(offset int64, data []byte) error {
return nil return nil
} }
// prevent extending too much space in a single writing
var maxOffset = this.ranges.Max()
if offset-maxOffset > 16<<20 {
var maxExtendSize int64 = 32 << 20
if fsutils.DiskIsExtremelyFast() {
maxExtendSize = 128 << 20
} else if fsutils.DiskIsFast() {
maxExtendSize = 64 << 20
}
if offset-maxOffset > maxExtendSize {
stat, err := this.rawWriter.Stat()
if err != nil {
return nil
}
// extend min size to prepare for file tail
const extendSizePerStep = 8 << 20
if stat.Size()+extendSizePerStep <= this.bodyOffset+offset+int64(len(data)) {
fsutils.WriterLimiter.Ack()
_ = this.rawWriter.Truncate(stat.Size() + extendSizePerStep)
fsutils.WriterLimiter.Release()
return nil
}
}
}
if this.bodyOffset == 0 { if this.bodyOffset == 0 {
var keyLength = 0 var keyLength = 0
if this.ranges.Version == 0 { // 以往的版本包含有Key if this.ranges.Version == 0 { // 以往的版本包含有Key
@@ -135,9 +163,9 @@ func (this *PartialFileWriter) WriteAt(offset int64, data []byte) error {
this.bodyOffset = SizeMeta + int64(keyLength) + this.headerSize this.bodyOffset = SizeMeta + int64(keyLength) + this.headerSize
} }
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
_, err := this.rawWriter.WriteAt(data, this.bodyOffset+offset) _, err := this.rawWriter.WriteAt(data, this.bodyOffset+offset)
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
if err != nil { if err != nil {
return err return err
} }
@@ -164,7 +192,9 @@ func (this *PartialFileWriter) WriteBodyLength(bodyLength int64) error {
_ = this.Discard() _ = this.Discard()
return err return err
} }
fsutils.WriterLimiter.Ack()
_, err = this.rawWriter.Write(bytes8) _, err = this.rawWriter.Write(bytes8)
fsutils.WriterLimiter.Release()
if err != nil { if err != nil {
_ = this.Discard() _ = this.Discard()
return err return err
@@ -181,9 +211,9 @@ func (this *PartialFileWriter) Close() error {
this.ranges.BodySize = this.bodySize this.ranges.BodySize = this.bodySize
err := this.ranges.WriteToFile(this.rangePath) err := this.ranges.WriteToFile(this.rangePath)
if err != nil { if err != nil {
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
_ = this.rawWriter.Close() _ = this.rawWriter.Close()
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
this.remove() this.remove()
return err return err
} }
@@ -192,25 +222,25 @@ func (this *PartialFileWriter) Close() error {
if this.isNew { if this.isNew {
err = this.WriteHeaderLength(types.Int(this.headerSize)) err = this.WriteHeaderLength(types.Int(this.headerSize))
if err != nil { if err != nil {
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
_ = this.rawWriter.Close() _ = this.rawWriter.Close()
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
this.remove() this.remove()
return err return err
} }
err = this.WriteBodyLength(this.bodySize) err = this.WriteBodyLength(this.bodySize)
if err != nil { if err != nil {
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
_ = this.rawWriter.Close() _ = this.rawWriter.Close()
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
this.remove() this.remove()
return err return err
} }
} }
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
err = this.rawWriter.Close() err = this.rawWriter.Close()
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
if err != nil { if err != nil {
this.remove() this.remove()
} }
@@ -224,13 +254,16 @@ func (this *PartialFileWriter) Discard() error {
this.endFunc() this.endFunc()
}) })
fsutils.WriteBegin() fsutils.WriterLimiter.Ack()
_ = this.rawWriter.Close() _ = this.rawWriter.Close()
fsutils.WriteEnd() fsutils.WriterLimiter.Release()
_ = os.Remove(this.rangePath) SharedPartialRangesQueue.Delete(this.rangePath)
_ = fsutils.Remove(this.rangePath)
err := fsutils.Remove(this.rawWriter.Name())
err := os.Remove(this.rawWriter.Name())
return err return err
} }
@@ -260,6 +293,9 @@ func (this *PartialFileWriter) IsNew() bool {
} }
func (this *PartialFileWriter) remove() { func (this *PartialFileWriter) remove() {
_ = os.Remove(this.rawWriter.Name()) _ = fsutils.Remove(this.rawWriter.Name())
_ = os.Remove(this.rangePath)
SharedPartialRangesQueue.Delete(this.rangePath)
_ = fsutils.Remove(this.rangePath)
} }

View File

@@ -4,6 +4,7 @@ package caches_test
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/caches" "github.com/TeaOSLab/EdgeNode/internal/caches"
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"os" "os"
"testing" "testing"
@@ -15,7 +16,7 @@ func TestPartialFileWriter_Write(t *testing.T) {
_ = os.Remove(path) _ = os.Remove(path)
var reader = func() { var reader = func() {
data, err := os.ReadFile(path) data, err := fsutils.ReadFile(path)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -0,0 +1,14 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package compressions
import "errors"
var ErrIsBusy = errors.New("the system is busy for compression")
func CanIgnore(err error) bool {
if err == nil {
return true
}
return errors.Is(err, ErrIsBusy)
}

View File

@@ -9,6 +9,7 @@ type Reader interface {
Reset(reader io.Reader) error Reset(reader io.Reader) error
RawClose() error RawClose() error
Close() error Close() error
IncreaseHit() uint32
SetPool(pool *ReaderPool) SetPool(pool *ReaderPool)
ResetFinish() ResetFinish()

View File

@@ -2,10 +2,13 @@
package compressions package compressions
import "sync/atomic"
type BaseReader struct { type BaseReader struct {
pool *ReaderPool pool *ReaderPool
isFinished bool isFinished bool
hits uint32
} }
func (this *BaseReader) SetPool(pool *ReaderPool) { func (this *BaseReader) SetPool(pool *ReaderPool) {
@@ -13,8 +16,11 @@ func (this *BaseReader) SetPool(pool *ReaderPool) {
} }
func (this *BaseReader) Finish(obj Reader) error { func (this *BaseReader) Finish(obj Reader) error {
if this.isFinished {
return nil
}
err := obj.RawClose() err := obj.RawClose()
if err == nil && this.pool != nil && !this.isFinished { if err == nil && this.pool != nil {
this.pool.Put(obj) this.pool.Put(obj)
} }
this.isFinished = true this.isFinished = true
@@ -24,3 +30,7 @@ func (this *BaseReader) Finish(obj Reader) error {
func (this *BaseReader) ResetFinish() { func (this *BaseReader) ResetFinish() {
this.isFinished = false this.isFinished = false
} }
func (this *BaseReader) IncreaseHit() uint32 {
return atomic.AddUint32(&this.hits, 1)
}

View File

@@ -6,6 +6,8 @@ import (
"io" "io"
) )
const maxReadHits = 1 << 20
type ReaderPool struct { type ReaderPool struct {
c chan Reader c chan Reader
newFunc func(reader io.Reader) (Reader, error) newFunc func(reader io.Reader) (Reader, error)
@@ -49,6 +51,11 @@ func (this *ReaderPool) Get(parentReader io.Reader) (Reader, error) {
} }
func (this *ReaderPool) Put(reader Reader) { func (this *ReaderPool) Put(reader Reader) {
if reader.IncreaseHit() > maxReadHits {
// do nothing to discard it
return
}
select { select {
case this.c <- reader: case this.c <- reader:
default: default:

View File

@@ -4,7 +4,6 @@ package compressions
import ( import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"io" "io"
) )
@@ -15,11 +14,8 @@ func init() {
return return
} }
var maxSize = utils.SystemMemoryGB() * 256
if maxSize == 0 { sharedBrotliReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) {
maxSize = 256
}
sharedBrotliReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
return newBrotliReader(reader) return newBrotliReader(reader)
}) })
} }

View File

@@ -4,7 +4,6 @@ package compressions
import ( import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"io" "io"
) )
@@ -15,11 +14,7 @@ func init() {
return return
} }
var maxSize = utils.SystemMemoryGB() * 256 sharedDeflateReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) {
if maxSize == 0 {
maxSize = 256
}
sharedDeflateReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
return newDeflateReader(reader) return newDeflateReader(reader)
}) })
} }

View File

@@ -4,7 +4,6 @@ package compressions
import ( import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"io" "io"
) )
@@ -15,11 +14,7 @@ func init() {
return return
} }
var maxSize = utils.SystemMemoryGB() * 256 sharedGzipReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) {
if maxSize == 0 {
maxSize = 256
}
sharedGzipReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
return newGzipReader(reader) return newGzipReader(reader)
}) })
} }

View File

@@ -4,7 +4,6 @@ package compressions
import ( import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"io" "io"
) )
@@ -15,11 +14,7 @@ func init() {
return return
} }
var maxSize = utils.SystemMemoryGB() * 256 sharedZSTDReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) {
if maxSize == 0 {
maxSize = 256
}
sharedZSTDReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
return newZSTDReader(reader) return newZSTDReader(reader)
}) })
} }

View File

@@ -18,7 +18,7 @@ func NewZSTDReader(reader io.Reader) (Reader, error) {
} }
func newZSTDReader(reader io.Reader) (Reader, error) { func newZSTDReader(reader io.Reader) (Reader, error) {
r, err := zstd.NewReader(reader) r, err := zstd.NewReader(reader, zstd.WithDecoderMaxWindow(256<<20))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -5,8 +5,10 @@ package compressions
import ( import (
"errors" "errors"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
"io" "io"
"net/http" "net/http"
"runtime"
) )
type ContentEncoding = string type ContentEncoding = string
@@ -88,3 +90,31 @@ func WrapHTTPResponse(resp *http.Response) {
resp.Header.Del("Content-Length") resp.Header.Del("Content-Length")
resp.Body = reader resp.Body = reader
} }
// 系统CPU线程数
var countCPU = runtime.NumCPU()
// GenerateCompressLevel 根据系统资源自动生成压缩级别
func GenerateCompressLevel(minLevel int, maxLevel int) (level int) {
if countCPU < 16 {
return minLevel
}
if countCPU < 32 {
return min(3, maxLevel)
}
return min(5, maxLevel)
}
// CalculatePoolSize 计算Pool尺寸
func CalculatePoolSize() int {
var maxSize = memutils.SystemMemoryGB() * 32
if maxSize == 0 {
maxSize = 128
}
if maxSize > 2048 {
maxSize = 2048
}
return maxSize
}

View File

@@ -0,0 +1,27 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package compressions_test
import (
"github.com/TeaOSLab/EdgeNode/internal/compressions"
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestGenerateCompressLevel(t *testing.T) {
var a = assert.NewAssertion(t)
t.Log(compressions.GenerateCompressLevel(0, 10))
t.Log(compressions.GenerateCompressLevel(1, 10))
t.Log(compressions.GenerateCompressLevel(1, 4))
{
var level = compressions.GenerateCompressLevel(1, 2)
t.Log(level)
a.IsTrue(level >= 1 && level <= 2)
}
}
func TestCalculatePoolSize(t *testing.T) {
t.Log(compressions.CalculatePoolSize())
}

View File

@@ -11,6 +11,7 @@ type Writer interface {
RawClose() error RawClose() error
Close() error Close() error
Level() int Level() int
IncreaseHit() uint32
SetPool(pool *WriterPool) SetPool(pool *WriterPool)
ResetFinish() ResetFinish()

View File

@@ -2,10 +2,16 @@
package compressions package compressions
import (
"sync/atomic"
)
type BaseWriter struct { type BaseWriter struct {
pool *WriterPool pool *WriterPool
isFinished bool isFinished bool
hits uint32
} }
func (this *BaseWriter) SetPool(pool *WriterPool) { func (this *BaseWriter) SetPool(pool *WriterPool) {
@@ -13,8 +19,11 @@ func (this *BaseWriter) SetPool(pool *WriterPool) {
} }
func (this *BaseWriter) Finish(obj Writer) error { func (this *BaseWriter) Finish(obj Writer) error {
if this.isFinished {
return nil
}
err := obj.RawClose() err := obj.RawClose()
if err == nil && this.pool != nil && !this.isFinished { if err == nil && this.pool != nil {
this.pool.Put(obj) this.pool.Put(obj)
} }
this.isFinished = true this.isFinished = true
@@ -24,3 +33,7 @@ func (this *BaseWriter) Finish(obj Writer) error {
func (this *BaseWriter) ResetFinish() { func (this *BaseWriter) ResetFinish() {
this.isFinished = false this.isFinished = false
} }
func (this *BaseWriter) IncreaseHit() uint32 {
return atomic.AddUint32(&this.hits, 1)
}

View File

@@ -19,12 +19,8 @@ func NewBrotliWriter(writer io.Writer, level int) (Writer, error) {
return sharedBrotliWriterPool.Get(writer, level) return sharedBrotliWriterPool.Get(writer, level)
} }
func newBrotliWriter(writer io.Writer, level int) (*BrotliWriter, error) { func newBrotliWriter(writer io.Writer) (*BrotliWriter, error) {
if level <= 0 { var level = GenerateCompressLevel(brotli.BestSpeed, brotli.BestCompression)
level = brotli.BestSpeed
} else if level > brotli.BestCompression {
level = brotli.BestCompression
}
return &BrotliWriter{ return &BrotliWriter{
writer: brotli.NewWriterOptions(writer, brotli.WriterOptions{ writer: brotli.NewWriterOptions(writer, brotli.WriterOptions{
Quality: level, Quality: level,

View File

@@ -18,12 +18,8 @@ func NewDeflateWriter(writer io.Writer, level int) (Writer, error) {
return sharedDeflateWriterPool.Get(writer, level) return sharedDeflateWriterPool.Get(writer, level)
} }
func newDeflateWriter(writer io.Writer, level int) (Writer, error) { func newDeflateWriter(writer io.Writer) (Writer, error) {
if level <= 0 { var level = GenerateCompressLevel(flate.BestSpeed, flate.BestCompression)
level = flate.BestSpeed
} else if level > flate.BestCompression {
level = flate.BestCompression
}
flateWriter, err := flate.NewWriter(writer, level) flateWriter, err := flate.NewWriter(writer, level)
if err != nil { if err != nil {

View File

@@ -18,12 +18,8 @@ func NewGzipWriter(writer io.Writer, level int) (Writer, error) {
return sharedGzipWriterPool.Get(writer, level) return sharedGzipWriterPool.Get(writer, level)
} }
func newGzipWriter(writer io.Writer, level int) (Writer, error) { func newGzipWriter(writer io.Writer) (Writer, error) {
if level <= 0 { var level = GenerateCompressLevel(gzip.BestSpeed, gzip.BestCompression)
level = gzip.BestSpeed
} else if level > gzip.BestCompression {
level = gzip.BestCompression
}
gzipWriter, err := gzip.NewWriterLevel(writer, level) gzipWriter, err := gzip.NewWriterLevel(writer, level)
if err != nil { if err != nil {

View File

@@ -5,12 +5,15 @@ package compressions_test
import ( import (
"bytes" "bytes"
"github.com/TeaOSLab/EdgeNode/internal/compressions" "github.com/TeaOSLab/EdgeNode/internal/compressions"
"strings"
"testing" "testing"
) )
func BenchmarkGzipWriter_Write(b *testing.B) { func BenchmarkGzipWriter_Write(b *testing.B) {
var data = []byte(strings.Repeat("A", 1024)) var data = make([]byte, 1024)
for i := 0; i < 1024; i++ {
data[i] = 'A' + byte(i%26)
}
b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
var buf = &bytes.Buffer{} var buf = &bytes.Buffer{}
@@ -36,7 +39,12 @@ func BenchmarkGzipWriter_Write(b *testing.B) {
} }
func BenchmarkGzipWriter_Write_Parallel(b *testing.B) { func BenchmarkGzipWriter_Write_Parallel(b *testing.B) {
var data = []byte(strings.Repeat("A", 1024)) var data = make([]byte, 1024)
for i := 0; i < 1024; i++ {
data[i] = 'A' + byte(i%26)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {

View File

@@ -3,38 +3,58 @@
package compressions package compressions
import ( import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"io" "io"
"time"
) )
const maxWriterHits = 1 << 20
var isBusy = false
func init() {
if !teaconst.IsMain {
return
}
goman.New(func() {
var ticker = time.NewTicker(100 * time.Millisecond)
for range ticker.C {
if isBusy {
isBusy = false
}
}
})
}
func IsBusy() bool {
return isBusy
}
type WriterPool struct { type WriterPool struct {
m map[int]chan Writer // level => chan Writer c chan Writer // level => chan Writer
newFunc func(writer io.Writer, level int) (Writer, error) newFunc func(writer io.Writer, level int) (Writer, error)
} }
func NewWriterPool(maxSize int, maxLevel int, newFunc func(writer io.Writer, level int) (Writer, error)) *WriterPool { func NewWriterPool(maxSize int, newFunc func(writer io.Writer, level int) (Writer, error)) *WriterPool {
if maxSize <= 0 { if maxSize <= 0 {
maxSize = 1024 maxSize = 1024
} }
var m = map[int]chan Writer{}
for i := 0; i <= maxLevel; i++ {
m[i] = make(chan Writer, maxSize)
}
return &WriterPool{ return &WriterPool{
m: m, c: make(chan Writer, maxSize),
newFunc: newFunc, newFunc: newFunc,
} }
} }
func (this *WriterPool) Get(parentWriter io.Writer, level int) (Writer, error) { func (this *WriterPool) Get(parentWriter io.Writer, level int) (Writer, error) {
c, ok := this.m[level] if isBusy {
if !ok { return nil, ErrIsBusy
c = this.m[0]
} }
select { select {
case writer := <-c: case writer := <-this.c:
writer.Reset(parentWriter) writer.Reset(parentWriter)
writer.ResetFinish() writer.ResetFinish()
return writer, nil return writer, nil
@@ -49,13 +69,14 @@ func (this *WriterPool) Get(parentWriter io.Writer, level int) (Writer, error) {
} }
func (this *WriterPool) Put(writer Writer) { func (this *WriterPool) Put(writer Writer) {
var level = writer.Level() if writer.IncreaseHit() > maxWriterHits {
c, ok := this.m[level] // do nothing to discard it
if !ok { return
c = this.m[0]
} }
select { select {
case c <- writer: case this.c <- writer:
default: default:
isBusy = true
} }
} }

View File

@@ -4,8 +4,6 @@ package compressions
import ( import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/andybalholm/brotli"
"io" "io"
) )
@@ -16,11 +14,7 @@ func init() {
return return
} }
var maxSize = utils.SystemMemoryGB() * 256 sharedBrotliWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) {
if maxSize == 0 { return newBrotliWriter(writer)
maxSize = 256
}
sharedBrotliWriterPool = NewWriterPool(maxSize, brotli.BestCompression, func(writer io.Writer, level int) (Writer, error) {
return newBrotliWriter(writer, level)
}) })
} }

View File

@@ -3,9 +3,7 @@
package compressions package compressions
import ( import (
"compress/flate"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"io" "io"
) )
@@ -16,11 +14,7 @@ func init() {
return return
} }
var maxSize = utils.SystemMemoryGB() * 256 sharedDeflateWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) {
if maxSize == 0 { return newDeflateWriter(writer)
maxSize = 256
}
sharedDeflateWriterPool = NewWriterPool(maxSize, flate.BestCompression, func(writer io.Writer, level int) (Writer, error) {
return newDeflateWriter(writer, level)
}) })
} }

View File

@@ -3,9 +3,7 @@
package compressions package compressions
import ( import (
"compress/gzip"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"io" "io"
) )
@@ -16,11 +14,7 @@ func init() {
return return
} }
var maxSize = utils.SystemMemoryGB() * 256 sharedGzipWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) {
if maxSize == 0 { return newGzipWriter(writer)
maxSize = 256
}
sharedGzipWriterPool = NewWriterPool(maxSize, gzip.BestCompression, func(writer io.Writer, level int) (Writer, error) {
return newGzipWriter(writer, level)
}) })
} }

View File

@@ -4,8 +4,6 @@ package compressions
import ( import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/klauspost/compress/zstd"
"io" "io"
) )
@@ -16,11 +14,7 @@ func init() {
return return
} }
var maxSize = utils.SystemMemoryGB() * 256 sharedZSTDWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) {
if maxSize == 0 { return newZSTDWriter(writer)
maxSize = 256
}
sharedZSTDWriterPool = NewWriterPool(maxSize, int(zstd.SpeedBestCompression), func(writer io.Writer, level int) (Writer, error) {
return newZSTDWriter(writer, level)
}) })
} }

View File

@@ -18,21 +18,18 @@ func NewZSTDWriter(writer io.Writer, level int) (Writer, error) {
return sharedZSTDWriterPool.Get(writer, level) return sharedZSTDWriterPool.Get(writer, level)
} }
func newZSTDWriter(writer io.Writer, level int) (Writer, error) { func newZSTDWriter(writer io.Writer) (Writer, error) {
if level < 0 { var level = 1
level = 0 var zstdLevel = zstd.SpeedFastest
}
var zstdLevel = zstd.EncoderLevelFromZstd(level) zstdWriter, err := zstd.NewWriter(writer, zstd.WithEncoderLevel(zstdLevel), zstd.WithWindowSize(16<<10), zstd.WithLowerEncoderMem(true))
zstdWriter, err := zstd.NewWriter(writer, zstd.WithEncoderLevel(zstdLevel))
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &ZSTDWriter{ return &ZSTDWriter{
writer: zstdWriter, writer: zstdWriter,
level: level, level: level,
}, nil }, nil
} }

View File

@@ -0,0 +1,67 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package conns_test
import (
"github.com/TeaOSLab/EdgeNode/internal/conns"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"net"
"runtime"
"testing"
"time"
)
type testConn struct {
net.Conn
addr net.Addr
}
func (this *testConn) Read(b []byte) (n int, err error) {
return
}
func (this *testConn) Write(b []byte) (n int, err error) {
return
}
func (this *testConn) Close() error {
return nil
}
func (this *testConn) LocalAddr() net.Addr {
return &net.TCPAddr{
IP: net.ParseIP(testutils.RandIP()),
Port: 1234,
}
}
func (this *testConn) RemoteAddr() net.Addr {
if this.addr != nil {
return this.addr
}
this.addr = &net.TCPAddr{
IP: net.ParseIP(testutils.RandIP()),
Port: 1234,
}
return this.addr
}
func (this *testConn) SetDeadline(t time.Time) error {
return nil
}
func (this *testConn) SetReadDeadline(t time.Time) error {
return nil
}
func (this *testConn) SetWriteDeadline(t time.Time) error {
return nil
}
func BenchmarkMap_Add(b *testing.B) {
runtime.GOMAXPROCS(512)
var m = conns.NewMap()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
var conn = &testConn{}
m.Add(conn)
m.Remove(conn)
}
})
}

View File

@@ -1,7 +1,7 @@
package teaconst package teaconst
const ( const (
Version = "1.3.3" Version = "1.3.7"
ProductName = "Edge Node" ProductName = "Edge Node"
ProcessName = "edge-node" ProcessName = "edge-node"
@@ -16,4 +16,6 @@ const (
AccessLogSockName = "edge-node.accesslog" AccessLogSockName = "edge-node.accesslog"
CacheGarbageSockName = "edge-node.cache.garbage" CacheGarbageSockName = "edge-node.cache.garbage"
EnableKVCacheStore = true // determine store cache keys in KVStore or sqlite
) )

View File

@@ -6,8 +6,10 @@ import (
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"os"
"runtime" "runtime"
"sync" "sync"
"time"
) )
var currentFirewall FirewallInterface var currentFirewall FirewallInterface
@@ -35,6 +37,23 @@ func Firewall() FirewallInterface {
return currentFirewall return currentFirewall
} }
// http firewall
{
endpoint, _ := os.LookupEnv("EDGE_HTTP_FIREWALL_ENDPOINT")
if len(endpoint) > 0 {
var httpFirewall = NewHTTPFirewall(endpoint)
for i := 0; i < 10; i++ {
if httpFirewall.IsReady() {
currentFirewall = httpFirewall
remotelogs.Println("FIREWALL", "using http firewall '"+endpoint+"'")
break
}
time.Sleep(1 * time.Second)
}
return httpFirewall
}
}
// nftables // nftables
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
nftables, err := NewNFTablesFirewall() nftables, err := NewNFTablesFirewall()

View File

@@ -0,0 +1,150 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package firewalls
import (
"encoding/json"
"errors"
"fmt"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"io"
"net/http"
"net/url"
)
type HTTPFirewall struct {
client *http.Client
endpoint string
}
func NewHTTPFirewall(endpoint string) *HTTPFirewall {
return &HTTPFirewall{
client: http.DefaultClient,
endpoint: endpoint,
}
}
// Name 名称
func (this *HTTPFirewall) Name() string {
result, err := this.get("/name", nil)
if err != nil {
return ""
}
return result.GetString("name")
}
// IsReady 是否已准备被调用
func (this *HTTPFirewall) IsReady() bool {
result, err := this.get("/isReady", nil)
if err != nil {
return false
}
return result.GetBool("result")
}
// IsMock 是否为模拟
func (this *HTTPFirewall) IsMock() bool {
result, err := this.get("/isMock", nil)
if err != nil {
return false
}
return result.GetBool("result")
}
// AllowPort 允许端口
func (this *HTTPFirewall) AllowPort(port int, protocol string) error {
_, err := this.get("/allowPort", map[string]string{
"port": types.String(port),
"protocol": protocol,
})
return err
}
// RemovePort 删除端口
func (this *HTTPFirewall) RemovePort(port int, protocol string) error {
_, err := this.get("/removePort", map[string]string{
"port": types.String(port),
"protocol": protocol,
})
return err
}
// RejectSourceIP 拒绝某个源IP连接
func (this *HTTPFirewall) RejectSourceIP(ip string, timeoutSeconds int) error {
_, err := this.get("/rejectSourceIP", map[string]string{
"ip": ip,
"timeoutSeconds": types.String(timeoutSeconds),
})
return err
}
// DropSourceIP 丢弃某个源IP数据
// ip 要封禁的IP
// timeoutSeconds 过期时间
// async 是否异步
func (this *HTTPFirewall) DropSourceIP(ip string, timeoutSeconds int, async bool) error {
var asyncString = "false"
if async {
asyncString = "true"
}
_, err := this.get("/dropSourceIP", map[string]string{
"ip": ip,
"timeoutSeconds": types.String(timeoutSeconds),
"async": asyncString,
})
return err
}
// RemoveSourceIP 删除某个源IP
func (this *HTTPFirewall) RemoveSourceIP(ip string) error {
_, err := this.get("/removeSourceIP", map[string]string{
"ip": ip,
})
return err
}
func (this *HTTPFirewall) get(path string, args map[string]string) (result maps.Map, err error) {
var urlString = this.endpoint + path
if len(args) > 0 {
var query = &url.Values{}
for k, v := range args {
query.Add(k, v)
}
urlString += "?" + query.Encode()
}
req, err := http.NewRequest(http.MethodGet, urlString, nil)
if err != nil {
return nil, err
}
resp, err := this.client.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, errors.New("server response code '" + types.String(resp.StatusCode) + "'")
}
defer func() {
_ = resp.Body.Close()
}()
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read response failed: %w", err)
}
if len(data) == 0 {
return maps.Map{}, nil
}
result = maps.Map{}
err = json.Unmarshal(data, &result)
if err != nil {
return nil, fmt.Errorf("decode response failed: %w", err)
}
return
}

View File

@@ -6,7 +6,7 @@ package firewalls
import ( import (
"errors" "errors"
"fmt" "fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils" "github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeNode/internal/conns" "github.com/TeaOSLab/EdgeNode/internal/conns"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/events" "github.com/TeaOSLab/EdgeNode/internal/events"
@@ -386,12 +386,12 @@ func (this *NFTablesFirewall) DropSourceIP(ip string, timeoutSeconds int, async
// 再次尝试关闭连接 // 再次尝试关闭连接
defer conns.SharedMap.CloseIPConns(ip) defer conns.SharedMap.CloseIPConns(ip)
var ipLong = configutils.IPString2Long(ip)
if strings.Contains(ip, ":") { // ipv6 if strings.Contains(ip, ":") { // ipv6
if len(this.denyIPv6Sets) == 0 { if len(this.denyIPv6Sets) == 0 {
return errors.New("ipv6 ip set not found") return errors.New("ipv6 ip set not found")
} }
return this.denyIPv6Sets[ipLong%uint64(len(this.denyIPv6Sets))].AddElement(data.To16(), &nftables.ElementOptions{ var setIndex = iputils.ParseIP(ip).Mod(len(this.denyIPv6Sets))
return this.denyIPv6Sets[setIndex].AddElement(data.To16(), &nftables.ElementOptions{
Timeout: time.Duration(timeoutSeconds) * time.Second, Timeout: time.Duration(timeoutSeconds) * time.Second,
}, false) }, false)
} }
@@ -400,7 +400,8 @@ func (this *NFTablesFirewall) DropSourceIP(ip string, timeoutSeconds int, async
if len(this.denyIPv4Sets) == 0 { if len(this.denyIPv4Sets) == 0 {
return errors.New("ipv4 ip set not found") return errors.New("ipv4 ip set not found")
} }
return this.denyIPv4Sets[ipLong%uint64(len(this.denyIPv4Sets))].AddElement(data.To4(), &nftables.ElementOptions{ var setIndex = iputils.ParseIP(ip).Mod(len(this.denyIPv4Sets))
return this.denyIPv4Sets[setIndex].AddElement(data.To4(), &nftables.ElementOptions{
Timeout: time.Duration(timeoutSeconds) * time.Second, Timeout: time.Duration(timeoutSeconds) * time.Second,
}, false) }, false)
} }
@@ -412,10 +413,10 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
return errors.New("invalid ip '" + ip + "'") return errors.New("invalid ip '" + ip + "'")
} }
var ipLong = configutils.IPString2Long(ip)
if strings.Contains(ip, ":") { // ipv6 if strings.Contains(ip, ":") { // ipv6
var setIndex = iputils.ParseIP(ip).Mod(len(this.denyIPv6Sets))
if len(this.denyIPv6Sets) > 0 { if len(this.denyIPv6Sets) > 0 {
err := this.denyIPv6Sets[ipLong%uint64(len(this.denyIPv6Sets))].DeleteElement(data.To16()) err := this.denyIPv6Sets[setIndex].DeleteElement(data.To16())
if err != nil { if err != nil {
return err return err
} }
@@ -433,7 +434,8 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
// ipv4 // ipv4
if len(this.denyIPv4Sets) > 0 { if len(this.denyIPv4Sets) > 0 {
err := this.denyIPv4Sets[ipLong%uint64(len(this.denyIPv4Sets))].DeleteElement(data.To4()) var setIndex = iputils.ParseIP(ip).Mod(len(this.denyIPv4Sets))
err := this.denyIPv4Sets[setIndex].DeleteElement(data.To4())
if err != nil { if err != nil {
return err return err
} }

View File

@@ -7,7 +7,6 @@ package nftables_test
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables" "github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec" executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
"os/exec"
"testing" "testing"
) )
@@ -21,7 +20,10 @@ func TestConn_Test(t *testing.T) {
} }
func TestConn_GetTable_NotFound(t *testing.T) { func TestConn_GetTable_NotFound(t *testing.T) {
var conn = nftables.NewConn() conn, err := nftables.NewConn()
if err != nil {
t.Fatal(err)
}
table, err := conn.GetTable("a", nftables.TableFamilyIPv4) table, err := conn.GetTable("a", nftables.TableFamilyIPv4)
if err != nil { if err != nil {
@@ -36,7 +38,10 @@ func TestConn_GetTable_NotFound(t *testing.T) {
} }
func TestConn_GetTable(t *testing.T) { func TestConn_GetTable(t *testing.T) {
var conn = nftables.NewConn() conn, err := nftables.NewConn()
if err != nil {
t.Fatal(err)
}
table, err := conn.GetTable("myFilter", nftables.TableFamilyIPv4) table, err := conn.GetTable("myFilter", nftables.TableFamilyIPv4)
if err != nil { if err != nil {
@@ -51,7 +56,10 @@ func TestConn_GetTable(t *testing.T) {
} }
func TestConn_AddTable(t *testing.T) { func TestConn_AddTable(t *testing.T) {
var conn = nftables.NewConn() conn, err := nftables.NewConn()
if err != nil {
t.Fatal(err)
}
{ {
table, err := conn.AddIPv4Table("test_ipv4") table, err := conn.AddIPv4Table("test_ipv4")
@@ -70,8 +78,12 @@ func TestConn_AddTable(t *testing.T) {
} }
func TestConn_DeleteTable(t *testing.T) { func TestConn_DeleteTable(t *testing.T) {
var conn = nftables.NewConn() conn, err := nftables.NewConn()
err := conn.DeleteTable("test_ipv4", nftables.TableFamilyIPv4) if err != nil {
t.Fatal(err)
}
err = conn.DeleteTable("test_ipv4", nftables.TableFamilyIPv4)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -1,6 +1,7 @@
package iplibrary package iplibrary
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime" "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
) )
@@ -14,36 +15,37 @@ const (
// IPItem IP条目 // IPItem IP条目
type IPItem struct { type IPItem struct {
Type string `json:"type"` Type string `json:"type"`
Id uint64 `json:"id"` Id uint64 `json:"id"`
IPFrom uint64 `json:"ipFrom"` IPFrom []byte `json:"ipFrom"`
IPTo uint64 `json:"ipTo"` IPTo []byte `json:"ipTo"`
ExpiredAt int64 `json:"expiredAt"` ExpiredAt int64 `json:"expiredAt"`
EventLevel string `json:"eventLevel"` EventLevel string `json:"eventLevel"`
} }
// Contains 检查是否包含某个IP // Contains 检查是否包含某个IP
func (this *IPItem) Contains(ip uint64) bool { func (this *IPItem) Contains(ipBytes []byte) bool {
switch this.Type { switch this.Type {
case IPItemTypeIPv4: case IPItemTypeIPv4:
return this.containsIPv4(ip) return this.containsIP(ipBytes)
case IPItemTypeIPv6: case IPItemTypeIPv6:
return this.containsIPv6(ip) return this.containsIP(ipBytes)
case IPItemTypeAll: case IPItemTypeAll:
return this.containsAll() return this.containsAll()
default: default:
return this.containsIPv4(ip) return this.containsIP(ipBytes)
} }
} }
// 检查是否包含某个IPv4 // 检查是否包含某个
func (this *IPItem) containsIPv4(ip uint64) bool { func (this *IPItem) containsIP(ipBytes []byte) bool {
if this.IPTo == 0 { if IsZero(this.IPTo) {
if this.IPFrom != ip { if iputils.CompareBytes(this.IPFrom, ipBytes) != 0 {
return false return false
} }
} else { } else {
if this.IPFrom > ip || this.IPTo < ip { if iputils.CompareBytes(this.IPFrom, ipBytes) > 0 || iputils.CompareBytes(this.IPTo, ipBytes) < 0 {
return false return false
} }
} }
@@ -53,17 +55,6 @@ func (this *IPItem) containsIPv4(ip uint64) bool {
return true return true
} }
// 检查是否包含某个IPv6
func (this *IPItem) containsIPv6(ip uint64) bool {
if this.IPFrom != ip {
return false
}
if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {
return false
}
return true
}
// 检查是否包所有IP // 检查是否包所有IP
func (this *IPItem) containsAll() bool { func (this *IPItem) containsAll() bool {
if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() { if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {

View File

@@ -1,98 +1,104 @@
package iplibrary package iplibrary_test
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils" "github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/assert" "github.com/iwind/TeaGo/assert"
"math/rand"
"runtime" "runtime"
"strconv"
"testing" "testing"
"time" "time"
) )
func TestIPItem_Contains(t *testing.T) { func TestIPItem_Contains(t *testing.T) {
a := assert.NewAssertion(t) var a = assert.NewAssertion(t)
{ {
item := &IPItem{ var item = &iplibrary.IPItem{
IPFrom: utils.IP2Long("192.168.1.100"), IPFrom: iputils.ToBytes("192.168.1.100"),
IPTo: 0, IPTo: nil,
ExpiredAt: 0, ExpiredAt: 0,
} }
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.100"))) a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.100")))
} }
{ {
item := &IPItem{ var item = &iplibrary.IPItem{
IPFrom: utils.IP2Long("192.168.1.100"), IPFrom: iputils.ToBytes("192.168.1.100"),
IPTo: 0, IPTo: nil,
ExpiredAt: time.Now().Unix() + 1, ExpiredAt: time.Now().Unix() + 1,
} }
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.100"))) a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.100")))
} }
{ {
item := &IPItem{ var item = &iplibrary.IPItem{
IPFrom: utils.IP2Long("192.168.1.100"), IPFrom: iputils.ToBytes("192.168.1.100"),
IPTo: 0, IPTo: nil,
ExpiredAt: time.Now().Unix() - 1, ExpiredAt: time.Now().Unix() - 1,
} }
a.IsFalse(item.Contains(utils.IP2Long("192.168.1.100"))) a.IsFalse(item.Contains(iputils.ToBytes("192.168.1.100")))
} }
{ {
item := &IPItem{ var item = &iplibrary.IPItem{
IPFrom: utils.IP2Long("192.168.1.100"), IPFrom: iputils.ToBytes("192.168.1.100"),
IPTo: 0, IPTo: nil,
ExpiredAt: 0, ExpiredAt: 0,
} }
a.IsFalse(item.Contains(utils.IP2Long("192.168.1.101"))) a.IsFalse(item.Contains(iputils.ToBytes("192.168.1.101")))
} }
{ {
item := &IPItem{ var item = &iplibrary.IPItem{
IPFrom: utils.IP2Long("192.168.1.1"), IPFrom: iputils.ToBytes("192.168.1.1"),
IPTo: utils.IP2Long("192.168.1.101"), IPTo: iputils.ToBytes("192.168.1.101"),
ExpiredAt: 0, ExpiredAt: 0,
} }
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.100"))) a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.100")))
} }
{ {
item := &IPItem{ var item = &iplibrary.IPItem{
IPFrom: utils.IP2Long("192.168.1.1"), IPFrom: iputils.ToBytes("192.168.1.1"),
IPTo: utils.IP2Long("192.168.1.100"), IPTo: iputils.ToBytes("192.168.1.100"),
ExpiredAt: 0, ExpiredAt: 0,
} }
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.100"))) a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.100")))
} }
{ {
item := &IPItem{ var item = &iplibrary.IPItem{
IPFrom: utils.IP2Long("192.168.1.1"), IPFrom: iputils.ToBytes("192.168.1.1"),
IPTo: utils.IP2Long("192.168.1.101"), IPTo: iputils.ToBytes("192.168.1.101"),
ExpiredAt: 0, ExpiredAt: 0,
} }
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.1"))) a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.1")))
} }
} }
func TestIPItem_Memory(t *testing.T) { func TestIPItem_Memory(t *testing.T) {
var isSingleTest = testutils.IsSingleTesting() var isSingleTest = testutils.IsSingleTesting()
var list = NewIPList() var list = iplibrary.NewIPList()
var count = 100 var count = 100
if isSingleTest { if isSingleTest {
count = 2_000_000 count = 2_000_000
} }
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
list.Add(&IPItem{ list.Add(&iplibrary.IPItem{
Type: "ip", Type: "ip",
Id: uint64(i), Id: uint64(i),
IPFrom: utils.IP2Long("192.168.1.1"), IPFrom: iputils.ToBytes("192.168.1.1"),
IPTo: 0, IPTo: nil,
ExpiredAt: time.Now().Unix(), ExpiredAt: time.Now().Unix(),
EventLevel: "", EventLevel: "",
}) })
} }
runtime.GC()
t.Log("waiting") t.Log("waiting")
if isSingleTest { if isSingleTest {
time.Sleep(10 * time.Second) time.Sleep(10 * time.Second)
@@ -102,15 +108,18 @@ func TestIPItem_Memory(t *testing.T) {
func BenchmarkIPItem_Contains(b *testing.B) { func BenchmarkIPItem_Contains(b *testing.B) {
runtime.GOMAXPROCS(1) runtime.GOMAXPROCS(1)
item := &IPItem{ var item = &iplibrary.IPItem{
IPFrom: utils.IP2Long("192.168.1.1"), IPFrom: iputils.ToBytes("192.168.1.1"),
IPTo: utils.IP2Long("192.168.1.101"), IPTo: iputils.ToBytes("192.168.1.101"),
ExpiredAt: 0, ExpiredAt: 0,
} }
ip := utils.IP2Long("192.168.1.1")
for i := 0; i < b.N; i++ { b.ResetTimer()
for j := 0; j < 10_000; j++ {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
var ip = iputils.ToBytes("192.168.1." + strconv.Itoa(rand.Int()%255))
item.Contains(ip) item.Contains(ip)
} }
} })
} }

View File

@@ -1,7 +1,7 @@
package iplibrary package iplibrary
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeNode/internal/utils/expires" "github.com/TeaOSLab/EdgeNode/internal/utils/expires"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime" "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"sort" "sort"
@@ -12,26 +12,32 @@ var GlobalBlackIPList = NewIPList()
var GlobalWhiteIPList = NewIPList() var GlobalWhiteIPList = NewIPList()
// IPList IP名单 // IPList IP名单
// TODO IP名单可以分片关闭这样让每一片的数据量减少查询更快 // TODO 对ipMap进行分区
type IPList struct { type IPList struct {
isDeleted bool isDeleted bool
itemsMap map[uint64]*IPItem // id => item itemsMap map[uint64]*IPItem // id => item
sortedItems []*IPItem
sortedRangeItems []*IPItem
ipMap map[string]*IPItem // ipFrom => IPItem
bufferItemsMap map[uint64]*IPItem // id => IPItem
allItemsMap map[uint64]*IPItem // id => item allItemsMap map[uint64]*IPItem // id => item
expireList *expires.List expireList *expires.List
locker sync.RWMutex mu sync.RWMutex
} }
func NewIPList() *IPList { func NewIPList() *IPList {
list := &IPList{ var list = &IPList{
itemsMap: map[uint64]*IPItem{}, itemsMap: map[uint64]*IPItem{},
allItemsMap: map[uint64]*IPItem{}, bufferItemsMap: map[uint64]*IPItem{},
allItemsMap: map[uint64]*IPItem{},
ipMap: map[string]*IPItem{},
} }
expireList := expires.NewList() var expireList = expires.NewList()
expireList.OnGC(func(itemId uint64) { expireList.OnGC(func(itemId uint64) {
list.Delete(itemId) list.Delete(itemId)
}) })
@@ -44,64 +50,66 @@ func (this *IPList) Add(item *IPItem) {
return return
} }
this.addItem(item, true) this.addItem(item, true, true)
} }
// AddDelay 延迟添加需要手工调用Sort()函数
func (this *IPList) AddDelay(item *IPItem) { func (this *IPList) AddDelay(item *IPItem) {
if this.isDeleted { if this.isDeleted || item == nil {
return return
} }
this.addItem(item, false) if !IsZero(item.IPTo) {
this.mu.Lock()
this.bufferItemsMap[item.Id] = item
this.mu.Unlock()
} else {
this.addItem(item, true, true)
}
} }
func (this *IPList) Sort() { func (this *IPList) Sort() {
this.locker.Lock() this.mu.Lock()
this.sortItems() this.sortRangeItems(false)
this.locker.Unlock() this.mu.Unlock()
} }
func (this *IPList) Delete(itemId uint64) { func (this *IPList) Delete(itemId uint64) {
this.locker.Lock() this.mu.Lock()
this.deleteItem(itemId) this.deleteItem(itemId)
this.locker.Unlock() this.mu.Unlock()
} }
// Contains 判断是否包含某个IP // Contains 判断是否包含某个IP
func (this *IPList) Contains(ip uint64) bool { func (this *IPList) Contains(ipBytes []byte) bool {
if this.isDeleted { if this.isDeleted {
return false return false
} }
this.locker.RLock() this.mu.RLock()
defer this.mu.RUnlock()
if len(this.allItemsMap) > 0 { if len(this.allItemsMap) > 0 {
this.locker.RUnlock()
return true return true
} }
var item = this.lookupIP(ip) var item = this.lookupIP(ipBytes)
this.locker.RUnlock()
return item != nil return item != nil
} }
// ContainsExpires 判断是否包含某个IP // ContainsExpires 判断是否包含某个IP
func (this *IPList) ContainsExpires(ip uint64) (expiresAt int64, ok bool) { func (this *IPList) ContainsExpires(ipBytes []byte) (expiresAt int64, ok bool) {
if this.isDeleted { if this.isDeleted {
return return
} }
this.locker.RLock() this.mu.RLock()
defer this.mu.RUnlock()
if len(this.allItemsMap) > 0 { if len(this.allItemsMap) > 0 {
this.locker.RUnlock()
return 0, true return 0, true
} }
var item = this.lookupIP(ip) var item = this.lookupIP(ipBytes)
this.locker.RUnlock()
if item == nil { if item == nil {
return return
@@ -119,7 +127,10 @@ func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found b
if len(ipStrings) == 0 { if len(ipStrings) == 0 {
return return
} }
this.locker.RLock()
this.mu.RLock()
defer this.mu.RUnlock()
if len(this.allItemsMap) > 0 { if len(this.allItemsMap) > 0 {
for _, allItem := range this.allItemsMap { for _, allItem := range this.allItemsMap {
item = allItem item = allItem
@@ -127,7 +138,6 @@ func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found b
} }
if item != nil { if item != nil {
this.locker.RUnlock()
found = true found = true
return return
} }
@@ -136,14 +146,12 @@ func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found b
if len(ipString) == 0 { if len(ipString) == 0 {
continue continue
} }
item = this.lookupIP(utils.IP2Long(ipString)) item = this.lookupIP(iputils.ToBytes(ipString))
if item != nil { if item != nil {
this.locker.RUnlock()
found = true found = true
return return
} }
} }
this.locker.RUnlock()
return return
} }
@@ -151,7 +159,27 @@ func (this *IPList) SetDeleted() {
this.isDeleted = true this.isDeleted = true
} }
func (this *IPList) addItem(item *IPItem, sortable bool) { func (this *IPList) SortedRangeItems() []*IPItem {
return this.sortedRangeItems
}
func (this *IPList) IPMap() map[string]*IPItem {
return this.ipMap
}
func (this *IPList) ItemsMap() map[uint64]*IPItem {
return this.itemsMap
}
func (this *IPList) AllItemsMap() map[uint64]*IPItem {
return this.allItemsMap
}
func (this *IPList) BufferItemsMap() map[uint64]*IPItem {
return this.bufferItemsMap
}
func (this *IPList) addItem(item *IPItem, lock bool, sortable bool) {
if item == nil { if item == nil {
return return
} }
@@ -160,20 +188,29 @@ func (this *IPList) addItem(item *IPItem, sortable bool) {
return return
} }
if item.IPFrom == 0 && item.IPTo == 0 { var shouldSort bool
if iputils.CompareBytes(item.IPFrom, item.IPTo) == 0 {
item.IPTo = nil
}
if IsZero(item.IPFrom) && IsZero(item.IPTo) {
if item.Type != IPItemTypeAll { if item.Type != IPItemTypeAll {
return return
} }
} else if item.IPTo > 0 { } else if !IsZero(item.IPTo) {
if item.IPFrom > item.IPTo { if iputils.CompareBytes(item.IPFrom, item.IPTo) > 0 {
item.IPFrom, item.IPTo = item.IPTo, item.IPFrom item.IPFrom, item.IPTo = item.IPTo, item.IPFrom
} else if item.IPFrom == 0 { } else if IsZero(item.IPFrom) {
item.IPFrom = item.IPTo item.IPFrom = item.IPTo
item.IPTo = 0 item.IPTo = nil
} }
} }
this.locker.Lock() if lock {
this.mu.Lock()
defer this.mu.Unlock()
}
// 是否已经存在 // 是否已经存在
_, ok := this.itemsMap[item.Id] _, ok := this.itemsMap[item.Id]
@@ -184,51 +221,72 @@ func (this *IPList) addItem(item *IPItem, sortable bool) {
this.itemsMap[item.Id] = item this.itemsMap[item.Id] = item
// 展开 // 展开
if item.IPFrom > 0 { if item.Type == IPItemTypeAll {
this.sortedItems = append(this.sortedItems, item)
} else {
this.allItemsMap[item.Id] = item this.allItemsMap[item.Id] = item
} else if !IsZero(item.IPFrom) {
if !IsZero(item.IPTo) {
this.sortedRangeItems = append(this.sortedRangeItems, item)
shouldSort = true
} else {
this.ipMap[ToHex(item.IPFrom)] = item
}
} }
if item.ExpiredAt > 0 { if item.ExpiredAt > 0 {
this.expireList.Add(item.Id, item.ExpiredAt) this.expireList.Add(item.Id, item.ExpiredAt)
} }
if sortable { if shouldSort && sortable {
this.sortItems() this.sortRangeItems(true)
} }
this.locker.Unlock()
} }
// 对列表进行排序 // 对列表进行排序
func (this *IPList) sortItems() { func (this *IPList) sortRangeItems(force bool) {
sort.Slice(this.sortedItems, func(i, j int) bool { if len(this.bufferItemsMap) > 0 {
var item1 = this.sortedItems[i] for _, item := range this.bufferItemsMap {
var item2 = this.sortedItems[j] this.addItem(item, false, false)
if item1.IPFrom == item2.IPFrom {
return item1.IPTo < item2.IPTo
} }
return item1.IPFrom < item2.IPFrom this.bufferItemsMap = map[uint64]*IPItem{}
}) force = true
}
if force {
sort.Slice(this.sortedRangeItems, func(i, j int) bool {
var item1 = this.sortedRangeItems[i]
var item2 = this.sortedRangeItems[j]
if iputils.CompareBytes(item1.IPFrom, item2.IPFrom) == 0 {
return iputils.CompareBytes(item1.IPTo, item2.IPTo) < 0
}
return iputils.CompareBytes(item1.IPFrom, item2.IPFrom) < 0
})
}
} }
// 不加锁的情况下查找Item // 不加锁的情况下查找Item
func (this *IPList) lookupIP(ip uint64) *IPItem { func (this *IPList) lookupIP(ipBytes []byte) *IPItem {
if len(this.sortedItems) == 0 { {
item, ok := this.ipMap[ToHex(ipBytes)]
if ok && (item.ExpiredAt == 0 || item.ExpiredAt > fasttime.Now().Unix()) {
return item
}
}
if len(this.sortedRangeItems) == 0 {
return nil return nil
} }
var count = len(this.sortedItems) var count = len(this.sortedRangeItems)
var resultIndex = -1 var resultIndex = -1
sort.Search(count, func(i int) bool { sort.Search(count, func(i int) bool {
var item = this.sortedItems[i] var item = this.sortedRangeItems[i]
if item.IPFrom < ip { var cmp = iputils.CompareBytes(item.IPFrom, ipBytes)
if item.IPTo >= ip { if cmp < 0 {
if iputils.CompareBytes(item.IPTo, ipBytes) >= 0 {
resultIndex = i resultIndex = i
} }
return false return false
} else if item.IPFrom == ip { } else if cmp == 0 {
resultIndex = i resultIndex = i
return false return false
} }
@@ -239,36 +297,54 @@ func (this *IPList) lookupIP(ip uint64) *IPItem {
return nil return nil
} }
return this.sortedItems[resultIndex] var item = this.sortedRangeItems[resultIndex]
if item.ExpiredAt == 0 || item.ExpiredAt > fasttime.Now().Unix() {
return item
}
return nil
} }
// 在不加锁的情况下删除某个Item // 在不加锁的情况下删除某个Item
// 将会被别的方法引用,切记不能加锁 // 将会被别的方法引用,切记不能加锁
func (this *IPList) deleteItem(itemId uint64) { func (this *IPList) deleteItem(itemId uint64) {
_, ok := this.itemsMap[itemId] // 从buffer中删除
if !ok { delete(this.bufferItemsMap, itemId)
// 从all items中删除
_, ok := this.allItemsMap[itemId]
if ok {
delete(this.allItemsMap, itemId)
}
// 检查是否存在
oldItem, existsOld := this.itemsMap[itemId]
if !existsOld {
return return
} }
// 从ipMap中删除
if IsZero(oldItem.IPTo) {
var ipHex = ToHex(oldItem.IPFrom)
ipItem, ok := this.ipMap[ipHex]
if ok && ipItem.Id == itemId {
delete(this.ipMap, ipHex)
}
}
delete(this.itemsMap, itemId) delete(this.itemsMap, itemId)
// 是否为All Item
_, ok = this.allItemsMap[itemId]
if ok {
delete(this.allItemsMap, itemId)
return
}
// 删除排序中的Item // 删除排序中的Item
var index = -1 if !IsZero(oldItem.IPTo) {
for itemIndex, item := range this.sortedItems { var index = -1
if item.Id == itemId { for itemIndex, item := range this.sortedRangeItems {
index = itemIndex if item.Id == itemId {
break index = itemIndex
break
}
}
if index >= 0 {
copy(this.sortedRangeItems[index:], this.sortedRangeItems[index+1:])
this.sortedRangeItems = this.sortedRangeItems[:len(this.sortedRangeItems)-1]
} }
} }
if index >= 0 {
copy(this.sortedItems[index:], this.sortedItems[index+1:])
this.sortedItems = this.sortedItems[:len(this.sortedItems)-1]
}
} }

View File

@@ -1,305 +1,13 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package iplibrary package iplibrary
import ( import "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/iwind/TeaGo/Tea"
"os"
"path/filepath"
"time"
)
type IPListDB struct { type IPListDB interface {
db *dbs.DB Name() string
DeleteExpiredItems() error
itemTableName string ReadMaxVersion() (int64, error)
versionTableName string ReadItems(offset int64, size int64) (items []*pb.IPItem, goNext bool, err error)
AddItem(item *pb.IPItem) error
deleteExpiredItemsStmt *dbs.Stmt
deleteItemStmt *dbs.Stmt
insertItemStmt *dbs.Stmt
selectItemsStmt *dbs.Stmt
selectMaxItemVersionStmt *dbs.Stmt
selectVersionStmt *dbs.Stmt
updateVersionStmt *dbs.Stmt
cleanTicker *time.Ticker
dir string
isClosed bool
}
func NewIPListDB() (*IPListDB, error) {
var db = &IPListDB{
itemTableName: "ipItems",
versionTableName: "versions",
dir: filepath.Clean(Tea.Root + "/data"),
cleanTicker: time.NewTicker(24 * time.Hour),
}
err := db.init()
return db, err
}
func (this *IPListDB) init() error {
// 检查目录是否存在
_, err := os.Stat(this.dir)
if err != nil {
err = os.MkdirAll(this.dir, 0777)
if err != nil {
return err
}
remotelogs.Println("IP_LIST_DB", "create data dir '"+this.dir+"'")
}
var path = this.dir + "/ip_list.db"
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
if err != nil {
return err
}
db.SetMaxOpenConns(1)
//_, err = db.Exec("VACUUM")
//if err != nil {
// return err
//}
this.db = db
// 恢复数据库
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
if len(recoverEnv) > 0 {
for _, indexName := range []string{"ip_list_itemId", "ip_list_expiredAt"} {
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
}
}
// 初始化数据库
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemTableName + `" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"listId" integer DEFAULT 0,
"listType" varchar(32),
"isGlobal" integer(1) DEFAULT 0,
"type" varchar(16),
"itemId" integer DEFAULT 0,
"ipFrom" varchar(64) DEFAULT 0,
"ipTo" varchar(64) DEFAULT 0,
"expiredAt" integer DEFAULT 0,
"eventLevel" varchar(32),
"isDeleted" integer(1) DEFAULT 0,
"version" integer DEFAULT 0,
"nodeId" integer DEFAULT 0,
"serverId" integer DEFAULT 0
);
CREATE INDEX IF NOT EXISTS "ip_list_itemId"
ON "` + this.itemTableName + `" (
"itemId" ASC
);
CREATE INDEX IF NOT EXISTS "ip_list_expiredAt"
ON "` + this.itemTableName + `" (
"expiredAt" ASC
);
`)
if err != nil {
return err
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.versionTableName + `" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"version" integer DEFAULT 0
);
`)
if err != nil {
return err
}
// 初始化SQL语句
this.deleteExpiredItemsStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "expiredAt">0 AND "expiredAt"<?`)
if err != nil {
return err
}
this.deleteItemStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "itemId"=?`)
if err != nil {
return err
}
this.insertItemStmt, err = this.db.Prepare(`INSERT INTO "` + this.itemTableName + `" ("listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
if err != nil {
return err
}
this.selectItemsStmt, err = this.db.Prepare(`SELECT "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId" FROM "` + this.itemTableName + `" WHERE isDeleted=0 ORDER BY "version" ASC, "itemId" ASC LIMIT ?, ?`)
if err != nil {
return err
}
this.selectMaxItemVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.itemTableName + `" ORDER BY "id" DESC LIMIT 1`)
if err != nil {
return err
}
this.selectVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.versionTableName + `" LIMIT 1`)
if err != nil {
return err
}
this.updateVersionStmt, err = this.db.Prepare(`REPLACE INTO "` + this.versionTableName + `" ("id", "version") VALUES (1, ?)`)
if err != nil {
return err
}
this.db = db
goman.New(func() {
events.OnClose(func() {
_ = this.Close()
this.cleanTicker.Stop()
})
for range this.cleanTicker.C {
err := this.DeleteExpiredItems()
if err != nil {
remotelogs.Error("IP_LIST_DB", "clean expired items failed: "+err.Error())
}
}
})
return nil
}
// DeleteExpiredItems 删除过期的条目
func (this *IPListDB) DeleteExpiredItems() error {
if this.isClosed {
return nil
}
_, err := this.deleteExpiredItemsStmt.Exec(time.Now().Unix() - 7*86400)
return err
}
func (this *IPListDB) AddItem(item *pb.IPItem) error {
if this.isClosed {
return nil
}
_, err := this.deleteItemStmt.Exec(item.Id)
if err != nil {
return err
}
// 如果是删除,则不再创建新记录
if item.IsDeleted {
return this.UpdateMaxVersion(item.Version)
}
_, err = this.insertItemStmt.Exec(item.ListId, item.ListType, item.IsGlobal, item.Type, item.Id, item.IpFrom, item.IpTo, item.ExpiredAt, item.EventLevel, item.IsDeleted, item.Version, item.NodeId, item.ServerId)
if err != nil {
return err
}
return this.UpdateMaxVersion(item.Version)
}
func (this *IPListDB) ReadItems(offset int64, size int64) (items []*pb.IPItem, err error) {
if this.isClosed {
return
}
rows, err := this.selectItemsStmt.Query(offset, size)
if err != nil {
return nil, err
}
defer func() {
_ = rows.Close()
}()
for rows.Next() {
// "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId"
var pbItem = &pb.IPItem{}
err = rows.Scan(&pbItem.ListId, &pbItem.ListType, &pbItem.IsGlobal, &pbItem.Type, &pbItem.Id, &pbItem.IpFrom, &pbItem.IpTo, &pbItem.ExpiredAt, &pbItem.EventLevel, &pbItem.IsDeleted, &pbItem.Version, &pbItem.NodeId, &pbItem.ServerId)
if err != nil {
return nil, err
}
items = append(items, pbItem)
}
return
}
// ReadMaxVersion 读取当前最大版本号
func (this *IPListDB) ReadMaxVersion() int64 {
if this.isClosed {
return 0
}
// from version table
{
var row = this.selectVersionStmt.QueryRow()
if row == nil {
return 0
}
var version int64
err := row.Scan(&version)
if err == nil {
return version
}
}
// from items table
{
var row = this.selectMaxItemVersionStmt.QueryRow()
if row == nil {
return 0
}
var version int64
err := row.Scan(&version)
if err != nil {
return 0
}
return version
}
}
// UpdateMaxVersion 修改版本号
func (this *IPListDB) UpdateMaxVersion(version int64) error {
if this.isClosed {
return nil
}
_, err := this.updateVersionStmt.Exec(version)
return err
}
func (this *IPListDB) Close() error {
this.isClosed = true
if this.db != nil {
for _, stmt := range []*dbs.Stmt{
this.deleteExpiredItemsStmt,
this.deleteItemStmt,
this.insertItemStmt,
this.selectItemsStmt,
this.selectMaxItemVersionStmt, // ipItems table
this.selectVersionStmt, // versions table
this.updateVersionStmt,
} {
if stmt != nil {
_ = stmt.Close()
}
}
return this.db.Close()
}
return nil
} }

View File

@@ -0,0 +1,233 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package iplibrary
import (
"encoding/binary"
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
"github.com/TeaOSLab/EdgeNode/internal/utils/kvstore"
"testing"
"time"
)
type KVIPList struct {
ipTable *kvstore.Table[*pb.IPItem]
versionsTable *kvstore.Table[int64]
encoder *IPItemEncoder[*pb.IPItem]
cleanTicker *time.Ticker
isClosed bool
offsetItemKey string
}
func NewKVIPList() (*KVIPList, error) {
var db = &KVIPList{
cleanTicker: time.NewTicker(24 * time.Hour),
encoder: &IPItemEncoder[*pb.IPItem]{},
}
err := db.init()
return db, err
}
func (this *KVIPList) init() error {
store, storeErr := kvstore.DefaultStore()
if storeErr != nil {
return storeErr
}
db, dbErr := store.NewDB("ip_list")
if dbErr != nil {
return dbErr
}
{
table, err := kvstore.NewTable[*pb.IPItem]("ip_items", this.encoder)
if err != nil {
return err
}
this.ipTable = table
err = table.AddFields("expiresAt")
if err != nil {
return err
}
db.AddTable(table)
}
{
table, err := kvstore.NewTable[int64]("versions", kvstore.NewIntValueEncoder[int64]())
if err != nil {
return err
}
this.versionsTable = table
db.AddTable(table)
}
goman.New(func() {
events.OnClose(func() {
_ = this.Close()
this.cleanTicker.Stop()
})
idles.RunTicker(this.cleanTicker, func() {
if this.isClosed {
return
}
deleteErr := this.DeleteExpiredItems()
if deleteErr != nil {
remotelogs.Error("IP_LIST_DB", "clean expired items failed: "+deleteErr.Error())
}
})
})
return nil
}
// Name 数据库名称代号
func (this *KVIPList) Name() string {
return "kvstore"
}
// DeleteExpiredItems 删除过期的条目
func (this *KVIPList) DeleteExpiredItems() error {
if this.isClosed {
return nil
}
for {
var found bool
var currentTime = fasttime.Now().Unix()
err := this.ipTable.
Query().
FieldAsc("expiresAt").
ForUpdate().
Limit(1000).
FindAll(func(tx *kvstore.Tx[*pb.IPItem], item kvstore.Item[*pb.IPItem]) (goNext bool, err error) {
if !item.Value.IsDeleted && item.Value.ExpiredAt == 0 { // never expires
return kvstore.Skip()
}
if item.Value.ExpiredAt < currentTime-7*86400 /** keep for 7 days **/ {
err = tx.Delete(item.Key)
if err != nil {
return false, err
}
found = true
return true, nil
}
found = false
return false, nil
})
if err != nil {
return err
}
if !found {
break
}
}
return nil
}
func (this *KVIPList) AddItem(item *pb.IPItem) error {
if this.isClosed {
return nil
}
// 先删除
var key = this.encoder.EncodeKey(item)
err := this.ipTable.Delete(key)
if err != nil {
return err
}
// 如果是删除,则不再创建新记录
if item.IsDeleted {
return this.UpdateMaxVersion(item.Version)
}
err = this.ipTable.Set(key, item)
if err != nil {
return err
}
return this.UpdateMaxVersion(item.Version)
}
func (this *KVIPList) ReadItems(offset int64, size int64) (items []*pb.IPItem, goNextLoop bool, err error) {
if this.isClosed {
return
}
err = this.ipTable.
Query().
Offset(this.offsetItemKey).
Limit(int(size)).
FindAll(func(tx *kvstore.Tx[*pb.IPItem], item kvstore.Item[*pb.IPItem]) (goNext bool, err error) {
this.offsetItemKey = item.Key
goNextLoop = true
if !item.Value.IsDeleted {
items = append(items, item.Value)
}
return true, nil
})
return
}
// ReadMaxVersion 读取当前最大版本号
func (this *KVIPList) ReadMaxVersion() (int64, error) {
if this.isClosed {
return 0, errors.New("database has been closed")
}
version, err := this.versionsTable.Get("version")
if err != nil {
if kvstore.IsNotFound(err) {
return 0, nil
}
return 0, err
}
return version, nil
}
// UpdateMaxVersion 修改版本号
func (this *KVIPList) UpdateMaxVersion(version int64) error {
if this.isClosed {
return nil
}
return this.versionsTable.Set("version", version)
}
func (this *KVIPList) TestInspect(t *testing.T) error {
return this.ipTable.
Query().
FindAll(func(tx *kvstore.Tx[*pb.IPItem], item kvstore.Item[*pb.IPItem]) (goNext bool, err error) {
if len(item.Key) != 8 {
return false, errors.New("invalid key '" + item.Key + "'")
}
t.Log(binary.BigEndian.Uint64([]byte(item.Key)), "=>", item.Value)
return true, nil
})
}
// Flush to disk
func (this *KVIPList) Flush() error {
return this.ipTable.DB().Store().Flush()
}
func (this *KVIPList) Close() error {
this.isClosed = true
return nil
}

View File

@@ -0,0 +1,55 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package iplibrary
import (
"encoding/binary"
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"google.golang.org/protobuf/proto"
"math"
)
type IPItemEncoder[T interface{ *pb.IPItem }] struct {
}
func NewIPItemEncoder[T interface{ *pb.IPItem }]() *IPItemEncoder[T] {
return &IPItemEncoder[T]{}
}
func (this *IPItemEncoder[T]) Encode(value T) ([]byte, error) {
return proto.Marshal(any(value).(*pb.IPItem))
}
func (this *IPItemEncoder[T]) EncodeField(value T, fieldName string) ([]byte, error) {
switch fieldName {
case "expiresAt":
var expiresAt = any(value).(*pb.IPItem).ExpiredAt
if expiresAt < 0 || expiresAt > int64(math.MaxUint32) {
expiresAt = 0
}
var b = make([]byte, 4)
binary.BigEndian.PutUint32(b, uint32(expiresAt))
return b, nil
}
return nil, errors.New("field '" + fieldName + "' not found")
}
func (this *IPItemEncoder[T]) Decode(valueBytes []byte) (value T, err error) {
var item = &pb.IPItem{}
err = proto.Unmarshal(valueBytes, item)
value = item
return
}
// EncodeKey generate key for ip item
func (this *IPItemEncoder[T]) EncodeKey(item *pb.IPItem) string {
var b = make([]byte, 8)
if item.Id < 0 {
item.Id = 0
}
binary.BigEndian.PutUint64(b, uint64(item.Id))
return string(b)
}

View File

@@ -0,0 +1,221 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package iplibrary_test
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"testing"
"time"
)
func TestKVIPList_AddItem(t *testing.T) {
kv, err := iplibrary.NewKVIPList()
if err != nil {
t.Fatal(err)
}
defer func() {
_ = kv.Flush()
}()
{
err = kv.AddItem(&pb.IPItem{
Id: 1,
IpFrom: "192.168.1.101",
IpTo: "",
Version: 1,
ExpiredAt: fasttime.NewFastTime().Unix() + 60,
ListId: 1,
IsDeleted: false,
ListType: "white",
})
if err != nil {
t.Fatal(err)
}
}
{
err = kv.AddItem(&pb.IPItem{
Id: 2,
IpFrom: "192.168.1.102",
IpTo: "",
Version: 2,
ExpiredAt: fasttime.NewFastTime().Unix() + 60,
ListId: 1,
IsDeleted: false,
ListType: "white",
})
if err != nil {
t.Fatal(err)
}
}
{
err = kv.AddItem(&pb.IPItem{
Id: 3,
IpFrom: "192.168.1.103",
IpTo: "",
Version: 3,
ExpiredAt: fasttime.NewFastTime().Unix() + 60,
ListId: 1,
IsDeleted: false,
ListType: "white",
})
if err != nil {
t.Fatal(err)
}
}
}
func TestKVIPList_AddItems_Many(t *testing.T) {
kv, err := iplibrary.NewKVIPList()
if err != nil {
t.Fatal(err)
}
defer func() {
_ = kv.Flush()
}()
var count = 2
var from = 1
if testutils.IsSingleTesting() {
count = 2_000_000
}
var before = time.Now()
defer func() {
t.Logf("cost: %.2f s", time.Since(before).Seconds())
}()
for i := from; i <= from+count; i++ {
err = kv.AddItem(&pb.IPItem{
Id: int64(i),
IpFrom: testutils.RandIP(),
IpTo: "",
Version: int64(i),
ExpiredAt: fasttime.NewFastTime().Unix() + 86400,
ListId: 1,
IsDeleted: false,
ListType: "white",
})
if err != nil {
t.Fatal(err)
}
}
}
func TestKVIPList_DeleteExpiredItems(t *testing.T) {
kv, err := iplibrary.NewKVIPList()
if err != nil {
t.Fatal(err)
}
defer func() {
_ = kv.Flush()
}()
err = kv.DeleteExpiredItems()
if err != nil {
t.Fatal(err)
}
}
func TestKVIPList_UpdateMaxVersion(t *testing.T) {
kv, err := iplibrary.NewKVIPList()
if err != nil {
t.Fatal(err)
}
defer func() {
_ = kv.Flush()
}()
err = kv.UpdateMaxVersion(101)
if err != nil {
t.Fatal(err)
}
maxVersion, err := kv.ReadMaxVersion()
if err != nil {
t.Fatal(err)
}
t.Log("version:", maxVersion)
}
func TestKVIPList_ReadMaxVersion(t *testing.T) {
kv, err := iplibrary.NewKVIPList()
if err != nil {
t.Fatal(err)
}
maxVersion, err := kv.ReadMaxVersion()
if err != nil {
t.Fatal(err)
}
t.Log("version:", maxVersion)
}
func TestKVIPList_ReadItems(t *testing.T) {
kv, err := iplibrary.NewKVIPList()
if err != nil {
t.Fatal(err)
}
for {
items, goNext, readErr := kv.ReadItems(0, 2)
if readErr != nil {
t.Fatal(readErr)
}
t.Log("====")
for _, item := range items {
t.Log(item.Id)
}
if !goNext {
break
}
}
}
func TestKVIPList_CountItems(t *testing.T) {
kv, err := iplibrary.NewKVIPList()
if err != nil {
t.Fatal(err)
}
var count int
var m = map[int64]zero.Zero{}
for {
items, goNext, readErr := kv.ReadItems(0, 1000)
if readErr != nil {
t.Fatal(readErr)
}
for _, item := range items {
count++
m[item.Id] = zero.Zero{}
}
if !goNext {
break
}
}
t.Log("count:", count, "len:", len(m))
}
func TestKVIPList_Inspect(t *testing.T) {
kv, err := iplibrary.NewKVIPList()
if err != nil {
t.Fatal(err)
}
err = kv.TestInspect(t)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,313 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package iplibrary
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeNode/internal/events"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
"github.com/iwind/TeaGo/Tea"
"os"
"path/filepath"
"time"
)
type SQLiteIPList struct {
db *dbs.DB
itemTableName string
versionTableName string
deleteExpiredItemsStmt *dbs.Stmt
deleteItemStmt *dbs.Stmt
insertItemStmt *dbs.Stmt
selectItemsStmt *dbs.Stmt
selectMaxItemVersionStmt *dbs.Stmt
selectVersionStmt *dbs.Stmt
updateVersionStmt *dbs.Stmt
cleanTicker *time.Ticker
dir string
isClosed bool
}
func NewSQLiteIPList() (*SQLiteIPList, error) {
var db = &SQLiteIPList{
itemTableName: "ipItems",
versionTableName: "versions",
dir: filepath.Clean(Tea.Root + "/data"),
cleanTicker: time.NewTicker(24 * time.Hour),
}
err := db.init()
return db, err
}
func (this *SQLiteIPList) init() error {
// 检查目录是否存在
_, err := os.Stat(this.dir)
if err != nil {
err = os.MkdirAll(this.dir, 0777)
if err != nil {
return err
}
remotelogs.Println("IP_LIST_DB", "create data dir '"+this.dir+"'")
}
var path = this.dir + "/ip_list.db"
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
if err != nil {
return err
}
db.SetMaxOpenConns(1)
//_, err = db.Exec("VACUUM")
//if err != nil {
// return err
//}
this.db = db
// 恢复数据库
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
if len(recoverEnv) > 0 {
for _, indexName := range []string{"ip_list_itemId", "ip_list_expiredAt"} {
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
}
}
// 初始化数据库
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemTableName + `" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"listId" integer DEFAULT 0,
"listType" varchar(32),
"isGlobal" integer(1) DEFAULT 0,
"type" varchar(16),
"itemId" integer DEFAULT 0,
"ipFrom" varchar(64) DEFAULT 0,
"ipTo" varchar(64) DEFAULT 0,
"expiredAt" integer DEFAULT 0,
"eventLevel" varchar(32),
"isDeleted" integer(1) DEFAULT 0,
"version" integer DEFAULT 0,
"nodeId" integer DEFAULT 0,
"serverId" integer DEFAULT 0
);
CREATE INDEX IF NOT EXISTS "ip_list_itemId"
ON "` + this.itemTableName + `" (
"itemId" ASC
);
CREATE INDEX IF NOT EXISTS "ip_list_expiredAt"
ON "` + this.itemTableName + `" (
"expiredAt" ASC
);
`)
if err != nil {
return err
}
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.versionTableName + `" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"version" integer DEFAULT 0
);
`)
if err != nil {
return err
}
// 初始化SQL语句
this.deleteExpiredItemsStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "expiredAt">0 AND "expiredAt"<?`)
if err != nil {
return err
}
this.deleteItemStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "itemId"=?`)
if err != nil {
return err
}
this.insertItemStmt, err = this.db.Prepare(`INSERT INTO "` + this.itemTableName + `" ("listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
if err != nil {
return err
}
this.selectItemsStmt, err = this.db.Prepare(`SELECT "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId" FROM "` + this.itemTableName + `" WHERE isDeleted=0 ORDER BY "version" ASC, "itemId" ASC LIMIT ?, ?`)
if err != nil {
return err
}
this.selectMaxItemVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.itemTableName + `" ORDER BY "id" DESC LIMIT 1`)
if err != nil {
return err
}
this.selectVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.versionTableName + `" LIMIT 1`)
if err != nil {
return err
}
this.updateVersionStmt, err = this.db.Prepare(`REPLACE INTO "` + this.versionTableName + `" ("id", "version") VALUES (1, ?)`)
if err != nil {
return err
}
this.db = db
goman.New(func() {
events.OnClose(func() {
_ = this.Close()
this.cleanTicker.Stop()
})
idles.RunTicker(this.cleanTicker, func() {
deleteErr := this.DeleteExpiredItems()
if deleteErr != nil {
remotelogs.Error("IP_LIST_DB", "clean expired items failed: "+deleteErr.Error())
}
})
})
return nil
}
// Name 数据库名称代号
func (this *SQLiteIPList) Name() string {
return "sqlite"
}
// DeleteExpiredItems 删除过期的条目
func (this *SQLiteIPList) DeleteExpiredItems() error {
if this.isClosed {
return nil
}
_, err := this.deleteExpiredItemsStmt.Exec(time.Now().Unix() - 7*86400)
return err
}
func (this *SQLiteIPList) AddItem(item *pb.IPItem) error {
if this.isClosed {
return nil
}
_, err := this.deleteItemStmt.Exec(item.Id)
if err != nil {
return err
}
// 如果是删除,则不再创建新记录
if item.IsDeleted {
return this.UpdateMaxVersion(item.Version)
}
_, err = this.insertItemStmt.Exec(item.ListId, item.ListType, item.IsGlobal, item.Type, item.Id, item.IpFrom, item.IpTo, item.ExpiredAt, item.EventLevel, item.IsDeleted, item.Version, item.NodeId, item.ServerId)
if err != nil {
return err
}
return this.UpdateMaxVersion(item.Version)
}
func (this *SQLiteIPList) ReadItems(offset int64, size int64) (items []*pb.IPItem, goNext bool, err error) {
if this.isClosed {
return
}
rows, err := this.selectItemsStmt.Query(offset, size)
if err != nil {
return nil, false, err
}
defer func() {
_ = rows.Close()
}()
for rows.Next() {
// "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId"
var pbItem = &pb.IPItem{}
err = rows.Scan(&pbItem.ListId, &pbItem.ListType, &pbItem.IsGlobal, &pbItem.Type, &pbItem.Id, &pbItem.IpFrom, &pbItem.IpTo, &pbItem.ExpiredAt, &pbItem.EventLevel, &pbItem.IsDeleted, &pbItem.Version, &pbItem.NodeId, &pbItem.ServerId)
if err != nil {
return nil, false, err
}
items = append(items, pbItem)
}
goNext = int64(len(items)) == size
return
}
// ReadMaxVersion 读取当前最大版本号
func (this *SQLiteIPList) ReadMaxVersion() (int64, error) {
if this.isClosed {
return 0, nil
}
// from version table
{
var row = this.selectVersionStmt.QueryRow()
if row == nil {
return 0, nil
}
var version int64
err := row.Scan(&version)
if err == nil {
return version, nil
}
}
// from items table
{
var row = this.selectMaxItemVersionStmt.QueryRow()
if row == nil {
return 0, nil
}
var version int64
err := row.Scan(&version)
if err != nil {
return 0, nil
}
return version, nil
}
}
// UpdateMaxVersion 修改版本号
func (this *SQLiteIPList) UpdateMaxVersion(version int64) error {
if this.isClosed {
return nil
}
_, err := this.updateVersionStmt.Exec(version)
return err
}
func (this *SQLiteIPList) Close() error {
this.isClosed = true
if this.db != nil {
for _, stmt := range []*dbs.Stmt{
this.deleteExpiredItemsStmt,
this.deleteItemStmt,
this.insertItemStmt,
this.selectItemsStmt,
this.selectMaxItemVersionStmt, // ipItems table
this.selectVersionStmt, // versions table
this.updateVersionStmt,
} {
if stmt != nil {
_ = stmt.Close()
}
}
return this.db.Close()
}
return nil
}

View File

@@ -11,8 +11,8 @@ import (
"time" "time"
) )
func TestIPListDB_AddItem(t *testing.T) { func TestSQLiteIPList_AddItem(t *testing.T) {
db, err := iplibrary.NewIPListDB() db, err := iplibrary.NewSQLiteIPList()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -58,8 +58,8 @@ func TestIPListDB_AddItem(t *testing.T) {
t.Log("ok") t.Log("ok")
} }
func TestIPListDB_ReadItems(t *testing.T) { func TestSQLiteIPList_ReadItems(t *testing.T) {
db, err := iplibrary.NewIPListDB() db, err := iplibrary.NewSQLiteIPList()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -71,15 +71,16 @@ func TestIPListDB_ReadItems(t *testing.T) {
_ = db.Close() _ = db.Close()
}() }()
items, err := db.ReadItems(0, 2) items, goNext, err := db.ReadItems(0, 2)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Log("goNext:", goNext)
logs.PrintAsJSON(items, t) logs.PrintAsJSON(items, t)
} }
func TestIPListDB_ReadMaxVersion(t *testing.T) { func TestSQLiteIPList_ReadMaxVersion(t *testing.T) {
db, err := iplibrary.NewIPListDB() db, err := iplibrary.NewSQLiteIPList()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -89,8 +90,8 @@ func TestIPListDB_ReadMaxVersion(t *testing.T) {
t.Log(db.ReadMaxVersion()) t.Log(db.ReadMaxVersion())
} }
func TestIPListDB_UpdateMaxVersion(t *testing.T) { func TestSQLiteIPList_UpdateMaxVersion(t *testing.T) {
db, err := iplibrary.NewIPListDB() db, err := iplibrary.NewSQLiteIPList()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -1,10 +1,15 @@
package iplibrary package iplibrary_test
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/utils" "fmt"
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/assert" "github.com/iwind/TeaGo/assert"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"math/rand"
"runtime" "runtime"
"runtime/debug" "runtime/debug"
"strconv" "strconv"
@@ -14,216 +19,278 @@ import (
) )
func TestIPList_Add_Empty(t *testing.T) { func TestIPList_Add_Empty(t *testing.T) {
ipList := NewIPList() var ipList = iplibrary.NewIPList()
ipList.Add(&IPItem{ ipList.Add(&iplibrary.IPItem{
Id: 1, Id: 1,
}) })
logs.PrintAsJSON(ipList.itemsMap, t) logs.PrintAsJSON(ipList.ItemsMap(), t)
logs.PrintAsJSON(ipList.allItemsMap, t) logs.PrintAsJSON(ipList.AllItemsMap(), t)
logs.PrintAsJSON(ipList.IPMap(), t)
} }
func TestIPList_Add_One(t *testing.T) { func TestIPList_Add_One(t *testing.T) {
ipList := NewIPList() var a = assert.NewAssertion(t)
ipList.Add(&IPItem{
var ipList = iplibrary.NewIPList()
ipList.Add(&iplibrary.IPItem{
Id: 1, Id: 1,
IPFrom: utils.IP2Long("192.168.1.1"), IPFrom: iputils.ToBytes("192.168.1.1"),
}) })
ipList.Add(&IPItem{ ipList.Add(&iplibrary.IPItem{
Id: 2, Id: 2,
IPTo: utils.IP2Long("192.168.1.2"), IPTo: iputils.ToBytes("192.168.1.2"),
}) })
ipList.Add(&IPItem{ ipList.Add(&iplibrary.IPItem{
Id: 3, Id: 3,
IPFrom: utils.IP2Long("192.168.0.2"), IPFrom: iputils.ToBytes("192.168.0.2"),
}) })
ipList.Add(&IPItem{ ipList.Add(&iplibrary.IPItem{
Id: 4, Id: 4,
IPFrom: utils.IP2Long("192.168.0.2"), IPFrom: iputils.ToBytes("192.168.0.2"),
IPTo: utils.IP2Long("192.168.0.1"), IPTo: iputils.ToBytes("192.168.0.1"),
}) })
ipList.Add(&IPItem{ ipList.Add(&iplibrary.IPItem{
Id: 5, Id: 5,
IPFrom: utils.IP2Long("2001:db8:0:1::101"), IPFrom: iputils.ToBytes("2001:db8:0:1::101"),
}) })
ipList.Add(&IPItem{ ipList.Add(&iplibrary.IPItem{
Id: 6, Id: 6,
IPFrom: 0, IPFrom: nil,
Type: "all", Type: "all",
}) })
t.Log("===items===") t.Log("===items===")
logs.PrintAsJSON(ipList.itemsMap, t) logs.PrintAsJSON(ipList.ItemsMap(), t)
t.Log("===sorted items===") t.Log("===sorted items===")
logs.PrintAsJSON(ipList.sortedItems, t) logs.PrintAsJSON(ipList.SortedRangeItems(), t)
t.Log("===all items===") t.Log("===all items===")
logs.PrintAsJSON(ipList.allItemsMap, t) // ip => items a.IsTrue(len(ipList.AllItemsMap()) == 1)
logs.PrintAsJSON(ipList.AllItemsMap(), t) // ip => items
t.Log("===ip items===")
logs.PrintAsJSON(ipList.IPMap())
} }
func TestIPList_Update(t *testing.T) { func TestIPList_Update(t *testing.T) {
ipList := NewIPList() var ipList = iplibrary.NewIPList()
ipList.Add(&IPItem{ ipList.Add(&iplibrary.IPItem{
Id: 1, Id: 1,
IPFrom: utils.IP2Long("192.168.1.1"), IPFrom: iputils.ToBytes("192.168.1.1"),
}) })
/**ipList.Add(&IPItem{
t.Log("===before===")
logs.PrintAsJSON(ipList.ItemsMap(), t)
logs.PrintAsJSON(ipList.SortedRangeItems(), t)
logs.PrintAsJSON(ipList.IPMap(), t)
/**ipList.Add(&iplibrary.IPItem{
Id: 2, Id: 2,
IPFrom: utils.IP2Long("192.168.1.1"), IPFrom: iputils.ToBytes("192.168.1.1"),
})**/ })**/
ipList.Add(&IPItem{ ipList.Add(&iplibrary.IPItem{
Id: 1, Id: 1,
IPTo: utils.IP2Long("192.168.1.2"), //IPFrom: 123,
IPTo: iputils.ToBytes("192.168.1.2"),
}) })
logs.PrintAsJSON(ipList.itemsMap, t)
logs.PrintAsJSON(ipList.sortedItems, t) t.Log("===after===")
logs.PrintAsJSON(ipList.ItemsMap(), t)
logs.PrintAsJSON(ipList.SortedRangeItems(), t)
logs.PrintAsJSON(ipList.IPMap(), t)
} }
func TestIPList_Update_AllItems(t *testing.T) { func TestIPList_Update_AllItems(t *testing.T) {
ipList := NewIPList() var ipList = iplibrary.NewIPList()
ipList.Add(&IPItem{ ipList.Add(&iplibrary.IPItem{
Id: 1, Id: 1,
Type: IPItemTypeAll, Type: iplibrary.IPItemTypeAll,
IPFrom: 0, IPFrom: nil,
}) })
ipList.Add(&IPItem{ ipList.Add(&iplibrary.IPItem{
Id: 1, Id: 1,
IPTo: 0, IPTo: nil,
}) })
t.Log("===items map===") t.Log("===items map===")
logs.PrintAsJSON(ipList.itemsMap, t) logs.PrintAsJSON(ipList.ItemsMap(), t)
t.Log("===all items map===") t.Log("===all items map===")
logs.PrintAsJSON(ipList.allItemsMap, t) logs.PrintAsJSON(ipList.AllItemsMap(), t)
t.Log("===ip map===")
logs.PrintAsJSON(ipList.IPMap())
} }
func TestIPList_Add_Range(t *testing.T) { func TestIPList_Add_Range(t *testing.T) {
ipList := NewIPList() var a = assert.NewAssertion(t)
ipList.Add(&IPItem{
var ipList = iplibrary.NewIPList()
ipList.Add(&iplibrary.IPItem{
Id: 1, Id: 1,
IPFrom: utils.IP2Long("192.168.1.1"), IPFrom: iputils.ToBytes("192.168.1.1"),
IPTo: utils.IP2Long("192.168.2.1"), IPTo: iputils.ToBytes("192.168.2.1"),
}) })
ipList.Add(&IPItem{ ipList.Add(&iplibrary.IPItem{
Id: 2, Id: 2,
IPTo: utils.IP2Long("192.168.1.2"), IPTo: iputils.ToBytes("192.168.1.2"),
}) })
t.Log(len(ipList.itemsMap), "ips") ipList.Add(&iplibrary.IPItem{
logs.PrintAsJSON(ipList.itemsMap, t) Id: 3,
logs.PrintAsJSON(ipList.allItemsMap, t) IPFrom: iputils.ToBytes("192.168.0.1"),
} IPTo: iputils.ToBytes("192.168.0.2"),
func TestIPList_Add_Overflow(t *testing.T) {
a := assert.NewAssertion(t)
ipList := NewIPList()
ipList.Add(&IPItem{
Id: 1,
IPFrom: utils.IP2Long("192.168.1.1"),
IPTo: utils.IP2Long("192.169.255.1"),
}) })
t.Log(len(ipList.itemsMap), "ips")
a.IsTrue(len(ipList.itemsMap) <= 65535) a.IsTrue(len(ipList.SortedRangeItems()) == 2)
t.Log(len(ipList.ItemsMap()), "ips")
t.Log("===items map===")
logs.PrintAsJSON(ipList.ItemsMap(), t)
t.Log("===sorted range items===")
logs.PrintAsJSON(ipList.SortedRangeItems())
t.Log("===all items map===")
logs.PrintAsJSON(ipList.AllItemsMap(), t)
t.Log("===ip map===")
logs.PrintAsJSON(ipList.IPMap(), t)
} }
func TestNewIPList_Memory(t *testing.T) { func TestNewIPList_Memory(t *testing.T) {
list := NewIPList() var list = iplibrary.NewIPList()
for i := 0; i < 200_0000; i++ { var count = 100
list.Add(&IPItem{ if testutils.IsSingleTesting() {
IPFrom: 1, count = 2_000_000
IPTo: 2, }
var stat1 = testutils.ReadMemoryStat()
for i := 0; i < count; i++ {
list.AddDelay(&iplibrary.IPItem{
Id: uint64(i),
IPFrom: iputils.ToBytes(testutils.RandIP()),
IPTo: iputils.ToBytes(testutils.RandIP()),
ExpiredAt: time.Now().Unix(), ExpiredAt: time.Now().Unix(),
}) })
} }
t.Log("ok") list.Sort()
runtime.GC()
var stat2 = testutils.ReadMemoryStat()
t.Log((stat2.HeapInuse-stat1.HeapInuse)>>20, "MB")
} }
func TestIPList_Contains(t *testing.T) { func TestIPList_Contains(t *testing.T) {
var a = assert.NewAssertion(t) var a = assert.NewAssertion(t)
list := NewIPList() var list = iplibrary.NewIPList()
for i := 0; i < 255; i++ { for i := 0; i < 255; i++ {
list.AddDelay(&IPItem{ list.Add(&iplibrary.IPItem{
Id: uint64(i), Id: uint64(i),
IPFrom: utils.IP2Long(strconv.Itoa(i) + ".168.0.1"), IPFrom: iputils.ToBytes(strconv.Itoa(i) + ".168.0.1"),
IPTo: utils.IP2Long(strconv.Itoa(i) + ".168.255.1"), IPTo: iputils.ToBytes(strconv.Itoa(i) + ".168.255.1"),
ExpiredAt: 0, ExpiredAt: 0,
}) })
} }
for i := 0; i < 255; i++ { for i := 0; i < 255; i++ {
list.AddDelay(&IPItem{ list.Add(&iplibrary.IPItem{
Id: uint64(1000 + i), Id: uint64(1000 + i),
IPFrom: utils.IP2Long("192.167.2." + strconv.Itoa(i)), IPFrom: iputils.ToBytes("192.167.2." + strconv.Itoa(i)),
}) })
} }
list.Sort()
t.Log(len(list.itemsMap), "ip")
before := time.Now() list.Add(&iplibrary.IPItem{
a.IsTrue(list.Contains(utils.IP2Long("192.168.1.100"))) Id: 10000,
a.IsTrue(list.Contains(utils.IP2Long("192.168.2.100"))) IPFrom: iputils.ToBytes("::1"),
a.IsFalse(list.Contains(utils.IP2Long("192.169.3.100"))) })
a.IsFalse(list.Contains(utils.IP2Long("192.167.3.100"))) list.Add(&iplibrary.IPItem{
a.IsTrue(list.Contains(utils.IP2Long("192.167.2.100"))) Id: 10001,
IPFrom: iputils.ToBytes("::2"),
IPTo: iputils.ToBytes("::5"),
})
t.Log(len(list.ItemsMap()), "ip")
var before = time.Now()
a.IsTrue(list.Contains(iputils.ToBytes("192.168.1.100")))
a.IsTrue(list.Contains(iputils.ToBytes("192.168.2.100")))
a.IsFalse(list.Contains(iputils.ToBytes("192.169.3.100")))
a.IsFalse(list.Contains(iputils.ToBytes("192.167.3.100")))
a.IsTrue(list.Contains(iputils.ToBytes("192.167.2.100")))
a.IsTrue(list.Contains(iputils.ToBytes("::1")))
a.IsTrue(list.Contains(iputils.ToBytes("::3")))
a.IsFalse(list.Contains(iputils.ToBytes("::8")))
t.Log(time.Since(before).Seconds()*1000, "ms") t.Log(time.Since(before).Seconds()*1000, "ms")
} }
func TestIPList_Contains_Many(t *testing.T) { func TestIPList_Contains_Many(t *testing.T) {
list := NewIPList() var list = iplibrary.NewIPList()
for i := 0; i < 1_000_000; i++ { for i := 0; i < 1_000_000; i++ {
list.AddDelay(&IPItem{ list.AddDelay(&iplibrary.IPItem{
Id: uint64(i), Id: uint64(i),
IPFrom: utils.IP2Long(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255))), IPFrom: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255))),
IPTo: utils.IP2Long(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255))), IPTo: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255))),
ExpiredAt: 0, ExpiredAt: 0,
}) })
} }
list.Sort()
t.Log(len(list.itemsMap), "ip")
before := time.Now() var before = time.Now()
_ = list.Contains(utils.IP2Long("192.168.1.100")) list.Sort()
t.Log(time.Since(before).Seconds()*1000, "ms") t.Log("sort cost:", time.Since(before).Seconds()*1000, "ms")
t.Log(len(list.ItemsMap()), "ip")
before = time.Now()
_ = list.Contains(iputils.ToBytes("192.168.1.100"))
t.Log("contains cost:", time.Since(before).Seconds()*1000, "ms")
} }
func TestIPList_ContainsAll(t *testing.T) { func TestIPList_ContainsAll(t *testing.T) {
list := NewIPList() var a = assert.NewAssertion(t)
list.Add(&IPItem{
Id: 1, {
Type: "all", var list = iplibrary.NewIPList()
IPFrom: 0, list.Add(&iplibrary.IPItem{
}) Id: 1,
b := list.Contains(utils.IP2Long("192.168.1.1")) Type: "all",
if b { IPFrom: nil,
t.Log(b) })
} else { var b = list.Contains(iputils.ToBytes("192.168.1.1"))
t.Fatal("'b' should be true") a.IsTrue(b)
list.Delete(1)
b = list.Contains(iputils.ToBytes("192.168.1.1"))
a.IsFalse(b)
} }
list.Delete(1) {
var list = iplibrary.NewIPList()
list.Add(&iplibrary.IPItem{
Id: 1,
Type: "all",
IPFrom: iputils.ToBytes("0.0.0.0"),
})
var b = list.Contains(iputils.ToBytes("192.168.1.1"))
a.IsTrue(b)
b = list.Contains(utils.IP2Long("192.168.1.1")) list.Delete(1)
if !b {
t.Log(b) b = list.Contains(iputils.ToBytes("192.168.1.1"))
} else { a.IsFalse(b)
t.Fatal("'b' should be false")
} }
} }
func TestIPList_ContainsIPStrings(t *testing.T) { func TestIPList_ContainsIPStrings(t *testing.T) {
var a = assert.NewAssertion(t) var a = assert.NewAssertion(t)
list := NewIPList() var list = iplibrary.NewIPList()
for i := 0; i < 255; i++ { for i := 0; i < 255; i++ {
list.Add(&IPItem{ list.Add(&iplibrary.IPItem{
Id: uint64(i), Id: uint64(i),
IPFrom: utils.IP2Long(strconv.Itoa(i) + ".168.0.1"), IPFrom: iputils.ToBytes(strconv.Itoa(i) + ".168.0.1"),
IPTo: utils.IP2Long(strconv.Itoa(i) + ".168.255.1"), IPTo: iputils.ToBytes(strconv.Itoa(i) + ".168.255.1"),
ExpiredAt: 0, ExpiredAt: 0,
}) })
} }
t.Log(len(list.itemsMap), "ip") t.Log(len(list.ItemsMap()), "ip")
{ {
item, ok := list.ContainsIPStrings([]string{"192.168.1.100"}) item, ok := list.ContainsIPStrings([]string{"192.168.1.100"})
@@ -238,85 +305,191 @@ func TestIPList_ContainsIPStrings(t *testing.T) {
} }
func TestIPList_Delete(t *testing.T) { func TestIPList_Delete(t *testing.T) {
list := NewIPList() var list = iplibrary.NewIPList()
list.Add(&IPItem{ list.Add(&iplibrary.IPItem{
Id: 1, Id: 1,
IPFrom: utils.IP2Long("192.168.0.1"), IPFrom: iputils.ToBytes("192.168.0.1"),
ExpiredAt: 0, ExpiredAt: 0,
}) })
list.Add(&IPItem{ list.Add(&iplibrary.IPItem{
Id: 2, Id: 2,
IPFrom: utils.IP2Long("192.168.0.1"), IPFrom: iputils.ToBytes("192.168.0.1"),
ExpiredAt: 0, ExpiredAt: 0,
}) })
t.Log("===BEFORE===") list.Add(&iplibrary.IPItem{
logs.PrintAsJSON(list.itemsMap, t) Id: 3,
logs.PrintAsJSON(list.allItemsMap, t) IPFrom: iputils.ToBytes("192.168.1.1"),
IPTo: iputils.ToBytes("192.168.2.1"),
ExpiredAt: 0,
})
t.Log("===before===")
logs.PrintAsJSON(list.ItemsMap(), t)
logs.PrintAsJSON(list.AllItemsMap(), t)
logs.PrintAsJSON(list.SortedRangeItems())
logs.PrintAsJSON(list.IPMap(), t)
{
var found bool
for _, item := range list.SortedRangeItems() {
if item.Id == 3 {
found = true
break
}
}
if !found {
t.Fatal("should be found")
}
}
list.Delete(1) list.Delete(1)
t.Log("===AFTER===") t.Log("===after===")
logs.PrintAsJSON(list.itemsMap, t) logs.PrintAsJSON(list.ItemsMap(), t)
logs.PrintAsJSON(list.allItemsMap, t) logs.PrintAsJSON(list.AllItemsMap(), t)
logs.PrintAsJSON(list.SortedRangeItems())
logs.PrintAsJSON(list.IPMap(), t)
list.Delete(3)
{
var found bool
for _, item := range list.SortedRangeItems() {
if item.Id == 3 {
found = true
break
}
}
if found {
t.Fatal("should be not found")
}
}
} }
func TestGC(t *testing.T) { func TestIPList_GC(t *testing.T) {
list := NewIPList() var a = assert.NewAssertion(t)
list.Add(&IPItem{
var list = iplibrary.NewIPList()
list.Add(&iplibrary.IPItem{
Id: 1, Id: 1,
IPFrom: utils.IP2Long("192.168.1.100"), IPFrom: iputils.ToBytes("192.168.1.100"),
IPTo: utils.IP2Long("192.168.1.101"), IPTo: iputils.ToBytes("192.168.1.101"),
ExpiredAt: time.Now().Unix() + 1, ExpiredAt: time.Now().Unix() + 1,
}) })
list.Add(&IPItem{ list.Add(&iplibrary.IPItem{
Id: 2, Id: 2,
IPFrom: utils.IP2Long("192.168.1.102"), IPFrom: iputils.ToBytes("192.168.1.102"),
IPTo: utils.IP2Long("192.168.1.103"), IPTo: iputils.ToBytes("192.168.1.103"),
ExpiredAt: 0, ExpiredAt: 0,
}) })
logs.PrintAsJSON(list.itemsMap, t) logs.PrintAsJSON(list.ItemsMap(), t)
logs.PrintAsJSON(list.allItemsMap, t) logs.PrintAsJSON(list.AllItemsMap(), t)
time.Sleep(3 * time.Second)
time.Sleep(2 * time.Second)
t.Log("===AFTER GC===") t.Log("===AFTER GC===")
logs.PrintAsJSON(list.itemsMap, t) logs.PrintAsJSON(list.ItemsMap(), t)
logs.PrintAsJSON(list.sortedItems, t) logs.PrintAsJSON(list.SortedRangeItems(), t)
a.IsTrue(len(list.ItemsMap()) == 1)
a.IsTrue(len(list.SortedRangeItems()) == 1)
} }
func TestTooManyLists(t *testing.T) { func TestManyLists(t *testing.T) {
debug.SetMaxThreads(20) debug.SetMaxThreads(20)
var lists = []*IPList{} var lists = []*iplibrary.IPList{}
var locker = &sync.Mutex{} var locker = &sync.Mutex{}
for i := 0; i < 1000; i++ { for i := 0; i < 1000; i++ {
locker.Lock() locker.Lock()
lists = append(lists, NewIPList()) lists = append(lists, iplibrary.NewIPList())
locker.Unlock() locker.Unlock()
} }
time.Sleep(1 * time.Second) if testutils.IsSingleTesting() {
time.Sleep(3 * time.Second)
}
t.Log(runtime.NumGoroutine()) t.Log(runtime.NumGoroutine())
t.Log(len(lists), "lists") t.Log(len(lists), "lists")
} }
func BenchmarkIPList_Add(b *testing.B) {
runtime.GOMAXPROCS(1)
var list = iplibrary.NewIPList()
for i := 1; i < 200_000; i++ {
list.AddDelay(&iplibrary.IPItem{
Id: uint64(i),
IPFrom: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
IPTo: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
ExpiredAt: time.Now().Unix() + 60,
})
}
list.Sort()
b.Log(len(list.ItemsMap()), "ip")
b.ResetTimer()
for i := 0; i < b.N; i++ {
var ip = fmt.Sprintf("%d.%d.%d.%d", rand.Int()%255, rand.Int()%255, rand.Int()%255, rand.Int()%255)
list.Add(&iplibrary.IPItem{
Type: "",
Id: uint64(i % 1_000_000),
IPFrom: iputils.ToBytes(ip),
IPTo: nil,
ExpiredAt: fasttime.Now().Unix() + 3600,
EventLevel: "",
})
}
}
func BenchmarkIPList_Contains(b *testing.B) { func BenchmarkIPList_Contains(b *testing.B) {
runtime.GOMAXPROCS(1) runtime.GOMAXPROCS(1)
var list = NewIPList() var list = iplibrary.NewIPList()
for i := 1; i < 200_000; i++ { for i := 1; i < 1_000_000; i++ {
list.AddDelay(&IPItem{ var item = &iplibrary.IPItem{
Id: uint64(i), Id: uint64(i),
IPFrom: utils.IP2Long(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"), IPFrom: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
IPTo: utils.IP2Long(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
ExpiredAt: time.Now().Unix() + 60, ExpiredAt: time.Now().Unix() + 60,
}) }
if i%100 == 0 {
item.IPTo = iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1")
}
list.Add(item)
} }
list.Sort()
b.Log(len(list.itemsMap), "ip") //b.Log(len(list.ItemsMap()), "ip")
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { b.RunParallel(func(pb *testing.PB) {
_ = list.Contains(utils.IP2Long("192.168.1.100")) for pb.Next() {
} _ = list.Contains(iputils.ToBytes(testutils.RandIP()))
}
})
}
func BenchmarkIPList_Sort(b *testing.B) {
var list = iplibrary.NewIPList()
for i := 0; i < 1_000_000; i++ {
var item = &iplibrary.IPItem{
Id: uint64(i),
IPFrom: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
ExpiredAt: time.Now().Unix() + 60,
}
if i%100 == 0 {
item.IPTo = iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1")
}
list.AddDelay(item)
}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
list.Sort()
}
})
} }

View File

@@ -3,8 +3,9 @@
package iplibrary package iplibrary
import ( import (
"encoding/hex"
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
) )
@@ -24,25 +25,25 @@ func AllowIP(ip string, serverId int64) (canGoNext bool, inAllowList bool, expir
} }
} }
var ipLong = utils.IP2Long(ip) var ipBytes = iputils.ToBytes(ip)
if ipLong == 0 { if IsZero(ipBytes) {
return false, false, 0 return false, false, 0
} }
// check white lists // check white lists
if GlobalWhiteIPList.Contains(ipLong) { if GlobalWhiteIPList.Contains(ipBytes) {
return true, true, 0 return true, true, 0
} }
if serverId > 0 { if serverId > 0 {
var list = SharedServerListManager.FindWhiteList(serverId, false) var list = SharedServerListManager.FindWhiteList(serverId, false)
if list != nil && list.Contains(ipLong) { if list != nil && list.Contains(ipBytes) {
return true, true, 0 return true, true, 0
} }
} }
// check black lists // check black lists
expiresAt, ok := GlobalBlackIPList.ContainsExpires(ipLong) expiresAt, ok := GlobalBlackIPList.ContainsExpires(ipBytes)
if ok { if ok {
return false, false, expiresAt return false, false, expiresAt
} }
@@ -50,7 +51,7 @@ func AllowIP(ip string, serverId int64) (canGoNext bool, inAllowList bool, expir
if serverId > 0 { if serverId > 0 {
var list = SharedServerListManager.FindBlackList(serverId, false) var list = SharedServerListManager.FindBlackList(serverId, false)
if list != nil { if list != nil {
expiresAt, ok = list.ContainsExpires(ipLong) expiresAt, ok = list.ContainsExpires(ipBytes)
if ok { if ok {
return false, false, expiresAt return false, false, expiresAt
} }
@@ -62,13 +63,13 @@ func AllowIP(ip string, serverId int64) (canGoNext bool, inAllowList bool, expir
// IsInWhiteList 检查IP是否在白名单中 // IsInWhiteList 检查IP是否在白名单中
func IsInWhiteList(ip string) bool { func IsInWhiteList(ip string) bool {
var ipLong = utils.IP2Long(ip) var ipBytes = iputils.ToBytes(ip)
if ipLong == 0 { if IsZero(ipBytes) {
return false return false
} }
// check white lists // check white lists
return GlobalWhiteIPList.Contains(ipLong) return GlobalWhiteIPList.Contains(ipBytes)
} }
// AllowIPStrings 检查一组IP是否被允许访问 // AllowIPStrings 检查一组IP是否被允许访问
@@ -84,3 +85,15 @@ func AllowIPStrings(ipStrings []string, serverId int64) bool {
} }
return true return true
} }
func IsZero(ipBytes []byte) bool {
return len(ipBytes) == 0
}
func ToHex(b []byte) string {
if len(b) == 0 {
return ""
}
return hex.EncodeToString(b)
}

View File

@@ -14,7 +14,7 @@ func TestIPIsAllowed(t *testing.T) {
} }
var manager = NewIPListManager() var manager = NewIPListManager()
manager.init() manager.Init()
var before = time.Now() var before = time.Now()
defer func() { defer func() {

View File

@@ -1,6 +1,7 @@
package iplibrary package iplibrary
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const" teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
@@ -8,10 +9,13 @@ import (
"github.com/TeaOSLab/EdgeNode/internal/goman" "github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc" "github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeNode/internal/trackers"
"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
"github.com/TeaOSLab/EdgeNode/internal/waf" "github.com/TeaOSLab/EdgeNode/internal/waf"
"github.com/TeaOSLab/EdgeNode/internal/zero" "github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/types"
"os"
"sync" "sync"
"time" "time"
) )
@@ -35,9 +39,9 @@ func init() {
var ticker = time.NewTicker(24 * time.Hour) var ticker = time.NewTicker(24 * time.Hour)
goman.New(func() { goman.New(func() {
for range ticker.C { idles.RunTicker(ticker, func() {
SharedIPListManager.DeleteExpiredItems() SharedIPListManager.DeleteExpiredItems()
} })
}) })
} }
@@ -45,13 +49,13 @@ func init() {
type IPListManager struct { type IPListManager struct {
ticker *time.Ticker ticker *time.Ticker
db *IPListDB db IPListDB
lastVersion int64 lastVersion int64
fetchPageSize int64 fetchPageSize int64
listMap map[int64]*IPList listMap map[int64]*IPList
locker sync.Mutex mu sync.RWMutex
isFirstTime bool isFirstTime bool
} }
@@ -65,10 +69,10 @@ func NewIPListManager() *IPListManager {
} }
func (this *IPListManager) Start() { func (this *IPListManager) Start() {
this.init() this.Init()
// 第一次读取 // 第一次读取
err := this.loop() err := this.Loop()
if err != nil { if err != nil {
remotelogs.ErrorObject("IP_LIST_MANAGER", err) remotelogs.ErrorObject("IP_LIST_MANAGER", err)
} }
@@ -83,7 +87,7 @@ func (this *IPListManager) Start() {
case <-this.ticker.C: case <-this.ticker.C:
case <-IPListUpdateNotify: case <-IPListUpdateNotify:
} }
err := this.loop() err = this.Loop()
if err != nil { if err != nil {
countErrors++ countErrors++
@@ -108,9 +112,20 @@ func (this *IPListManager) Stop() {
} }
} }
func (this *IPListManager) init() { func (this *IPListManager) Init() {
// 从数据库中当中读取数据 // 从数据库中当中读取数据
db, err := NewIPListDB() // 检查sqlite文件是否存在以便决定使用sqlite还是kv
var sqlitePath = Tea.Root + "/data/ip_list.db"
_, sqliteErr := os.Stat(sqlitePath)
var db IPListDB
var err error
if sqliteErr == nil || !teaconst.EnableKVCacheStore {
db, err = NewSQLiteIPList()
} else {
db, err = NewKVIPList()
}
if err != nil { if err != nil {
remotelogs.Error("IP_LIST_MANAGER", "create ip list local database failed: "+err.Error()) remotelogs.Error("IP_LIST_MANAGER", "create ip list local database failed: "+err.Error())
} else { } else {
@@ -120,22 +135,28 @@ func (this *IPListManager) init() {
_ = db.DeleteExpiredItems() _ = db.DeleteExpiredItems()
// 本地数据库中最大版本号 // 本地数据库中最大版本号
this.lastVersion = db.ReadMaxVersion() this.lastVersion, err = db.ReadMaxVersion()
if err != nil {
remotelogs.Error("IP_LIST_MANAGER", "find max version failed: "+err.Error())
this.lastVersion = 0
}
remotelogs.Println("IP_LIST_MANAGER", "starting from '"+db.Name()+"' version '"+types.String(this.lastVersion)+"' ...")
// 从本地数据库中加载 // 从本地数据库中加载
var offset int64 = 0 var offset int64 = 0
var size int64 = 2_000 var size int64 = 2_000
var tr = trackers.Begin("IP_LIST_MANAGER:load")
defer tr.End()
for { for {
items, err := db.ReadItems(offset, size) items, goNext, readErr := db.ReadItems(offset, size)
var l = len(items) var l = len(items)
if err != nil { if readErr != nil {
remotelogs.Error("IP_LIST_MANAGER", "read ip list from local database failed: "+err.Error()) remotelogs.Error("IP_LIST_MANAGER", "read ip list from local database failed: "+readErr.Error())
} else { } else {
if l == 0 {
break
}
this.processItems(items, false) this.processItems(items, false)
if int64(l) < size { if !goNext {
break break
} }
} }
@@ -144,7 +165,7 @@ func (this *IPListManager) init() {
} }
} }
func (this *IPListManager) loop() error { func (this *IPListManager) Loop() error {
// 是否同步IP名单 // 是否同步IP名单
nodeConfig, _ := nodeconfigs.SharedNodeConfig() nodeConfig, _ := nodeconfigs.SharedNodeConfig()
if nodeConfig != nil && !nodeConfig.EnableIPLists { if nodeConfig != nil && !nodeConfig.EnableIPLists {
@@ -213,9 +234,10 @@ func (this *IPListManager) fetch() (hasNext bool, err error) {
} }
func (this *IPListManager) FindList(listId int64) *IPList { func (this *IPListManager) FindList(listId int64) *IPList {
this.locker.Lock() this.mu.RLock()
var list = this.listMap[listId] var list = this.listMap[listId]
this.locker.Unlock() this.mu.RUnlock()
return list return list
} }
@@ -225,6 +247,10 @@ func (this *IPListManager) DeleteExpiredItems() {
} }
} }
func (this *IPListManager) ListMap() map[int64]*IPList {
return this.listMap
}
// 处理IP条目 // 处理IP条目
func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) { func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
var changedLists = map[*IPList]zero.Zero{} var changedLists = map[*IPList]zero.Zero{}
@@ -251,15 +277,15 @@ func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
list = GlobalWhiteIPList list = GlobalWhiteIPList
} }
} else { // 其他List } else { // 其他List
this.locker.Lock() this.mu.Lock()
list = this.listMap[item.ListId] list = this.listMap[item.ListId]
this.locker.Unlock() this.mu.Unlock()
} }
if list == nil { if list == nil {
list = NewIPList() list = NewIPList()
this.locker.Lock() this.mu.Lock()
this.listMap[item.ListId] = list this.listMap[item.ListId] = list
this.locker.Unlock() this.mu.Unlock()
} }
changedLists[list] = zero.New() changedLists[list] = zero.New()
@@ -281,8 +307,8 @@ func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
list.AddDelay(&IPItem{ list.AddDelay(&IPItem{
Id: uint64(item.Id), Id: uint64(item.Id),
Type: item.Type, Type: item.Type,
IPFrom: utils.IP2Long(item.IpFrom), IPFrom: iputils.ToBytes(item.IpFrom),
IPTo: utils.IP2Long(item.IpTo), IPTo: iputils.ToBytes(item.IpTo),
ExpiredAt: item.ExpiredAt, ExpiredAt: item.ExpiredAt,
EventLevel: item.EventLevel, EventLevel: item.EventLevel,
}) })
@@ -294,8 +320,10 @@ func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
} }
} }
for changedList := range changedLists { if len(changedLists) > 0 {
changedList.Sort() for changedList := range changedLists {
changedList.Sort()
}
} }
if fromRemote { if fromRemote {
@@ -308,9 +336,14 @@ func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
// 调试IP信息 // 调试IP信息
func (this *IPListManager) debugItem(item *pb.IPItem) { func (this *IPListManager) debugItem(item *pb.IPItem) {
var ipRange = item.IpFrom
if len(item.IpTo) > 0 {
ipRange += " - " + item.IpTo
}
if item.IsDeleted { if item.IsDeleted {
remotelogs.Debug("IP_ITEM_DEBUG", "delete '"+item.IpFrom+"'") remotelogs.Debug("IP_ITEM_DEBUG", "delete '"+ipRange+"'")
} else { } else {
remotelogs.Debug("IP_ITEM_DEBUG", "add '"+item.IpFrom+"'") remotelogs.Debug("IP_ITEM_DEBUG", "add '"+ipRange+"'")
} }
} }

View File

@@ -1,7 +1,8 @@
package iplibrary package iplibrary_test
import ( import (
"github.com/TeaOSLab/EdgeNode/internal/utils" "github.com/TeaOSLab/EdgeCommon/pkg/iputils"
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils" "github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
"testing" "testing"
@@ -13,11 +14,11 @@ func TestIPListManager_init(t *testing.T) {
return return
} }
var manager = NewIPListManager() var manager = iplibrary.NewIPListManager()
manager.init() manager.Init()
t.Log(manager.listMap) t.Log(manager.ListMap())
t.Log(SharedServerListManager.blackMap) t.Log(iplibrary.SharedServerListManager.BlackMap())
logs.PrintAsJSON(GlobalBlackIPList.sortedItems, t) logs.PrintAsJSON(iplibrary.GlobalBlackIPList.SortedRangeItems(), t)
} }
func TestIPListManager_check(t *testing.T) { func TestIPListManager_check(t *testing.T) {
@@ -25,15 +26,15 @@ func TestIPListManager_check(t *testing.T) {
return return
} }
var manager = NewIPListManager() var manager = iplibrary.NewIPListManager()
manager.init() manager.Init()
var before = time.Now() var before = time.Now()
defer func() { defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms") t.Log(time.Since(before).Seconds()*1000, "ms")
}() }()
t.Log(SharedServerListManager.FindBlackList(23, true).Contains(utils.IP2Long("127.0.0.2"))) t.Log(iplibrary.SharedServerListManager.FindBlackList(23, true).Contains(iputils.ToBytes("127.0.0.2")))
t.Log(GlobalBlackIPList.Contains(utils.IP2Long("127.0.0.6"))) t.Log(iplibrary.GlobalBlackIPList.Contains(iputils.ToBytes("127.0.0.6")))
} }
func TestIPListManager_loop(t *testing.T) { func TestIPListManager_loop(t *testing.T) {
@@ -41,9 +42,9 @@ func TestIPListManager_loop(t *testing.T) {
return return
} }
var manager = NewIPListManager() var manager = iplibrary.NewIPListManager()
manager.Start() manager.Start()
err := manager.loop() err := manager.Loop()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -59,3 +59,7 @@ func (this *ServerListManager) FindBlackList(serverId int64, autoCreate bool) *I
return nil return nil
} }
func (this *ServerListManager) BlackMap() map[int64]*IPList {
return this.blackMap
}

View File

@@ -26,8 +26,8 @@ func init() {
type Manager struct { type Manager struct {
isQuiting bool isQuiting bool
taskMap map[int64]*Task // itemId => *Task taskMap map[int64]Task // itemId => Task
categoryTaskMap map[string][]*Task // category => []*Task categoryTaskMap map[string][]Task // category => []Task
locker sync.RWMutex locker sync.RWMutex
hasHTTPMetrics bool hasHTTPMetrics bool
@@ -37,8 +37,8 @@ type Manager struct {
func NewManager() *Manager { func NewManager() *Manager {
return &Manager{ return &Manager{
taskMap: map[int64]*Task{}, taskMap: map[int64]Task{},
categoryTaskMap: map[string][]*Task{}, categoryTaskMap: map[string][]Task{},
} }
} }
@@ -64,11 +64,20 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
if err != nil { if err != nil {
remotelogs.Error("METRIC_MANAGER", "stop task '"+strconv.FormatInt(itemId, 10)+"' failed: "+err.Error()) remotelogs.Error("METRIC_MANAGER", "stop task '"+strconv.FormatInt(itemId, 10)+"' failed: "+err.Error())
} }
// deleted
if newItem != nil && !newItem.IsOn {
deleteErr := task.Delete()
if deleteErr != nil {
remotelogs.Error("METRIC_MANAGER", "delete task '"+strconv.FormatInt(itemId, 10)+"' failed: "+err.Error())
}
}
delete(this.taskMap, itemId) delete(this.taskMap, itemId)
} else { // 更新已存在的 } else { // 更新已存在的
if newItem.Version != task.item.Version { if newItem.Version != task.Item().Version {
remotelogs.Println("METRIC_MANAGER", "update task '"+strconv.FormatInt(itemId, 10)+"'") remotelogs.Println("METRIC_MANAGER", "update task '"+strconv.FormatInt(itemId, 10)+"'")
task.item = newItem task.SetItem(newItem)
} }
} }
} }
@@ -81,7 +90,14 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
_, ok := this.taskMap[newItem.Id] _, ok := this.taskMap[newItem.Id]
if !ok { if !ok {
remotelogs.Println("METRIC_MANAGER", "start task '"+strconv.FormatInt(newItem.Id, 10)+"'") remotelogs.Println("METRIC_MANAGER", "start task '"+strconv.FormatInt(newItem.Id, 10)+"'")
task := NewTask(newItem) var task Task
if CheckSQLiteDB(newItem.Id) || !teaconst.EnableKVCacheStore {
task = NewSQLiteTask(newItem)
} else {
task = NewKVTask(newItem)
}
err := task.Init() err := task.Init()
if err != nil { if err != nil {
remotelogs.Error("METRIC_MANAGER", "initialized task failed: "+err.Error()) remotelogs.Error("METRIC_MANAGER", "initialized task failed: "+err.Error())
@@ -100,13 +116,13 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
this.hasHTTPMetrics = false this.hasHTTPMetrics = false
this.hasTCPMetrics = false this.hasTCPMetrics = false
this.hasUDPMetrics = false this.hasUDPMetrics = false
this.categoryTaskMap = map[string][]*Task{} this.categoryTaskMap = map[string][]Task{}
for _, task := range this.taskMap { for _, task := range this.taskMap {
var tasks = this.categoryTaskMap[task.item.Category] var tasks = this.categoryTaskMap[task.Item().Category]
tasks = append(tasks, task) tasks = append(tasks, task)
this.categoryTaskMap[task.item.Category] = tasks this.categoryTaskMap[task.Item().Category] = tasks
switch task.item.Category { switch task.Item().Category {
case serverconfigs.MetricItemCategoryHTTP: case serverconfigs.MetricItemCategoryHTTP:
this.hasHTTPMetrics = true this.hasHTTPMetrics = true
case serverconfigs.MetricItemCategoryTCP: case serverconfigs.MetricItemCategoryTCP:
@@ -144,6 +160,10 @@ func (this *Manager) HasUDPMetrics() bool {
return this.hasUDPMetrics return this.hasUDPMetrics
} }
func (this *Manager) TaskMap() map[int64]Task {
return this.taskMap
}
// Quit 退出管理器 // Quit 退出管理器
func (this *Manager) Quit() { func (this *Manager) Quit() {
this.isQuiting = true this.isQuiting = true
@@ -154,6 +174,6 @@ func (this *Manager) Quit() {
for _, task := range this.taskMap { for _, task := range this.taskMap {
_ = task.Stop() _ = task.Stop()
} }
this.taskMap = map[int64]*Task{} this.taskMap = map[int64]Task{}
this.locker.Unlock() this.locker.Unlock()
} }

View File

@@ -1,18 +1,19 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics package metrics_test
import ( import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/metrics"
"testing" "testing"
) )
func TestNewManager(t *testing.T) { func TestNewManager(t *testing.T) {
var manager = NewManager() var manager = metrics.NewManager()
{ {
manager.Update([]*serverconfigs.MetricItemConfig{}) manager.Update([]*serverconfigs.MetricItemConfig{})
for _, task := range manager.taskMap { for _, task := range manager.TaskMap() {
t.Log(task.item.Id) t.Log(task.Item().Id)
} }
} }
{ {
@@ -28,8 +29,8 @@ func TestNewManager(t *testing.T) {
Id: 3, Id: 3,
}, },
}) })
for _, task := range manager.taskMap { for _, task := range manager.TaskMap() {
t.Log("task:", task.item.Id) t.Log("task:", task.Item().Id)
} }
} }
@@ -43,8 +44,8 @@ func TestNewManager(t *testing.T) {
Id: 2, Id: 2,
}, },
}) })
for _, task := range manager.taskMap { for _, task := range manager.TaskMap() {
t.Log("task:", task.item.Id) t.Log("task:", task.Item().Id)
} }
} }
@@ -56,8 +57,8 @@ func TestNewManager(t *testing.T) {
Version: 1, Version: 1,
}, },
}) })
for _, task := range manager.taskMap { for _, task := range manager.TaskMap() {
t.Log("task:", task.item.Id) t.Log("task:", task.Item().Id)
} }
} }
} }

View File

@@ -3,20 +3,120 @@
package metrics package metrics
import ( import (
"bytes"
"encoding/binary"
"errors"
byteutils "github.com/TeaOSLab/EdgeNode/internal/utils/byte"
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv" "github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
"strconv" "strconv"
"strings" "strings"
) )
type Stat struct { type Stat struct {
ServerId int64 ServerId int64 `json:"serverId"`
Keys []string Keys []string `json:"keys"`
Hash string Hash string `json:"hash"`
Value int64 Value int64 `json:"value"`
Time string Time string `json:"time"`
} }
func SumStat(serverId int64, keys []string, time string, version int32, itemId int64) string { func UniqueKey(serverId int64, keys []string, time string, version int32, itemId int64) string {
var keysData = strings.Join(keys, "$EDGE$") var keysData = strings.Join(keys, "$EDGE$")
return strconv.FormatUint(fnv.HashString(strconv.FormatInt(serverId, 10)+"@"+keysData+"@"+time+"@"+strconv.Itoa(int(version))+"@"+strconv.FormatInt(itemId, 10)), 10) return strconv.FormatUint(fnv.HashString(strconv.FormatInt(serverId, 10)+"@"+keysData+"@"+time+"@"+strconv.Itoa(int(version))+"@"+strconv.FormatInt(itemId, 10)), 10)
} }
func (this *Stat) UniqueKey(version int32, itemId int64) string {
return UniqueKey(this.ServerId, this.Keys, this.Time, version, itemId)
}
func (this *Stat) FullKey(version int32, itemId int64) string {
return this.Time + "_" + string(int32ToBigEndian(version)) + this.UniqueKey(version, itemId)
}
func (this *Stat) EncodeValueKey(version int32) string {
if this.Value < 0 {
this.Value = 0
}
return string(byteutils.Concat([]byte(this.Time), []byte{'_'}, int32ToBigEndian(version), int64ToBigEndian(this.ServerId), int64ToBigEndian(this.Value), []byte(this.Hash)))
}
func (this *Stat) EncodeSumKey(version int32) string {
return string(byteutils.Concat([]byte(this.Time), []byte{'_'}, int32ToBigEndian(version), int64ToBigEndian(this.ServerId)))
}
func DecodeValueKey(valueKey string) (serverId int64, timeString string, version int32, value int64, hash string, err error) {
var b = []byte(valueKey)
var timeIndex = bytes.Index(b, []byte{'_'})
if timeIndex < 0 {
return
}
timeString = string(b[:timeIndex])
b = b[timeIndex+1:]
if len(b) < 20+1 {
err = errors.New("invalid value key")
return
}
version = int32(binary.BigEndian.Uint32(b[0:4]))
serverId = int64(binary.BigEndian.Uint64(b[4:12]))
value = int64(binary.BigEndian.Uint64(b[12:20]))
hash = string(b[20:])
return
}
func DecodeSumKey(sumKey string) (serverId int64, timeString string, version int32, err error) {
var b = []byte(sumKey)
var timeIndex = bytes.Index(b, []byte{'_'})
if timeIndex < 0 {
return
}
timeString = string(b[:timeIndex])
b = b[timeIndex+1:]
if len(b) < 12 {
err = errors.New("invalid sum key")
return
}
version = int32(binary.BigEndian.Uint32(b[:4]))
serverId = int64(binary.BigEndian.Uint64(b[4:12]))
return
}
func EncodeSumValue(count uint64, total uint64) []byte {
var result [16]byte
binary.BigEndian.PutUint64(result[:8], count)
binary.BigEndian.PutUint64(result[8:], total)
return result[:]
}
func DecodeSumValue(data []byte) (count uint64, total uint64) {
if len(data) != 16 {
return
}
count = binary.BigEndian.Uint64(data[:8])
total = binary.BigEndian.Uint64(data[8:])
return
}
func int64ToBigEndian(i int64) []byte {
if i < 0 {
i = 0
}
var b = make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(i))
return b
}
func int32ToBigEndian(i int32) []byte {
if i < 0 {
i = 0
}
var b = make([]byte, 4)
binary.BigEndian.PutUint32(b, uint32(i))
return b
}

View File

@@ -0,0 +1,69 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package metrics_test
import (
"github.com/TeaOSLab/EdgeNode/internal/metrics"
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestStat_EncodeValueKey(t *testing.T) {
var a = assert.NewAssertion(t)
var stat = &metrics.Stat{
ServerId: 1,
Keys: []string{"${remoteAddr}"},
Hash: "123456",
Value: 123,
Time: "20240101",
}
var valueKey = stat.EncodeValueKey(100)
t.Log(valueKey)
serverId, timeString, version, value, hash, err := metrics.DecodeValueKey(valueKey)
if err != nil {
t.Fatal(err)
}
t.Log(serverId, timeString, value, version, hash)
a.IsTrue(serverId == 1)
a.IsTrue(timeString == "20240101")
a.IsTrue(value == 123)
a.IsTrue(version == 100)
a.IsTrue(hash == "123456")
}
func TestStat_EncodeSumKey(t *testing.T) {
var a = assert.NewAssertion(t)
var stat = &metrics.Stat{
ServerId: 1,
Keys: []string{"${remoteAddr}"},
Hash: "123456",
Value: 123,
Time: "20240101",
}
var sumKey = stat.EncodeSumKey(100)
t.Log(sumKey)
serverId, timeString, version, err := metrics.DecodeSumKey(sumKey)
if err != nil {
t.Fatal(err)
}
t.Log(serverId, timeString, version)
a.IsTrue(serverId == 1)
a.IsTrue(timeString == "20240101")
a.IsTrue(version == 100)
}
func TestStat_EncodeSumValue(t *testing.T) {
var a = assert.NewAssertion(t)
var b = metrics.EncodeSumValue(123, 456)
t.Log(b)
count, sum := metrics.DecodeSumValue(b)
a.IsTrue(count == 123)
a.IsTrue(sum == 456)
}

View File

@@ -14,7 +14,7 @@ func BenchmarkSumStat(b *testing.B) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
for pb.Next() { for pb.Next() {
metrics.SumStat(1, []string{"1.2.3.4"}, timeutil.Format("Ymd"), 1, 1) metrics.UniqueKey(1, []string{"1.2.3.4"}, timeutil.Format("Ymd"), 1, 1)
} }
}) })
} }

View File

@@ -1,544 +1,21 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package metrics package metrics
import ( import (
"encoding/json"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/trackers"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/types"
"os"
"strconv"
"sync"
"sync/atomic"
"time" "time"
) )
const MaxQueueSize = 256 // TODO 可以配置,可以在单个任务里配置 type Task interface {
Init() error
// Task 单个指标任务 Item() *serverconfigs.MetricItemConfig
// 数据库存储: SetItem(item *serverconfigs.MetricItemConfig)
// Add(obj MetricInterface)
// data/ InsertStat(stat *Stat) error
// metric.$ID.db Upload(pauseDuration time.Duration) error
// stats Start() error
// id, keys, value, time, serverId, hash Stop() error
// 原理: Delete() error
// 添加或者有变更时 isUploaded = false CleanExpired() error
// 上传时检查 isUploaded 状态
// 只上传每个服务中排序最前面的 N 个数据
type Task struct {
item *serverconfigs.MetricItemConfig
isLoaded bool
db *dbs.DB
statTableName string
isStopped bool
cleanTicker *utils.Ticker
uploadTicker *utils.Ticker
cleanVersion int32
insertStatStmt *dbs.Stmt
deleteByVersionStmt *dbs.Stmt
deleteByExpiresTimeStmt *dbs.Stmt
selectTopStmt *dbs.Stmt
sumStmt *dbs.Stmt
serverIdMap map[int64]zero.Zero // 所有的服务Ids
timeMap map[string]zero.Zero // time => bool
serverIdMapLocker sync.Mutex
statsMap map[string]*Stat // 待写入队列hash => *Stat
statsLocker sync.RWMutex
statsTicker *utils.Ticker
}
// NewTask 获取新任务
func NewTask(item *serverconfigs.MetricItemConfig) *Task {
return &Task{
item: item,
serverIdMap: map[int64]zero.Zero{},
timeMap: map[string]zero.Zero{},
statsMap: map[string]*Stat{},
}
}
// Init 初始化
func (this *Task) Init() error {
this.statTableName = "stats"
// 检查目录是否存在
var dir = Tea.Root + "/data"
_, err := os.Stat(dir)
if err != nil {
err = os.MkdirAll(dir, 0777)
if err != nil {
return err
}
remotelogs.Println("METRIC", "create data dir '"+dir+"'")
}
var path = dir + "/metric." + types.String(this.item.Id) + ".db"
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
if err != nil {
return err
}
db.SetMaxOpenConns(1)
this.db = db
// 恢复数据库
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
if len(recoverEnv) > 0 {
for _, indexName := range []string{"serverId", "hash"} {
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
}
}
if teaconst.EnableDBStat {
this.db.EnableStat(true)
}
//创建统计表
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.statTableName + `" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"hash" varchar(32),
"keys" varchar(1024),
"value" real DEFAULT 0,
"time" varchar(32),
"serverId" integer DEFAULT 0,
"version" integer DEFAULT 0,
"isUploaded" integer DEFAULT 0
);
CREATE INDEX IF NOT EXISTS "serverId"
ON "` + this.statTableName + `" (
"serverId" ASC,
"version" ASC
);
CREATE UNIQUE INDEX IF NOT EXISTS "hash"
ON "` + this.statTableName + `" (
"hash" ASC
);`)
if err != nil {
return err
}
// insert stat stmt
this.insertStatStmt, err = db.Prepare(`INSERT INTO "stats" ("serverId", "hash", "keys", "value", "time", "version", "isUploaded") VALUES (?, ?, ?, ?, ?, ?, 0) ON CONFLICT("hash") DO UPDATE SET "value"="value"+?, "isUploaded"=0`)
if err != nil {
return err
}
// delete by version
this.deleteByVersionStmt, err = db.Prepare(`DELETE FROM "` + this.statTableName + `" WHERE "version"<?`)
if err != nil {
return err
}
// delete by expires time
this.deleteByExpiresTimeStmt, err = db.Prepare(`DELETE FROM "` + this.statTableName + `" WHERE "time"<?`)
if err != nil {
return err
}
// select topN stmt
this.selectTopStmt, err = db.Prepare(`SELECT "id", "hash", "keys", "value", "isUploaded" FROM "` + this.statTableName + `" WHERE "serverId"=? AND "version"=? AND time=? ORDER BY "value" DESC LIMIT 20`)
if err != nil {
return err
}
// sum stmt
this.sumStmt, err = db.Prepare(`SELECT COUNT(*), IFNULL(SUM(value), 0) FROM "` + this.statTableName + `" WHERE "serverId"=? AND "version"=? AND time=?`)
if err != nil {
return err
}
// 所有的服务IDs
err = this.loadServerIdMap()
if err != nil {
return err
}
this.isLoaded = true
return nil
}
// Start 启动任务
func (this *Task) Start() error {
// 读取数据
this.statsTicker = utils.NewTicker(1 * time.Minute)
goman.New(func() {
for this.statsTicker.Next() {
var tr = trackers.Begin("[METRIC]DUMP_STATS_TO_LOCAL_DATABASE")
this.statsLocker.Lock()
var statsMap = this.statsMap
this.statsMap = map[string]*Stat{}
this.statsLocker.Unlock()
for _, stat := range statsMap {
err := this.InsertStat(stat)
if err != nil {
remotelogs.Error("METRIC", "insert stat failed: "+err.Error())
}
}
tr.End()
}
})
// 清理
this.cleanTicker = utils.NewTicker(24 * time.Hour)
goman.New(func() {
for this.cleanTicker.Next() {
var tr = trackers.Begin("[METRIC]CLEAN_EXPIRED")
err := this.CleanExpired()
tr.End()
if err != nil {
remotelogs.Error("METRIC", "clean expired stats failed: "+err.Error())
}
}
})
// 上传
this.uploadTicker = utils.NewTicker(this.item.UploadDuration())
goman.New(func() {
for this.uploadTicker.Next() {
var tr = trackers.Begin("[METRIC]UPLOAD_STATS")
err := this.Upload(1 * time.Second)
tr.End()
if err != nil && !rpc.IsConnError(err) {
remotelogs.Error("METRIC", "upload stats failed: "+err.Error())
}
}
})
return nil
}
// Add 添加数据
func (this *Task) Add(obj MetricInterface) {
if this.isStopped || !this.isLoaded {
return
}
var keys = []string{}
for _, key := range this.item.Keys {
var k = obj.MetricKey(key)
// 忽略499状态
if key == "${status}" && k == "499" {
return
}
keys = append(keys, k)
}
v, ok := obj.MetricValue(this.item.Value)
if !ok {
return
}
var hash = SumStat(obj.MetricServerId(), keys, this.item.CurrentTime(), this.item.Version, this.item.Id)
var countItems int
this.statsLocker.RLock()
oldStat, ok := this.statsMap[hash]
if !ok {
countItems = len(this.statsMap)
}
this.statsLocker.RUnlock()
if ok {
atomic.AddInt64(&oldStat.Value, 1)
} else {
// 防止过载
if countItems < MaxQueueSize {
this.statsLocker.Lock()
this.statsMap[hash] = &Stat{
ServerId: obj.MetricServerId(),
Keys: keys,
Value: v,
Time: this.item.CurrentTime(),
Hash: hash,
}
this.statsLocker.Unlock()
}
}
}
// Stop 停止任务
func (this *Task) Stop() error {
this.isStopped = true
if this.cleanTicker != nil {
this.cleanTicker.Stop()
}
if this.uploadTicker != nil {
this.uploadTicker.Stop()
}
if this.statsTicker != nil {
this.statsTicker.Stop()
}
_ = this.insertStatStmt.Close()
_ = this.deleteByVersionStmt.Close()
_ = this.deleteByExpiresTimeStmt.Close()
_ = this.selectTopStmt.Close()
_ = this.sumStmt.Close()
if this.db != nil {
_ = this.db.Close()
}
return nil
}
// InsertStat 写入数据
func (this *Task) InsertStat(stat *Stat) error {
if this.isStopped {
return nil
}
if stat == nil {
return nil
}
this.serverIdMapLocker.Lock()
this.serverIdMap[stat.ServerId] = zero.New()
this.timeMap[stat.Time] = zero.New()
this.serverIdMapLocker.Unlock()
keyData, err := json.Marshal(stat.Keys)
if err != nil {
return err
}
_, err = this.insertStatStmt.Exec(stat.ServerId, stat.Hash, keyData, stat.Value, stat.Time, this.item.Version, stat.Value)
if err != nil {
return err
}
return nil
}
// CleanExpired 清理数据
func (this *Task) CleanExpired() error {
if this.isStopped {
return nil
}
// 清除低版本数据
if this.cleanVersion < this.item.Version {
_, err := this.deleteByVersionStmt.Exec(this.item.Version)
if err != nil {
return err
}
this.cleanVersion = this.item.Version
}
// 清除过期的数据
_, err := this.deleteByExpiresTimeStmt.Exec(this.item.LocalExpiresTime())
if err != nil {
return err
}
return nil
}
// Upload 上传数据
func (this *Task) Upload(pauseDuration time.Duration) error {
if this.isStopped {
return nil
}
this.serverIdMapLocker.Lock()
// 服务IDs
var serverIds []int64
for serverId := range this.serverIdMap {
serverIds = append(serverIds, serverId)
}
this.serverIdMap = map[int64]zero.Zero{} // 清空数据
// 时间
var times = []string{}
for t := range this.timeMap {
times = append(times, t)
}
this.timeMap = map[string]zero.Zero{} // 清空数据
this.serverIdMapLocker.Unlock()
rpcClient, err := rpc.SharedRPC()
if err != nil {
return err
}
for _, serverId := range serverIds {
for _, currentTime := range times {
idStrings, err := func(serverId int64, currentTime string) (ids []string, err error) {
var t = trackers.Begin("[METRIC]SELECT_TOP_STMT")
rows, err := this.selectTopStmt.Query(serverId, this.item.Version, currentTime)
t.End()
if err != nil {
return nil, err
}
var isClosed bool
defer func() {
if isClosed {
return
}
_ = rows.Close()
}()
var pbStats []*pb.UploadingMetricStat
for rows.Next() {
var pbStat = &pb.UploadingMetricStat{}
// "id", "hash", "keys", "value", "isUploaded"
var isUploaded int
var keysData []byte
err = rows.Scan(&pbStat.Id, &pbStat.Hash, &keysData, &pbStat.Value, &isUploaded)
if err != nil {
return nil, err
}
// TODO 先不判断是否已经上传需要改造API进行配合
/**if isUploaded == 1 {
continue
}**/
if len(keysData) > 0 {
err = json.Unmarshal(keysData, &pbStat.Keys)
if err != nil {
return nil, err
}
}
pbStats = append(pbStats, pbStat)
ids = append(ids, strconv.FormatInt(pbStat.Id, 10))
}
// 提前关闭
_ = rows.Close()
isClosed = true
// 上传
if len(pbStats) > 0 {
// 计算总和
count, total, err := this.sum(serverId, currentTime)
if err != nil {
return nil, err
}
_, err = rpcClient.MetricStatRPC.UploadMetricStats(rpcClient.Context(), &pb.UploadMetricStatsRequest{
MetricStats: pbStats,
Time: currentTime,
ServerId: serverId,
ItemId: this.item.Id,
Version: this.item.Version,
Count: count,
Total: float32(total),
})
if err != nil {
return nil, err
}
}
return
}(serverId, currentTime)
if err != nil {
return err
}
if len(idStrings) > 0 {
// 设置为已上传
// TODO 先不判断是否已经上传需要改造API进行配合
/**_, err = this.db.Exec(`UPDATE "` + this.statTableName + `" SET isUploaded=1 WHERE id IN (` + strings.Join(idStrings, ",") + `)`)
if err != nil {
return err
}**/
}
}
// 休息一下,防止短时间内上传数据过多
if pauseDuration > 0 {
time.Sleep(pauseDuration)
}
}
return nil
}
// 加载服务ID
func (this *Task) loadServerIdMap() error {
{
rows, err := this.db.Query(`SELECT DISTINCT "serverId" FROM `+this.statTableName+" WHERE version=?", this.item.Version)
if err != nil {
return err
}
defer func() {
_ = rows.Close()
}()
var serverId int64
for rows.Next() {
err = rows.Scan(&serverId)
if err != nil {
return err
}
this.serverIdMapLocker.Lock()
this.serverIdMap[serverId] = zero.New()
this.serverIdMapLocker.Unlock()
}
}
{
rows, err := this.db.Query(`SELECT DISTINCT "time" FROM `+this.statTableName+" WHERE version=?", this.item.Version)
if err != nil {
return err
}
defer func() {
_ = rows.Close()
}()
var timeString string
for rows.Next() {
err = rows.Scan(&timeString)
if err != nil {
return err
}
this.serverIdMapLocker.Lock()
this.timeMap[timeString] = zero.New()
this.serverIdMapLocker.Unlock()
}
}
return nil
}
// 计算数量和综合
func (this *Task) sum(serverId int64, time string) (count int64, total float64, err error) {
rows, err := this.sumStmt.Query(serverId, this.item.Version, time)
if err != nil {
return 0, 0, err
}
defer func() {
_ = rows.Close()
}()
if rows.Next() {
err = rows.Scan(&count, &total)
if err != nil {
return 0, 0, err
}
}
return
} }

View File

@@ -0,0 +1,75 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package metrics
import (
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"sync"
"sync/atomic"
)
type BaseTask struct {
itemConfig *serverconfigs.MetricItemConfig
isLoaded bool
isStopped bool
statsMap map[string]*Stat // 待写入队列hash => *Stat
statsLocker sync.RWMutex
}
// Add 添加数据
func (this *BaseTask) Add(obj MetricInterface) {
if this.isStopped || !this.isLoaded {
return
}
var keys = []string{}
for _, key := range this.itemConfig.Keys {
var k = obj.MetricKey(key)
// 忽略499状态
if key == "${status}" && k == "499" {
return
}
keys = append(keys, k)
}
v, ok := obj.MetricValue(this.itemConfig.Value)
if !ok {
return
}
var hash = UniqueKey(obj.MetricServerId(), keys, this.itemConfig.CurrentTime(), this.itemConfig.Version, this.itemConfig.Id)
var countItems int
this.statsLocker.RLock()
oldStat, ok := this.statsMap[hash]
if !ok {
countItems = len(this.statsMap)
}
this.statsLocker.RUnlock()
if ok {
atomic.AddInt64(&oldStat.Value, 1)
} else {
// 防止过载
if countItems < MaxQueueSize {
this.statsLocker.Lock()
this.statsMap[hash] = &Stat{
ServerId: obj.MetricServerId(),
Keys: keys,
Value: v,
Time: this.itemConfig.CurrentTime(),
Hash: hash,
}
this.statsLocker.Unlock()
}
}
}
func (this *BaseTask) Item() *serverconfigs.MetricItemConfig {
return this.itemConfig
}
func (this *BaseTask) SetItem(itemConfig *serverconfigs.MetricItemConfig) {
this.itemConfig = itemConfig
}

538
internal/metrics/task_kv.go Normal file
View File

@@ -0,0 +1,538 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package metrics
import (
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/trackers"
"github.com/TeaOSLab/EdgeNode/internal/utils"
byteutils "github.com/TeaOSLab/EdgeNode/internal/utils/byte"
"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
"github.com/TeaOSLab/EdgeNode/internal/utils/kvstore"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/cockroachdb/pebble"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/types"
"strings"
"sync"
"testing"
"time"
)
// TODO sumValues不用每次insertStat的时候都保存
// KVTask KV存储实现的任务管理
type KVTask struct {
BaseTask
itemsTable *kvstore.Table[*Stat] // hash => *Stat
valuesTable *kvstore.Table[[]byte] // time_version_serverId_value_hash => []byte(nil)
sumTable *kvstore.Table[[]byte] // time_version_serverId => [count][total]
serverTimeMap map[string]zero.Zero // 有变更的网站 serverId_time => Zero
serverIdMapLocker sync.Mutex
statsTicker *utils.Ticker
cleanTicker *time.Ticker
uploadTicker *utils.Ticker
valuesCacheMap map[string]int64 // hash => value
}
func NewKVTask(itemConfig *serverconfigs.MetricItemConfig) *KVTask {
return &KVTask{
BaseTask: BaseTask{
itemConfig: itemConfig,
statsMap: map[string]*Stat{},
},
serverTimeMap: map[string]zero.Zero{},
valuesCacheMap: map[string]int64{},
}
}
func (this *KVTask) Init() error {
store, err := kvstore.DefaultStore()
if err != nil {
return err
}
db, err := store.NewDB("metrics" + types.String(this.itemConfig.Id))
if err != nil {
return err
}
{
table, tableErr := kvstore.NewTable[*Stat]("items", &ItemEncoder[*Stat]{})
if tableErr != nil {
return tableErr
}
db.AddTable(table)
this.itemsTable = table
}
{
table, tableErr := kvstore.NewTable[[]byte]("values", kvstore.NewNilValueEncoder())
if tableErr != nil {
return tableErr
}
db.AddTable(table)
this.valuesTable = table
}
{
table, tableErr := kvstore.NewTable[[]byte]("sum_values", kvstore.NewBytesValueEncoder())
if tableErr != nil {
return tableErr
}
db.AddTable(table)
this.sumTable = table
}
// 所有的服务IDs
err = this.loadServerIdMap()
if err != nil {
return err
}
this.isLoaded = true
return nil
}
func (this *KVTask) InsertStat(stat *Stat) error {
if this.isStopped {
return nil
}
if stat == nil {
return nil
}
var version = this.itemConfig.Version
this.serverIdMapLocker.Lock()
this.serverTimeMap[types.String(stat.ServerId)+"_"+stat.Time] = zero.New()
this.serverIdMapLocker.Unlock()
if len(stat.Hash) == 0 {
stat.Hash = stat.UniqueKey(version, this.itemConfig.Id)
}
var isNew bool
var newValue = stat.Value
// insert or update
{
var statKey = stat.FullKey(version, this.itemConfig.Id)
oldStat, err := this.itemsTable.Get(statKey)
var oldValue int64
if err != nil {
if !kvstore.IsNotFound(err) {
return err
}
isNew = true
} else {
oldValue = oldStat.Value
// delete old value from valuesTable
err = this.valuesTable.Delete(oldStat.EncodeValueKey(version))
if err != nil {
return err
}
}
oldValue += stat.Value
stat.Value = oldValue
err = this.itemsTable.Set(statKey, stat)
if err != nil {
return err
}
// insert value into valuesTable
err = this.valuesTable.Insert(stat.EncodeValueKey(version), nil)
if err != nil {
return err
}
}
// sum
{
var sumKey = stat.EncodeSumKey(version)
sumResult, err := this.sumTable.Get(sumKey)
var count uint64
var total uint64
if err != nil {
if !kvstore.IsNotFound(err) {
return err
}
} else {
count, total = DecodeSumValue(sumResult)
}
if isNew {
count++
}
total += uint64(newValue)
err = this.sumTable.Set(sumKey, EncodeSumValue(count, total))
if err != nil {
return err
}
}
return nil
}
func (this *KVTask) Upload(pauseDuration time.Duration) error {
var uploadTr = trackers.Begin("METRIC:UPLOAD_STATS")
defer uploadTr.End()
if this.isStopped {
return nil
}
this.serverIdMapLocker.Lock()
// 服务IDs
var serverTimeMap = this.serverTimeMap
this.serverTimeMap = map[string]zero.Zero{} // 清空数据
this.serverIdMapLocker.Unlock()
if len(serverTimeMap) == 0 {
return nil
}
// 控制缓存map不要太长
if len(this.valuesCacheMap) > 4096 {
var newMap = map[string]int64{}
var countElements int
for k, v := range this.valuesCacheMap {
newMap[k] = v
countElements++
if countElements >= 2048 {
break
}
}
this.valuesCacheMap = newMap
}
// 开始上传
rpcClient, err := rpc.SharedRPC()
if err != nil {
return err
}
var totalCount int
for serverTime := range serverTimeMap {
count, uploadErr := func(serverTime string) (int, error) {
serverIdString, timeString, found := strings.Cut(serverTime, "_")
if !found {
return 0, nil
}
var serverId = types.Int64(serverIdString)
if serverId <= 0 {
return 0, nil
}
return this.uploadServerStats(rpcClient, serverId, timeString)
}(serverTime)
if uploadErr != nil {
return uploadErr
}
totalCount += count
// 休息一下,防止短时间内上传数据过多
if pauseDuration > 0 && totalCount >= 100 {
time.Sleep(pauseDuration)
uploadTr.Add(-pauseDuration)
}
}
return nil
}
func (this *KVTask) Start() error {
// 读取数据
this.statsTicker = utils.NewTicker(1 * time.Minute)
if Tea.IsTesting() {
this.statsTicker = utils.NewTicker(10 * time.Second)
}
goman.New(func() {
for this.statsTicker.Next() {
var tr = trackers.Begin("METRIC:DUMP_STATS_TO_LOCAL_DATABASE")
this.statsLocker.Lock()
var statsMap = this.statsMap
this.statsMap = map[string]*Stat{}
this.statsLocker.Unlock()
for _, stat := range statsMap {
err := this.InsertStat(stat)
if err != nil {
remotelogs.Error("METRIC", "insert stat failed: "+err.Error())
}
}
tr.End()
}
})
// 清理
this.cleanTicker = time.NewTicker(24 * time.Hour)
goman.New(func() {
idles.RunTicker(this.cleanTicker, func() {
var tr = trackers.Begin("METRIC:CLEAN_EXPIRED")
err := this.CleanExpired()
tr.End()
if err != nil {
remotelogs.Error("METRIC", "clean expired stats failed: "+err.Error())
}
})
})
// 上传
this.uploadTicker = utils.NewTicker(this.itemConfig.UploadDuration())
goman.New(func() {
for this.uploadTicker.Next() {
err := this.Upload(1 * time.Second)
if err != nil && !rpc.IsConnError(err) {
remotelogs.Error("METRIC", "upload stats failed: "+err.Error())
}
}
})
return nil
}
func (this *KVTask) Stop() error {
this.isStopped = true
if this.cleanTicker != nil {
this.cleanTicker.Stop()
}
if this.uploadTicker != nil {
this.uploadTicker.Stop()
}
if this.statsTicker != nil {
this.statsTicker.Stop()
}
return nil
}
func (this *KVTask) Delete() error {
this.isStopped = true
return this.itemsTable.DB().Truncate()
}
func (this *KVTask) CleanExpired() error {
if this.isStopped {
return nil
}
var versionBytes = int32ToBigEndian(this.itemConfig.Version)
var expiresTime = this.itemConfig.LocalExpiresTime()
var rangeEnd = append([]byte(expiresTime+"_"), versionBytes...)
rangeEnd = append(rangeEnd, 0xFF, 0xFF)
{
err := this.itemsTable.DeleteRange("", string(rangeEnd))
if err != nil {
return err
}
}
{
err := this.valuesTable.DeleteRange("", string(rangeEnd))
if err != nil {
return err
}
}
{
err := this.sumTable.DeleteRange("", string(rangeEnd))
if err != nil {
return err
}
}
return nil
}
func (this *KVTask) Flush() error {
return this.itemsTable.DB().Store().Flush()
}
func (this *KVTask) TestInspect(t *testing.T) {
var db = this.itemsTable.DB()
it, err := db.Store().RawDB().NewIter(&pebble.IterOptions{
LowerBound: []byte(db.Namespace()),
UpperBound: append([]byte(db.Namespace()), 0xFF),
})
if err != nil {
t.Fatal(err)
}
defer func() {
_ = it.Close()
}()
for it.First(); it.Valid(); it.Next() {
valueBytes, valueErr := it.ValueAndErr()
if valueErr != nil {
t.Fatal(valueErr)
}
var key = string(it.Key()[len(db.Namespace())-1:])
t.Log(key, "=>", string(valueBytes))
if strings.HasPrefix(key, "$values$K$") {
_, _, _, value, hash, _ := DecodeValueKey(key[len("$values$K$"):])
t.Log(" |", hash, "=>", value)
} else if strings.HasPrefix(key, "$sumValues$K$") {
count, sum := DecodeSumValue(valueBytes)
t.Log(" |", count, sum)
}
}
}
func (this *KVTask) Truncate() error {
var db = this.itemsTable.DB()
err := db.Truncate()
if err != nil {
return err
}
return db.Store().Flush()
}
func (this *KVTask) uploadServerStats(rpcClient *rpc.RPCClient, serverId int64, currentTime string) (countValues int, uploadErr error) {
var pbStats []*pb.UploadingMetricStat
var keepKeys []string
var prefix = string(byteutils.Concat([]byte(currentTime), []byte{'_'}, int32ToBigEndian(this.itemConfig.Version), int64ToBigEndian(serverId)))
var newCachedKeys = map[string]int64{}
queryErr := this.valuesTable.
Query().
Prefix(prefix).
Desc().
Limit(20).
FindAll(func(tx *kvstore.Tx[[]byte], item kvstore.Item[[]byte]) (goNext bool, err error) {
_, _, version, value, hash, decodeErr := DecodeValueKey(item.Key)
if decodeErr != nil {
return false, decodeErr
}
if value <= 0 {
return true, nil
}
// value not changed for the key
if this.valuesCacheMap[hash] == value {
keepKeys = append(keepKeys, hash)
return true, nil
}
newCachedKeys[hash] = value
stat, valueErr := this.itemsTable.Get(string(byteutils.Concat([]byte(currentTime), []byte{'_'}, int32ToBigEndian(version), []byte(hash))))
if valueErr != nil {
if kvstore.IsNotFound(valueErr) {
return true, nil
}
return false, valueErr
}
if stat == nil {
return true, nil
}
pbStats = append(pbStats, &pb.UploadingMetricStat{
Id: 0, // not used in node
Hash: hash,
Keys: stat.Keys,
Value: float32(value),
})
return true, nil
})
if queryErr != nil {
return 0, queryErr
}
// count & total
var count, total uint64
{
sumValue, err := this.sumTable.Get(prefix)
if err != nil {
if kvstore.IsNotFound(err) {
return 0, nil
}
return 0, err
}
count, total = DecodeSumValue(sumValue)
}
_, err := rpcClient.MetricStatRPC.UploadMetricStats(rpcClient.Context(), &pb.UploadMetricStatsRequest{
MetricStats: pbStats,
Time: currentTime,
ServerId: serverId,
ItemId: this.itemConfig.Id,
Version: this.itemConfig.Version,
Count: int64(count),
Total: float32(total),
KeepKeys: keepKeys,
})
if err != nil {
return 0, err
}
// put into cache map MUST be after uploading success
for k, v := range newCachedKeys {
this.valuesCacheMap[k] = v
}
return len(pbStats), nil
}
func (this *KVTask) loadServerIdMap() error {
var offsetKey string
var currentTime = this.itemConfig.CurrentTime()
for {
var found bool
err := this.sumTable.
Query().
Limit(1000).
Offset(offsetKey).
FindAll(func(tx *kvstore.Tx[[]byte], item kvstore.Item[[]byte]) (goNext bool, err error) {
offsetKey = item.Key
found = true
serverId, timeString, version, decodeErr := DecodeSumKey(item.Key)
if decodeErr != nil {
return false, decodeErr
}
if version != this.itemConfig.Version || timeString != currentTime {
return true, nil
}
this.serverIdMapLocker.Lock()
this.serverTimeMap[types.String(serverId)+"_"+timeString] = zero.New()
this.serverIdMapLocker.Unlock()
return true, nil
})
if err != nil {
return err
}
if !found {
break
}
}
return nil
}

View File

@@ -0,0 +1,24 @@
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package metrics
import (
"encoding/json"
"errors"
)
type ItemEncoder[T interface{ *Stat }] struct {
}
func (this *ItemEncoder[T]) Encode(value T) ([]byte, error) {
return json.Marshal(value)
}
func (this *ItemEncoder[T]) EncodeField(value T, fieldName string) ([]byte, error) {
return nil, errors.New("invalid field name '" + fieldName + "'")
}
func (this *ItemEncoder[T]) Decode(valueBytes []byte) (value T, err error) {
err = json.Unmarshal(valueBytes, &value)
return
}

View File

@@ -37,8 +37,8 @@ func (this *testObj) MetricCategory() string {
return "http" return "http"
} }
func TestTask_Init(t *testing.T) { func TestKVTask_Init(t *testing.T) {
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{ var task = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
Id: 1, Id: 1,
IsOn: false, IsOn: false,
Category: "", Category: "",
@@ -57,8 +57,8 @@ func TestTask_Init(t *testing.T) {
t.Log("ok") t.Log("ok")
} }
func TestTask_Add(t *testing.T) { func TestKVTask_Add(t *testing.T) {
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{ var task = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
Id: 1, Id: 1,
IsOn: false, IsOn: false,
Category: "", Category: "",
@@ -80,15 +80,18 @@ func TestTask_Add(t *testing.T) {
}() }()
task.Add(&testObj{ip: "127.0.0.2"}) task.Add(&testObj{ip: "127.0.0.2"})
time.Sleep(1 * time.Second) // waiting for inserting
if testutils.IsSingleTesting() {
time.Sleep(1 * time.Second) // waiting for inserting
}
} }
func TestTask_Add_Many(t *testing.T) { func TestKVTask_Add_Many(t *testing.T) {
if !testutils.IsSingleTesting() { if !testutils.IsSingleTesting() {
return return
} }
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{ var task = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
Id: 1, Id: 1,
IsOn: false, IsOn: false,
Category: "", Category: "",
@@ -120,7 +123,7 @@ func TestTask_Add_Many(t *testing.T) {
} }
} }
func TestTask_InsertStat(t *testing.T) { func TestKVTask_InsertStat(t *testing.T) {
var item = &serverconfigs.MetricItemConfig{ var item = &serverconfigs.MetricItemConfig{
Id: 1, Id: 1,
IsOn: false, IsOn: false,
@@ -131,11 +134,19 @@ func TestTask_InsertStat(t *testing.T) {
Value: "${countRequest}", Value: "${countRequest}",
Version: 1, Version: 1,
} }
var task = metrics.NewTask(item) var task = metrics.NewKVTask(item)
err := task.Init() err := task.Init()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer func() {
err = task.Flush()
if err != nil {
t.Fatal(err)
}
}()
err = task.Start() err = task.Start()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -144,21 +155,50 @@ func TestTask_InsertStat(t *testing.T) {
_ = task.Stop() _ = task.Stop()
}() }()
err = task.InsertStat(&metrics.Stat{ {
ServerId: 1, err = task.InsertStat(&metrics.Stat{
Keys: []string{"127.0.0.1"}, ServerId: 1,
Hash: "", Keys: []string{"127.0.0.1"},
Value: 1, Hash: "",
Time: item.CurrentTime(), Value: 1,
}) Time: item.CurrentTime(),
if err != nil { })
t.Fatal(err) if err != nil {
t.Fatal(err)
}
} }
t.Log("ok")
{
err = task.InsertStat(&metrics.Stat{
ServerId: 2,
Keys: []string{"127.0.0.2"},
Hash: "",
Value: 3,
Time: item.CurrentTime(),
})
if err != nil {
t.Fatal(err)
}
}
{
err = task.InsertStat(&metrics.Stat{
ServerId: 1,
Keys: []string{"127.0.0.3"},
Hash: "",
Value: 2,
Time: item.CurrentTime(),
})
if err != nil {
t.Fatal(err)
}
}
TestKVTask_TestInspect(t)
} }
func TestTask_CleanExpired(t *testing.T) { func TestKVTask_CleanExpired(t *testing.T) {
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{ var task = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
Id: 1, Id: 1,
IsOn: false, IsOn: false,
Category: "", Category: "",
@@ -184,12 +224,18 @@ func TestTask_CleanExpired(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Log("ok")
defer func() {
_ = task.Flush()
}()
t.Log("=== inspect ===")
task.TestInspect(t)
} }
func TestTask_Upload(t *testing.T) { func TestKVTask_Upload(t *testing.T) {
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{ var task = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
Id: 1, Id: 31,
IsOn: false, IsOn: false,
Category: "", Category: "",
Period: 1, Period: 1,
@@ -218,11 +264,51 @@ func TestTask_Upload(t *testing.T) {
t.Log("ok") t.Log("ok")
} }
var testingTask *metrics.Task func TestKVTask_TestInspect(t *testing.T) {
var task = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
Id: 1,
IsOn: false,
Category: "",
Period: 1,
PeriodUnit: serverconfigs.MetricItemPeriodUnitDay,
Keys: []string{"${remoteAddr}"},
Value: "${countRequest}",
Version: 1,
})
err := task.Init()
if err != nil {
t.Fatal(err)
}
task.TestInspect(t)
}
func TestKVTask_Truncate(t *testing.T) {
var task = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
Id: 1,
IsOn: false,
Category: "",
Period: 1,
PeriodUnit: serverconfigs.MetricItemPeriodUnitDay,
Keys: []string{"${remoteAddr}"},
Value: "${countRequest}",
Version: 1,
})
err := task.Init()
if err != nil {
t.Fatal(err)
}
if testutils.IsSingleTesting() {
_ = task.Truncate()
}
}
var testingTask metrics.Task
var testingTaskInitOnce = &sync.Once{} var testingTaskInitOnce = &sync.Once{}
func initTestingTask() { func initTestingTask() {
testingTask = metrics.NewTask(&serverconfigs.MetricItemConfig{ testingTask = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
Id: 1, Id: 1,
IsOn: false, IsOn: false,
Category: "tcp", Category: "tcp",
@@ -243,7 +329,7 @@ func initTestingTask() {
} }
} }
func BenchmarkTask_Add(b *testing.B) { func BenchmarkKVTask_Add(b *testing.B) {
runtime.GOMAXPROCS(1) runtime.GOMAXPROCS(1)
testingTaskInitOnce.Do(func() { testingTaskInitOnce.Do(func() {

View File

@@ -0,0 +1,506 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package metrics
import (
"encoding/json"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
"github.com/TeaOSLab/EdgeNode/internal/goman"
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
"github.com/TeaOSLab/EdgeNode/internal/rpc"
"github.com/TeaOSLab/EdgeNode/internal/trackers"
"github.com/TeaOSLab/EdgeNode/internal/utils"
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
"github.com/TeaOSLab/EdgeNode/internal/zero"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/types"
"os"
"strconv"
"sync"
"time"
)
const MaxQueueSize = 256 // TODO 可以配置,可以在单个任务里配置
// SQLiteTask 单个指标任务
// 数据库存储:
//
// data/
// metric.$ID.db
// stats
// id, keys, value, time, serverId, hash
// 原理:
// 添加或者有变更时 isUploaded = false
// 上传时检查 isUploaded 状态
// 只上传每个服务中排序最前面的 N 个数据
type SQLiteTask struct {
BaseTask
db *dbs.DB
statTableName string
cleanTicker *time.Ticker
uploadTicker *utils.Ticker
cleanVersion int32
insertStatStmt *dbs.Stmt
deleteByVersionStmt *dbs.Stmt
deleteByExpiresTimeStmt *dbs.Stmt
selectTopStmt *dbs.Stmt
sumStmt *dbs.Stmt
serverIdMap map[int64]zero.Zero // 所有的服务Ids
timeMap map[string]zero.Zero // time => bool
serverIdMapLocker sync.Mutex
statsTicker *utils.Ticker
}
// NewSQLiteTask 获取新任务
func NewSQLiteTask(item *serverconfigs.MetricItemConfig) *SQLiteTask {
return &SQLiteTask{
BaseTask: BaseTask{
itemConfig: item,
statsMap: map[string]*Stat{},
},
serverIdMap: map[int64]zero.Zero{},
timeMap: map[string]zero.Zero{},
}
}
func CheckSQLiteDB(itemId int64) bool {
var path = Tea.Root + "/data/metric." + types.String(itemId) + ".db"
_, err := os.Stat(path)
return err == nil
}
// Init 初始化
func (this *SQLiteTask) Init() error {
this.statTableName = "stats"
// 检查目录是否存在
var dir = Tea.Root + "/data"
_, err := os.Stat(dir)
if err != nil {
err = os.MkdirAll(dir, 0777)
if err != nil {
return err
}
remotelogs.Println("METRIC", "create data dir '"+dir+"'")
}
var path = dir + "/metric." + types.String(this.itemConfig.Id) + ".db"
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
if err != nil {
return err
}
db.SetMaxOpenConns(1)
this.db = db
// 恢复数据库
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
if len(recoverEnv) > 0 {
for _, indexName := range []string{"serverId", "hash"} {
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
}
}
if teaconst.EnableDBStat {
this.db.EnableStat(true)
}
//创建统计表
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.statTableName + `" (
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
"hash" varchar(32),
"keys" varchar(1024),
"value" real DEFAULT 0,
"time" varchar(32),
"serverId" integer DEFAULT 0,
"version" integer DEFAULT 0,
"isUploaded" integer DEFAULT 0
);
CREATE INDEX IF NOT EXISTS "serverId"
ON "` + this.statTableName + `" (
"serverId" ASC,
"version" ASC
);
CREATE UNIQUE INDEX IF NOT EXISTS "hash"
ON "` + this.statTableName + `" (
"hash" ASC
);`)
if err != nil {
return err
}
// insert stat stmt
this.insertStatStmt, err = db.Prepare(`INSERT INTO "stats" ("serverId", "hash", "keys", "value", "time", "version", "isUploaded") VALUES (?, ?, ?, ?, ?, ?, 0) ON CONFLICT("hash") DO UPDATE SET "value"="value"+?, "isUploaded"=0`)
if err != nil {
return err
}
// delete by version
this.deleteByVersionStmt, err = db.Prepare(`DELETE FROM "` + this.statTableName + `" WHERE "version"<?`)
if err != nil {
return err
}
// delete by expires time
this.deleteByExpiresTimeStmt, err = db.Prepare(`DELETE FROM "` + this.statTableName + `" WHERE "time"<?`)
if err != nil {
return err
}
// select topN stmt
this.selectTopStmt, err = db.Prepare(`SELECT "id", "hash", "keys", "value", "isUploaded" FROM "` + this.statTableName + `" WHERE "serverId"=? AND "version"=? AND time=? ORDER BY "value" DESC LIMIT 20`)
if err != nil {
return err
}
// sum stmt
this.sumStmt, err = db.Prepare(`SELECT COUNT(*), IFNULL(SUM(value), 0) FROM "` + this.statTableName + `" WHERE "serverId"=? AND "version"=? AND time=?`)
if err != nil {
return err
}
// 所有的服务IDs
err = this.loadServerIdMap()
if err != nil {
return err
}
this.isLoaded = true
return nil
}
// Start 启动任务
func (this *SQLiteTask) Start() error {
// 读取数据
this.statsTicker = utils.NewTicker(1 * time.Minute)
goman.New(func() {
for this.statsTicker.Next() {
var tr = trackers.Begin("METRIC:DUMP_STATS_TO_LOCAL_DATABASE")
this.statsLocker.Lock()
var statsMap = this.statsMap
this.statsMap = map[string]*Stat{}
this.statsLocker.Unlock()
for _, stat := range statsMap {
err := this.InsertStat(stat)
if err != nil {
remotelogs.Error("METRIC", "insert stat failed: "+err.Error())
}
}
tr.End()
}
})
// 清理
this.cleanTicker = time.NewTicker(24 * time.Hour)
goman.New(func() {
idles.RunTicker(this.cleanTicker, func() {
var tr = trackers.Begin("METRIC:CLEAN_EXPIRED")
err := this.CleanExpired()
tr.End()
if err != nil {
remotelogs.Error("METRIC", "clean expired stats failed: "+err.Error())
}
})
})
// 上传
this.uploadTicker = utils.NewTicker(this.itemConfig.UploadDuration())
goman.New(func() {
for this.uploadTicker.Next() {
err := this.Upload(1 * time.Second)
if err != nil && !rpc.IsConnError(err) {
remotelogs.Error("METRIC", "upload stats failed: "+err.Error())
}
}
})
return nil
}
// Stop 停止任务
func (this *SQLiteTask) Stop() error {
this.isStopped = true
if this.cleanTicker != nil {
this.cleanTicker.Stop()
}
if this.uploadTicker != nil {
this.uploadTicker.Stop()
}
if this.statsTicker != nil {
this.statsTicker.Stop()
}
_ = this.insertStatStmt.Close()
_ = this.deleteByVersionStmt.Close()
_ = this.deleteByExpiresTimeStmt.Close()
_ = this.selectTopStmt.Close()
_ = this.sumStmt.Close()
if this.db != nil {
_ = this.db.Close()
}
return nil
}
func (this *SQLiteTask) Delete() error {
return nil
}
// InsertStat 写入数据
func (this *SQLiteTask) InsertStat(stat *Stat) error {
if this.isStopped {
return nil
}
if stat == nil {
return nil
}
this.serverIdMapLocker.Lock()
this.serverIdMap[stat.ServerId] = zero.New()
this.timeMap[stat.Time] = zero.New()
this.serverIdMapLocker.Unlock()
keyData, err := json.Marshal(stat.Keys)
if err != nil {
return err
}
_, err = this.insertStatStmt.Exec(stat.ServerId, stat.Hash, keyData, stat.Value, stat.Time, this.itemConfig.Version, stat.Value)
if err != nil {
return err
}
return nil
}
// CleanExpired 清理数据
func (this *SQLiteTask) CleanExpired() error {
if this.isStopped {
return nil
}
// 清除低版本数据
if this.cleanVersion < this.itemConfig.Version {
_, err := this.deleteByVersionStmt.Exec(this.itemConfig.Version)
if err != nil {
return err
}
this.cleanVersion = this.itemConfig.Version
}
// 清除过期的数据
_, err := this.deleteByExpiresTimeStmt.Exec(this.itemConfig.LocalExpiresTime())
if err != nil {
return err
}
return nil
}
// Upload 上传数据
func (this *SQLiteTask) Upload(pauseDuration time.Duration) error {
var uploadTr = trackers.Begin("METRIC:UPLOAD_STATS")
defer uploadTr.End()
if this.isStopped {
return nil
}
this.serverIdMapLocker.Lock()
// 服务IDs
var serverIds []int64
for serverId := range this.serverIdMap {
serverIds = append(serverIds, serverId)
}
this.serverIdMap = map[int64]zero.Zero{} // 清空数据
// 时间
var times = []string{}
for t := range this.timeMap {
times = append(times, t)
}
this.timeMap = map[string]zero.Zero{} // 清空数据
this.serverIdMapLocker.Unlock()
rpcClient, err := rpc.SharedRPC()
if err != nil {
return err
}
for _, serverId := range serverIds {
for _, currentTime := range times {
idStrings, err := func(serverId int64, currentTime string) (ids []string, err error) {
var t = trackers.Begin("METRIC:SELECT_TOP_STMT")
rows, err := this.selectTopStmt.Query(serverId, this.itemConfig.Version, currentTime)
t.End()
if err != nil {
return nil, err
}
var isClosed bool
defer func() {
if isClosed {
return
}
_ = rows.Close()
}()
var pbStats []*pb.UploadingMetricStat
for rows.Next() {
var pbStat = &pb.UploadingMetricStat{}
// "id", "hash", "keys", "value", "isUploaded"
var isUploaded int
var keysData []byte
err = rows.Scan(&pbStat.Id, &pbStat.Hash, &keysData, &pbStat.Value, &isUploaded)
if err != nil {
return nil, err
}
// TODO 先不判断是否已经上传需要改造API进行配合
/**if isUploaded == 1 {
continue
}**/
if len(keysData) > 0 {
err = json.Unmarshal(keysData, &pbStat.Keys)
if err != nil {
return nil, err
}
}
pbStats = append(pbStats, pbStat)
ids = append(ids, strconv.FormatInt(pbStat.Id, 10))
}
// 提前关闭
_ = rows.Close()
isClosed = true
// 上传
if len(pbStats) > 0 {
// 计算总和
count, total, err := this.sum(serverId, currentTime)
if err != nil {
return nil, err
}
_, err = rpcClient.MetricStatRPC.UploadMetricStats(rpcClient.Context(), &pb.UploadMetricStatsRequest{
MetricStats: pbStats,
Time: currentTime,
ServerId: serverId,
ItemId: this.itemConfig.Id,
Version: this.itemConfig.Version,
Count: count,
Total: float32(total),
})
if err != nil {
return nil, err
}
}
return
}(serverId, currentTime)
if err != nil {
return err
}
if len(idStrings) > 0 {
// 设置为已上传
// TODO 先不判断是否已经上传需要改造API进行配合
/**_, err = this.db.Exec(`UPDATE "` + this.statTableName + `" SET isUploaded=1 WHERE id IN (` + strings.Join(idStrings, ",") + `)`)
if err != nil {
return err
}**/
}
}
// 休息一下,防止短时间内上传数据过多
if pauseDuration > 0 {
time.Sleep(pauseDuration)
uploadTr.Add(-pauseDuration)
}
}
return nil
}
// 加载服务ID
func (this *SQLiteTask) loadServerIdMap() error {
{
rows, err := this.db.Query(`SELECT DISTINCT "serverId" FROM `+this.statTableName+" WHERE version=?", this.itemConfig.Version)
if err != nil {
return err
}
defer func() {
_ = rows.Close()
}()
var serverId int64
for rows.Next() {
err = rows.Scan(&serverId)
if err != nil {
return err
}
this.serverIdMapLocker.Lock()
this.serverIdMap[serverId] = zero.New()
this.serverIdMapLocker.Unlock()
}
}
{
rows, err := this.db.Query(`SELECT DISTINCT "time" FROM `+this.statTableName+" WHERE version=?", this.itemConfig.Version)
if err != nil {
return err
}
defer func() {
_ = rows.Close()
}()
var timeString string
for rows.Next() {
err = rows.Scan(&timeString)
if err != nil {
return err
}
this.serverIdMapLocker.Lock()
this.timeMap[timeString] = zero.New()
this.serverIdMapLocker.Unlock()
}
}
return nil
}
// 计算数量和综合
func (this *SQLiteTask) sum(serverId int64, time string) (count int64, total float64, err error) {
rows, err := this.sumStmt.Query(serverId, this.itemConfig.Version, time)
if err != nil {
return 0, 0, err
}
defer func() {
_ = rows.Close()
}()
if rows.Next() {
err = rows.Scan(&count, &total)
if err != nil {
return 0, 0, err
}
}
return
}

View File

@@ -209,7 +209,7 @@ func (this *APIStream) handleWriteCache(message *pb.NodeStreamMessage) error {
storage.AddToList(&caches.Item{ storage.AddToList(&caches.Item{
Type: writer.ItemType(), Type: writer.ItemType(),
Key: msg.Key, Key: msg.Key,
ExpiredAt: expiredAt, ExpiresAt: expiredAt,
HeaderSize: writer.HeaderSize(), HeaderSize: writer.HeaderSize(),
BodySize: writer.BodySize(), BodySize: writer.BodySize(),
}) })
@@ -282,14 +282,14 @@ func (this *APIStream) handleStatCache(message *pb.NodeStreamMessage) error {
} }
sizeFormat := "" sizeFormat := ""
if stat.Size < 1024 { if stat.Size < (1 << 10) {
sizeFormat = strconv.FormatInt(stat.Size, 10) + " Bytes" sizeFormat = strconv.FormatInt(stat.Size, 10) + " Bytes"
} else if stat.Size < 1024*1024 { } else if stat.Size < (1 << 20) {
sizeFormat = fmt.Sprintf("%.2f KB", float64(stat.Size)/1024) sizeFormat = fmt.Sprintf("%.2f KiB", float64(stat.Size)/(1<<10))
} else if stat.Size < 1024*1024*1024 { } else if stat.Size < (1 << 30) {
sizeFormat = fmt.Sprintf("%.2f MB", float64(stat.Size)/1024/1024) sizeFormat = fmt.Sprintf("%.2f MiB", float64(stat.Size)/(1<<20))
} else { } else {
sizeFormat = fmt.Sprintf("%.2f GB", float64(stat.Size)/1024/1024/1024) sizeFormat = fmt.Sprintf("%.2f GiB", float64(stat.Size)/(1<<30))
} }
this.replyOk(message.RequestId, "size:"+sizeFormat+", count:"+strconv.Itoa(stat.Count)) this.replyOk(message.RequestId, "size:"+sizeFormat+", count:"+strconv.Itoa(stat.Count))

Some files were not shown because too many files have changed in this diff Show More