Compare commits
397 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c2d488c37 | ||
|
|
d84b844e53 | ||
|
|
095c381ae5 | ||
|
|
7d11b3c63b | ||
|
|
45ba4fe5f1 | ||
|
|
ac341da05b | ||
|
|
12b9c37095 | ||
|
|
125b25ea27 | ||
|
|
6b1d595d58 | ||
|
|
f26b80e9c1 | ||
|
|
6e1bb43a9e | ||
|
|
35e7ce1435 | ||
|
|
5eb247b999 | ||
|
|
c94e93859f | ||
|
|
d694319191 | ||
|
|
84e61f7765 | ||
|
|
196f0612dc | ||
|
|
4cd5e12686 | ||
|
|
aca128c19d | ||
|
|
5052d20fbd | ||
|
|
74790bea4a | ||
|
|
e922c12611 | ||
|
|
cc632d557b | ||
|
|
a7dd101dbf | ||
|
|
1e1cd5a643 | ||
|
|
6888ccc350 | ||
|
|
8d9a4b6d4f | ||
|
|
470b33e90f | ||
|
|
74a559b1a0 | ||
|
|
9db86b011a | ||
|
|
c94a51f47b | ||
|
|
2a9ec05d45 | ||
|
|
c9811d78a9 | ||
|
|
65435ab32d | ||
|
|
614c7a4687 | ||
|
|
ef541a2d8f | ||
|
|
9141a1434e | ||
|
|
17af07cce0 | ||
|
|
cfa57fac66 | ||
|
|
47523eaa73 | ||
|
|
27a24c6a8a | ||
|
|
9bc2b1a651 | ||
|
|
4f24b7f39c | ||
|
|
4607a1f4e7 | ||
|
|
0f2068b161 | ||
|
|
c039691a71 | ||
|
|
930ee44065 | ||
|
|
8a9aac7d72 | ||
|
|
e50bbb962d | ||
|
|
9ff936d0c1 | ||
|
|
f53727b09c | ||
|
|
525ce1f923 | ||
|
|
16e7cd800c | ||
|
|
3f34bfc0b0 | ||
|
|
548cd1002b | ||
|
|
3423865868 | ||
|
|
037bc8e0de | ||
|
|
e03292de28 | ||
|
|
ee2565905e | ||
|
|
05881b457d | ||
|
|
b116effc6c | ||
|
|
536efeeb9c | ||
|
|
e8638e4bec | ||
|
|
c9db722129 | ||
|
|
90de472bd5 | ||
|
|
50c6c60abf | ||
|
|
cc10372fe1 | ||
|
|
05c98a0656 | ||
|
|
1a790fe391 | ||
|
|
7dbd73cb59 | ||
|
|
4dfa571547 | ||
|
|
9f77f62308 | ||
|
|
facea1ed96 | ||
|
|
e367814db3 | ||
|
|
3a15408c98 | ||
|
|
c504b37118 | ||
|
|
74708dc02f | ||
|
|
0c097498bb | ||
|
|
981c063eff | ||
|
|
5e35c50113 | ||
|
|
e6c2869ff2 | ||
|
|
358bec2e9b | ||
|
|
1cd644f2eb | ||
|
|
f783e5c331 | ||
|
|
c39b1c794f | ||
|
|
2633d43897 | ||
|
|
88dca006c4 | ||
|
|
98feb26b79 | ||
|
|
ac6683e79d | ||
|
|
99d24afbcd | ||
|
|
ba19a9f4c4 | ||
|
|
7fea67a2b5 | ||
|
|
ecd2e6955e | ||
|
|
09d60a3047 | ||
|
|
e24f390412 | ||
|
|
eeacec1a4e | ||
|
|
30cd6373c5 | ||
|
|
87a6ab0559 | ||
|
|
59f27215d3 | ||
|
|
768384dcf0 | ||
|
|
3b52ac0fd2 | ||
|
|
41343b2264 | ||
|
|
d084059f04 | ||
|
|
9253c44ba5 | ||
|
|
ddec0bf2e0 | ||
|
|
aeba1805af | ||
|
|
ecff37e080 | ||
|
|
d31dac75be | ||
|
|
4571c84102 | ||
|
|
6a9f59bee0 | ||
|
|
f1951869f1 | ||
|
|
cfd4195c0f | ||
|
|
d793472b42 | ||
|
|
1e56247b9c | ||
|
|
c34a38857a | ||
|
|
57fa7036dc | ||
|
|
b8a3ac750f | ||
|
|
9d6692db0c | ||
|
|
ad94327226 | ||
|
|
aee1ff9609 | ||
|
|
822e967874 | ||
|
|
2acf890b8e | ||
|
|
3909695b44 | ||
|
|
d0f420a945 | ||
|
|
7618338f38 | ||
|
|
9b2a704e7f | ||
|
|
630c1ec63b | ||
|
|
5b93b28690 | ||
|
|
3aa68b5ffc | ||
|
|
adb0069c59 | ||
|
|
211da66226 | ||
|
|
81911c4073 | ||
|
|
6ff3230bab | ||
|
|
f01eae3590 | ||
|
|
47e8761209 | ||
|
|
8449fe7c0b | ||
|
|
7f3e6ddc65 | ||
|
|
6ca8b6837c | ||
|
|
a9969430a3 | ||
|
|
7c5c06191d | ||
|
|
afc533c3e4 | ||
|
|
71c891ae14 | ||
|
|
e8570bfd09 | ||
|
|
534d64673f | ||
|
|
6bff5c978b | ||
|
|
38a214878a | ||
|
|
149e04d0e4 | ||
|
|
5733d466ca | ||
|
|
a95e2e3259 | ||
|
|
a80a89edf5 | ||
|
|
b8f7d4110f | ||
|
|
00e76a6a09 | ||
|
|
79fa9d88a1 | ||
|
|
c460421279 | ||
|
|
69e4dd6cfe | ||
|
|
b73f0ae2c9 | ||
|
|
2af577380e | ||
|
|
89bfdc478f | ||
|
|
b6e8221ac1 | ||
|
|
ea6d3d7107 | ||
|
|
7d8bdfcd45 | ||
|
|
70fe1b5d2b | ||
|
|
a7caf0260a | ||
|
|
d547793eee | ||
|
|
d92f27c44b | ||
|
|
8561ff3e2d | ||
|
|
0736b20d33 | ||
|
|
263acb775b | ||
|
|
7a1fff8504 | ||
|
|
5c5adf690f | ||
|
|
1eca5099df | ||
|
|
5f2ad8b096 | ||
|
|
4405cfd405 | ||
|
|
2f6aa0c14f | ||
|
|
bb50ecd682 | ||
|
|
ae1454f9bb | ||
|
|
3781b1920a | ||
|
|
125ad9c606 | ||
|
|
e516300dc7 | ||
|
|
6c1d24c3e5 | ||
|
|
6f230b30e0 | ||
|
|
8a0318b4f3 | ||
|
|
d9fddcb001 | ||
|
|
9113c4c1b3 | ||
|
|
f0762fe1b9 | ||
|
|
9054b8ec05 | ||
|
|
5ad25e34c6 | ||
|
|
d4cca10301 | ||
|
|
8597884ec5 | ||
|
|
9f469f5b77 | ||
|
|
5bc4f5b359 | ||
|
|
494ff5b5bb | ||
|
|
bac0060a74 | ||
|
|
062ca1cfcd | ||
|
|
8a4373e984 | ||
|
|
99670e46a5 | ||
|
|
64642c6680 | ||
|
|
f7a7b50eda | ||
|
|
f5265f1832 | ||
|
|
2d6f7522fc | ||
|
|
cfb15e764d | ||
|
|
406d8de999 | ||
|
|
2ca79953b9 | ||
|
|
d067cd8437 | ||
|
|
e4937685bc | ||
|
|
6ac2343aa7 | ||
|
|
1d80bc640d | ||
|
|
b19c431b89 | ||
|
|
a1e1b5ef98 | ||
|
|
e93275ac5c | ||
|
|
a6059ab070 | ||
|
|
5b0f94f317 | ||
|
|
0b4ac55aa6 | ||
|
|
9080c78b13 | ||
|
|
aa7d67e387 | ||
|
|
405b3615fe | ||
|
|
7534af09ed | ||
|
|
cd3bf0cad4 | ||
|
|
41ebdfb7d2 | ||
|
|
fa7d4963cb | ||
|
|
627d9721b3 | ||
|
|
8c3cd53dc3 | ||
|
|
5995be8489 | ||
|
|
eabaa84252 | ||
|
|
40e137e69e | ||
|
|
6f51fe52f8 | ||
|
|
dd4071e7dc | ||
|
|
7e5b3254eb | ||
|
|
ad1947379d | ||
|
|
2ff2afca36 | ||
|
|
9f36678d75 | ||
|
|
288074c8b3 | ||
|
|
0a290251cd | ||
|
|
edf98f1889 | ||
|
|
02c3d24038 | ||
|
|
2a2f4ff7b1 | ||
|
|
d416a2186c | ||
|
|
94b9c23323 | ||
|
|
70d8507c4b | ||
|
|
2eee314ec8 | ||
|
|
93521963b1 | ||
|
|
f456b6bc6b | ||
|
|
d30e2b2cc8 | ||
|
|
25bf4ab55a | ||
|
|
9536c49a8f | ||
|
|
3f19d88246 | ||
|
|
9931d057a9 | ||
|
|
c61ae6c8ba | ||
|
|
249dad3e97 | ||
|
|
f9fbd23a77 | ||
|
|
22eb143dee | ||
|
|
075c11a3cf | ||
|
|
f4258ed00e | ||
|
|
8ac115f865 | ||
|
|
53f109861c | ||
|
|
f034a1cfb3 | ||
|
|
ae74114fca | ||
|
|
cdfc37ac14 | ||
|
|
362a4d9ef4 | ||
|
|
7653c16586 | ||
|
|
f1a4a7ebc6 | ||
|
|
0f0436c7a8 | ||
|
|
cc881c070c | ||
|
|
10db4e3ccd | ||
|
|
a2a33a65e8 | ||
|
|
5a4162987c | ||
|
|
b46744cb13 | ||
|
|
8b7845fd15 | ||
|
|
a129178abc | ||
|
|
02875374aa | ||
|
|
8859625ce7 | ||
|
|
6dccd0ad46 | ||
|
|
18b76013b9 | ||
|
|
fa04c041df | ||
|
|
c8142741ff | ||
|
|
547874aa43 | ||
|
|
45c6b2ddac | ||
|
|
eb145393ab | ||
|
|
d767ca177a | ||
|
|
c8fc2815b9 | ||
|
|
3b2ba1aad6 | ||
|
|
33bb06fbc3 | ||
|
|
3793d684fa | ||
|
|
2390a3ef61 | ||
|
|
ffda81715f | ||
|
|
f991700031 | ||
|
|
e1ba6a90ff | ||
|
|
354161037b | ||
|
|
00d28df3ee | ||
|
|
23abed0949 | ||
|
|
2acf01dcb7 | ||
|
|
470c6a8b0e | ||
|
|
efc2810d1d | ||
|
|
de2374577f | ||
|
|
2a1f949c13 | ||
|
|
959e274063 | ||
|
|
b6a2bd37b1 | ||
|
|
3e60c9913a | ||
|
|
fd7f3f4029 | ||
|
|
2705a5d444 | ||
|
|
556055cfcb | ||
|
|
67a0d06944 | ||
|
|
a16d8f1afa | ||
|
|
1bab7bfcba | ||
|
|
5928875623 | ||
|
|
734cf81ff0 | ||
|
|
de8c2e13f1 | ||
|
|
0742dc963d | ||
|
|
1fdce3ef7e | ||
|
|
2079b0ebee | ||
|
|
c706f2fdf1 | ||
|
|
bd3247668d | ||
|
|
73024fe38c | ||
|
|
db520858b3 | ||
|
|
84c8a351a9 | ||
|
|
c6c0823d30 | ||
|
|
1be64adb6a | ||
|
|
d0610a5001 | ||
|
|
a2f7511a46 | ||
|
|
6e8c886cd6 | ||
|
|
03f2130827 | ||
|
|
9fea0749a0 | ||
|
|
71e0d9ce07 | ||
|
|
24e69028f8 | ||
|
|
34521dbc5c | ||
|
|
2c59ae4a5b | ||
|
|
d0bd7bb88d | ||
|
|
47c24b4aa8 | ||
|
|
4eb58a3082 | ||
|
|
c10c7cc157 | ||
|
|
7fd8d7756b | ||
|
|
032c118f49 | ||
|
|
b6cab3919a | ||
|
|
8edd30bdd8 | ||
|
|
a8c8d80e3b | ||
|
|
c43b6b37ea | ||
|
|
ac2d57d2f1 | ||
|
|
83ac62cda3 | ||
|
|
c0909a2cd0 | ||
|
|
a73b9f2674 | ||
|
|
3e79b71afc | ||
|
|
d3caccbb55 | ||
|
|
f95bac8d38 | ||
|
|
41d2ab728b | ||
|
|
b319061e85 | ||
|
|
99b8686a49 | ||
|
|
fe8c5b505a | ||
|
|
f88d0982ed | ||
|
|
a9389d53e1 | ||
|
|
fc4b45fec7 | ||
|
|
9b22e6cf69 | ||
|
|
7bd7f7da45 | ||
|
|
b68e6517df | ||
|
|
bbae229d08 | ||
|
|
c73a6cbfe8 | ||
|
|
e869e8e4d6 | ||
|
|
bde4e8507f | ||
|
|
5ae25cffa0 | ||
|
|
44b721d0d3 | ||
|
|
a2d6b7e0a8 | ||
|
|
95d65481e3 | ||
|
|
71522243b5 | ||
|
|
953533d9a3 | ||
|
|
31509392c9 | ||
|
|
dc30469b2c | ||
|
|
fa2903ebb9 | ||
|
|
c43387bf6a | ||
|
|
47f5cbeac9 | ||
|
|
2aea68fac4 | ||
|
|
9f7e6559d3 | ||
|
|
e352e3125c | ||
|
|
6b5e93f5ad | ||
|
|
cea5ae9c6c | ||
|
|
80b2bbf1eb | ||
|
|
c87f25d7bf | ||
|
|
82aff804a6 | ||
|
|
973e67acdb | ||
|
|
93bd9daf76 | ||
|
|
d8c121662c | ||
|
|
5f33249c5d | ||
|
|
1beafc9976 | ||
|
|
b3857adc0f | ||
|
|
e75010692c | ||
|
|
f2df4a1560 | ||
|
|
7a4b68de97 | ||
|
|
6fce430469 | ||
|
|
8856094dd1 | ||
|
|
20bee16d28 | ||
|
|
b1cd971a21 | ||
|
|
d976a39711 | ||
|
|
accd0236ea | ||
|
|
fc401a1426 | ||
|
|
7e8c09a684 | ||
|
|
37ddff86f1 | ||
|
|
4dc25fb71e | ||
|
|
42883fbe22 | ||
|
|
a88d9a07be |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
*_plus.go
|
||||
*_plus_test.go
|
||||
*-plus.sh
|
||||
74
.golangci.yaml
Normal file
74
.golangci.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
# https://golangci-lint.run/usage/configuration/
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- ifshort
|
||||
- exhaustivestruct
|
||||
- golint
|
||||
- nosnakecase
|
||||
- scopelint
|
||||
- varcheck
|
||||
- structcheck
|
||||
- interfacer
|
||||
- maligned
|
||||
- deadcode
|
||||
- dogsled
|
||||
- wrapcheck
|
||||
- wastedassign
|
||||
- varnamelen
|
||||
- testpackage
|
||||
- thelper
|
||||
- nilerr
|
||||
- sqlclosecheck
|
||||
- paralleltest
|
||||
- nonamedreturns
|
||||
- nlreturn
|
||||
- nakedret
|
||||
- ireturn
|
||||
- interfacebloat
|
||||
- gosmopolitan
|
||||
- gomnd
|
||||
- goerr113
|
||||
- gochecknoglobals
|
||||
- exhaustruct
|
||||
- errorlint
|
||||
- depguard
|
||||
- exhaustive
|
||||
- containedctx
|
||||
- wsl
|
||||
- cyclop
|
||||
- dupword
|
||||
- errchkjson
|
||||
- contextcheck
|
||||
- tagalign
|
||||
- dupl
|
||||
- forbidigo
|
||||
- funlen
|
||||
- goconst
|
||||
- godox
|
||||
- gosec
|
||||
- lll
|
||||
- nestif
|
||||
- revive
|
||||
- unparam
|
||||
- stylecheck
|
||||
- gocritic
|
||||
- gofumpt
|
||||
- gomoddirectives
|
||||
- godot
|
||||
- gofmt
|
||||
- gocognit
|
||||
- mirror
|
||||
- gocyclo
|
||||
- gochecknoinits
|
||||
- gci
|
||||
- maintidx
|
||||
- prealloc
|
||||
- goimports
|
||||
- errname
|
||||
- musttag
|
||||
- forcetypeassert
|
||||
- whitespace
|
||||
- noctx
|
||||
- rowserrcheck
|
||||
@@ -6,8 +6,11 @@ function build() {
|
||||
VERSION=$(lookup-version "$ROOT"/../internal/const/const.go)
|
||||
DIST=$ROOT/"../dist/${NAME}"
|
||||
MUSL_DIR="/usr/local/opt/musl-cross/bin"
|
||||
GCC_X86_64_DIR="/usr/local/Cellar/x86_64-unknown-linux-gnu/10.3.0/bin"
|
||||
GCC_ARM64_DIR="/usr/local/Cellar/aarch64-unknown-linux-gnu/10.3.0/bin"
|
||||
SRCDIR=$(realpath "$ROOT/..")
|
||||
|
||||
# for macOS users: precompiled gcc can be downloaded from https://github.com/messense/homebrew-macos-cross-toolchains
|
||||
GCC_X86_64_DIR="/usr/local/gcc/x86_64-unknown-linux-gnu/bin"
|
||||
GCC_ARM64_DIR="/usr/local/gcc/aarch64-unknown-linux-gnu/bin"
|
||||
|
||||
OS=${1}
|
||||
ARCH=${2}
|
||||
@@ -49,21 +52,27 @@ function build() {
|
||||
fi
|
||||
fi
|
||||
|
||||
cp "$ROOT"/configs/api.template.yaml "$DIST"/configs
|
||||
cp "$ROOT"/configs/api_node.template.yaml "$DIST"/configs
|
||||
cp "$ROOT"/configs/cluster.template.yaml "$DIST"/configs
|
||||
cp -R "$ROOT"/www "$DIST"/
|
||||
cp -R "$ROOT"/pages "$DIST"/
|
||||
|
||||
# we support TOA on linux/amd64 only
|
||||
if [ "$OS" == "linux" -a "$ARCH" == "amd64" ]
|
||||
# we support TOA on linux only
|
||||
if [ "$OS" == "linux" ] && [ -f "${ROOT}/edge-toa/edge-toa-${ARCH}" ]
|
||||
then
|
||||
cp -R "$ROOT"/edge-toa "$DIST"
|
||||
if [ ! -d "$DIST/edge-toa" ]
|
||||
then
|
||||
mkdir "$DIST/edge-toa"
|
||||
fi
|
||||
cp "${ROOT}/edge-toa/edge-toa-${ARCH}" "$DIST/edge-toa/edge-toa"
|
||||
fi
|
||||
|
||||
echo "building ..."
|
||||
|
||||
CC_PATH=""
|
||||
CXX_PATH=""
|
||||
CGO_LDFLAGS=""
|
||||
CGO_CFLAGS=""
|
||||
BUILD_TAG=$TAG
|
||||
if [[ `uname -a` == *"Darwin"* && "${OS}" == "linux" ]]; then
|
||||
if [ "${ARCH}" == "amd64" ]; then
|
||||
@@ -73,7 +82,7 @@ function build() {
|
||||
CC_PATH="x86_64-unknown-linux-gnu-gcc"
|
||||
CXX_PATH="x86_64-unknown-linux-gnu-g++"
|
||||
if [ "$TAG" = "plus" ]; then
|
||||
BUILD_TAG="plus,script"
|
||||
BUILD_TAG="plus,script,packet"
|
||||
fi
|
||||
else
|
||||
CC_PATH="x86_64-linux-musl-gcc"
|
||||
@@ -91,7 +100,7 @@ function build() {
|
||||
CC_PATH="aarch64-unknown-linux-gnu-gcc"
|
||||
CXX_PATH="aarch64-unknown-linux-gnu-g++"
|
||||
if [ "$TAG" = "plus" ]; then
|
||||
BUILD_TAG="plus,script"
|
||||
BUILD_TAG="plus,script,packet"
|
||||
fi
|
||||
else
|
||||
CC_PATH="aarch64-linux-musl-gcc"
|
||||
@@ -111,10 +120,31 @@ function build() {
|
||||
CXX_PATH="mips64el-linux-musl-g++"
|
||||
fi
|
||||
fi
|
||||
|
||||
# libpcap
|
||||
if [ "$OS" == "linux" ] && [[ "$ARCH" == "amd64" || "$ARCH" == "arm64" ]] && [ "$TAG" == "plus" ]; then
|
||||
CGO_LDFLAGS="-L${SRCDIR}/libs/libpcap/${ARCH} -lpcap -L${SRCDIR}/libs/libbrotli/${ARCH} -lbrotlienc -lbrotlidec -lbrotlicommon"
|
||||
CGO_CFLAGS="-I${SRCDIR}/libs/libpcap/src/libpcap -I${SRCDIR}/libs/libpcap/src/libpcap/pcap -I${SRCDIR}/libs/libbrotli/src/brotli/c/include"
|
||||
fi
|
||||
|
||||
if [ ! -z $CC_PATH ]; then
|
||||
env CC=$MUSL_DIR/$CC_PATH CXX=$MUSL_DIR/$CXX_PATH GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
env CC=$MUSL_DIR/$CC_PATH \
|
||||
CXX=$MUSL_DIR/$CXX_PATH GOOS="${OS}" \
|
||||
GOARCH="${ARCH}" CGO_ENABLED=1 \
|
||||
CGO_LDFLAGS="${CGO_LDFLAGS}" \
|
||||
CGO_CFLAGS="${CGO_CFLAGS}" \
|
||||
go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags "-linkmode external -extldflags -static -s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
else
|
||||
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 go build -trimpath -tags $TAG -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
if [[ `uname` == *"Linux"* ]] && [ "$OS" == "linux" ] && [[ "$ARCH" == "amd64" || "$ARCH" == "arm64" ]] && [ "$TAG" == "plus" ]; then
|
||||
BUILD_TAG="plus,script,packet"
|
||||
fi
|
||||
|
||||
env GOOS="${OS}" GOARCH="${ARCH}" CGO_ENABLED=1 CGO_LDFLAGS="${CGO_LDFLAGS}" CGO_CFLAGS="${CGO_CFLAGS}" go build -trimpath -tags $BUILD_TAG -o "$DIST"/bin/${NAME} -ldflags="-s -w" "$ROOT"/../cmd/edge-node/main.go
|
||||
fi
|
||||
|
||||
if [ ! -f "${DIST}/bin/${NAME}" ]; then
|
||||
echo "build failed!"
|
||||
exit
|
||||
fi
|
||||
|
||||
# delete hidden files
|
||||
|
||||
2
build/configs/.gitignore
vendored
2
build/configs/.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
node.json
|
||||
api.yaml
|
||||
api_node.yaml
|
||||
cluster.yaml
|
||||
api_cluster.yaml
|
||||
*.cache
|
||||
@@ -1,2 +1,2 @@
|
||||
* `api.template.yaml` - API相关配置模板
|
||||
* `api_node.template.yaml` - API相关配置模板
|
||||
* `cluster.template.yaml` - 通过集群自动接入节点模板
|
||||
@@ -1,4 +0,0 @@
|
||||
rpc:
|
||||
endpoints: [ "" ]
|
||||
nodeId: ""
|
||||
secret: ""
|
||||
3
build/configs/api_node.template.yaml
Normal file
3
build/configs/api_node.template.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
rpc.endpoints: [ "" ]
|
||||
nodeId: ""
|
||||
secret: ""
|
||||
Binary file not shown.
11
build/test.sh
Executable file
11
build/test.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
TAG=${1}
|
||||
|
||||
if [ -z "$TAG" ]; then
|
||||
TAG="community"
|
||||
fi
|
||||
|
||||
# reference: https://pkg.go.dev/cmd/go/internal/test
|
||||
go clean -testcache
|
||||
go test -timeout 10s -tags="${TAG}" -cover ../...
|
||||
@@ -5,28 +5,106 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/apps"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/nodes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"gopkg.in/yaml.v3"
|
||||
"net"
|
||||
"net/http"
|
||||
_ "net/http/pprof"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var app = apps.NewAppCmd().
|
||||
Version(teaconst.Version).
|
||||
Product(teaconst.ProductName).
|
||||
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|pprof|accesslog]").
|
||||
Usage(teaconst.ProcessName + " [trackers|goman|conns|gc]").
|
||||
Usage(teaconst.ProcessName + " [-v|start|stop|restart|status|quit|test|reload|service|daemon|config|pprof|accesslog|uninstall]").
|
||||
Usage(teaconst.ProcessName + " [trackers|goman|conns|gc|bandwidth|disk|cache.garbage]").
|
||||
Usage(teaconst.ProcessName + " [ip.drop|ip.reject|ip.remove|ip.close] IP")
|
||||
|
||||
app.On("start:before", func() {
|
||||
// validate config
|
||||
_, err := configs.LoadAPIConfig()
|
||||
if err != nil {
|
||||
// validate cluster config
|
||||
_, clusterErr := configs.LoadClusterConfig()
|
||||
if clusterErr != nil { // fail again
|
||||
fmt.Println("[ERROR]start failed: load api config from '" + Tea.ConfigFile(configs.ConfigFileName) + "' failed: " + err.Error())
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
})
|
||||
app.On("uninstall", func() {
|
||||
// service
|
||||
fmt.Println("Uninstall service ...")
|
||||
var manager = utils.NewServiceManager(teaconst.ProcessName, teaconst.ProductName)
|
||||
go func() {
|
||||
_ = manager.Uninstall()
|
||||
}()
|
||||
|
||||
// stop
|
||||
fmt.Println("Stopping ...")
|
||||
_, _ = gosock.NewTmpSock(teaconst.ProcessName).SendTimeout(&gosock.Command{Code: "stop"}, 1*time.Second)
|
||||
|
||||
// delete files
|
||||
var exe, _ = os.Executable()
|
||||
if len(exe) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var dir = filepath.Dir(filepath.Dir(exe)) // ROOT / bin / exe
|
||||
|
||||
// verify dir
|
||||
{
|
||||
fmt.Println("Checking '" + dir + "' ...")
|
||||
for _, subDir := range []string{"bin/" + filepath.Base(exe), "configs", "logs"} {
|
||||
_, err := os.Stat(dir + "/" + subDir)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]program directory structure has been broken, please remove it manually.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("Removing '" + dir + "' ...")
|
||||
err := os.RemoveAll(dir)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]remove failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// delete symbolic links
|
||||
fmt.Println("Removing symbolic links ...")
|
||||
_ = os.Remove("/usr/bin/" + teaconst.ProcessName)
|
||||
_ = os.Remove("/var/log/" + teaconst.ProcessName)
|
||||
|
||||
// delete configs
|
||||
// nothing to delete for EdgeNode
|
||||
|
||||
// delete sock
|
||||
fmt.Println("Removing temporary files ...")
|
||||
var tempDir = os.TempDir()
|
||||
_ = os.Remove(tempDir + "/" + teaconst.ProcessName + ".sock")
|
||||
_ = os.Remove(tempDir + "/" + teaconst.AccessLogSockName)
|
||||
|
||||
// cache ...
|
||||
fmt.Println("Please delete cache directories by yourself.")
|
||||
|
||||
// done
|
||||
fmt.Println("[DONE]")
|
||||
})
|
||||
app.On("test", func() {
|
||||
err := nodes.NewNode().Test()
|
||||
if err != nil {
|
||||
@@ -151,11 +229,18 @@ func main() {
|
||||
})
|
||||
app.On("gc", func() {
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
_, err := sock.Send(&gosock.Command{Code: "gc"})
|
||||
reply, err := sock.Send(&gosock.Command{Code: "gc"})
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]" + err.Error())
|
||||
} else {
|
||||
fmt.Println("ok")
|
||||
if reply == nil {
|
||||
fmt.Println("ok")
|
||||
} else {
|
||||
var paramMap = maps.NewMap(reply.Params)
|
||||
var pauseMS = paramMap.GetFloat64("pauseMS")
|
||||
var costMS = paramMap.GetFloat64("costMS")
|
||||
fmt.Printf("ok, cost: %.4fms, pause: %.4fms", costMS, pauseMS)
|
||||
}
|
||||
}
|
||||
})
|
||||
app.On("ip.drop", func() {
|
||||
@@ -365,6 +450,116 @@ func main() {
|
||||
}
|
||||
fmt.Println(string(statsJSON))
|
||||
})
|
||||
app.On("disk", func() {
|
||||
var args = os.Args[2:]
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
case "speed":
|
||||
speedMB, isFast, err := fsutils.CheckDiskIsFast()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]" + err.Error())
|
||||
} else {
|
||||
fmt.Printf("Speed: %.0fMB/s\n", speedMB)
|
||||
if isFast {
|
||||
fmt.Println("IsFast: true")
|
||||
} else {
|
||||
fmt.Println("IsFast: false")
|
||||
}
|
||||
}
|
||||
default:
|
||||
fmt.Println("Usage: edge-node disk [speed]")
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Usage: edge-node disk [speed]")
|
||||
}
|
||||
})
|
||||
app.On("cache.garbage", func() {
|
||||
fmt.Println("scanning ...")
|
||||
|
||||
var shouldDelete bool
|
||||
for _, arg := range os.Args {
|
||||
if strings.TrimLeft(arg, "-") == "delete" {
|
||||
shouldDelete = true
|
||||
}
|
||||
}
|
||||
|
||||
var progressSock = gosock.NewTmpSock(teaconst.CacheGarbageSockName)
|
||||
progressSock.OnCommand(func(cmd *gosock.Command) {
|
||||
var params = maps.NewMap(cmd.Params)
|
||||
if cmd.Code == "progress" {
|
||||
fmt.Printf("%.2f%% %d\n", params.GetFloat64("progress")*100, params.GetInt("count"))
|
||||
_ = cmd.ReplyOk()
|
||||
}
|
||||
})
|
||||
go func() {
|
||||
_ = progressSock.Listen()
|
||||
}()
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
var sock = gosock.NewTmpSock(teaconst.ProcessName)
|
||||
reply, err := sock.Send(&gosock.Command{
|
||||
Code: "cache.garbage",
|
||||
Params: map[string]any{"delete": shouldDelete},
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]" + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var params = maps.NewMap(reply.Params)
|
||||
if params.GetBool("isOk") {
|
||||
var count = params.GetInt("count")
|
||||
fmt.Println("found", count, "bad caches")
|
||||
|
||||
if count > 0 {
|
||||
fmt.Println("======")
|
||||
var sampleFiles = params.GetSlice("sampleFiles")
|
||||
for _, file := range sampleFiles {
|
||||
fmt.Println(types.String(file))
|
||||
}
|
||||
if count > len(sampleFiles) {
|
||||
fmt.Println("... more files")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Println("[ERROR]" + params.GetString("error"))
|
||||
}
|
||||
})
|
||||
app.On("config", func() {
|
||||
var configString = os.Args[len(os.Args)-1]
|
||||
if configString == "config" {
|
||||
fmt.Println("Usage: edge-node config '\nrpc.endpoints: [\"...\"]\nnodeId: \"...\"\nsecret: \"...\"\n'")
|
||||
return
|
||||
}
|
||||
|
||||
var config = &configs.APIConfig{}
|
||||
err := yaml.Unmarshal([]byte(configString), config)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]decode config failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]validate config failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// marshal again
|
||||
configYAML, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]encode config failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile(Tea.Root + "/configs/api_node.yaml", configYAML, 0666)
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]write config failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("success")
|
||||
})
|
||||
app.Run(func() {
|
||||
var node = nodes.NewNode()
|
||||
node.Start()
|
||||
|
||||
80
go.mod
80
go.mod
@@ -5,60 +5,78 @@ go 1.18
|
||||
replace (
|
||||
github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
|
||||
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 (
|
||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||
github.com/andybalholm/brotli v1.0.4
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible
|
||||
github.com/andybalholm/brotli v1.0.5
|
||||
github.com/aws/aws-sdk-go v1.44.279
|
||||
github.com/baidubce/bce-sdk-go v0.9.153
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||
github.com/fsnotify/fsnotify v1.5.1
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/golang/protobuf v1.5.2
|
||||
github.com/google/nftables v0.0.0-20220407195405-950e408d48c6
|
||||
github.com/iwind/TeaGo v0.0.0-20220807030847-31de8e1cbe55
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/google/nftables v0.1.0
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible
|
||||
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d
|
||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
|
||||
github.com/iwind/gowebp v0.0.0-20220819053541-c235395277b5
|
||||
github.com/klauspost/compress v1.15.8
|
||||
github.com/mattn/go-sqlite3 v1.14.9
|
||||
github.com/mdlayher/netlink v1.4.2
|
||||
github.com/iwind/gowebp v0.0.0-20231026013903-1c22b0d78cc4
|
||||
github.com/klauspost/compress v1.17.2
|
||||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
github.com/mdlayher/netlink v1.7.1
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/mssola/user_agent v0.5.3
|
||||
github.com/mssola/useragent v1.0.0
|
||||
github.com/pires/go-proxyproto v0.6.1
|
||||
github.com/qiniu/go-sdk/v7 v7.16.0
|
||||
github.com/quic-go/quic-go v0.39.2
|
||||
github.com/shirou/gopsutil/v3 v3.22.2
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab
|
||||
golang.org/x/text v0.3.7
|
||||
google.golang.org/grpc v1.45.0
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.41
|
||||
golang.org/x/image v0.13.0
|
||||
golang.org/x/net v0.17.0
|
||||
golang.org/x/sys v0.13.0
|
||||
google.golang.org/grpc v1.59.0
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
rogchap.com/v8go v0.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.4.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/chai2010/webp v1.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/clbanning/mxj v1.8.4 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-sql-driver/mysql v1.5.0 // indirect
|
||||
github.com/google/go-cmp v0.5.7 // indirect
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/google/go-cmp v0.5.9 // indirect
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/native v1.0.0 // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb // indirect
|
||||
github.com/mdlayher/socket v0.4.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/mozillazg/go-httpheader v0.2.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
||||
github.com/quic-go/qpack v0.4.0 // indirect
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 // indirect
|
||||
github.com/tdewolff/minify/v2 v2.12.7 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.6.6 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.9 // indirect
|
||||
github.com/tklauser/numcpus v0.3.0 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/tools v0.1.8 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
honnef.co/go/tools v0.2.2 // indirect
|
||||
go.uber.org/mock v0.3.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
||||
golang.org/x/mod v0.13.0 // indirect
|
||||
golang.org/x/sync v0.4.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.14.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||
)
|
||||
|
||||
389
go.sum
389
go.sum
@@ -1,32 +1,23 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
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 v2.2.7+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/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/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.153/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg=
|
||||
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
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.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chai2010/webp v1.1.1 h1:jTRmEccAJ4MGrhFOrPMpNGIJ/eybIgwKpcACsrTEapk=
|
||||
github.com/chai2010/webp v1.1.1/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
||||
github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
|
||||
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
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/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/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
|
||||
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -35,296 +26,228 @@ github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ
|
||||
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/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
|
||||
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-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/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 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/nftables v0.0.0-20220407195405-950e408d48c6 h1:btadZscaRmsi/+fOhkyUguRpSnrf6dykNEWxDeUCj9I=
|
||||
github.com/google/nftables v0.0.0-20220407195405-950e408d48c6/go.mod h1:0F8on3JWMkm+xahTHItkiu/E1SPqMd0TOxNweQv8ptE=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/iwind/TeaGo v0.0.0-20220807030847-31de8e1cbe55 h1:shQNx0flJFBwKsGE7Hs3bI2bDz+YF0zl/4qE8B2KRiY=
|
||||
github.com/iwind/TeaGo v0.0.0-20220807030847-31de8e1cbe55/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/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/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
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/pprof v0.0.0-20231023181126-ff6d637d2a7b h1:RMpPgZTSApbPf7xaVel+QkoGPRLFLrwFO89uDUHEGf0=
|
||||
github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b/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.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
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/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d h1:XnTIj781NdSipts60fVqbgZorVAKVSRaA6nqVNfBQ1g=
|
||||
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d/go.mod h1:fi/Pq+/5m2HZoseM+39dMF57ANXRt6w4PkGu3NXPc5s=
|
||||
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/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/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/gowebp v0.0.0-20220819053541-c235395277b5 h1:SgKhrqRhWVpu0ZKxQM8MGjdhy7hlH3Xax8snwsZWSic=
|
||||
github.com/iwind/gowebp v0.0.0-20220819053541-c235395277b5/go.mod h1:Re7TEhwL+ygnxFg52fC0PWy01ULAIZp2QR0q5WwEOQA=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
|
||||
github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786 h1:N527AHMa793TP5z5GNAn/VLPzlc0ewzWdeP/25gDfgQ=
|
||||
github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs=
|
||||
github.com/iwind/gowebp v0.0.0-20231026013903-1c22b0d78cc4 h1:eyymORsZg0tZ0niyolYF4nao4sdNUI+Ll40s96tKHBY=
|
||||
github.com/iwind/gowebp v0.0.0-20231026013903-1c22b0d78cc4/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/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/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk=
|
||||
github.com/josharian/native v1.0.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/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||
github.com/klauspost/compress v1.15.8 h1:JahtItbkWjf2jzm/T+qgMxkP9EMHsqEUA6vCMGmXvhA=
|
||||
github.com/klauspost/compress v1.15.8/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4=
|
||||
github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
||||
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.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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/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/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
|
||||
github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60 h1:tHdB+hQRHU10CfcK0furo6rSNgZ38JT8uPh70c/pFD8=
|
||||
github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60/go.mod h1:aYbhishWc4Ai3I2U4Gaa2n3kHWSwzme6EsG/46HRQbE=
|
||||
github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
|
||||
github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
|
||||
github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
|
||||
github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
|
||||
github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
|
||||
github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
|
||||
github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
|
||||
github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||
github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
|
||||
github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
|
||||
github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
|
||||
github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q=
|
||||
github.com/mdlayher/netlink v1.4.2 h1:3sbnJWe/LETovA7yRZIX3f9McVOWV3OySH6iIBxiFfI=
|
||||
github.com/mdlayher/netlink v1.4.2/go.mod h1:13VaingaArGUTUxFLf/iEovKxXji32JAtF858jZYEug=
|
||||
github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc=
|
||||
github.com/mdlayher/socket v0.0.0-20211007213009-516dcbdf0267/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
|
||||
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb h1:2dC7L10LmTqlyMVzFJ00qM25lqESg9Z4u3GuEXN5iHY=
|
||||
github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
|
||||
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/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mdlayher/netlink v1.7.1 h1:FdUaT/e33HjEXagwELR8R3/KL1Fq5x3G5jgHLp/BTmg=
|
||||
github.com/mdlayher/netlink v1.7.1/go.mod h1:nKO5CSjE/DJjVhk/TNp6vCE1ktVxEA8VEh8drhZzxsQ=
|
||||
github.com/mdlayher/socket v0.4.0 h1:280wsy40IC9M9q1uPGcLBwXpcTQDtoGwVt+BNoITxIw=
|
||||
github.com/mdlayher/socket v0.4.0/go.mod h1:xxFqz5GRCUN3UEOm9CZqEJsAbe1C8OwSK46NlmWuVoc=
|
||||
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/mssola/user_agent v0.5.3 h1:lBRPML9mdFuIZgI2cmlQ+atbpJdLdeVl2IDodjBR578=
|
||||
github.com/mssola/user_agent v0.5.3/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw=
|
||||
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
|
||||
github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
|
||||
github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o=
|
||||
github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0 h1:0jY9lJquiL8fcf3M4LAXN5aMlS/b2BV86HFFPCPMgE4=
|
||||
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
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/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/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
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/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYXOwye868w=
|
||||
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/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
|
||||
github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs=
|
||||
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.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.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
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/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/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
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/tdewolff/minify/v2 v2.12.7/go.mod h1:ZRKTheiOGyLSK8hOZWWv+YoJAECzDivNgAlVYDHp/Ws=
|
||||
github.com/tdewolff/parse/v2 v2.6.6 h1:Yld+0CrKUJaCV78DL1G2nk3C9lKrxyRTux5aaK/AkDo=
|
||||
github.com/tdewolff/parse/v2 v2.6.6/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs=
|
||||
github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tdewolff/test v1.0.9 h1:SswqJCmeN4B+9gEAi/5uqT0qpi1y2/2O47V/1hhGZT0=
|
||||
github.com/tdewolff/test v1.0.9/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
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/tencentyun/cos-go-sdk-v5 v0.7.41 h1:iU0Li/Np78H4SBna0ECQoF3mpgi6ImLXU+doGzPFXGc=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.41/go.mod h1:4dCEtLHGh8QPxHEkgq+nFaky7yZxQuYwgSJM87icDaw=
|
||||
github.com/tklauser/go-sysconf v0.3.9 h1:JeUVdAOWhhxVcU6Eqr/ATFHgXk/mmiItdKeJPev3vTo=
|
||||
github.com/tklauser/go-sysconf v0.3.9/go.mod h1:11DU/5sG7UexIrp/O6g35hrWzu0JxlwQ3LSFUzyeuhs=
|
||||
github.com/tklauser/numcpus v0.3.0 h1:ILuRUQBtssgnxw0XXIjKUC56fgnOrFoQQ/4+DeU2biQ=
|
||||
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/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
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/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/mock v0.3.0 h1:3mUxI1No2/60yUYax92Pt8eNOEecx2D3lcXZh2NEZJo=
|
||||
go.uber.org/mock v0.3.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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539 h1:/eM0PCrQI2xd471rI+snWuu251/+/jpBpZqir2mPdnU=
|
||||
golang.org/x/image v0.0.0-20220722155232-062f8c9fd539/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
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.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
|
||||
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
|
||||
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.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.13.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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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 h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/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-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/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-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||
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-20220811171246-fbc7d0a398ab/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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
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.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
|
||||
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e h1:fNKDNuUyC4WH+inqDMpfXDdfvwfYILbsX+oskGZ8hxg=
|
||||
google.golang.org/genproto v0.0.0-20220317150908-0efb43f6373e/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M=
|
||||
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
|
||||
google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
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 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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/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.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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
|
||||
honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk=
|
||||
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
|
||||
rogchap.com/v8go v0.7.0 h1:kgjbiO4zE5itA962ze6Hqmbs4HgZbGzmueCXsZtremg=
|
||||
rogchap.com/v8go v0.7.0/go.mod h1:MxgP3pL2MW4dpme/72QRs8sgNMmM0pRc8DPhcuLWPAs=
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package apps
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -86,8 +89,8 @@ func (this *AppCmd) Print() {
|
||||
fmt.Println("")
|
||||
fmt.Println("Options:")
|
||||
|
||||
spaces := 20
|
||||
max := 40
|
||||
var spaces = 20
|
||||
var max = 40
|
||||
for _, option := range this.options {
|
||||
l := len(option.Code)
|
||||
if l < max && l > spaces {
|
||||
@@ -125,9 +128,12 @@ func (this *AppCmd) On(arg string, callback func()) {
|
||||
// Run 运行
|
||||
func (this *AppCmd) Run(main func()) {
|
||||
// 获取参数
|
||||
args := os.Args[1:]
|
||||
var args = os.Args[1:]
|
||||
if len(args) > 0 {
|
||||
switch args[0] {
|
||||
var mainArg = args[0]
|
||||
this.callDirective(mainArg + ":before")
|
||||
|
||||
switch mainArg {
|
||||
case "-v", "version", "-version", "--version":
|
||||
this.runVersion()
|
||||
return
|
||||
@@ -150,19 +156,19 @@ func (this *AppCmd) Run(main func()) {
|
||||
|
||||
// 查找指令
|
||||
for _, directive := range this.directives {
|
||||
if directive.Arg == args[0] {
|
||||
if directive.Arg == mainArg {
|
||||
directive.Callback()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("unknown command '" + args[0] + "'")
|
||||
fmt.Println("unknown command '" + mainArg + "'")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 日志
|
||||
writer := new(LogWriter)
|
||||
var writer = new(LogWriter)
|
||||
writer.Init()
|
||||
logs.SetWriter(writer)
|
||||
|
||||
@@ -190,7 +196,7 @@ func (this *AppCmd) runStart() {
|
||||
|
||||
_ = os.Setenv("EdgeBackground", "on")
|
||||
|
||||
cmd := exec.Command(os.Args[0])
|
||||
var cmd = exec.Command(this.exe())
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Foreground: false,
|
||||
Setsid: true,
|
||||
@@ -202,6 +208,9 @@ func (this *AppCmd) runStart() {
|
||||
return
|
||||
}
|
||||
|
||||
// create symbolic links
|
||||
_ = this.createSymLinks()
|
||||
|
||||
fmt.Println(this.product+" started ok, pid:", cmd.Process.Pid)
|
||||
}
|
||||
|
||||
@@ -215,7 +224,7 @@ func (this *AppCmd) runStop() {
|
||||
|
||||
// 从systemd中停止
|
||||
if runtime.GOOS == "linux" {
|
||||
systemctl, _ := exec.LookPath("systemctl")
|
||||
systemctl, _ := executils.LookPath("systemctl")
|
||||
if len(systemctl) > 0 {
|
||||
go func() {
|
||||
// 有可能会长时间执行,这里不阻塞进程
|
||||
@@ -276,3 +285,69 @@ func (this *AppCmd) ParseOptions(args []string) map[string][]string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (this *AppCmd) exe() string {
|
||||
var exe, _ = os.Executable()
|
||||
if len(exe) == 0 {
|
||||
exe = os.Args[0]
|
||||
}
|
||||
return exe
|
||||
}
|
||||
|
||||
// 创建软链接
|
||||
func (this *AppCmd) createSymLinks() error {
|
||||
if runtime.GOOS != "linux" {
|
||||
return nil
|
||||
}
|
||||
|
||||
var exe, _ = os.Executable()
|
||||
if len(exe) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errorList = []string{}
|
||||
|
||||
// bin
|
||||
{
|
||||
var target = "/usr/bin/" + teaconst.ProcessName
|
||||
old, _ := filepath.EvalSymlinks(target)
|
||||
if old != exe {
|
||||
_ = os.Remove(target)
|
||||
err := os.Symlink(exe, target)
|
||||
if err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// log
|
||||
{
|
||||
var realPath = filepath.Dir(filepath.Dir(exe)) + "/logs/run.log"
|
||||
var target = "/var/log/" + teaconst.ProcessName + ".log"
|
||||
old, _ := filepath.EvalSymlinks(target)
|
||||
if old != realPath {
|
||||
_ = os.Remove(target)
|
||||
err := os.Symlink(realPath, target)
|
||||
if err != nil {
|
||||
errorList = append(errorList, err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(errorList) > 0 {
|
||||
return errors.New(strings.Join(errorList, "\n"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *AppCmd) callDirective(code string) {
|
||||
for _, directive := range this.directives {
|
||||
if directive.Arg == code {
|
||||
if directive.Callback != nil {
|
||||
directive.Callback()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@ import "errors"
|
||||
// 常用的几个错误
|
||||
var (
|
||||
ErrNotFound = errors.New("cache not found")
|
||||
ErrFileIsWriting = errors.New("the file is writing")
|
||||
ErrFileIsWriting = errors.New("the cache file is updating")
|
||||
ErrInvalidRange = errors.New("invalid range")
|
||||
ErrEntityTooLarge = errors.New("entity too large")
|
||||
ErrWritingUnavailable = errors.New("writing unavailable")
|
||||
ErrWritingQueueFull = errors.New("writing queue full")
|
||||
ErrTooManyOpenFiles = errors.New("too many open files")
|
||||
ErrServerIsBusy = errors.New("server is busy")
|
||||
)
|
||||
|
||||
// CapacityError 容量错误
|
||||
@@ -34,16 +34,14 @@ func CanIgnoreErr(err error) bool {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
if err == ErrFileIsWriting ||
|
||||
err == ErrEntityTooLarge ||
|
||||
err == ErrWritingUnavailable ||
|
||||
err == ErrWritingQueueFull ||
|
||||
err == ErrTooManyOpenFiles {
|
||||
if errors.Is(err, ErrFileIsWriting) ||
|
||||
errors.Is(err, ErrEntityTooLarge) ||
|
||||
errors.Is(err, ErrWritingUnavailable) ||
|
||||
errors.Is(err, ErrWritingQueueFull) ||
|
||||
errors.Is(err, ErrServerIsBusy) {
|
||||
return true
|
||||
}
|
||||
_, ok := err.(*CapacityError)
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
var capacityErr *CapacityError
|
||||
return errors.As(err, &capacityErr)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package caches
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCanIgnoreErr(t *testing.T) {
|
||||
a := assert.NewAssertion(t)
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
a.IsTrue(CanIgnoreErr(ErrFileIsWriting))
|
||||
a.IsTrue(CanIgnoreErr(NewCapacityError("over capcity")))
|
||||
a.IsFalse(CanIgnoreErr(ErrNotFound))
|
||||
a.IsTrue(caches.CanIgnoreErr(caches.ErrFileIsWriting))
|
||||
a.IsTrue(caches.CanIgnoreErr(fmt.Errorf("error: %w", caches.ErrFileIsWriting)))
|
||||
a.IsTrue(errors.Is(fmt.Errorf("error: %w", caches.ErrFileIsWriting), caches.ErrFileIsWriting))
|
||||
a.IsTrue(errors.Is(caches.ErrFileIsWriting, caches.ErrFileIsWriting))
|
||||
a.IsTrue(caches.CanIgnoreErr(caches.NewCapacityError("over capacity")))
|
||||
a.IsTrue(caches.CanIgnoreErr(fmt.Errorf("error: %w", caches.NewCapacityError("over capacity"))))
|
||||
a.IsFalse(caches.CanIgnoreErr(caches.ErrNotFound))
|
||||
a.IsFalse(caches.CanIgnoreErr(errors.New("test error")))
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package caches
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ItemType = int
|
||||
@@ -16,7 +15,7 @@ const (
|
||||
// 计算当前周
|
||||
// 不要用YW,因为需要计算两周是否临近
|
||||
func currentWeek() int32 {
|
||||
return int32(time.Now().Unix() / 86400)
|
||||
return int32(fasttime.Now().Unix() / 86400)
|
||||
}
|
||||
|
||||
type Item struct {
|
||||
@@ -29,10 +28,7 @@ type Item struct {
|
||||
MetaSize int64 `json:"metaSize"`
|
||||
Host string `json:"host"` // 主机名
|
||||
ServerId int64 `json:"serverId"` // 服务ID
|
||||
|
||||
Week1Hits int64 `json:"week1Hits"`
|
||||
Week2Hits int64 `json:"week2Hits"`
|
||||
Week int32 `json:"week"`
|
||||
Week int32 `json:"week"`
|
||||
}
|
||||
|
||||
func (this *Item) IsExpired() bool {
|
||||
@@ -47,20 +43,6 @@ func (this *Item) Size() int64 {
|
||||
return this.HeaderSize + this.BodySize
|
||||
}
|
||||
|
||||
func (this *Item) IncreaseHit(week int32) {
|
||||
if this.Week == week {
|
||||
this.Week2Hits++
|
||||
} else {
|
||||
if week-this.Week == 1 {
|
||||
this.Week1Hits = this.Week2Hits
|
||||
} else {
|
||||
this.Week1Hits = 0
|
||||
}
|
||||
this.Week2Hits = 1
|
||||
this.Week = week
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Item) RequestURI() string {
|
||||
var schemeIndex = strings.Index(this.Key, "://")
|
||||
if schemeIndex <= 0 {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package caches
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -11,27 +13,18 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestItem_IncreaseHit(t *testing.T) {
|
||||
var week = currentWeek()
|
||||
|
||||
var item = &Item{}
|
||||
//item.Week = 2704
|
||||
item.Week2Hits = 100
|
||||
item.IncreaseHit(week)
|
||||
t.Log("week:", item.Week, "week1:", item.Week1Hits, "week2:", item.Week2Hits)
|
||||
|
||||
item.IncreaseHit(week)
|
||||
t.Log("week:", item.Week, "week1:", item.Week1Hits, "week2:", item.Week2Hits)
|
||||
}
|
||||
|
||||
func TestItems_Memory(t *testing.T) {
|
||||
var stat = &runtime.MemStats{}
|
||||
runtime.ReadMemStats(stat)
|
||||
var memory1 = stat.HeapInuse
|
||||
|
||||
var items = []*Item{}
|
||||
for i := 0; i < 10_000_000; i++ {
|
||||
items = append(items, &Item{
|
||||
var items = []*caches.Item{}
|
||||
var count = 100
|
||||
if testutils.IsSingleTesting() {
|
||||
count = 10_000_000
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
items = append(items, &caches.Item{
|
||||
Key: types.String(i),
|
||||
})
|
||||
}
|
||||
@@ -41,18 +34,13 @@ func TestItems_Memory(t *testing.T) {
|
||||
|
||||
t.Log(memory1, memory2, (memory2-memory1)/1024/1024, "M")
|
||||
|
||||
var weekItems = make(map[string]*Item, 10_000_000)
|
||||
|
||||
for _, item := range items {
|
||||
weekItems[item.Key] = item
|
||||
}
|
||||
|
||||
runtime.ReadMemStats(stat)
|
||||
var memory3 = stat.HeapInuse
|
||||
t.Log(memory2, memory3, (memory3-memory2)/1024/1024, "M")
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
t.Log(len(items), len(weekItems))
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func TestItems_Memory2(t *testing.T) {
|
||||
@@ -61,7 +49,12 @@ func TestItems_Memory2(t *testing.T) {
|
||||
var memory1 = stat.HeapInuse
|
||||
|
||||
var items = map[int32]map[string]zero.Zero{}
|
||||
for i := 0; i < 10_000_000; i++ {
|
||||
var count = 100
|
||||
if testutils.IsSingleTesting() {
|
||||
count = 10_000_000
|
||||
}
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
var week = int32((time.Now().Unix() - int64(86400*rands.Int(0, 300))) / (86400 * 7))
|
||||
m, ok := items[week]
|
||||
if !ok {
|
||||
@@ -76,7 +69,9 @@ func TestItems_Memory2(t *testing.T) {
|
||||
|
||||
t.Log(memory1, memory2, (memory2-memory1)/1024/1024, "M")
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
for w, i := range items {
|
||||
t.Log(w, len(i))
|
||||
}
|
||||
@@ -88,7 +83,7 @@ func TestItem_RequestURI(t *testing.T) {
|
||||
"https://goedge.cn:8080/hello/world",
|
||||
"https://goedge.cn/hello/world?v=1&t=123",
|
||||
} {
|
||||
var item = &Item{Key: u}
|
||||
var item = &caches.Item{Key: u}
|
||||
t.Log(u, "=>", item.RequestURI())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,18 @@ package caches
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/ttlcache"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -21,12 +25,11 @@ const CountFileDB = 20
|
||||
type FileList struct {
|
||||
dir string
|
||||
dbList [CountFileDB]*FileListDB
|
||||
total int64
|
||||
|
||||
onAdd func(item *Item)
|
||||
onRemove func(item *Item)
|
||||
|
||||
memoryCache *ttlcache.Cache
|
||||
memoryCache *ttlcache.Cache[zero.Zero]
|
||||
|
||||
// 老数据库地址
|
||||
oldDir string
|
||||
@@ -35,7 +38,7 @@ type FileList struct {
|
||||
func NewFileList(dir string) ListInterface {
|
||||
return &FileList{
|
||||
dir: dir,
|
||||
memoryCache: ttlcache.NewCache(),
|
||||
memoryCache: ttlcache.NewCache[zero.Zero](),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,25 +64,37 @@ func (this *FileList) Init() error {
|
||||
}
|
||||
|
||||
remotelogs.Println("CACHE", "loading database from '"+dir+"' ...")
|
||||
var wg = &sync.WaitGroup{}
|
||||
var locker = sync.Mutex{}
|
||||
var lastErr error
|
||||
|
||||
for i := 0; i < CountFileDB; i++ {
|
||||
var db = NewFileListDB()
|
||||
err = db.Open(dir + "/db-" + types.String(i) + ".db")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var db = NewFileListDB()
|
||||
dbErr := db.Open(dir + "/db-" + types.String(i) + ".db")
|
||||
if dbErr != nil {
|
||||
lastErr = dbErr
|
||||
return
|
||||
}
|
||||
|
||||
this.dbList[i] = db
|
||||
dbErr = db.Init()
|
||||
if dbErr != nil {
|
||||
lastErr = dbErr
|
||||
return
|
||||
}
|
||||
|
||||
locker.Lock()
|
||||
this.dbList[i] = db
|
||||
locker.Unlock()
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// 读取总数量
|
||||
this.total = 0
|
||||
for _, db := range this.dbList {
|
||||
this.total += db.total
|
||||
if lastErr != nil {
|
||||
return lastErr
|
||||
}
|
||||
|
||||
// 升级老版本数据库
|
||||
@@ -102,16 +117,12 @@ func (this *FileList) Add(hash string, item *Item) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := db.AddAsync(hash, item)
|
||||
err := db.AddSync(hash, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.total, 1)
|
||||
|
||||
// 这里不增加点击量,以减少对数据库的操作次数
|
||||
|
||||
this.memoryCache.Write(hash, 1, item.ExpiredAt)
|
||||
this.memoryCache.Write(hash, zero.Zero{}, this.maxExpiresAtForMemoryCache(item.ExpiredAt))
|
||||
|
||||
if this.onAdd != nil {
|
||||
this.onAdd(item)
|
||||
@@ -144,15 +155,27 @@ func (this *FileList) Exist(hash string) (bool, error) {
|
||||
var expiredAt int64
|
||||
err := row.Scan(&expiredAt)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
this.memoryCache.Write(hash, 1, expiredAt)
|
||||
this.memoryCache.Write(hash, zero.Zero{}, this.maxExpiresAtForMemoryCache(expiredAt))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (this *FileList) ExistQuick(hash string) (isReady bool, found bool) {
|
||||
var db = this.GetDB(hash)
|
||||
|
||||
if !db.IsReady() || !db.HashMapIsLoaded() {
|
||||
return
|
||||
}
|
||||
|
||||
isReady = true
|
||||
found = db.hashMap.Exist(hash)
|
||||
return
|
||||
}
|
||||
|
||||
// CleanPrefix 清理某个前缀的缓存数据
|
||||
func (this *FileList) CleanPrefix(prefix string) error {
|
||||
if len(prefix) == 0 {
|
||||
@@ -214,7 +237,7 @@ func (this *FileList) CleanMatchPrefix(prefix string) error {
|
||||
}
|
||||
|
||||
func (this *FileList) Remove(hash string) error {
|
||||
_, err := this.remove(hash)
|
||||
_, err := this.remove(hash, false)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -233,11 +256,16 @@ func (this *FileList) Purge(count int, callback func(hash string) error) (int, e
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if len(hashStrings) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
countFound += len(hashStrings)
|
||||
|
||||
// 不在 rows.Next() 循环中操作是为了避免死锁
|
||||
for _, hash := range hashStrings {
|
||||
err = this.Remove(hash)
|
||||
_, err = this.remove(hash, true)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -247,6 +275,11 @@ func (this *FileList) Purge(count int, callback func(hash string) error) (int, e
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = db.writeDB.Exec(`DELETE FROM "cacheItems" WHERE "hash" IN ('` + strings.Join(hashStrings, "', '") + `')`)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return countFound, nil
|
||||
@@ -264,24 +297,27 @@ func (this *FileList) PurgeLFU(count int, callback func(hash string) error) erro
|
||||
return err
|
||||
}
|
||||
|
||||
if len(hashStrings) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 不在 rows.Next() 循环中操作是为了避免死锁
|
||||
for _, hash := range hashStrings {
|
||||
notFound, err := this.remove(hash)
|
||||
_, err = this.remove(hash, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if notFound {
|
||||
err = db.DeleteHitAsync(hash)
|
||||
if err != nil {
|
||||
return db.WrapError(err)
|
||||
}
|
||||
}
|
||||
|
||||
err = callback(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = db.writeDB.Exec(`DELETE FROM "cacheItems" WHERE "hash" IN ('` + strings.Join(hashStrings, "', '") + `')`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -296,8 +332,6 @@ func (this *FileList) CleanAll() error {
|
||||
}
|
||||
}
|
||||
|
||||
atomic.StoreInt64(&this.total, 0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -332,7 +366,15 @@ func (this *FileList) Stat(check func(hash string) bool) (*Stat, error) {
|
||||
// Count 总数量
|
||||
// 常用的方法,所以避免直接查询数据库
|
||||
func (this *FileList) Count() (int64, error) {
|
||||
return atomic.LoadInt64(&this.total), nil
|
||||
var total int64
|
||||
for _, db := range this.dbList {
|
||||
count, err := db.Total()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
total += count
|
||||
}
|
||||
return total, nil
|
||||
}
|
||||
|
||||
// IncreaseHit 增加点击量
|
||||
@@ -376,7 +418,16 @@ func (this *FileList) GetDB(hash string) *FileListDB {
|
||||
return this.dbList[fnv.HashString(hash)%CountFileDB]
|
||||
}
|
||||
|
||||
func (this *FileList) remove(hash string) (notFound bool, err error) {
|
||||
func (this *FileList) HashMapIsLoaded() bool {
|
||||
for _, db := range this.dbList {
|
||||
if !db.HashMapIsLoaded() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (this *FileList) remove(hash string, isDeleted bool) (notFound bool, err error) {
|
||||
var db = this.GetDB(hash)
|
||||
|
||||
if !db.IsReady() {
|
||||
@@ -392,37 +443,16 @@ func (this *FileList) remove(hash string) (notFound bool, err error) {
|
||||
// 从缓存中删除
|
||||
this.memoryCache.Delete(hash)
|
||||
|
||||
var row = db.selectByHashStmt.QueryRow(hash)
|
||||
if row.Err() != nil {
|
||||
if row.Err() == sql.ErrNoRows {
|
||||
return true, nil
|
||||
if !isDeleted {
|
||||
err = db.DeleteSync(hash)
|
||||
if err != nil {
|
||||
return false, db.WrapError(err)
|
||||
}
|
||||
return false, row.Err()
|
||||
}
|
||||
|
||||
var item = &Item{Type: ItemTypeFile}
|
||||
err = row.Scan(&item.Key, &item.HeaderSize, &item.BodySize, &item.MetaSize, &item.ExpiredAt)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return true, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
|
||||
err = db.DeleteAsync(hash)
|
||||
if err != nil {
|
||||
return false, db.WrapError(err)
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.total, -1)
|
||||
|
||||
err = db.DeleteHitAsync(hash)
|
||||
if err != nil {
|
||||
return false, db.WrapError(err)
|
||||
}
|
||||
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(item)
|
||||
// when remove file item, no any extra information needed
|
||||
this.onRemove(nil)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
@@ -450,7 +480,7 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
|
||||
remotelogs.Println("CACHE", "upgrading local database finished")
|
||||
}()
|
||||
|
||||
db, err := dbs.OpenWriter("file:" + indexDBPath + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=OFF&_locking_mode=EXCLUSIVE")
|
||||
db, err := dbs.OpenWriter("file:" + indexDBPath + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -515,9 +545,6 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
|
||||
MetaSize: metaSize,
|
||||
Host: host,
|
||||
ServerId: serverId,
|
||||
Week1Hits: 0,
|
||||
Week2Hits: 0,
|
||||
Week: 0,
|
||||
})
|
||||
if err != nil {
|
||||
if brokenOnError {
|
||||
@@ -537,3 +564,11 @@ func (this *FileList) UpgradeV3(oldDir string, brokenOnError bool) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileList) maxExpiresAtForMemoryCache(expiresAt int64) int64 {
|
||||
var maxTimestamp = fasttime.Now().Unix() + 3600
|
||||
if expiresAt > maxTimestamp {
|
||||
return maxTimestamp
|
||||
}
|
||||
return expiresAt
|
||||
}
|
||||
|
||||
@@ -4,19 +4,17 @@ package caches
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
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"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -28,17 +26,13 @@ type FileListDB struct {
|
||||
readDB *dbs.DB
|
||||
writeDB *dbs.DB
|
||||
|
||||
writeBatch *dbs.Batch
|
||||
|
||||
hashMap *FileListHashMap
|
||||
|
||||
itemsTableName string
|
||||
hitsTableName string
|
||||
|
||||
total int64
|
||||
|
||||
isClosed bool
|
||||
isReady bool
|
||||
isClosed bool // 是否已关闭
|
||||
isReady bool // 是否已完成初始化
|
||||
hashMapIsLoaded bool // Hash是否已加载
|
||||
|
||||
// cacheItems
|
||||
existsByHashStmt *dbs.Stmt // 根据hash检查是否存在
|
||||
@@ -53,16 +47,10 @@ type FileListDB struct {
|
||||
deleteByHashStmt *dbs.Stmt // 根据hash删除数据
|
||||
deleteByHashSQL string
|
||||
|
||||
statStmt *dbs.Stmt // 统计
|
||||
purgeStmt *dbs.Stmt // 清理
|
||||
deleteAllStmt *dbs.Stmt // 删除所有数据
|
||||
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
|
||||
updateAccessWeekSQL string // 修改访问日期
|
||||
|
||||
// hits
|
||||
insertHitSQL string // 写入数据
|
||||
increaseHitSQL string // 增加点击量
|
||||
deleteHitByHashSQL string // 根据hash删除数据
|
||||
statStmt *dbs.Stmt // 统计
|
||||
purgeStmt *dbs.Stmt // 清理
|
||||
deleteAllStmt *dbs.Stmt // 删除所有数据
|
||||
listOlderItemsStmt *dbs.Stmt // 读取较早存储的缓存
|
||||
}
|
||||
|
||||
func NewFileListDB() *FileListDB {
|
||||
@@ -75,17 +63,17 @@ func (this *FileListDB) Open(dbPath string) error {
|
||||
this.dbPath = dbPath
|
||||
|
||||
// 动态调整Cache值
|
||||
var cacheSize = 32000
|
||||
var cacheSize = 512
|
||||
var memoryGB = utils.SystemMemoryGB()
|
||||
if memoryGB >= 8 {
|
||||
cacheSize += 32000 * memoryGB / 8
|
||||
if memoryGB >= 1 {
|
||||
cacheSize = 256 * memoryGB
|
||||
}
|
||||
|
||||
// write db
|
||||
// 这里不能加 EXCLUSIVE 锁,不然异步事务可能会失败
|
||||
writeDB, err := dbs.OpenWriter("file:" + dbPath + "?cache=private&mode=rwc&_journal_mode=WAL&_sync=OFF&_cache_size=" + types.String(cacheSize) + "&_secure_delete=FAST")
|
||||
writeDB, err := dbs.OpenWriter("file:" + dbPath + "?cache=private&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_cache_size=" + types.String(cacheSize) + "&_secure_delete=FAST")
|
||||
if err != nil {
|
||||
return errors.New("open write database failed: " + err.Error())
|
||||
return fmt.Errorf("open write database failed: %w", err)
|
||||
}
|
||||
|
||||
writeDB.SetMaxOpenConns(1)
|
||||
@@ -110,24 +98,14 @@ func (this *FileListDB) Open(dbPath string) error {
|
||||
}
|
||||
}
|
||||
|
||||
this.writeBatch = dbs.NewBatch(writeDB.RawDB(), 4)
|
||||
this.writeBatch.OnFail(func(err error) {
|
||||
remotelogs.Warn("LIST_FILE_DB", "run batch failed: "+err.Error()+" ("+filepath.Base(this.dbPath)+")")
|
||||
})
|
||||
|
||||
goman.New(func() {
|
||||
this.writeBatch.Exec()
|
||||
})
|
||||
|
||||
if teaconst.EnableDBStat {
|
||||
this.writeBatch.EnableStat(true)
|
||||
this.writeDB.EnableStat(true)
|
||||
}
|
||||
|
||||
// read db
|
||||
readDB, err := dbs.OpenReader("file:" + dbPath + "?cache=private&mode=ro&_journal_mode=WAL&_sync=OFF&_cache_size=" + types.String(cacheSize))
|
||||
if err != nil {
|
||||
return errors.New("open read database failed: " + err.Error())
|
||||
return fmt.Errorf("open read database failed: %w", err)
|
||||
}
|
||||
|
||||
readDB.SetMaxOpenConns(runtime.NumCPU())
|
||||
@@ -143,33 +121,20 @@ func (this *FileListDB) Open(dbPath string) error {
|
||||
|
||||
func (this *FileListDB) Init() error {
|
||||
this.itemsTableName = "cacheItems"
|
||||
this.hitsTableName = "hits"
|
||||
|
||||
// 创建
|
||||
var err = this.initTables(1)
|
||||
if err != nil {
|
||||
return errors.New("init tables failed: " + err.Error())
|
||||
return fmt.Errorf("init tables failed: %w", err)
|
||||
}
|
||||
|
||||
// 读取总数量
|
||||
row := this.readDB.QueryRow(`SELECT COUNT(*) FROM "` + this.itemsTableName + `"`)
|
||||
if row.Err() != nil {
|
||||
return row.Err()
|
||||
}
|
||||
var total int64
|
||||
err = row.Scan(&total)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.total = total
|
||||
|
||||
// 常用语句
|
||||
this.existsByHashStmt, err = this.readDB.Prepare(`SELECT "expiredAt" FROM "` + this.itemsTableName + `" INDEXED BY "hash" WHERE "hash"=? AND expiredAt>? LIMIT 1`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.insertSQL = `INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt", "accessWeek") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
this.insertSQL = `INSERT INTO "` + this.itemsTableName + `" ("hash", "key", "headerSize", "bodySize", "metaSize", "expiredAt", "staleAt", "host", "serverId", "createdAt") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
this.insertStmt, err = this.writeDB.Prepare(this.insertSQL)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -180,7 +145,7 @@ func (this *FileListDB) Init() error {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectHashListStmt, err = this.readDB.Prepare(`SELECT "id", "hash" FROM "` + this.itemsTableName + `" WHERE id>:id ORDER BY id ASC LIMIT 2000`)
|
||||
this.selectHashListStmt, err = this.readDB.Prepare(`SELECT "id", "hash" FROM "` + this.itemsTableName + `" WHERE id>? ORDER BY id ASC LIMIT 2000`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -206,28 +171,15 @@ func (this *FileListDB) Init() error {
|
||||
return err
|
||||
}
|
||||
|
||||
this.listOlderItemsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" ORDER BY "accessWeek" ASC, "id" ASC LIMIT ?`)
|
||||
this.listOlderItemsStmt, err = this.readDB.Prepare(`SELECT "hash" FROM "` + this.itemsTableName + `" ORDER BY "id" ASC LIMIT ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.updateAccessWeekSQL = `UPDATE "` + this.itemsTableName + `" SET "accessWeek"=? WHERE "hash"=?`
|
||||
|
||||
this.insertHitSQL = `INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?)`
|
||||
|
||||
this.increaseHitSQL = `INSERT INTO "` + this.hitsTableName + `" ("hash", "week2Hits", "week") VALUES (?, 1, ?) ON CONFLICT("hash") DO UPDATE SET "week1Hits"=IIF("week"=?, "week1Hits", "week2Hits"), "week2Hits"=IIF("week"=?, "week2Hits"+1, 1), "week"=?`
|
||||
|
||||
this.deleteHitByHashSQL = `DELETE FROM "` + this.hitsTableName + `" WHERE "hash"=?`
|
||||
|
||||
this.isReady = true
|
||||
|
||||
// 加载HashMap
|
||||
go func() {
|
||||
err := this.hashMap.Load(this)
|
||||
if err != nil {
|
||||
remotelogs.Error("LIST_FILE_DB", "load hash map failed: "+err.Error()+"(file: "+this.dbPath+")")
|
||||
}
|
||||
}()
|
||||
go this.loadHashMap()
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -236,20 +188,15 @@ func (this *FileListDB) IsReady() bool {
|
||||
return this.isReady
|
||||
}
|
||||
|
||||
func (this *FileListDB) Total() int64 {
|
||||
return this.total
|
||||
}
|
||||
|
||||
func (this *FileListDB) AddAsync(hash string, item *Item) error {
|
||||
this.hashMap.Add(hash)
|
||||
|
||||
if item.StaleAt == 0 {
|
||||
item.StaleAt = item.ExpiredAt
|
||||
func (this *FileListDB) Total() (int64, error) {
|
||||
// 读取总数量
|
||||
var row = this.readDB.QueryRow(`SELECT COUNT(*) FROM "` + this.itemsTableName + `"`)
|
||||
if row.Err() != nil {
|
||||
return 0, row.Err()
|
||||
}
|
||||
|
||||
this.writeBatch.Add(this.insertSQL, hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix(), timeutil.Format("YW"))
|
||||
return nil
|
||||
|
||||
var total int64
|
||||
err := row.Scan(&total)
|
||||
return total, err
|
||||
}
|
||||
|
||||
func (this *FileListDB) AddSync(hash string, item *Item) error {
|
||||
@@ -259,7 +206,7 @@ func (this *FileListDB) AddSync(hash string, item *Item) error {
|
||||
item.StaleAt = item.ExpiredAt
|
||||
}
|
||||
|
||||
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix(), timeutil.Format("YW"))
|
||||
_, err := this.insertStmt.Exec(hash, item.Key, item.HeaderSize, item.BodySize, item.MetaSize, item.ExpiredAt, item.StaleAt, item.Host, item.ServerId, fasttime.Now().Unix())
|
||||
if err != nil {
|
||||
return this.WrapError(err)
|
||||
}
|
||||
@@ -267,13 +214,6 @@ func (this *FileListDB) AddSync(hash string, item *Item) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) DeleteAsync(hash string) error {
|
||||
this.hashMap.Delete(hash)
|
||||
|
||||
this.writeBatch.Add(this.deleteByHashSQL, hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) DeleteSync(hash string) error {
|
||||
this.hashMap.Delete(hash)
|
||||
|
||||
@@ -362,14 +302,7 @@ func (this *FileListDB) ListHashes(lastId int64) (hashList []string, maxId int64
|
||||
}
|
||||
|
||||
func (this *FileListDB) IncreaseHitAsync(hash string) error {
|
||||
var week = timeutil.Format("YW")
|
||||
this.writeBatch.Add(this.increaseHitSQL, hash, week, week, week, week)
|
||||
this.writeBatch.Add(this.updateAccessWeekSQL, week, hash)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *FileListDB) DeleteHitAsync(hash string) error {
|
||||
this.writeBatch.Add(this.deleteHitByHashSQL, hash)
|
||||
// do nothing
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -378,10 +311,9 @@ func (this *FileListDB) CleanPrefix(prefix string) error {
|
||||
return nil
|
||||
}
|
||||
var count = int64(10000)
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||
for {
|
||||
result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+int64(staleLife), unixTime, prefix)
|
||||
result, err := this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET expiredAt=0,staleAt=? WHERE id IN (SELECT id FROM "`+this.itemsTableName+`" WHERE expiredAt>0 AND createdAt<=? AND INSTR("key", ?)=1 LIMIT `+types.String(count)+`)`, unixTime+DefaultStaleCacheSeconds, unixTime, prefix)
|
||||
if err != nil {
|
||||
return this.WrapError(err)
|
||||
}
|
||||
@@ -425,15 +357,14 @@ func (this *FileListDB) CleanMatchKey(key string) error {
|
||||
queryKey = strings.Replace(queryKey, "*", "%", 1)
|
||||
|
||||
// TODO 检查大批量数据下的操作性能
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey)
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+DefaultStaleCacheSeconds, host, "*."+host, queryKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryKey+SuffixAll+"%")
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+DefaultStaleCacheSeconds, host, "*."+host, queryKey+SuffixAll+"%")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -467,10 +398,9 @@ func (this *FileListDB) CleanMatchPrefix(prefix string) error {
|
||||
queryPrefix += "%"
|
||||
|
||||
// TODO 检查大批量数据下的操作性能
|
||||
var staleLife = 600 // TODO 需要可以设置
|
||||
var unixTime = fasttime.Now().Unix() // 只删除当前的,不删除新的
|
||||
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+int64(staleLife), host, "*."+host, queryPrefix)
|
||||
_, err = this.writeDB.Exec(`UPDATE "`+this.itemsTableName+`" SET "expiredAt"=0, "staleAt"=? WHERE "host" GLOB ? AND "host" NOT GLOB ? AND "key" LIKE ? ESCAPE '\'`, unixTime+DefaultStaleCacheSeconds, host, "*."+host, queryPrefix)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -490,6 +420,10 @@ func (this *FileListDB) CleanAll() error {
|
||||
}
|
||||
|
||||
func (this *FileListDB) Close() error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.isClosed = true
|
||||
this.isReady = false
|
||||
|
||||
@@ -521,10 +455,6 @@ func (this *FileListDB) Close() error {
|
||||
_ = this.listOlderItemsStmt.Close()
|
||||
}
|
||||
|
||||
if this.writeBatch != nil {
|
||||
this.writeBatch.Close()
|
||||
}
|
||||
|
||||
var errStrings []string
|
||||
|
||||
if this.readDB != nil {
|
||||
@@ -551,7 +481,11 @@ func (this *FileListDB) WrapError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return errors.New(err.Error() + "(file: " + this.dbPath + ")")
|
||||
return fmt.Errorf("%w (file: %s)", err, this.dbPath)
|
||||
}
|
||||
|
||||
func (this *FileListDB) HashMapIsLoaded() bool {
|
||||
return this.hashMapIsLoaded
|
||||
}
|
||||
|
||||
// 初始化
|
||||
@@ -572,8 +506,7 @@ func (this *FileListDB) initTables(times int) error {
|
||||
"staleAt" integer DEFAULT 0,
|
||||
"createdAt" integer DEFAULT 0,
|
||||
"host" varchar(128),
|
||||
"serverId" integer,
|
||||
"accessWeek" varchar(6)
|
||||
"serverId" integer
|
||||
);
|
||||
|
||||
DROP INDEX IF EXISTS "createdAt";
|
||||
@@ -589,8 +522,6 @@ CREATE INDEX IF NOT EXISTS "hash"
|
||||
ON "` + this.itemsTableName + `" (
|
||||
"hash" ASC
|
||||
);
|
||||
|
||||
ALTER TABLE "cacheItems" ADD "accessWeek" varchar(6);
|
||||
`)
|
||||
|
||||
if err != nil {
|
||||
@@ -614,32 +545,9 @@ ALTER TABLE "cacheItems" ADD "accessWeek" varchar(6);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除hits表
|
||||
{
|
||||
_, err := this.writeDB.Exec(`CREATE TABLE IF NOT EXISTS "` + this.hitsTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"hash" varchar(32),
|
||||
"week1Hits" integer DEFAULT 0,
|
||||
"week2Hits" integer DEFAULT 0,
|
||||
"week" varchar(6)
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "hits_hash"
|
||||
ON "` + this.hitsTableName + `" (
|
||||
"hash" ASC
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
// 尝试删除重建
|
||||
if times < 3 {
|
||||
_, dropErr := this.writeDB.Exec(`DROP TABLE "` + this.hitsTableName + `"`)
|
||||
if dropErr == nil {
|
||||
return this.initTables(times + 1)
|
||||
}
|
||||
return this.WrapError(err)
|
||||
}
|
||||
|
||||
return this.WrapError(err)
|
||||
}
|
||||
_, _ = this.writeDB.Exec(`DROP TABLE "hits"`)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -673,13 +581,46 @@ func (this *FileListDB) shouldRecover() bool {
|
||||
}
|
||||
var errString = ""
|
||||
var shouldRecover = false
|
||||
for result.Next() {
|
||||
err = result.Scan(&errString)
|
||||
if result.Next() {
|
||||
_ = result.Scan(&errString)
|
||||
if strings.TrimSpace(errString) != "ok" {
|
||||
shouldRecover = true
|
||||
}
|
||||
break
|
||||
}
|
||||
_ = result.Close()
|
||||
return shouldRecover
|
||||
}
|
||||
|
||||
// 删除数据库文件
|
||||
func (this *FileListDB) deleteDB() {
|
||||
_ = os.Remove(this.dbPath)
|
||||
_ = os.Remove(this.dbPath + "-shm")
|
||||
_ = os.Remove(this.dbPath + "-wal")
|
||||
}
|
||||
|
||||
// 加载Hash列表
|
||||
func (this *FileListDB) loadHashMap() {
|
||||
this.hashMapIsLoaded = false
|
||||
|
||||
err := this.hashMap.Load(this)
|
||||
if err != nil {
|
||||
remotelogs.Error("LIST_FILE_DB", "load hash map failed: "+err.Error()+"(file: "+this.dbPath+")")
|
||||
|
||||
// 自动修复错误
|
||||
// TODO 将来希望能尽可能恢复以往数据库中的内容
|
||||
if strings.Contains(err.Error(), "database is closed") || strings.Contains(err.Error(), "database disk image is malformed") {
|
||||
_ = this.Close()
|
||||
this.deleteDB()
|
||||
remotelogs.Println("LIST_FILE_DB", "recreating the database (file:"+this.dbPath+") ...")
|
||||
err = this.Open(this.dbPath)
|
||||
if err != nil {
|
||||
remotelogs.Error("LIST_FILE_DB", "recreate the database failed: "+err.Error()+" (file:"+this.dbPath+")")
|
||||
} else {
|
||||
_ = this.Init()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.hashMapIsLoaded = true
|
||||
}
|
||||
|
||||
@@ -4,14 +4,26 @@ package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFileListDB_ListLFUItems(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var db = caches.NewFileListDB()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||
//err := db.Open(Tea.Root + "/data/cache-index/p1/db-0.db")
|
||||
if err != nil {
|
||||
@@ -22,10 +34,6 @@ func TestFileListDB_ListLFUItems(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
hashList, err := db.ListLFUItems(100)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -33,28 +41,26 @@ func TestFileListDB_ListLFUItems(t *testing.T) {
|
||||
t.Log("[", len(hashList), "]", hashList)
|
||||
}
|
||||
|
||||
func TestFileListDB_IncreaseHitAsync(t *testing.T) {
|
||||
var db = caches.NewFileListDB()
|
||||
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = db.Init()
|
||||
err = db.IncreaseHitAsync("4598e5231ba47d6ec7aa9ea640ff2eaf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// wait transaction
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func TestFileListDB_CleanMatchKey(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var db = caches.NewFileListDB()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.CleanMatchKey("https://*.goedge.cn/large-text")
|
||||
if err != nil {
|
||||
@@ -68,12 +74,25 @@ func TestFileListDB_CleanMatchKey(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileListDB_CleanMatchPrefix(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var db = caches.NewFileListDB()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
err := db.Open(Tea.Root + "/data/cache-db-large.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.CleanMatchPrefix("https://*.goedge.cn/large-text")
|
||||
if err != nil {
|
||||
@@ -85,3 +104,67 @@ func TestFileListDB_CleanMatchPrefix(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileListDB_Memory(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var db = caches.NewFileListDB()
|
||||
|
||||
defer func() {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
err := db.Open(Tea.Root + "/data/cache-index/p1/db-0.db")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log(db.Total())
|
||||
|
||||
// load hashes
|
||||
var maxId int64
|
||||
var hashList []string
|
||||
var before = time.Now()
|
||||
for i := 0; i < 1_000; i++ {
|
||||
hashList, maxId, err = db.ListHashes(maxId)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(hashList) == 0 {
|
||||
t.Log("hashes loaded", time.Since(before).Seconds()*1000, "ms")
|
||||
break
|
||||
}
|
||||
if i%100 == 0 {
|
||||
t.Log(i)
|
||||
}
|
||||
}
|
||||
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
|
||||
//time.Sleep(600 * time.Second)
|
||||
|
||||
for i := 0; i < 1_000; i++ {
|
||||
_, err = db.ListLFUItems(5000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if i%100 == 0 {
|
||||
t.Log(i)
|
||||
}
|
||||
}
|
||||
|
||||
t.Log("loaded")
|
||||
|
||||
runtime.GC()
|
||||
debug.FreeOSMemory()
|
||||
|
||||
time.Sleep(600 * time.Second)
|
||||
}
|
||||
|
||||
@@ -9,18 +9,36 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
const HashMapSharding = 31
|
||||
|
||||
var bigIntPool = sync.Pool{
|
||||
New: func() any {
|
||||
return big.NewInt(0)
|
||||
},
|
||||
}
|
||||
|
||||
// FileListHashMap 文件Hash列表
|
||||
type FileListHashMap struct {
|
||||
m map[uint64]zero.Zero
|
||||
m []map[uint64]zero.Zero
|
||||
|
||||
lockers []*sync.RWMutex
|
||||
|
||||
locker sync.RWMutex
|
||||
isAvailable bool
|
||||
isReady bool
|
||||
}
|
||||
|
||||
func NewFileListHashMap() *FileListHashMap {
|
||||
var m = make([]map[uint64]zero.Zero, HashMapSharding)
|
||||
var lockers = make([]*sync.RWMutex, HashMapSharding)
|
||||
|
||||
for i := 0; i < HashMapSharding; i++ {
|
||||
m[i] = map[uint64]zero.Zero{}
|
||||
lockers[i] = &sync.RWMutex{}
|
||||
}
|
||||
|
||||
return &FileListHashMap{
|
||||
m: map[uint64]zero.Zero{},
|
||||
m: m,
|
||||
lockers: lockers,
|
||||
isAvailable: false,
|
||||
isReady: false,
|
||||
}
|
||||
@@ -35,6 +53,7 @@ func (this *FileListHashMap) Load(db *FileListDB) error {
|
||||
this.isAvailable = true
|
||||
|
||||
var lastId int64
|
||||
var maxLoops = 50_000
|
||||
for {
|
||||
hashList, maxId, err := db.ListHashes(lastId)
|
||||
if err != nil {
|
||||
@@ -45,6 +64,11 @@ func (this *FileListHashMap) Load(db *FileListDB) error {
|
||||
}
|
||||
this.AddHashes(hashList)
|
||||
lastId = maxId
|
||||
|
||||
maxLoops--
|
||||
if maxLoops <= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.isReady = true
|
||||
@@ -56,9 +80,11 @@ func (this *FileListHashMap) Add(hash string) {
|
||||
return
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
this.m[this.bigInt(hash)] = zero.New()
|
||||
this.locker.Unlock()
|
||||
hashInt, index := this.bigInt(hash)
|
||||
|
||||
this.lockers[index].Lock()
|
||||
this.m[index][hashInt] = zero.New()
|
||||
this.lockers[index].Unlock()
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) AddHashes(hashes []string) {
|
||||
@@ -66,11 +92,12 @@ func (this *FileListHashMap) AddHashes(hashes []string) {
|
||||
return
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
for _, hash := range hashes {
|
||||
this.m[this.bigInt(hash)] = zero.New()
|
||||
hashInt, index := this.bigInt(hash)
|
||||
this.lockers[index].Lock()
|
||||
this.m[index][hashInt] = zero.New()
|
||||
this.lockers[index].Unlock()
|
||||
}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) Delete(hash string) {
|
||||
@@ -78,9 +105,10 @@ func (this *FileListHashMap) Delete(hash string) {
|
||||
return
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
delete(this.m, this.bigInt(hash))
|
||||
this.locker.Unlock()
|
||||
hashInt, index := this.bigInt(hash)
|
||||
this.lockers[index].Lock()
|
||||
delete(this.m[index], hashInt)
|
||||
this.lockers[index].Unlock()
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) Exist(hash string) bool {
|
||||
@@ -91,16 +119,28 @@ func (this *FileListHashMap) Exist(hash string) bool {
|
||||
// 只有完全Ready时才能判断是否为false
|
||||
return true
|
||||
}
|
||||
this.locker.RLock()
|
||||
_, ok := this.m[this.bigInt(hash)]
|
||||
this.locker.RUnlock()
|
||||
|
||||
hashInt, index := this.bigInt(hash)
|
||||
|
||||
this.lockers[index].RLock()
|
||||
_, ok := this.m[index][hashInt]
|
||||
this.lockers[index].RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) Clean() {
|
||||
this.locker.Lock()
|
||||
this.m = map[uint64]zero.Zero{}
|
||||
this.locker.Unlock()
|
||||
for i := 0; i < HashMapSharding; i++ {
|
||||
this.lockers[i].Lock()
|
||||
}
|
||||
|
||||
// 这里不能简单清空 this.m ,避免导致别的数据无法写入 map 而产生 panic
|
||||
for i := 0; i < HashMapSharding; i++ {
|
||||
this.m[i] = map[uint64]zero.Zero{}
|
||||
}
|
||||
|
||||
for i := HashMapSharding - 1; i >= 0; i-- {
|
||||
this.lockers[i].Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) IsReady() bool {
|
||||
@@ -108,13 +148,36 @@ func (this *FileListHashMap) IsReady() bool {
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) Len() int {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
return len(this.m)
|
||||
for i := 0; i < HashMapSharding; i++ {
|
||||
this.lockers[i].Lock()
|
||||
}
|
||||
|
||||
var count = 0
|
||||
for _, shard := range this.m {
|
||||
count += len(shard)
|
||||
}
|
||||
|
||||
for i := HashMapSharding - 1; i >= 0; i-- {
|
||||
this.lockers[i].Unlock()
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) bigInt(hash string) uint64 {
|
||||
var bigInt = big.NewInt(0)
|
||||
bigInt.SetString(hash, 16)
|
||||
return bigInt.Uint64()
|
||||
func (this *FileListHashMap) SetIsAvailable(isAvailable bool) {
|
||||
this.isAvailable = isAvailable
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) SetIsReady(isReady bool) {
|
||||
this.isReady = isReady
|
||||
}
|
||||
|
||||
func (this *FileListHashMap) bigInt(hash string) (hashInt uint64, index int) {
|
||||
var bigInt = bigIntPool.Get().(*big.Int)
|
||||
bigInt.SetString(hash, 16)
|
||||
hashInt = bigInt.Uint64()
|
||||
bigIntPool.Put(bigInt)
|
||||
|
||||
index = int(hashInt % HashMapSharding)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -4,8 +4,11 @@ package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"math/big"
|
||||
@@ -20,15 +23,19 @@ func TestFileListHashMap_Memory(t *testing.T) {
|
||||
runtime.ReadMemStats(stat1)
|
||||
|
||||
var m = caches.NewFileListHashMap()
|
||||
m.SetIsAvailable(true)
|
||||
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
m.Add(stringutil.Md5(types.String(i)))
|
||||
}
|
||||
|
||||
t.Log("added:", m.Len(), "hashes")
|
||||
|
||||
var stat2 = &runtime.MemStats{}
|
||||
runtime.ReadMemStats(stat2)
|
||||
|
||||
t.Log("ready", (stat2.Alloc-stat1.Alloc)/1024/1024, "M")
|
||||
t.Log("remains:", m.Len(), "hashes")
|
||||
}
|
||||
|
||||
func TestFileListHashMap_Memory2(t *testing.T) {
|
||||
@@ -48,26 +55,53 @@ func TestFileListHashMap_Memory2(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileListHashMap_BigInt(t *testing.T) {
|
||||
var bigInt = big.NewInt(0)
|
||||
|
||||
for _, s := range []string{"1", "2", "3", "123", "123456"} {
|
||||
var hash = stringutil.Md5(s)
|
||||
|
||||
var bigInt = big.NewInt(0)
|
||||
var bigInt1 = big.NewInt(0)
|
||||
bigInt1.SetString(hash, 16)
|
||||
|
||||
bigInt.SetString(hash, 16)
|
||||
t.Log(s, "=>", bigInt.Uint64(), "hash:", hash, "format:", strconv.FormatUint(bigInt.Uint64(), 16))
|
||||
|
||||
t.Log(s, "=>", bigInt1.Uint64(), "hash:", hash, "format:", strconv.FormatUint(bigInt1.Uint64(), 16), strconv.FormatUint(bigInt.Uint64(), 16))
|
||||
|
||||
if strconv.FormatUint(bigInt1.Uint64(), 16) != strconv.FormatUint(bigInt.Uint64(), 16) {
|
||||
t.Fatal("not equal")
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
var hash = stringutil.Md5(types.String(i))
|
||||
|
||||
var bigInt1 = big.NewInt(0)
|
||||
bigInt1.SetString(hash, 16)
|
||||
|
||||
bigInt.SetString(hash, 16)
|
||||
|
||||
if bigInt1.Uint64() != bigInt.Uint64() {
|
||||
t.Fatal(i, "not equal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileListHashMap_Load(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var m = caches.NewFileListHashMap()
|
||||
var before = time.Now()
|
||||
var db = list.GetDB("abc")
|
||||
@@ -84,6 +118,25 @@ func TestFileListHashMap_Load(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFileListHashMap_Delete(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var m = caches.NewFileListHashMap()
|
||||
m.SetIsReady(true)
|
||||
m.SetIsAvailable(true)
|
||||
m.Add("a")
|
||||
a.IsTrue(m.Len() == 1)
|
||||
m.Delete("a")
|
||||
a.IsTrue(m.Len() == 0)
|
||||
}
|
||||
|
||||
func TestFileListHashMap_Clean(t *testing.T) {
|
||||
var m = caches.NewFileListHashMap()
|
||||
m.SetIsAvailable(true)
|
||||
m.Clean()
|
||||
m.Add("a")
|
||||
}
|
||||
|
||||
func Benchmark_BigInt(b *testing.B) {
|
||||
var hash = stringutil.Md5("123456")
|
||||
b.ResetTimer()
|
||||
@@ -94,3 +147,23 @@ func Benchmark_BigInt(b *testing.B) {
|
||||
_ = bigInt.Uint64()
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFileListHashMap_Exist(b *testing.B) {
|
||||
var m = caches.NewFileListHashMap()
|
||||
m.SetIsAvailable(true)
|
||||
m.SetIsReady(true)
|
||||
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
m.Add(types.String(i))
|
||||
}
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
m.Add(types.String(rands.Int64()))
|
||||
_ = m.Exist(types.String(rands.Int64()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package caches_test
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -16,7 +17,16 @@ import (
|
||||
)
|
||||
|
||||
func TestFileList_Init(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -28,7 +38,16 @@ func TestFileList_Init(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Add(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -59,16 +78,21 @@ func TestFileList_Add(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Add_Many(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var before = time.Now()
|
||||
for i := 0; i < 10_000_000; i++ {
|
||||
u := "https://edge.teaos.cn/123456" + strconv.Itoa(i)
|
||||
@@ -91,16 +115,20 @@ func TestFileList_Add_Many(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Exist(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
total, _ := list.Count()
|
||||
t.Log("total:", total)
|
||||
|
||||
@@ -127,10 +155,14 @@ func TestFileList_Exist(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Exist_Many_DB(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
// 测试在多个数据库下的性能
|
||||
var listSlice = []caches.ListInterface{}
|
||||
for i := 1; i <= 10; i++ {
|
||||
list := caches.NewFileList(Tea.Root + "/data/data" + strconv.Itoa(i))
|
||||
var list = caches.NewFileList(Tea.Root + "/data/data" + strconv.Itoa(i))
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -138,6 +170,12 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
|
||||
listSlice = append(listSlice, list)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
for _, list := range listSlice {
|
||||
_ = list.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
var wg = sync.WaitGroup{}
|
||||
var threads = 8
|
||||
wg.Add(threads)
|
||||
@@ -180,16 +218,21 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_CleanPrefix(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
before := time.Now()
|
||||
err = list.CleanPrefix("123")
|
||||
if err != nil {
|
||||
@@ -199,16 +242,20 @@ func TestFileList_CleanPrefix(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Remove(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1").(*caches.FileList)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
list.OnRemove(func(item *caches.Item) {
|
||||
t.Logf("remove %#v", item)
|
||||
})
|
||||
@@ -223,14 +270,20 @@ func TestFileList_Remove(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Purge(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var count = 0
|
||||
_, err = list.Purge(caches.CountFileDB*2, func(hash string) error {
|
||||
@@ -245,16 +298,17 @@ func TestFileList_Purge(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_PurgeLFU(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err = list.IncreaseHit(stringutil.Md5("123456"))
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -272,16 +326,21 @@ func TestFileList_PurgeLFU(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Stat(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
stat, err := list.Stat(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -290,7 +349,16 @@ func TestFileList_Stat(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_Count(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -305,7 +373,16 @@ func TestFileList_Count(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileList_CleanAll(t *testing.T) {
|
||||
list := caches.NewFileList(Tea.Root + "/data")
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -318,32 +395,17 @@ func TestFileList_CleanAll(t *testing.T) {
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestFileList_IncreaseHit(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
func TestFileList_UpgradeV3(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p43").(*caches.FileList)
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
for i := 0; i < 1000_000; i++ {
|
||||
err = list.IncreaseHit(stringutil.Md5("abc" + types.String(i)))
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
func TestFileList_UpgradeV3(t *testing.T) {
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p43").(*caches.FileList)
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -362,7 +424,16 @@ func TestFileList_UpgradeV3(t *testing.T) {
|
||||
}
|
||||
|
||||
func BenchmarkFileList_Exist(b *testing.B) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var list = caches.NewFileList(Tea.Root + "/data/cache-index/p1")
|
||||
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
||||
@@ -2,7 +2,6 @@ package caches
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"net"
|
||||
"net/url"
|
||||
@@ -19,9 +18,6 @@ type MemoryList struct {
|
||||
|
||||
itemMaps map[string]map[string]*Item // prefix => { hash => item }
|
||||
|
||||
weekItemMaps map[int32]map[string]zero.Zero // week => { hash => Zero }
|
||||
minWeek int32
|
||||
|
||||
prefixes []string
|
||||
locker sync.RWMutex
|
||||
onAdd func(item *Item)
|
||||
@@ -32,9 +28,7 @@ type MemoryList struct {
|
||||
|
||||
func NewMemoryList() ListInterface {
|
||||
return &MemoryList{
|
||||
itemMaps: map[string]map[string]*Item{},
|
||||
weekItemMaps: map[int32]map[string]zero.Zero{},
|
||||
minWeek: currentWeek(),
|
||||
itemMaps: map[string]map[string]*Item{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +50,6 @@ func (this *MemoryList) Reset() error {
|
||||
for key := range this.itemMaps {
|
||||
this.itemMaps[key] = map[string]*Item{}
|
||||
}
|
||||
this.weekItemMaps = map[int32]map[string]zero.Zero{}
|
||||
this.locker.Unlock()
|
||||
|
||||
atomic.StoreInt64(&this.count, 0)
|
||||
@@ -65,10 +58,6 @@ func (this *MemoryList) Reset() error {
|
||||
}
|
||||
|
||||
func (this *MemoryList) Add(hash string, item *Item) error {
|
||||
if item.Week == 0 {
|
||||
item.Week = currentWeek()
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
|
||||
prefix := this.prefix(hash)
|
||||
@@ -81,14 +70,6 @@ func (this *MemoryList) Add(hash string, item *Item) error {
|
||||
// 先删除,为了可以正确触发统计
|
||||
oldItem, ok := itemMap[hash]
|
||||
if ok {
|
||||
// 从week map中删除
|
||||
if oldItem.Week > 0 {
|
||||
wm, ok := this.weekItemMaps[oldItem.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
}
|
||||
|
||||
// 回调
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(oldItem)
|
||||
@@ -104,14 +85,6 @@ func (this *MemoryList) Add(hash string, item *Item) error {
|
||||
|
||||
itemMap[hash] = item
|
||||
|
||||
// week map
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
wm[hash] = zero.New()
|
||||
} else {
|
||||
this.weekItemMaps[item.Week] = map[string]zero.Zero{hash: zero.New()}
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -242,14 +215,6 @@ func (this *MemoryList) Remove(hash string) error {
|
||||
|
||||
atomic.AddInt64(&this.count, -1)
|
||||
delete(itemMap, hash)
|
||||
|
||||
// week map
|
||||
if item.Week > 0 {
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
@@ -261,12 +226,12 @@ func (this *MemoryList) Remove(hash string) error {
|
||||
// callback 每次发现过期key的调用
|
||||
func (this *MemoryList) Purge(count int, callback func(hash string) error) (int, error) {
|
||||
this.locker.Lock()
|
||||
deletedHashList := []string{}
|
||||
var deletedHashList = []string{}
|
||||
|
||||
if this.purgeIndex >= len(this.prefixes) {
|
||||
this.purgeIndex = 0
|
||||
}
|
||||
prefix := this.prefixes[this.purgeIndex]
|
||||
var prefix = this.prefixes[this.purgeIndex]
|
||||
|
||||
this.purgeIndex++
|
||||
|
||||
@@ -290,14 +255,6 @@ func (this *MemoryList) Purge(count int, callback func(hash string) error) (int,
|
||||
delete(itemMap, hash)
|
||||
deletedHashList = append(deletedHashList, hash)
|
||||
|
||||
// week map
|
||||
if item.Week > 0 {
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
}
|
||||
|
||||
countFound++
|
||||
}
|
||||
|
||||
@@ -322,63 +279,48 @@ func (this *MemoryList) PurgeLFU(count int, callback func(hash string) error) er
|
||||
return nil
|
||||
}
|
||||
|
||||
var week = currentWeek()
|
||||
if this.minWeek > week {
|
||||
this.minWeek = week
|
||||
}
|
||||
|
||||
var deletedHashList = []string{}
|
||||
|
||||
var week = currentWeek()
|
||||
var round = 0
|
||||
|
||||
this.locker.Lock()
|
||||
|
||||
Loop:
|
||||
for w := this.minWeek; w <= week; w++ {
|
||||
this.minWeek = w
|
||||
for {
|
||||
var found = false
|
||||
round++
|
||||
for _, itemMap := range this.itemMaps {
|
||||
for hash, item := range itemMap {
|
||||
found = true
|
||||
|
||||
this.locker.Lock()
|
||||
wm, ok := this.weekItemMaps[w]
|
||||
if ok {
|
||||
var wc = len(wm)
|
||||
if wc == 0 {
|
||||
delete(this.weekItemMaps, w)
|
||||
} else {
|
||||
if wc <= count {
|
||||
delete(this.weekItemMaps, w)
|
||||
if week-item.Week <= 1 /** 最近有在使用 **/ && round <= 3 /** 查找轮数过多还不满足数量要求的就不再限制 **/ {
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO 未来支持按照点击量排序
|
||||
for hash := range wm {
|
||||
count--
|
||||
|
||||
if count < 0 {
|
||||
this.locker.Unlock()
|
||||
break Loop
|
||||
}
|
||||
|
||||
delete(wm, hash)
|
||||
|
||||
itemMap, ok := this.itemMaps[this.prefix(hash)]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
item, ok := itemMap[hash]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(item)
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.count, -1)
|
||||
delete(itemMap, hash)
|
||||
deletedHashList = append(deletedHashList, hash)
|
||||
if this.onRemove != nil {
|
||||
this.onRemove(item)
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.count, -1)
|
||||
delete(itemMap, hash)
|
||||
deletedHashList = append(deletedHashList, hash)
|
||||
|
||||
count--
|
||||
if count <= 0 {
|
||||
break Loop
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
} else {
|
||||
delete(this.weekItemMaps, w)
|
||||
}
|
||||
this.locker.Unlock()
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
|
||||
// 执行外部操作
|
||||
for _, hash := range deletedHashList {
|
||||
if callback != nil {
|
||||
@@ -451,30 +393,26 @@ func (this *MemoryList) IncreaseHit(hash string) error {
|
||||
|
||||
item, ok := itemMap[hash]
|
||||
if ok {
|
||||
var week = currentWeek()
|
||||
|
||||
// 交换位置
|
||||
if item.Week > 0 && item.Week != week {
|
||||
wm, ok := this.weekItemMaps[item.Week]
|
||||
if ok {
|
||||
delete(wm, hash)
|
||||
}
|
||||
wm, ok = this.weekItemMaps[week]
|
||||
if ok {
|
||||
wm[hash] = zero.New()
|
||||
} else {
|
||||
this.weekItemMaps[week] = map[string]zero.Zero{hash: zero.New()}
|
||||
}
|
||||
}
|
||||
|
||||
item.IncreaseHit(week)
|
||||
item.Week = currentWeek()
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *MemoryList) print(t *testing.T) {
|
||||
func (this *MemoryList) Prefixes() []string {
|
||||
return this.prefixes
|
||||
}
|
||||
|
||||
func (this *MemoryList) ItemMaps() map[string]map[string]*Item {
|
||||
return this.itemMaps
|
||||
}
|
||||
|
||||
func (this *MemoryList) PurgeIndex() int {
|
||||
return this.purgeIndex
|
||||
}
|
||||
|
||||
func (this *MemoryList) Print(t *testing.T) {
|
||||
this.locker.Lock()
|
||||
for _, itemMap := range this.itemMaps {
|
||||
if len(itemMap) > 0 {
|
||||
|
||||
@@ -1,78 +1,81 @@
|
||||
package caches
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMemoryList_Add(t *testing.T) {
|
||||
list := NewMemoryList().(*MemoryList)
|
||||
list := caches.NewMemoryList().(*caches.MemoryList)
|
||||
_ = list.Init()
|
||||
_ = list.Add("a", &Item{
|
||||
_ = list.Add("a", &caches.Item{
|
||||
Key: "a1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("b", &Item{
|
||||
_ = list.Add("b", &caches.Item{
|
||||
Key: "b1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("123456", &Item{
|
||||
_ = list.Add("123456", &caches.Item{
|
||||
Key: "c1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
t.Log(list.prefixes)
|
||||
logs.PrintAsJSON(list.itemMaps, t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
t.Log(list.Prefixes())
|
||||
logs.PrintAsJSON(list.ItemMaps(), t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_Remove(t *testing.T) {
|
||||
list := NewMemoryList().(*MemoryList)
|
||||
list := caches.NewMemoryList().(*caches.MemoryList)
|
||||
_ = list.Init()
|
||||
_ = list.Add("a", &Item{
|
||||
_ = list.Add("a", &caches.Item{
|
||||
Key: "a1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("b", &Item{
|
||||
_ = list.Add("b", &caches.Item{
|
||||
Key: "b1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Remove("b")
|
||||
list.print(t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
list.Print(t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_Purge(t *testing.T) {
|
||||
list := NewMemoryList().(*MemoryList)
|
||||
list := caches.NewMemoryList().(*caches.MemoryList)
|
||||
_ = list.Init()
|
||||
_ = list.Add("a", &Item{
|
||||
_ = list.Add("a", &caches.Item{
|
||||
Key: "a1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("b", &Item{
|
||||
_ = list.Add("b", &caches.Item{
|
||||
Key: "b1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("c", &Item{
|
||||
_ = list.Add("c", &caches.Item{
|
||||
Key: "c1",
|
||||
ExpiredAt: time.Now().Unix() - 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("d", &Item{
|
||||
_ = list.Add("d", &caches.Item{
|
||||
Key: "d1",
|
||||
ExpiredAt: time.Now().Unix() - 2,
|
||||
HeaderSize: 1024,
|
||||
@@ -81,73 +84,82 @@ func TestMemoryList_Purge(t *testing.T) {
|
||||
t.Log("delete:", hash)
|
||||
return nil
|
||||
})
|
||||
list.print(t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
list.Print(t)
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
_, _ = list.Purge(100, func(hash string) error {
|
||||
t.Log("delete:", hash)
|
||||
return nil
|
||||
})
|
||||
t.Log(list.purgeIndex)
|
||||
t.Log(list.PurgeIndex())
|
||||
}
|
||||
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_Purge_Large_List(t *testing.T) {
|
||||
list := NewMemoryList().(*MemoryList)
|
||||
var list = caches.NewMemoryList().(*caches.MemoryList)
|
||||
_ = list.Init()
|
||||
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
_ = list.Add("a"+strconv.Itoa(i), &Item{
|
||||
var count = 100
|
||||
if testutils.IsSingleTesting() {
|
||||
count = 1_000_000
|
||||
}
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
_ = list.Add("a"+strconv.Itoa(i), &caches.Item{
|
||||
Key: "a" + strconv.Itoa(i),
|
||||
ExpiredAt: time.Now().Unix() + int64(rands.Int(0, 24*3600)),
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Hour)
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(1 * time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryList_Stat(t *testing.T) {
|
||||
list := NewMemoryList()
|
||||
list := caches.NewMemoryList()
|
||||
_ = list.Init()
|
||||
_ = list.Add("a", &Item{
|
||||
_ = list.Add("a", &caches.Item{
|
||||
Key: "a1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("b", &Item{
|
||||
_ = list.Add("b", &caches.Item{
|
||||
Key: "b1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("c", &Item{
|
||||
_ = list.Add("c", &caches.Item{
|
||||
Key: "c1",
|
||||
ExpiredAt: time.Now().Unix(),
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
_ = list.Add("d", &Item{
|
||||
_ = list.Add("d", &caches.Item{
|
||||
Key: "d1",
|
||||
ExpiredAt: time.Now().Unix() - 2,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
result, _ := list.Stat(func(hash string) bool {
|
||||
// 随机测试
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
return rand.Int()%2 == 0
|
||||
})
|
||||
t.Log(result)
|
||||
}
|
||||
|
||||
func TestMemoryList_CleanPrefix(t *testing.T) {
|
||||
list := NewMemoryList()
|
||||
list := caches.NewMemoryList()
|
||||
_ = list.Init()
|
||||
before := time.Now()
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
var count = 100
|
||||
if testutils.IsSingleTesting() {
|
||||
count = 1_000_000
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
key := "https://www.teaos.cn/hello/" + strconv.Itoa(i/10000) + "/" + strconv.Itoa(i) + ".html"
|
||||
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &Item{
|
||||
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &caches.Item{
|
||||
Key: key,
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
BodySize: 0,
|
||||
@@ -169,27 +181,69 @@ func TestMemoryList_CleanPrefix(t *testing.T) {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}
|
||||
|
||||
func TestMapRandomDelete(t *testing.T) {
|
||||
var countMap = map[int]int{} // k => count
|
||||
|
||||
var count = 1000
|
||||
if testutils.IsSingleTesting() {
|
||||
count = 1_000_000
|
||||
}
|
||||
|
||||
for j := 0; j < count; j++ {
|
||||
var m = map[int]bool{}
|
||||
for i := 0; i < 100; i++ {
|
||||
m[i] = true
|
||||
}
|
||||
|
||||
var count = 0
|
||||
for k := range m {
|
||||
delete(m, k)
|
||||
count++
|
||||
if count >= 10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for k := range m {
|
||||
countMap[k]++
|
||||
}
|
||||
}
|
||||
|
||||
var counts = []int{}
|
||||
for _, count := range countMap {
|
||||
counts = append(counts, count)
|
||||
}
|
||||
sort.Ints(counts)
|
||||
t.Log("["+types.String(len(counts))+"]", counts)
|
||||
}
|
||||
|
||||
func TestMemoryList_PurgeLFU(t *testing.T) {
|
||||
var list = NewMemoryList().(*MemoryList)
|
||||
list.minWeek = 2704
|
||||
var list = caches.NewMemoryList().(*caches.MemoryList)
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
|
||||
t.Log("current week:", currentWeek())
|
||||
_ = list.Add("1", &caches.Item{})
|
||||
_ = list.Add("2", &caches.Item{})
|
||||
_ = list.Add("3", &caches.Item{})
|
||||
_ = list.Add("4", &caches.Item{})
|
||||
_ = list.Add("5", &caches.Item{})
|
||||
|
||||
_ = list.Add("1", &Item{})
|
||||
_ = list.Add("2", &Item{})
|
||||
_ = list.Add("3", &Item{})
|
||||
_ = list.Add("4", &Item{})
|
||||
_ = list.Add("5", &Item{})
|
||||
_ = list.Add("6", &Item{Week: 2704})
|
||||
_ = list.Add("7", &Item{Week: 2704})
|
||||
_ = list.Add("8", &Item{Week: 2705})
|
||||
//_ = list.IncreaseHit("1")
|
||||
//_ = list.IncreaseHit("2")
|
||||
//_ = list.IncreaseHit("3")
|
||||
//_ = list.IncreaseHit("4")
|
||||
//_ = list.IncreaseHit("5")
|
||||
|
||||
err := list.PurgeLFU(2, func(hash string) error {
|
||||
count, err := list.Count()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("count items before purge:", count)
|
||||
|
||||
err = list.PurgeLFU(5, func(hash string) error {
|
||||
t.Log("purge lfu:", hash)
|
||||
return nil
|
||||
})
|
||||
@@ -198,66 +252,76 @@ func TestMemoryList_PurgeLFU(t *testing.T) {
|
||||
}
|
||||
t.Log("ok")
|
||||
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_IncreaseHit(t *testing.T) {
|
||||
var list = NewMemoryList().(*MemoryList)
|
||||
var item = &Item{}
|
||||
item.Week = 2705
|
||||
item.Week2Hits = 100
|
||||
|
||||
_ = list.Add("a", &Item{})
|
||||
_ = list.Add("a", item)
|
||||
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
|
||||
_ = list.IncreaseHit("a")
|
||||
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
|
||||
_ = list.IncreaseHit("a")
|
||||
t.Log("hits1:", item.Week1Hits, "hits2:", item.Week2Hits, "week:", item.Week)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
count, err = list.Count()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("count items left:", count)
|
||||
}
|
||||
|
||||
func TestMemoryList_CleanAll(t *testing.T) {
|
||||
var list = NewMemoryList().(*MemoryList)
|
||||
var item = &Item{}
|
||||
item.Week = 2705
|
||||
item.Week2Hits = 100
|
||||
|
||||
_ = list.Add("a", &Item{})
|
||||
var list = caches.NewMemoryList().(*caches.MemoryList)
|
||||
_ = list.Add("a", &caches.Item{})
|
||||
_ = list.CleanAll()
|
||||
logs.PrintAsJSON(list.itemMaps, t)
|
||||
logs.PrintAsJSON(list.weekItemMaps, t)
|
||||
logs.PrintAsJSON(list.ItemMaps(), t)
|
||||
t.Log(list.Count())
|
||||
}
|
||||
|
||||
func TestMemoryList_GC(t *testing.T) {
|
||||
list := NewMemoryList().(*MemoryList)
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
list := caches.NewMemoryList().(*caches.MemoryList)
|
||||
_ = list.Init()
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
key := "https://www.teaos.cn/hello" + strconv.Itoa(i/100000) + "/" + strconv.Itoa(i) + ".html"
|
||||
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &Item{
|
||||
_ = list.Add(fmt.Sprintf("%d", xxhash.Sum64String(key)), &caches.Item{
|
||||
Key: key,
|
||||
ExpiredAt: 0,
|
||||
BodySize: 0,
|
||||
HeaderSize: 0,
|
||||
})
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
t.Log("clean...", len(list.itemMaps))
|
||||
t.Log("clean...", len(list.ItemMaps()))
|
||||
_ = list.CleanAll()
|
||||
t.Log("cleanAll...", len(list.itemMaps))
|
||||
t.Log("cleanAll...", len(list.ItemMaps()))
|
||||
before := time.Now()
|
||||
//runtime.GC()
|
||||
t.Log("gc cost:", time.Since(before).Seconds()*1000, "ms")
|
||||
|
||||
timeout := time.NewTimer(2 * time.Minute)
|
||||
<-timeout.C
|
||||
t.Log("2 minutes passed")
|
||||
if testutils.IsSingleTesting() {
|
||||
timeout := time.NewTimer(2 * time.Minute)
|
||||
<-timeout.C
|
||||
t.Log("2 minutes passed")
|
||||
|
||||
time.Sleep(30 * time.Minute)
|
||||
time.Sleep(30 * time.Minute)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMemoryList(b *testing.B) {
|
||||
var list = caches.NewMemoryList()
|
||||
err := list.Init()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
_ = list.Add(stringutil.Md5(types.String(i)), &caches.Item{
|
||||
Key: "a1",
|
||||
ExpiredAt: time.Now().Unix() + 3600,
|
||||
HeaderSize: 1024,
|
||||
})
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, _ = list.Exist(types.String("a" + types.String(rands.Int(1, 10000))))
|
||||
_ = list.Add("a"+types.String(rands.Int(1, 100000)), &caches.Item{})
|
||||
_, _ = list.Purge(1000, func(hash string) error {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
package caches
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"golang.org/x/sys/unix"
|
||||
"strconv"
|
||||
"sync"
|
||||
)
|
||||
@@ -19,7 +22,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
events.On(events.EventQuit, func() {
|
||||
events.OnClose(func() {
|
||||
remotelogs.Println("CACHE", "quiting cache manager")
|
||||
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{})
|
||||
})
|
||||
@@ -33,6 +36,9 @@ type Manager struct {
|
||||
SubDiskDirs []*serverconfigs.CacheDir
|
||||
MaxMemoryCapacity *shared.SizeCapacity
|
||||
|
||||
CountFileStorages int
|
||||
CountMemoryStorages int
|
||||
|
||||
policyMap map[int64]*serverconfigs.HTTPCachePolicy // policyId => []*Policy
|
||||
storageMap map[int64]StorageInterface // policyId => *Storage
|
||||
locker sync.RWMutex
|
||||
@@ -141,6 +147,16 @@ func (this *Manager) UpdatePolicies(newPolicies []*serverconfigs.HTTPCachePolicy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.CountFileStorages = 0
|
||||
this.CountFileStorages = 0
|
||||
for _, storage := range this.storageMap {
|
||||
_, isFileStorage := storage.(*FileStorage)
|
||||
this.CountMemoryStorages++
|
||||
if isFileStorage {
|
||||
this.CountFileStorages++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FindPolicy 获取Policy信息
|
||||
@@ -148,8 +164,7 @@ func (this *Manager) FindPolicy(policyId int64) *serverconfigs.HTTPCachePolicy {
|
||||
this.locker.RLock()
|
||||
defer this.locker.RUnlock()
|
||||
|
||||
p, _ := this.policyMap[policyId]
|
||||
return p
|
||||
return this.policyMap[policyId]
|
||||
}
|
||||
|
||||
// FindStorageWithPolicy 根据策略ID查找存储
|
||||
@@ -157,8 +172,7 @@ func (this *Manager) FindStorageWithPolicy(policyId int64) StorageInterface {
|
||||
this.locker.RLock()
|
||||
defer this.locker.RUnlock()
|
||||
|
||||
storage, _ := this.storageMap[policyId]
|
||||
return storage
|
||||
return this.storageMap[policyId]
|
||||
}
|
||||
|
||||
// NewStorageWithPolicy 根据策略获取存储对象
|
||||
@@ -172,14 +186,41 @@ func (this *Manager) NewStorageWithPolicy(policy *serverconfigs.HTTPCachePolicy)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StorageMap 获取已有的存储对象
|
||||
func (this *Manager) StorageMap() map[int64]StorageInterface {
|
||||
return this.storageMap
|
||||
}
|
||||
|
||||
// TotalDiskSize 消耗的磁盘尺寸
|
||||
func (this *Manager) TotalDiskSize() int64 {
|
||||
this.locker.RLock()
|
||||
defer this.locker.RUnlock()
|
||||
|
||||
var total = int64(0)
|
||||
var sidMap = map[string]bool{} // partition sid => bool
|
||||
for _, storage := range this.storageMap {
|
||||
total += storage.TotalDiskSize()
|
||||
// 这里不能直接用 storage.TotalDiskSize() 相加,因为多个缓存策略缓存目录可能处在同一个分区目录下
|
||||
fileStorage, ok := storage.(*FileStorage)
|
||||
if ok {
|
||||
var options = fileStorage.options // copy
|
||||
if options != nil {
|
||||
var dir = options.Dir // copy
|
||||
if len(dir) == 0 {
|
||||
continue
|
||||
}
|
||||
var stat = &unix.Statfs_t{}
|
||||
err := unix.Statfs(dir, stat)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var sid = fmt.Sprintf("%d_%d", stat.Fsid.Val[0], stat.Fsid.Val[1])
|
||||
if sidMap[sid] {
|
||||
continue
|
||||
}
|
||||
sidMap[sid] = true
|
||||
total += int64(stat.Blocks-stat.Bfree) * int64(stat.Bsize) // we add extra int64() for darwin
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if total < 0 {
|
||||
@@ -234,3 +275,33 @@ func (this *Manager) FindAllStorages() []StorageInterface {
|
||||
}
|
||||
return storages
|
||||
}
|
||||
|
||||
// ScanGarbageCaches 清理目录中“失联”的缓存文件
|
||||
func (this *Manager) ScanGarbageCaches(callback func(path string) error) error {
|
||||
var storages = this.FindAllStorages()
|
||||
for _, storage := range storages {
|
||||
fileStorage, ok := storage.(*FileStorage)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
err := fileStorage.ScanGarbageCaches(callback)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaxSystemMemoryBytesPerStorage 计算单个策略能使用的系统最大内存
|
||||
func (this *Manager) MaxSystemMemoryBytesPerStorage() int64 {
|
||||
var count = this.CountMemoryStorages
|
||||
if count < 1 {
|
||||
count = 1
|
||||
}
|
||||
|
||||
var resultBytes = int64(utils.SystemMemoryBytes()) / 3 / int64(count) // 1/3 of the system memory
|
||||
if resultBytes < 1<<30 {
|
||||
resultBytes = 1 << 30
|
||||
}
|
||||
return resultBytes
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package caches
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"testing"
|
||||
)
|
||||
@@ -10,7 +11,7 @@ import (
|
||||
func TestManager_UpdatePolicies(t *testing.T) {
|
||||
{
|
||||
var policies = []*serverconfigs.HTTPCachePolicy{}
|
||||
SharedManager.UpdatePolicies(policies)
|
||||
caches.SharedManager.UpdatePolicies(policies)
|
||||
printManager(t)
|
||||
}
|
||||
|
||||
@@ -38,7 +39,7 @@ func TestManager_UpdatePolicies(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
SharedManager.UpdatePolicies(policies)
|
||||
caches.SharedManager.UpdatePolicies(policies)
|
||||
printManager(t)
|
||||
}
|
||||
|
||||
@@ -52,9 +53,8 @@ func TestManager_UpdatePolicies(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: 2,
|
||||
Type: serverconfigs.CachePolicyStorageFile,
|
||||
MaxKeys: 1,
|
||||
Id: 2,
|
||||
Type: serverconfigs.CachePolicyStorageFile,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
@@ -67,7 +67,7 @@ func TestManager_UpdatePolicies(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
SharedManager.UpdatePolicies(policies)
|
||||
caches.SharedManager.UpdatePolicies(policies)
|
||||
printManager(t)
|
||||
}
|
||||
}
|
||||
@@ -81,8 +81,8 @@ func TestManager_ChangePolicy_Memory(t *testing.T) {
|
||||
Capacity: &shared.SizeCapacity{Count: 1, Unit: shared.SizeCapacityUnitGB},
|
||||
},
|
||||
}
|
||||
SharedManager.UpdatePolicies(policies)
|
||||
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
||||
caches.SharedManager.UpdatePolicies(policies)
|
||||
caches.SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
||||
{
|
||||
Id: 1,
|
||||
Type: serverconfigs.CachePolicyStorageMemory,
|
||||
@@ -95,20 +95,20 @@ func TestManager_ChangePolicy_Memory(t *testing.T) {
|
||||
func TestManager_ChangePolicy_File(t *testing.T) {
|
||||
var policies = []*serverconfigs.HTTPCachePolicy{
|
||||
{
|
||||
Id: 1,
|
||||
Type: serverconfigs.CachePolicyStorageFile,
|
||||
Options: map[string]interface{}{
|
||||
Id: 1,
|
||||
Type: serverconfigs.CachePolicyStorageFile,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/data/cache-index/p1",
|
||||
},
|
||||
Capacity: &shared.SizeCapacity{Count: 1, Unit: shared.SizeCapacityUnitGB},
|
||||
},
|
||||
}
|
||||
SharedManager.UpdatePolicies(policies)
|
||||
SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
||||
caches.SharedManager.UpdatePolicies(policies)
|
||||
caches.SharedManager.UpdatePolicies([]*serverconfigs.HTTPCachePolicy{
|
||||
{
|
||||
Id: 1,
|
||||
Type: serverconfigs.CachePolicyStorageFile,
|
||||
Options: map[string]interface{}{
|
||||
Id: 1,
|
||||
Type: serverconfigs.CachePolicyStorageFile,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/data/cache-index/p1",
|
||||
},
|
||||
Capacity: &shared.SizeCapacity{Count: 2, Unit: shared.SizeCapacityUnitGB},
|
||||
@@ -116,10 +116,17 @@ func TestManager_ChangePolicy_File(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestManager_MaxSystemMemoryBytesPerStorage(t *testing.T) {
|
||||
for i := 0; i < 100; i++ {
|
||||
caches.SharedManager.CountMemoryStorages = i
|
||||
t.Log(i, caches.SharedManager.MaxSystemMemoryBytesPerStorage()>>30, "GB")
|
||||
}
|
||||
}
|
||||
|
||||
func printManager(t *testing.T) {
|
||||
t.Log("===manager==")
|
||||
t.Log("storage:")
|
||||
for _, storage := range SharedManager.storageMap {
|
||||
for _, storage := range caches.SharedManager.StorageMap() {
|
||||
t.Log(" storage:", storage.Policy().Id)
|
||||
}
|
||||
t.Log("===============")
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
modeSlow int32 = 1
|
||||
modeFast int32 = 2
|
||||
)
|
||||
|
||||
// MaxOpenFiles max open files manager
|
||||
type MaxOpenFiles struct {
|
||||
ticker *time.Ticker
|
||||
mode int32
|
||||
}
|
||||
|
||||
func NewMaxOpenFiles() *MaxOpenFiles {
|
||||
var f = &MaxOpenFiles{}
|
||||
f.ticker = time.NewTicker(1 * time.Second)
|
||||
f.init()
|
||||
return f
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) init() {
|
||||
goman.New(func() {
|
||||
for range this.ticker.C {
|
||||
// reset mode
|
||||
atomic.StoreInt32(&this.mode, modeFast)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) Fast() {
|
||||
atomic.AddInt32(&this.mode, modeFast)
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) FinishAll() {
|
||||
this.Fast()
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) Slow() {
|
||||
atomic.StoreInt32(&this.mode, modeSlow)
|
||||
}
|
||||
|
||||
func (this *MaxOpenFiles) Next() bool {
|
||||
return atomic.LoadInt32(&this.mode) != modeSlow
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewMaxOpenFiles(t *testing.T) {
|
||||
var maxOpenFiles = caches.NewMaxOpenFiles()
|
||||
maxOpenFiles.Fast()
|
||||
t.Log("fast:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Slow()
|
||||
t.Log("slow:", maxOpenFiles.Next())
|
||||
time.Sleep(1*time.Second + 1*time.Millisecond)
|
||||
t.Log("slow 1 second:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Slow()
|
||||
t.Log("slow:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Slow()
|
||||
t.Log("slow:", maxOpenFiles.Next())
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
t.Log("slow 1 second:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Slow()
|
||||
t.Log("slow:", maxOpenFiles.Next())
|
||||
|
||||
maxOpenFiles.Fast()
|
||||
t.Log("fast:", maxOpenFiles.Next())
|
||||
}
|
||||
375
internal/caches/memory_fragment_pool.go
Normal file
375
internal/caches/memory_fragment_pool.go
Normal file
@@ -0,0 +1,375 @@
|
||||
// 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
|
||||
}
|
||||
313
internal/caches/memory_fragment_pool_test.go
Normal file
313
internal/caches/memory_fragment_pool_test.go
Normal file
@@ -0,0 +1,313 @@
|
||||
// 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()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -12,14 +12,16 @@ type OpenFile struct {
|
||||
meta []byte
|
||||
header []byte
|
||||
version int64
|
||||
size int64
|
||||
}
|
||||
|
||||
func NewOpenFile(fp *os.File, meta []byte, header []byte, version int64) *OpenFile {
|
||||
func NewOpenFile(fp *os.File, meta []byte, header []byte, version int64, size int64) *OpenFile {
|
||||
return &OpenFile{
|
||||
fp: fp,
|
||||
meta: meta,
|
||||
header: header,
|
||||
version: version,
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
package caches
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
@@ -14,26 +16,34 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
maxOpenFileSize = 256 << 20
|
||||
)
|
||||
|
||||
type OpenFileCache struct {
|
||||
poolMap map[string]*OpenFilePool // file path => Pool
|
||||
poolList *linkedlist.List
|
||||
poolList *linkedlist.List[*OpenFilePool]
|
||||
watcher *fsnotify.Watcher
|
||||
|
||||
locker sync.RWMutex
|
||||
|
||||
maxSize int
|
||||
count int
|
||||
maxCount int
|
||||
capacitySize int64
|
||||
|
||||
count int
|
||||
usedSize int64
|
||||
}
|
||||
|
||||
func NewOpenFileCache(maxSize int) (*OpenFileCache, error) {
|
||||
if maxSize <= 0 {
|
||||
maxSize = 16384
|
||||
func NewOpenFileCache(maxCount int) (*OpenFileCache, error) {
|
||||
if maxCount <= 0 {
|
||||
maxCount = 16384
|
||||
}
|
||||
|
||||
var cache = &OpenFileCache{
|
||||
maxSize: maxSize,
|
||||
poolMap: map[string]*OpenFilePool{},
|
||||
poolList: linkedlist.NewList(),
|
||||
maxCount: maxCount,
|
||||
poolMap: map[string]*OpenFilePool{},
|
||||
poolList: linkedlist.NewList[*OpenFilePool](),
|
||||
capacitySize: (int64(utils.SystemMemoryGB()) << 30) / 16,
|
||||
}
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
@@ -58,24 +68,36 @@ func (this *OpenFileCache) Get(filename string) *OpenFile {
|
||||
pool, ok := this.poolMap[filename]
|
||||
this.locker.RUnlock()
|
||||
if ok {
|
||||
file, consumed := pool.Get()
|
||||
file, consumed, consumedSize := pool.Get()
|
||||
if consumed {
|
||||
this.locker.Lock()
|
||||
this.count--
|
||||
this.usedSize -= consumedSize
|
||||
|
||||
// pool如果为空,也不需要从列表中删除,避免put时需要重新创建
|
||||
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
return file
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *OpenFileCache) Put(filename string, file *OpenFile) {
|
||||
if file.size > maxOpenFileSize {
|
||||
return
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
// 如果超过当前容量,则关闭最早的
|
||||
if this.count >= this.maxCount || this.usedSize+file.size >= this.capacitySize {
|
||||
this.consumeHead()
|
||||
return
|
||||
}
|
||||
|
||||
pool, ok := this.poolMap[filename]
|
||||
var success bool
|
||||
if ok {
|
||||
@@ -92,35 +114,7 @@ func (this *OpenFileCache) Put(filename string, file *OpenFile) {
|
||||
// 检查长度
|
||||
if success {
|
||||
this.count++
|
||||
|
||||
// 如果超过当前容量,则关闭最早的
|
||||
if this.count > this.maxSize {
|
||||
var delta = this.maxSize / 100 // 清理1%
|
||||
if delta == 0 {
|
||||
delta = 1
|
||||
}
|
||||
for i := 0; i < delta; i++ {
|
||||
var head = this.poolList.Head()
|
||||
if head == nil {
|
||||
break
|
||||
}
|
||||
|
||||
var headPool = head.Value.(*OpenFilePool)
|
||||
headFile, consumed := headPool.Get()
|
||||
if consumed {
|
||||
this.count--
|
||||
if headFile != nil {
|
||||
_ = headFile.Close()
|
||||
}
|
||||
}
|
||||
|
||||
if headPool.Len() == 0 {
|
||||
delete(this.poolMap, headPool.filename)
|
||||
this.poolList.Remove(head)
|
||||
_ = this.watcher.Remove(headPool.filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
this.usedSize += file.size
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,6 +130,7 @@ func (this *OpenFileCache) Close(filename string) {
|
||||
this.poolList.Remove(pool.linkItem)
|
||||
_ = this.watcher.Remove(filename)
|
||||
this.count -= pool.Len()
|
||||
this.usedSize -= pool.usedSize
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
@@ -155,18 +150,55 @@ func (this *OpenFileCache) CloseAll() {
|
||||
this.poolList.Reset()
|
||||
_ = this.watcher.Close()
|
||||
this.count = 0
|
||||
this.usedSize = 0
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *OpenFileCache) SetCapacity(capacityBytes int64) {
|
||||
this.capacitySize = capacityBytes
|
||||
}
|
||||
|
||||
func (this *OpenFileCache) Debug() {
|
||||
var ticker = time.NewTicker(5 * time.Second)
|
||||
goman.New(func() {
|
||||
for range ticker.C {
|
||||
logs.Println("==== " + types.String(this.count) + " ====")
|
||||
this.poolList.Range(func(item *linkedlist.Item) (goNext bool) {
|
||||
logs.Println(filepath.Base(item.Value.(*OpenFilePool).Filename()), item.Value.(*OpenFilePool).Len())
|
||||
logs.Println("==== " + types.String(this.count) + ", " + fmt.Sprintf("%.4fMB", float64(this.usedSize)/(1<<20)) + " ====")
|
||||
this.poolList.Range(func(item *linkedlist.Item[*OpenFilePool]) (goNext bool) {
|
||||
logs.Println(filepath.Base(item.Value.Filename()), item.Value.Len())
|
||||
return true
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (this *OpenFileCache) consumeHead() {
|
||||
var delta = 1
|
||||
|
||||
if this.count > 100 {
|
||||
delta = 2
|
||||
}
|
||||
|
||||
for i := 0; i < delta; i++ {
|
||||
var head = this.poolList.Head()
|
||||
if head == nil {
|
||||
break
|
||||
}
|
||||
|
||||
var headPool = head.Value
|
||||
headFile, consumed, consumedSize := headPool.Get()
|
||||
if consumed {
|
||||
this.count--
|
||||
this.usedSize -= consumedSize
|
||||
|
||||
if headFile != nil {
|
||||
_ = headFile.Close()
|
||||
}
|
||||
}
|
||||
|
||||
if headPool.Len() == 0 {
|
||||
delete(this.poolMap, headPool.filename)
|
||||
this.poolList.Remove(head)
|
||||
_ = this.watcher.Remove(headPool.filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -14,16 +16,38 @@ func TestNewOpenFileCache_Close(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cache.Debug()
|
||||
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
|
||||
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0, 1<<20))
|
||||
|
||||
cache.Get("b.txt")
|
||||
cache.Get("d.txt")
|
||||
cache.Get("d.txt") // not exist
|
||||
cache.Close("a.txt")
|
||||
|
||||
time.Sleep(100 * time.Second)
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(100 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewOpenFileCache_OverSize(t *testing.T) {
|
||||
cache, err := caches.NewOpenFileCache(1024)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cache.SetCapacity(1 << 30)
|
||||
|
||||
cache.Debug()
|
||||
|
||||
for i := 0; i < 100; i++ {
|
||||
cache.Put("a"+types.String(i)+".txt", caches.NewOpenFile(nil, nil, nil, 0, 128<<20))
|
||||
}
|
||||
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(100 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewOpenFileCache_CloseAll(t *testing.T) {
|
||||
@@ -32,12 +56,14 @@ func TestNewOpenFileCache_CloseAll(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cache.Debug()
|
||||
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0))
|
||||
cache.Put("a.txt", caches.NewOpenFile(nil, nil, nil, 0, 1))
|
||||
cache.Put("b.txt", caches.NewOpenFile(nil, nil, nil, 0, 1))
|
||||
cache.Put("c.txt", caches.NewOpenFile(nil, nil, nil, 0, 1))
|
||||
cache.Get("b.txt")
|
||||
cache.Get("d.txt")
|
||||
cache.CloseAll()
|
||||
|
||||
time.Sleep(6 * time.Second)
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(6 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,11 @@ import (
|
||||
|
||||
type OpenFilePool struct {
|
||||
c chan *OpenFile
|
||||
linkItem *linkedlist.Item
|
||||
linkItem *linkedlist.Item[*OpenFilePool]
|
||||
filename string
|
||||
version int64
|
||||
isClosed bool
|
||||
usedSize int64
|
||||
}
|
||||
|
||||
func NewOpenFilePool(filename string) *OpenFilePool {
|
||||
@@ -21,7 +22,7 @@ func NewOpenFilePool(filename string) *OpenFilePool {
|
||||
c: make(chan *OpenFile, 1024),
|
||||
version: fasttime.Now().UnixMilli(),
|
||||
}
|
||||
pool.linkItem = linkedlist.NewItem(pool)
|
||||
pool.linkItem = linkedlist.NewItem[*OpenFilePool](pool)
|
||||
return pool
|
||||
}
|
||||
|
||||
@@ -29,27 +30,29 @@ func (this *OpenFilePool) Filename() string {
|
||||
return this.filename
|
||||
}
|
||||
|
||||
func (this *OpenFilePool) Get() (*OpenFile, bool) {
|
||||
func (this *OpenFilePool) Get() (resultFile *OpenFile, consumed bool, consumedSize int64) {
|
||||
// 如果已经关闭,直接返回
|
||||
if this.isClosed {
|
||||
return nil, false
|
||||
return nil, false, 0
|
||||
}
|
||||
|
||||
select {
|
||||
case file := <-this.c:
|
||||
if file != nil {
|
||||
this.usedSize -= file.size
|
||||
|
||||
err := file.SeekStart()
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return nil, true
|
||||
return nil, true, file.size
|
||||
}
|
||||
file.version = this.version
|
||||
|
||||
return file, true
|
||||
return file, true, file.size
|
||||
}
|
||||
return nil, false
|
||||
return nil, false, 0
|
||||
default:
|
||||
return nil, false
|
||||
return nil, false, 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +72,7 @@ func (this *OpenFilePool) Put(file *OpenFile) bool {
|
||||
// 加入Pool
|
||||
select {
|
||||
case this.c <- file:
|
||||
this.usedSize += file.size
|
||||
return true
|
||||
default:
|
||||
// 多余的直接关闭
|
||||
@@ -81,6 +85,10 @@ func (this *OpenFilePool) Len() int {
|
||||
return len(this.c)
|
||||
}
|
||||
|
||||
func (this *OpenFilePool) TotalSize() int64 {
|
||||
return this.usedSize
|
||||
}
|
||||
|
||||
func (this *OpenFilePool) SetClosing() {
|
||||
this.isClosed = true
|
||||
}
|
||||
|
||||
@@ -13,15 +13,15 @@ func TestOpenFilePool_Get(t *testing.T) {
|
||||
var pool = caches.NewOpenFilePool("a")
|
||||
t.Log(pool.Filename())
|
||||
t.Log(pool.Get())
|
||||
t.Log(pool.Put(caches.NewOpenFile(nil, nil, nil, 0)))
|
||||
t.Log(pool.Put(caches.NewOpenFile(nil, nil, nil, 0, 1)))
|
||||
t.Log(pool.Get())
|
||||
t.Log(pool.Get())
|
||||
}
|
||||
|
||||
func TestOpenFilePool_Close(t *testing.T) {
|
||||
var pool = caches.NewOpenFilePool("a")
|
||||
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
|
||||
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
|
||||
pool.Put(caches.NewOpenFile(nil, nil, nil, 0, 1))
|
||||
pool.Put(caches.NewOpenFile(nil, nil, nil, 0, 1))
|
||||
pool.Close()
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ func TestOpenFilePool_Concurrent(t *testing.T) {
|
||||
defer wg.Done()
|
||||
|
||||
if rands.Int(0, 1) == 1 {
|
||||
pool.Put(caches.NewOpenFile(nil, nil, nil, 0))
|
||||
pool.Put(caches.NewOpenFile(nil, nil, nil, 0, 1))
|
||||
}
|
||||
if rands.Int(0, 1) == 0 {
|
||||
pool.Get()
|
||||
|
||||
@@ -366,7 +366,7 @@ func (this *FileReader) Close() error {
|
||||
} else {
|
||||
var cacheMeta = make([]byte, len(this.meta))
|
||||
copy(cacheMeta, this.meta)
|
||||
this.openFileCache.Put(this.fp.Name(), NewOpenFile(this.fp, cacheMeta, this.header, this.LastModified()))
|
||||
this.openFileCache.Put(this.fp.Name(), NewOpenFile(this.fp, cacheMeta, this.header, this.LastModified(), this.bodySize))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,21 +8,29 @@ import (
|
||||
)
|
||||
|
||||
func TestFileReader(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, path, _ := storage.keyPath("my-key")
|
||||
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Log("file '" + path + "' not exists")
|
||||
return
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
@@ -58,6 +66,10 @@ func TestFileReader_ReadHeader(t *testing.T) {
|
||||
var path = "/Users/WorkSpace/EdgeProject/EdgeCache/p43/12/6b/126bbed90fc80f2bdfb19558948b0d49.cache"
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Log("'" + path + "' not exists")
|
||||
return
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
@@ -66,6 +78,11 @@ func TestFileReader_ReadHeader(t *testing.T) {
|
||||
var reader = NewFileReader(fp)
|
||||
err = reader.Init()
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Log("file '" + path + "' not exists")
|
||||
return
|
||||
}
|
||||
|
||||
t.Fatal(err)
|
||||
}
|
||||
var buf = make([]byte, 16*1024)
|
||||
@@ -79,13 +96,16 @@ func TestFileReader_ReadHeader(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileReader_Range(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -109,6 +129,10 @@ func TestFileReader_Range(t *testing.T) {
|
||||
|
||||
fp, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
t.Log("'" + path + "' not exists")
|
||||
return
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
|
||||
@@ -3,6 +3,7 @@ package caches
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
@@ -19,7 +20,7 @@ type PartialFileReader struct {
|
||||
func NewPartialFileReader(fp *os.File) *PartialFileReader {
|
||||
return &PartialFileReader{
|
||||
FileReader: NewFileReader(fp),
|
||||
rangePath: partialRangesFilePath(fp.Name()),
|
||||
rangePath: PartialRangesFilePath(fp.Name()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
|
||||
// 读取Range
|
||||
ranges, err := NewPartialRangesFromFile(this.rangePath)
|
||||
if err != nil {
|
||||
return errors.New("read ranges failed: " + err.Error())
|
||||
return fmt.Errorf("read ranges failed: %w", err)
|
||||
}
|
||||
this.ranges = ranges
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
@@ -21,9 +23,9 @@ import (
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
"golang.org/x/sys/unix"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"github.com/iwind/gosock/pkg/gosock"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -32,7 +34,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
@@ -53,22 +54,19 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
|
||||
HotItemSize = 1024 // 热点数据数量
|
||||
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
||||
FileTmpSuffix = ".tmp"
|
||||
MinDiskSpace = 5 << 30 // 当前磁盘最小剩余空间
|
||||
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
|
||||
HotItemSize = 1024 // 热点数据数量
|
||||
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
||||
FileTmpSuffix = ".tmp"
|
||||
DefaultMinDiskFreeSpace uint64 = 5 << 30 // 当前磁盘最小剩余空间
|
||||
DefaultStaleCacheSeconds = 1200 // 过时缓存留存时间
|
||||
HashKeyLength = 32
|
||||
)
|
||||
|
||||
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
||||
var sharedWritingFileKeyLocker = sync.Mutex{}
|
||||
|
||||
var maxOpenFiles = NewMaxOpenFiles()
|
||||
|
||||
const maxOpenFilesSlowCost = 1000 * time.Microsecond // us
|
||||
const protectingLoadWhenDump = false
|
||||
|
||||
// FileStorage 文件缓存
|
||||
//
|
||||
// 文件结构:
|
||||
@@ -77,7 +75,6 @@ type FileStorage struct {
|
||||
policy *serverconfigs.HTTPCachePolicy
|
||||
options *serverconfigs.HTTPFileCacheStorage // 二级缓存
|
||||
memoryStorage *MemoryStorage // 一级缓存
|
||||
totalSize int64
|
||||
|
||||
list ListInterface
|
||||
locker sync.RWMutex
|
||||
@@ -92,8 +89,8 @@ type FileStorage struct {
|
||||
|
||||
openFileCache *OpenFileCache
|
||||
|
||||
mainDir string
|
||||
mainDiskIsFull bool
|
||||
mainDiskIsFull bool
|
||||
mainDiskTotalSize uint64
|
||||
|
||||
subDirs []*FileDir
|
||||
}
|
||||
@@ -114,6 +111,15 @@ func (this *FileStorage) Policy() *serverconfigs.HTTPCachePolicy {
|
||||
|
||||
// CanUpdatePolicy 检查策略是否可以更新
|
||||
func (this *FileStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy) bool {
|
||||
if newPolicy == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查类型
|
||||
if newPolicy.Type != serverconfigs.CachePolicyStorageFile {
|
||||
return false
|
||||
}
|
||||
|
||||
// 检查路径是否有变化
|
||||
oldOptionsJSON, err := json.Marshal(this.policy.Options)
|
||||
if err != nil {
|
||||
@@ -167,6 +173,7 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
|
||||
IsFull: false,
|
||||
})
|
||||
}
|
||||
this.subDirs = subDirs
|
||||
this.checkDiskSpace()
|
||||
|
||||
err = newOptions.Init()
|
||||
@@ -203,6 +210,9 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
|
||||
if newPolicy.PersistenceAutoPurgeInterval != this.policy.PersistenceAutoPurgeInterval {
|
||||
this.initPurgeTicker()
|
||||
}
|
||||
|
||||
// reset ignored keys
|
||||
this.ignoreKeys.Reset()
|
||||
}
|
||||
|
||||
// Init 初始化
|
||||
@@ -255,19 +265,6 @@ func (this *FileStorage) Init() error {
|
||||
}
|
||||
list.(*FileList).SetOldDir(dir + "/p" + types.String(this.policy.Id))
|
||||
this.list = list
|
||||
stat, err := list.Stat(func(hash string) bool {
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.totalSize = stat.Size
|
||||
this.list.OnAdd(func(item *Item) {
|
||||
atomic.AddInt64(&this.totalSize, item.TotalSize())
|
||||
})
|
||||
this.list.OnRemove(func(item *Item) {
|
||||
atomic.AddInt64(&this.totalSize, -item.TotalSize())
|
||||
})
|
||||
|
||||
// 检查目录是否存在
|
||||
_, err = os.Stat(dir)
|
||||
@@ -277,26 +274,24 @@ func (this *FileStorage) Init() error {
|
||||
} else {
|
||||
err = os.MkdirAll(dir, 0777)
|
||||
if err != nil {
|
||||
return errors.New("[CACHE]can not create dir:" + err.Error())
|
||||
return fmt.Errorf("[CACHE]can not create dir: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
// 统计
|
||||
var count = stat.Count
|
||||
var size = stat.Size
|
||||
|
||||
var totalSize = this.TotalDiskSize()
|
||||
var cost = time.Since(before).Seconds() * 1000
|
||||
sizeMB := strconv.FormatInt(size, 10) + " Bytes"
|
||||
if size > 1*sizes.G {
|
||||
sizeMB = fmt.Sprintf("%.3f G", float64(size)/float64(sizes.G))
|
||||
} else if size > 1*sizes.M {
|
||||
sizeMB = fmt.Sprintf("%.3f M", float64(size)/float64(sizes.M))
|
||||
} else if size > 1*sizes.K {
|
||||
sizeMB = fmt.Sprintf("%.3f K", float64(size)/float64(sizes.K))
|
||||
var sizeMB = types.String(totalSize) + " Bytes"
|
||||
if totalSize > 1*sizes.G {
|
||||
sizeMB = fmt.Sprintf("%.3f G", float64(totalSize)/float64(sizes.G))
|
||||
} else if totalSize > 1*sizes.M {
|
||||
sizeMB = fmt.Sprintf("%.3f M", float64(totalSize)/float64(sizes.M))
|
||||
} else if totalSize > 1*sizes.K {
|
||||
sizeMB = fmt.Sprintf("%.3f K", float64(totalSize)/float64(sizes.K))
|
||||
}
|
||||
remotelogs.Println("CACHE", "init policy "+strconv.FormatInt(this.policy.Id, 10)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, count: "+message.NewPrinter(language.English).Sprintf("%d", count)+", size: "+sizeMB)
|
||||
remotelogs.Println("CACHE", "init policy "+types.String(this.policy.Id)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, size: "+sizeMB)
|
||||
}()
|
||||
|
||||
// 初始化list
|
||||
@@ -319,6 +314,9 @@ func (this *FileStorage) Init() error {
|
||||
// 检查磁盘空间
|
||||
this.checkDiskSpace()
|
||||
|
||||
// clean *.trash directories
|
||||
this.cleanAllDeletedDirs()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -430,25 +428,32 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
}
|
||||
|
||||
// 是否已忽略
|
||||
if this.ignoreKeys.Has(key) {
|
||||
if maxSize > 0 && this.ignoreKeys.Has(types.String(maxSize)+"$"+key) {
|
||||
return nil, ErrEntityTooLarge
|
||||
}
|
||||
|
||||
// 检查磁盘是否超出容量
|
||||
// 需要在内存缓存之前执行,避免成功写进到了内存缓存,但无法刷到磁盘
|
||||
var capacityBytes = this.diskCapacityBytes()
|
||||
if capacityBytes > 0 && capacityBytes <= this.TotalDiskSize()+(32<<20 /** 余量 **/) {
|
||||
return nil, NewCapacityError("write file cache failed: over disk size, current: " + types.String(this.TotalDiskSize()) + ", capacity: " + types.String(capacityBytes))
|
||||
}
|
||||
|
||||
// 先尝试内存缓存
|
||||
// 我们限定仅小文件优先存在内存中
|
||||
var maxMemorySize = FileToMemoryMaxSize
|
||||
if maxSize > maxMemorySize {
|
||||
if maxSize > 0 && maxSize < maxMemorySize {
|
||||
maxMemorySize = maxSize
|
||||
}
|
||||
var memoryStorage = this.memoryStorage
|
||||
if !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) {
|
||||
if !fsutils.DiskIsExtremelyFast() && !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) {
|
||||
writer, err := memoryStorage.OpenWriter(key, expiredAt, status, headerSize, bodySize, maxMemorySize, false)
|
||||
if err == nil {
|
||||
return writer, nil
|
||||
}
|
||||
|
||||
// 如果队列满了,则等待
|
||||
if err == ErrWritingQueueFull {
|
||||
if errors.Is(err, ErrWritingQueueFull) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -459,12 +464,12 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
_, ok := sharedWritingFileKeyMap[key]
|
||||
if ok {
|
||||
sharedWritingFileKeyLocker.Unlock()
|
||||
return nil, ErrFileIsWriting
|
||||
return nil, fmt.Errorf("%w(001)", ErrFileIsWriting)
|
||||
}
|
||||
|
||||
if !isFlushing && !maxOpenFiles.Next() {
|
||||
if !isFlushing && !fsutils.WriteReady() {
|
||||
sharedWritingFileKeyLocker.Unlock()
|
||||
return nil, ErrTooManyOpenFiles
|
||||
return nil, ErrServerIsBusy
|
||||
}
|
||||
|
||||
sharedWritingFileKeyMap[key] = zero.New()
|
||||
@@ -473,26 +478,10 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
if !isOk {
|
||||
sharedWritingFileKeyLocker.Lock()
|
||||
delete(sharedWritingFileKeyMap, key)
|
||||
if len(sharedWritingFileKeyMap) == 0 {
|
||||
maxOpenFiles.FinishAll()
|
||||
}
|
||||
sharedWritingFileKeyLocker.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
// 检查是否超出最大值
|
||||
count, err := this.list.Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if this.policy.MaxKeys > 0 && count > this.policy.MaxKeys {
|
||||
return nil, NewCapacityError("write file cache failed: too many keys in cache storage")
|
||||
}
|
||||
var capacityBytes = this.diskCapacityBytes()
|
||||
if capacityBytes > 0 && capacityBytes <= this.totalSize {
|
||||
return nil, NewCapacityError("write file cache failed: over disk size, current total size: " + strconv.FormatInt(this.totalSize, 10) + " bytes, capacity: " + strconv.FormatInt(capacityBytes, 10))
|
||||
}
|
||||
|
||||
var hash = stringutil.Md5(key)
|
||||
|
||||
dir, diskIsFull := this.subDir(hash)
|
||||
@@ -514,9 +503,9 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
stat, err := os.Stat(cachePath)
|
||||
|
||||
// 检查两次写入缓存的时间是否过于相近,分片内容不受此限制
|
||||
if err == nil && !isPartial && time.Now().Sub(stat.ModTime()) <= 1*time.Second {
|
||||
if err == nil && !isPartial && time.Since(stat.ModTime()) <= 1*time.Second {
|
||||
// 防止并发连续写入
|
||||
return nil, ErrFileIsWriting
|
||||
return nil, fmt.Errorf("%w(002)", ErrFileIsWriting)
|
||||
}
|
||||
|
||||
// 构造文件名
|
||||
@@ -582,8 +571,9 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
if isNewCreated && existsFile {
|
||||
flags |= os.O_TRUNC
|
||||
}
|
||||
var before = time.Now()
|
||||
fsutils.WriteBegin()
|
||||
writer, err := os.OpenFile(tmpPath, flags, 0666)
|
||||
fsutils.WriteEnd()
|
||||
if err != nil {
|
||||
// TODO 检查在各个系统中的稳定性
|
||||
if os.IsNotExist(err) {
|
||||
@@ -596,13 +586,6 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if !isFlushing {
|
||||
if time.Since(before) >= maxOpenFilesSlowCost {
|
||||
maxOpenFiles.Slow()
|
||||
} else {
|
||||
maxOpenFiles.Fast()
|
||||
}
|
||||
}
|
||||
|
||||
var removeOnFailure = true
|
||||
defer func() {
|
||||
@@ -623,7 +606,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
err = syscall.Flock(int(writer.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
if err != nil {
|
||||
removeOnFailure = false
|
||||
return nil, ErrFileIsWriting
|
||||
return nil, fmt.Errorf("%w (003)", ErrFileIsWriting)
|
||||
}
|
||||
|
||||
var metaBodySize int64 = -1
|
||||
@@ -652,7 +635,9 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
metaBodySize = bodySize
|
||||
}
|
||||
|
||||
fsutils.WriteBegin()
|
||||
_, err = writer.Write(metaBytes)
|
||||
fsutils.WriteEnd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -663,18 +648,12 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
return NewPartialFileWriter(writer, key, expiredAt, metaHeaderSize, metaBodySize, isNewCreated, isPartial, partialBodyOffset, partialRanges, func() {
|
||||
sharedWritingFileKeyLocker.Lock()
|
||||
delete(sharedWritingFileKeyMap, key)
|
||||
if len(sharedWritingFileKeyMap) == 0 {
|
||||
maxOpenFiles.FinishAll()
|
||||
}
|
||||
sharedWritingFileKeyLocker.Unlock()
|
||||
}), nil
|
||||
} else {
|
||||
return NewFileWriter(this, writer, key, expiredAt, metaHeaderSize, metaBodySize, -1, func() {
|
||||
return NewFileWriter(this, writer, key, expiredAt, metaHeaderSize, metaBodySize, maxSize, func() {
|
||||
sharedWritingFileKeyLocker.Lock()
|
||||
delete(sharedWritingFileKeyMap, key)
|
||||
if len(sharedWritingFileKeyMap) == 0 {
|
||||
maxOpenFiles.FinishAll()
|
||||
}
|
||||
sharedWritingFileKeyLocker.Unlock()
|
||||
}), nil
|
||||
}
|
||||
@@ -788,7 +767,7 @@ func (this *FileStorage) CleanAll() error {
|
||||
return err
|
||||
}
|
||||
for _, info := range subDirs {
|
||||
subDir := info.Name()
|
||||
var subDir = info.Name()
|
||||
|
||||
// 检查目录名
|
||||
if !dirNameReg.MatchString(subDir) {
|
||||
@@ -796,7 +775,7 @@ func (this *FileStorage) CleanAll() error {
|
||||
}
|
||||
|
||||
// 修改目录名
|
||||
tmpDir := dir + "/" + subDir + "-deleted"
|
||||
var tmpDir = dir + "/" + subDir + "." + timeutil.Format("YmdHis") + ".trash"
|
||||
err = os.Rename(dir+"/"+subDir, tmpDir)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -807,7 +786,11 @@ func (this *FileStorage) CleanAll() error {
|
||||
goman.New(func() {
|
||||
err = this.cleanDeletedDirs(dir)
|
||||
if err != nil {
|
||||
remotelogs.Warn("CACHE", "delete '*-deleted' dirs failed: "+err.Error())
|
||||
remotelogs.Warn("CACHE", "delete '*.trash' dirs failed: "+err.Error())
|
||||
} else {
|
||||
// try to clean again, to delete writing files when deleting
|
||||
time.Sleep(10 * time.Minute)
|
||||
_ = this.cleanDeletedDirs(dir)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -899,7 +882,10 @@ func (this *FileStorage) Stop() {
|
||||
memoryStorage.Stop()
|
||||
})
|
||||
|
||||
_ = this.list.Reset()
|
||||
if this.list != nil {
|
||||
_ = this.list.Reset()
|
||||
}
|
||||
|
||||
if this.purgeTicker != nil {
|
||||
this.purgeTicker.Stop()
|
||||
}
|
||||
@@ -907,7 +893,9 @@ func (this *FileStorage) Stop() {
|
||||
this.hotTicker.Stop()
|
||||
}
|
||||
|
||||
_ = this.list.Close()
|
||||
if this.list != nil {
|
||||
_ = this.list.Close()
|
||||
}
|
||||
|
||||
var openFileCache = this.openFileCache
|
||||
if openFileCache != nil {
|
||||
@@ -919,7 +907,11 @@ func (this *FileStorage) Stop() {
|
||||
|
||||
// TotalDiskSize 消耗的磁盘尺寸
|
||||
func (this *FileStorage) TotalDiskSize() int64 {
|
||||
return atomic.LoadInt64(&this.totalSize)
|
||||
stat, err := fsutils.StatDeviceCache(this.options.Dir)
|
||||
if err == nil {
|
||||
return int64(stat.UsedSize())
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// TotalMemorySize 内存尺寸
|
||||
@@ -932,8 +924,8 @@ func (this *FileStorage) TotalMemorySize() int64 {
|
||||
}
|
||||
|
||||
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
||||
func (this *FileStorage) IgnoreKey(key string) {
|
||||
this.ignoreKeys.Push(key)
|
||||
func (this *FileStorage) IgnoreKey(key string, maxSize int64) {
|
||||
this.ignoreKeys.Push(types.String(maxSize) + "$" + key)
|
||||
}
|
||||
|
||||
// CanSendfile 是否支持Sendfile
|
||||
@@ -955,7 +947,7 @@ func (this *FileStorage) keyPath(key string) (hash string, path string, diskIsFu
|
||||
|
||||
// 获取Hash对应的文件路径
|
||||
func (this *FileStorage) hashPath(hash string) (path string, diskIsFull bool) {
|
||||
if len(hash) != 32 {
|
||||
if len(hash) != HashKeyLength {
|
||||
return "", false
|
||||
}
|
||||
var dir string
|
||||
@@ -1019,28 +1011,31 @@ func (this *FileStorage) initList() error {
|
||||
// 清理任务
|
||||
// TODO purge每个分区
|
||||
func (this *FileStorage) purgeLoop() {
|
||||
// 检查磁盘剩余空间
|
||||
this.checkDiskSpace()
|
||||
// load
|
||||
systemLoad, _ := load.Avg()
|
||||
|
||||
// TODO 计算平均最近每日新增用量
|
||||
|
||||
// 计算是否应该开启LFU清理
|
||||
var capacityBytes = this.diskCapacityBytes()
|
||||
var startLFU = false
|
||||
var requireFullLFU = false // 是否需要完整执行LFU
|
||||
var lfuFreePercent = this.policy.PersistenceLFUFreePercent
|
||||
if lfuFreePercent <= 0 {
|
||||
lfuFreePercent = 5
|
||||
}
|
||||
|
||||
var hasFullDisk = this.mainDiskIsFull
|
||||
if !hasFullDisk {
|
||||
var subDirs = this.subDirs // copy slice
|
||||
for _, subDir := range subDirs {
|
||||
if subDir.IsFull {
|
||||
hasFullDisk = true
|
||||
break
|
||||
if systemLoad == nil || systemLoad.Load5 > 10 {
|
||||
// 2TB级别以上
|
||||
if capacityBytes>>30 > 2000 {
|
||||
lfuFreePercent = 100 /** GB **/ / float32(capacityBytes>>30) * 100 /** % **/
|
||||
if lfuFreePercent > 3 {
|
||||
lfuFreePercent = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hasFullDisk = this.hasFullDisk()
|
||||
if hasFullDisk {
|
||||
startLFU = true
|
||||
} else {
|
||||
@@ -1059,23 +1054,45 @@ func (this *FileStorage) purgeLoop() {
|
||||
var times = 1
|
||||
|
||||
// 空闲时间多清理
|
||||
if utils.SharedFreeHoursManager.IsFreeHour() {
|
||||
times = 5
|
||||
if systemLoad != nil {
|
||||
if systemLoad.Load5 < 3 {
|
||||
times = 5
|
||||
} else if systemLoad.Load5 < 5 {
|
||||
times = 3
|
||||
} else if systemLoad.Load5 < 10 {
|
||||
times = 2
|
||||
}
|
||||
}
|
||||
|
||||
// 高速硬盘多清理
|
||||
if fsutils.DiskIsExtremelyFast() {
|
||||
times *= 8
|
||||
} else if fsutils.DiskIsFast() {
|
||||
times *= 4
|
||||
}
|
||||
|
||||
// 处于LFU阈值时,多清理
|
||||
if startLFU {
|
||||
times = 5
|
||||
times *= 5
|
||||
}
|
||||
|
||||
var purgeCount = this.policy.PersistenceAutoPurgeCount
|
||||
if purgeCount <= 0 {
|
||||
purgeCount = 1000
|
||||
|
||||
if fsutils.DiskIsExtremelyFast() {
|
||||
purgeCount = 4000
|
||||
} else if fsutils.DiskIsFast() {
|
||||
purgeCount = 2000
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < times; i++ {
|
||||
countFound, err := this.list.Purge(purgeCount, func(hash string) error {
|
||||
path, _ := this.hashPath(hash)
|
||||
fsutils.WriteBegin()
|
||||
err := this.removeCacheFile(path)
|
||||
fsutils.WriteEnd()
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
||||
}
|
||||
@@ -1088,6 +1105,10 @@ func (this *FileStorage) purgeLoop() {
|
||||
}
|
||||
|
||||
if countFound < purgeCount {
|
||||
if i == 0 && startLFU {
|
||||
requireFullLFU = true
|
||||
}
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
@@ -1097,28 +1118,61 @@ func (this *FileStorage) purgeLoop() {
|
||||
|
||||
// 磁盘空间不足时,清除老旧的缓存
|
||||
if startLFU {
|
||||
var maxCount = 1000
|
||||
var maxLoops = 5
|
||||
|
||||
if fsutils.DiskIsExtremelyFast() {
|
||||
maxCount = 4000
|
||||
} else if fsutils.DiskIsFast() {
|
||||
maxCount = 2000
|
||||
}
|
||||
|
||||
var total, _ = this.list.Count()
|
||||
if total > 0 {
|
||||
var count = types.Int(math.Ceil(float64(total) * float64(lfuFreePercent*2) / 100))
|
||||
if count > 0 {
|
||||
// 限制单次清理的条数,防止占用太多系统资源
|
||||
if count > 2000 {
|
||||
count = 2000
|
||||
for {
|
||||
maxLoops--
|
||||
if maxLoops <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
remotelogs.Println("CACHE", "LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count))
|
||||
// 开始清理
|
||||
var count = types.Int(math.Ceil(float64(total) * float64(lfuFreePercent*2) / 100))
|
||||
if count <= 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// 限制单次清理的条数,防止占用太多系统资源
|
||||
if count > maxCount {
|
||||
count = maxCount
|
||||
}
|
||||
|
||||
var before = time.Now()
|
||||
err := this.list.PurgeLFU(count, func(hash string) error {
|
||||
path, _ := this.hashPath(hash)
|
||||
fsutils.WriteBegin()
|
||||
err := this.removeCacheFile(path)
|
||||
fsutils.WriteEnd()
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
remotelogs.Error("CACHE", "purge '"+path+"' error: "+err.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
var prefix = ""
|
||||
if requireFullLFU {
|
||||
prefix = "fully "
|
||||
}
|
||||
remotelogs.Println("CACHE", prefix+"LFU purge policy '"+this.policy.Name+"' id: "+types.String(this.policy.Id)+", count: "+types.String(count)+", cost: "+fmt.Sprintf("%.2fms", time.Since(before).Seconds()*1000))
|
||||
|
||||
if err != nil {
|
||||
remotelogs.Warn("CACHE", "purge file storage in LFU failed: "+err.Error())
|
||||
}
|
||||
|
||||
// 检查硬盘空间状态
|
||||
if !requireFullLFU && !this.hasFullDisk() {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1126,11 +1180,16 @@ func (this *FileStorage) purgeLoop() {
|
||||
|
||||
// 热点数据任务
|
||||
func (this *FileStorage) hotLoop() {
|
||||
var memoryStorage = this.memoryStorage
|
||||
var memoryStorage = this.memoryStorage // copy
|
||||
if memoryStorage == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// check memory space size
|
||||
if !memoryStorage.HasFreeSpaceForHotItems() {
|
||||
return
|
||||
}
|
||||
|
||||
this.hotMapLocker.Lock()
|
||||
if len(this.hotMap) == 0 {
|
||||
this.hotMapLocker.Unlock()
|
||||
@@ -1142,6 +1201,9 @@ func (this *FileStorage) hotLoop() {
|
||||
|
||||
var result = []*HotItem{} // [ {key: ..., hits: ...}, ... ]
|
||||
for _, v := range this.hotMap {
|
||||
if v.Hits <= 1 {
|
||||
continue
|
||||
}
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
@@ -1245,16 +1307,43 @@ func (this *FileStorage) hotLoop() {
|
||||
|
||||
func (this *FileStorage) diskCapacityBytes() int64 {
|
||||
var c1 = this.policy.CapacityBytes()
|
||||
if SharedManager.MaxDiskCapacity != nil {
|
||||
var c2 = SharedManager.MaxDiskCapacity.Bytes()
|
||||
var nodeCapacity = SharedManager.MaxDiskCapacity // copy
|
||||
if nodeCapacity != nil {
|
||||
var c2 = nodeCapacity.Bytes()
|
||||
if c2 > 0 {
|
||||
if this.mainDiskTotalSize > 0 && c2 >= int64(this.mainDiskTotalSize) {
|
||||
c2 = int64(this.mainDiskTotalSize) * 95 / 100 // keep 5% free
|
||||
}
|
||||
return c2
|
||||
}
|
||||
}
|
||||
|
||||
if c1 <= 0 || (this.mainDiskTotalSize > 0 && c1 >= int64(this.mainDiskTotalSize)) {
|
||||
c1 = int64(this.mainDiskTotalSize) * 95 / 100 // keep 5% free
|
||||
}
|
||||
|
||||
return c1
|
||||
}
|
||||
|
||||
// 清理 *-deleted 目录
|
||||
// remove all *.trash directories under policy directory
|
||||
func (this *FileStorage) cleanAllDeletedDirs() {
|
||||
var rootDirs = []string{this.options.Dir}
|
||||
var subDirs = this.subDirs // copy slice
|
||||
if len(subDirs) > 0 {
|
||||
for _, subDir := range subDirs {
|
||||
rootDirs = append(rootDirs, subDir.Path)
|
||||
}
|
||||
}
|
||||
|
||||
for _, rootDir := range rootDirs {
|
||||
var dir = rootDir + "/p" + types.String(this.policy.Id)
|
||||
goman.New(func() {
|
||||
_ = this.cleanDeletedDirs(dir)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 清理 *.trash 目录
|
||||
// 由于在很多硬盘上耗时非常久,所以应该放在后台运行
|
||||
func (this *FileStorage) cleanDeletedDirs(dir string) error {
|
||||
fp, err := os.Open(dir)
|
||||
@@ -1269,8 +1358,8 @@ func (this *FileStorage) cleanDeletedDirs(dir string) error {
|
||||
return err
|
||||
}
|
||||
for _, info := range subDirs {
|
||||
subDir := info.Name()
|
||||
if !strings.HasSuffix(subDir, "-deleted") {
|
||||
var subDir = info.Name()
|
||||
if !strings.HasSuffix(subDir, ".trash") {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1291,32 +1380,15 @@ func (this *FileStorage) increaseHit(key string, hash string, reader Reader) {
|
||||
if rate <= 0 {
|
||||
rate = 1000
|
||||
}
|
||||
if this.lastHotSize == 0 {
|
||||
// 自动降低采样率来增加热点数据的缓存几率
|
||||
rate = rate / 10
|
||||
}
|
||||
if rands.Int(0, rate) == 0 {
|
||||
var memoryStorage = this.memoryStorage
|
||||
|
||||
var hitErr = this.list.IncreaseHit(hash)
|
||||
if hitErr != nil {
|
||||
// 此错误可以忽略
|
||||
remotelogs.Error("CACHE", "increase hit failed: "+hitErr.Error())
|
||||
}
|
||||
|
||||
// 增加到热点
|
||||
// 这里不收录缓存尺寸过大的文件
|
||||
if memoryStorage != nil && reader.BodySize() > 0 && reader.BodySize() < 128*sizes.M {
|
||||
this.hotMapLocker.Lock()
|
||||
hotItem, ok := this.hotMap[key]
|
||||
|
||||
// 限制热点数据存活时间
|
||||
var unixTime = time.Now().Unix()
|
||||
var expiresAt = reader.ExpiresAt()
|
||||
if expiresAt <= 0 || expiresAt > unixTime+HotItemLifeSeconds {
|
||||
expiresAt = unixTime + HotItemLifeSeconds
|
||||
}
|
||||
|
||||
if ok {
|
||||
hotItem.Hits++
|
||||
} else if len(this.hotMap) < HotItemSize { // 控制数量
|
||||
@@ -1326,6 +1398,15 @@ func (this *FileStorage) increaseHit(key string, hash string, reader Reader) {
|
||||
}
|
||||
}
|
||||
this.hotMapLocker.Unlock()
|
||||
|
||||
// 只有重复点击的才增加点击量
|
||||
if ok {
|
||||
var hitErr = this.list.IncreaseHit(hash)
|
||||
if hitErr != nil {
|
||||
// 此错误可以忽略
|
||||
remotelogs.Error("CACHE", "increase hit failed: "+hitErr.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1342,11 +1423,15 @@ func (this *FileStorage) removeCacheFile(path string) error {
|
||||
err = nil
|
||||
|
||||
// 删除Partial相关
|
||||
var partialPath = partialRangesFilePath(path)
|
||||
var partialPath = PartialRangesFilePath(path)
|
||||
if openFileCache != nil {
|
||||
openFileCache.Close(partialPath)
|
||||
}
|
||||
_ = os.Remove(partialPath)
|
||||
|
||||
_, statErr := os.Stat(partialPath)
|
||||
if statErr == nil {
|
||||
_ = os.Remove(partialPath)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -1359,7 +1444,6 @@ func (this *FileStorage) createMemoryStorage() error {
|
||||
Name: this.policy.Name,
|
||||
Description: this.policy.Description,
|
||||
Capacity: this.options.MemoryPolicy.Capacity,
|
||||
MaxKeys: this.policy.MaxKeys,
|
||||
MaxSize: &shared.SizeCapacity{Count: 128, Unit: shared.SizeCapacityUnitMB}, // TODO 将来可以修改
|
||||
Type: serverconfigs.CachePolicyStorageMemory,
|
||||
Options: this.policy.Options,
|
||||
@@ -1426,7 +1510,7 @@ func (this *FileStorage) initOpenFileCache() {
|
||||
}
|
||||
|
||||
func (this *FileStorage) runMemoryStorageSafety(f func(memoryStorage *MemoryStorage)) {
|
||||
var memoryStorage = this.memoryStorage
|
||||
var memoryStorage = this.memoryStorage // copy
|
||||
if memoryStorage != nil {
|
||||
f(memoryStorage)
|
||||
}
|
||||
@@ -1434,25 +1518,64 @@ func (this *FileStorage) runMemoryStorageSafety(f func(memoryStorage *MemoryStor
|
||||
|
||||
// 检查磁盘剩余空间
|
||||
func (this *FileStorage) checkDiskSpace() {
|
||||
if this.options != nil && len(this.options.Dir) > 0 {
|
||||
var stat unix.Statfs_t
|
||||
err := unix.Statfs(this.options.Dir, &stat)
|
||||
var minFreeSize = DefaultMinDiskFreeSpace
|
||||
|
||||
var options = this.options // copy
|
||||
if options != nil && options.MinFreeSize != nil && options.MinFreeSize.Bytes() > 0 {
|
||||
minFreeSize = uint64(options.MinFreeSize.Bytes())
|
||||
}
|
||||
|
||||
if options != nil && len(options.Dir) > 0 {
|
||||
stat, err := fsutils.StatDevice(options.Dir)
|
||||
if err == nil {
|
||||
var availableBytes = stat.Bavail * uint64(stat.Bsize)
|
||||
this.mainDiskIsFull = availableBytes < MinDiskSpace
|
||||
this.mainDiskIsFull = stat.FreeSize() < minFreeSize
|
||||
this.mainDiskTotalSize = stat.TotalSize()
|
||||
|
||||
// check capacity (only on main directory) when node capacity had not been set
|
||||
if !this.mainDiskIsFull {
|
||||
var capacityBytes int64
|
||||
var maxDiskCapacity = SharedManager.MaxDiskCapacity // copy
|
||||
if maxDiskCapacity != nil && maxDiskCapacity.Bytes() > 0 {
|
||||
capacityBytes = SharedManager.MaxDiskCapacity.Bytes()
|
||||
} else {
|
||||
var policy = this.policy // copy
|
||||
if policy != nil {
|
||||
capacityBytes = policy.CapacityBytes() // copy
|
||||
}
|
||||
}
|
||||
|
||||
if capacityBytes > 0 && stat.UsedSize() >= uint64(capacityBytes) {
|
||||
this.mainDiskIsFull = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var subDirs = this.subDirs // copy slice
|
||||
for _, subDir := range subDirs {
|
||||
var stat unix.Statfs_t
|
||||
err := unix.Statfs(subDir.Path, &stat)
|
||||
stat, err := fsutils.StatDevice(subDir.Path)
|
||||
if err == nil {
|
||||
var availableBytes = stat.Bavail * uint64(stat.Bsize)
|
||||
subDir.IsFull = availableBytes < MinDiskSpace
|
||||
subDir.IsFull = stat.FreeSize() < minFreeSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否有已满的磁盘分区
|
||||
func (this *FileStorage) hasFullDisk() bool {
|
||||
this.checkDiskSpace()
|
||||
|
||||
var hasFullDisk = this.mainDiskIsFull
|
||||
if !hasFullDisk {
|
||||
var subDirs = this.subDirs // copy slice
|
||||
for _, subDir := range subDirs {
|
||||
if subDir.IsFull {
|
||||
hasFullDisk = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasFullDisk
|
||||
}
|
||||
|
||||
// 获取目录
|
||||
func (this *FileStorage) subDir(hash string) (dirPath string, dirIsFull bool) {
|
||||
var suffix = "/p" + types.String(this.policy.Id) + "/" + hash[:2] + "/" + hash[2:4]
|
||||
@@ -1482,6 +1605,121 @@ func (this *FileStorage) subDir(hash string) (dirPath string, dirIsFull bool) {
|
||||
return subDir.Path + suffix, subDir.IsFull
|
||||
}
|
||||
|
||||
// ScanGarbageCaches 清理目录中“失联”的缓存文件
|
||||
// “失联”为不在HashMap中的文件
|
||||
func (this *FileStorage) ScanGarbageCaches(fileCallback func(path string) error) error {
|
||||
if !this.list.(*FileList).HashMapIsLoaded() {
|
||||
return errors.New("cache list is loading")
|
||||
}
|
||||
|
||||
var mainDir = this.options.Dir
|
||||
var allDirs = []string{mainDir}
|
||||
var subDirs = this.subDirs // copy
|
||||
for _, subDir := range subDirs {
|
||||
allDirs = append(allDirs, subDir.Path)
|
||||
}
|
||||
|
||||
var countDirs = 0
|
||||
|
||||
// process progress
|
||||
var progressSock = gosock.NewTmpSock(teaconst.CacheGarbageSockName)
|
||||
_, sockErr := progressSock.SendTimeout(&gosock.Command{Code: "progress", Params: map[string]any{"progress": 0}}, 1*time.Second)
|
||||
var canReportProgress = sockErr == nil
|
||||
var lastProgress float64
|
||||
var countFound = 0
|
||||
|
||||
for _, subDir := range allDirs {
|
||||
var dir0 = subDir + "/p" + types.String(this.policy.Id)
|
||||
dir1Matches, err := filepath.Glob(dir0 + "/*")
|
||||
if err != nil {
|
||||
// ignore error
|
||||
continue
|
||||
}
|
||||
|
||||
for _, dir1 := range dir1Matches {
|
||||
if len(filepath.Base(dir1)) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
dir2Matches, err := filepath.Glob(dir1 + "/*")
|
||||
if err != nil {
|
||||
// ignore error
|
||||
continue
|
||||
}
|
||||
for _, dir2 := range dir2Matches {
|
||||
if len(filepath.Base(dir2)) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
countDirs++
|
||||
|
||||
// report progress
|
||||
if canReportProgress {
|
||||
var progress = float64(countDirs) / 65536
|
||||
if fmt.Sprintf("%.2f", lastProgress) != fmt.Sprintf("%.2f", progress) {
|
||||
lastProgress = progress
|
||||
_, _ = progressSock.SendTimeout(&gosock.Command{Code: "progress", Params: map[string]any{
|
||||
"progress": progress,
|
||||
"count": countFound,
|
||||
}}, 100*time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
fileMatches, err := filepath.Glob(dir2 + "/*.cache")
|
||||
if err != nil {
|
||||
// ignore error
|
||||
continue
|
||||
}
|
||||
|
||||
for _, file := range fileMatches {
|
||||
var filename = filepath.Base(file)
|
||||
var hash = strings.TrimSuffix(filename, ".cache")
|
||||
if len(hash) != HashKeyLength {
|
||||
continue
|
||||
}
|
||||
|
||||
isReady, found := this.list.(*FileList).ExistQuick(hash)
|
||||
if !isReady {
|
||||
continue
|
||||
}
|
||||
|
||||
if found {
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查文件正在被写入
|
||||
stat, err := os.Stat(file)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if fasttime.Now().Unix()-stat.ModTime().Unix() < 300 /** 5 minutes **/ {
|
||||
continue
|
||||
}
|
||||
|
||||
if fileCallback != nil {
|
||||
countFound++
|
||||
err = fileCallback(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 100% progress
|
||||
if canReportProgress && lastProgress != 1 {
|
||||
_, _ = progressSock.SendTimeout(&gosock.Command{Code: "progress", Params: map[string]any{
|
||||
"progress": 1,
|
||||
"count": countFound,
|
||||
}}, 100*time.Millisecond)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 计算字节数字代号
|
||||
func (this *FileStorage) charCode(r byte) uint8 {
|
||||
if r >= '0' && r <= '9' {
|
||||
return r - '0'
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
@@ -18,7 +19,11 @@ import (
|
||||
)
|
||||
|
||||
func TestFileStorage_Init(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
@@ -26,6 +31,8 @@ func TestFileStorage_Init(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -40,17 +47,26 @@ func TestFileStorage_Init(t *testing.T) {
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
storage.purgeLoop()
|
||||
t.Log(storage.list.(*FileList).total, "entries left")
|
||||
t.Log(storage.list.(*FileList).Stat(func(hash string) bool {
|
||||
return true
|
||||
}))
|
||||
}
|
||||
|
||||
func TestFileStorage_OpenWriter(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -88,6 +104,10 @@ func TestFileStorage_OpenWriter(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_OpenWriter_Partial(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 2,
|
||||
IsOn: true,
|
||||
@@ -95,6 +115,9 @@ func TestFileStorage_OpenWriter_Partial(t *testing.T) {
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -123,13 +146,20 @@ func TestFileStorage_OpenWriter_Partial(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -146,7 +176,7 @@ func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
|
||||
t.Log(writer)
|
||||
|
||||
resp := &http.Response{
|
||||
StatusCode: 200,
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header{
|
||||
"Content-Type": []string{"text/html; charset=utf-8"},
|
||||
"Last-Modified": []string{"Wed, 06 Jan 2021 10:03:29 GMT"},
|
||||
@@ -188,13 +218,20 @@ func TestFileStorage_OpenWriter_HTTP(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -214,7 +251,7 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
|
||||
|
||||
writer, err := storage.OpenWriter("abc"+strconv.Itoa(i), time.Now().Unix()+3600, 200, -1, -1, -1, false)
|
||||
if err != nil {
|
||||
if err != ErrFileIsWriting {
|
||||
if errors.Is(err, ErrFileIsWriting) {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
@@ -243,13 +280,20 @@ func TestFileStorage_Concurrent_Open_DifferentFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -269,7 +313,7 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
|
||||
|
||||
writer, err := storage.OpenWriter("abc"+strconv.Itoa(0), time.Now().Unix()+3600, 200, -1, -1, -1, false)
|
||||
if err != nil {
|
||||
if err != ErrFileIsWriting {
|
||||
if errors.Is(err, ErrFileIsWriting) {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
@@ -299,13 +343,20 @@ func TestFileStorage_Concurrent_Open_SameFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Read(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -335,13 +386,20 @@ func TestFileStorage_Read(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Read_HTTP_Response(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -388,13 +446,20 @@ func TestFileStorage_Read_HTTP_Response(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Read_NotFound(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -421,13 +486,20 @@ func TestFileStorage_Read_NotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Delete(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -440,13 +512,20 @@ func TestFileStorage_Delete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Stat(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -465,13 +544,20 @@ func TestFileStorage_Stat(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_CleanAll(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -496,13 +582,20 @@ func TestFileStorage_CleanAll(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_Stop(t *testing.T) {
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -511,6 +604,10 @@ func TestFileStorage_Stop(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_DecodeFile(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
@@ -518,6 +615,9 @@ func TestFileStorage_DecodeFile(t *testing.T) {
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -527,22 +627,55 @@ func TestFileStorage_DecodeFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestFileStorage_RemoveCacheFile(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(nil)
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
t.Log(storage.removeCacheFile("/Users/WorkSpace/EdgeProject/EdgeCache/p43/15/7e/157eba0dfc6dfb6fbbf20b1f9e584674.cache"))
|
||||
}
|
||||
|
||||
func TestFileStorage_ScanGarbageCaches(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 43,
|
||||
Options: map[string]any{"dir": "/Users/WorkSpace/EdgeProject/EdgeCache"},
|
||||
})
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = storage.ScanGarbageCaches(func(path string) error {
|
||||
t.Log(path, PartialRangesFilePath(path))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFileStorage_Read(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
_ = utils.SetRLimit(1024 * 1024)
|
||||
|
||||
storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
IsOn: true,
|
||||
Options: map[string]interface{}{
|
||||
"dir": Tea.Root + "/caches",
|
||||
},
|
||||
})
|
||||
|
||||
defer storage.Stop()
|
||||
|
||||
err := storage.Init()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
|
||||
@@ -54,7 +54,7 @@ type StorageInterface interface {
|
||||
AddToList(item *Item)
|
||||
|
||||
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
||||
IgnoreKey(key string)
|
||||
IgnoreKey(key string, maxSize int64)
|
||||
|
||||
// CanSendfile 是否支持Sendfile
|
||||
CanSendfile() bool
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
package caches
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"github.com/shirou/gopsutil/v3/load"
|
||||
"math"
|
||||
"runtime"
|
||||
"strconv"
|
||||
@@ -30,6 +29,11 @@ type MemoryItem struct {
|
||||
Status int
|
||||
IsDone bool
|
||||
ModifiedAt int64
|
||||
|
||||
IsPrepared bool
|
||||
WriteOffset int64
|
||||
|
||||
isReferring bool // if it is referring by other objects
|
||||
}
|
||||
|
||||
func (this *MemoryItem) IsExpired() bool {
|
||||
@@ -50,7 +54,7 @@ type MemoryStorage struct {
|
||||
|
||||
purgeTicker *utils.Ticker
|
||||
|
||||
totalSize int64
|
||||
usedSize int64
|
||||
writingKeyMap map[string]zero.Zero // key => bool
|
||||
|
||||
ignoreKeys *setutils.FixedSet
|
||||
@@ -62,7 +66,7 @@ func NewMemoryStorage(policy *serverconfigs.HTTPCachePolicy, parentStorage Stora
|
||||
|
||||
if parentStorage != nil {
|
||||
if queueSize <= 0 {
|
||||
queueSize = 2048 + int(policy.CapacityBytes()/sizes.G)*2048
|
||||
queueSize = utils.SystemMemoryGB() * 100_000
|
||||
}
|
||||
|
||||
dirtyChan = make(chan string, queueSize)
|
||||
@@ -85,10 +89,10 @@ func (this *MemoryStorage) Init() error {
|
||||
_ = this.list.Init()
|
||||
|
||||
this.list.OnAdd(func(item *Item) {
|
||||
atomic.AddInt64(&this.totalSize, item.TotalSize())
|
||||
atomic.AddInt64(&this.usedSize, item.TotalSize())
|
||||
})
|
||||
this.list.OnRemove(func(item *Item) {
|
||||
atomic.AddInt64(&this.totalSize, -item.TotalSize())
|
||||
atomic.AddInt64(&this.usedSize, -item.TotalSize())
|
||||
})
|
||||
|
||||
this.initPurgeTicker()
|
||||
@@ -111,17 +115,29 @@ func (this *MemoryStorage) Init() error {
|
||||
|
||||
// OpenReader 读取缓存
|
||||
func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool) (Reader, error) {
|
||||
hash := this.hash(key)
|
||||
var hash = this.hash(key)
|
||||
|
||||
// check if exists in list
|
||||
exists, _ := this.list.Exist(types.String(hash))
|
||||
if !exists {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
// read from valuesMap
|
||||
this.locker.RLock()
|
||||
item := this.valuesMap[hash]
|
||||
var item = this.valuesMap[hash]
|
||||
|
||||
if item != nil {
|
||||
item.isReferring = true
|
||||
}
|
||||
|
||||
if item == nil || !item.IsDone {
|
||||
this.locker.RUnlock()
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
if useStale || (item.ExpiresAt > fasttime.Now().Unix()) {
|
||||
reader := NewMemoryReader(item)
|
||||
var reader = NewMemoryReader(item)
|
||||
err := reader.Init()
|
||||
if err != nil {
|
||||
this.locker.RUnlock()
|
||||
@@ -129,17 +145,6 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
|
||||
// 增加点击量
|
||||
// 1/1000采样
|
||||
// TODO 考虑是否在缓存策略里设置
|
||||
if rands.Int(0, 1000) == 0 {
|
||||
var hitErr = this.list.IncreaseHit(types.String(hash))
|
||||
if hitErr != nil {
|
||||
// 此错误可以忽略
|
||||
remotelogs.Error("CACHE", "increase hit failed: "+hitErr.Error())
|
||||
}
|
||||
}
|
||||
|
||||
return reader, nil
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
@@ -151,13 +156,13 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
|
||||
|
||||
// OpenWriter 打开缓存写入器等待写入
|
||||
func (this *MemoryStorage) OpenWriter(key string, expiredAt int64, status int, headerSize int, bodySize int64, maxSize int64, isPartial bool) (Writer, error) {
|
||||
if this.ignoreKeys.Has(key) {
|
||||
if maxSize > 0 && this.ignoreKeys.Has(types.String(maxSize)+"$"+key) {
|
||||
return nil, ErrEntityTooLarge
|
||||
}
|
||||
|
||||
// TODO 内存缓存暂时不支持分块内容存储
|
||||
if isPartial {
|
||||
return nil, ErrFileIsWriting
|
||||
return nil, fmt.Errorf("%w (004)", ErrFileIsWriting)
|
||||
}
|
||||
return this.openWriter(key, expiredAt, status, headerSize, bodySize, maxSize, true)
|
||||
}
|
||||
@@ -172,7 +177,7 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
|
||||
if isDirty &&
|
||||
this.parentStorage != nil &&
|
||||
this.dirtyQueueSize > 0 &&
|
||||
len(this.dirtyChan) == this.dirtyQueueSize { // 缓存时间过长
|
||||
len(this.dirtyChan) >= this.dirtyQueueSize-int(fsutils.DiskMaxWrites) /** delta **/ { // 缓存时间过长
|
||||
return nil, ErrWritingQueueFull
|
||||
}
|
||||
|
||||
@@ -183,7 +188,7 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
|
||||
var isWriting = false
|
||||
_, ok := this.writingKeyMap[key]
|
||||
if ok {
|
||||
return nil, ErrFileIsWriting
|
||||
return nil, fmt.Errorf("%w (005)", ErrFileIsWriting)
|
||||
}
|
||||
this.writingKeyMap[key] = zero.New()
|
||||
defer func() {
|
||||
@@ -193,36 +198,38 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
|
||||
}()
|
||||
|
||||
// 检查是否过期
|
||||
hash := this.hash(key)
|
||||
var hash = this.hash(key)
|
||||
item, ok := this.valuesMap[hash]
|
||||
if ok && !item.IsExpired() {
|
||||
return nil, ErrFileIsWriting
|
||||
var hashString = types.String(hash)
|
||||
exists, _ := this.list.Exist(hashString)
|
||||
if !exists {
|
||||
// remove from values map
|
||||
delete(this.valuesMap, hash)
|
||||
_ = this.list.Remove(hashString)
|
||||
item = nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("%w (006)", ErrFileIsWriting)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否超出最大值
|
||||
totalKeys, err := this.list.Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if this.policy.MaxKeys > 0 && totalKeys > this.policy.MaxKeys {
|
||||
return nil, NewCapacityError("write memory cache failed: too many keys in cache storage")
|
||||
}
|
||||
capacityBytes := this.memoryCapacityBytes()
|
||||
// 检查是否超出容量最大值
|
||||
var capacityBytes = this.memoryCapacityBytes()
|
||||
if bodySize < 0 {
|
||||
bodySize = 0
|
||||
}
|
||||
if capacityBytes > 0 && capacityBytes <= this.totalSize+bodySize {
|
||||
return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.totalSize, 10) + " bytes")
|
||||
if capacityBytes > 0 && capacityBytes <= atomic.LoadInt64(&this.usedSize)+bodySize {
|
||||
return nil, NewCapacityError("write memory cache failed: over memory size: " + strconv.FormatInt(capacityBytes, 10) + ", current size: " + strconv.FormatInt(this.usedSize, 10) + " bytes")
|
||||
}
|
||||
|
||||
// 先删除
|
||||
err = this.deleteWithoutLocker(key)
|
||||
err := this.deleteWithoutLocker(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
isWriting = true
|
||||
return NewMemoryWriter(this, key, expiresAt, status, isDirty, maxSize, func() {
|
||||
return NewMemoryWriter(this, key, expiresAt, status, isDirty, bodySize, maxSize, func(valueItem *MemoryItem) {
|
||||
this.locker.Lock()
|
||||
delete(this.writingKeyMap, key)
|
||||
this.locker.Unlock()
|
||||
@@ -254,7 +261,7 @@ func (this *MemoryStorage) CleanAll() error {
|
||||
this.locker.Lock()
|
||||
this.valuesMap = map[uint64]*MemoryItem{}
|
||||
_ = this.list.Reset()
|
||||
atomic.StoreInt64(&this.totalSize, 0)
|
||||
atomic.StoreInt64(&this.usedSize, 0)
|
||||
this.locker.Unlock()
|
||||
return nil
|
||||
}
|
||||
@@ -353,15 +360,23 @@ func (this *MemoryStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy
|
||||
if newPolicy.CapacityBytes() == 0 {
|
||||
_ = this.CleanAll()
|
||||
}
|
||||
|
||||
// reset ignored keys
|
||||
this.ignoreKeys.Reset()
|
||||
}
|
||||
|
||||
// CanUpdatePolicy 检查策略是否可以更新
|
||||
func (this *MemoryStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy) bool {
|
||||
return true
|
||||
return newPolicy != nil && newPolicy.Type == serverconfigs.CachePolicyStorageMemory
|
||||
}
|
||||
|
||||
// AddToList 将缓存添加到列表
|
||||
func (this *MemoryStorage) AddToList(item *Item) {
|
||||
// skip added item
|
||||
if item.MetaSize > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
item.MetaSize = int64(len(item.Key)) + 128 /** 128是我们评估的数据结构的长度 **/
|
||||
var hash = types.String(this.hash(item.Key))
|
||||
|
||||
@@ -379,12 +394,12 @@ func (this *MemoryStorage) TotalDiskSize() int64 {
|
||||
|
||||
// TotalMemorySize 内存尺寸
|
||||
func (this *MemoryStorage) TotalMemorySize() int64 {
|
||||
return atomic.LoadInt64(&this.totalSize)
|
||||
return atomic.LoadInt64(&this.usedSize)
|
||||
}
|
||||
|
||||
// IgnoreKey 忽略某个Key,即不缓存某个Key
|
||||
func (this *MemoryStorage) IgnoreKey(key string) {
|
||||
this.ignoreKeys.Push(key)
|
||||
func (this *MemoryStorage) IgnoreKey(key string, maxSize int64) {
|
||||
this.ignoreKeys.Push(types.String(maxSize) + "$" + key)
|
||||
}
|
||||
|
||||
// CanSendfile 是否支持Sendfile
|
||||
@@ -392,6 +407,11 @@ func (this *MemoryStorage) CanSendfile() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// HasFreeSpaceForHotItems 是否有足够的空间提供给热门内容
|
||||
func (this *MemoryStorage) HasFreeSpaceForHotItems() bool {
|
||||
return atomic.LoadInt64(&this.usedSize) < this.memoryCapacityBytes()*3/4
|
||||
}
|
||||
|
||||
// 计算Key Hash
|
||||
func (this *MemoryStorage) hash(key string) uint64 {
|
||||
return xxhash.Sum64String(key)
|
||||
@@ -399,22 +419,6 @@ func (this *MemoryStorage) hash(key string) uint64 {
|
||||
|
||||
// 清理任务
|
||||
func (this *MemoryStorage) purgeLoop() {
|
||||
// 计算是否应该开启LFU清理
|
||||
var capacityBytes = this.policy.CapacityBytes()
|
||||
var startLFU = false
|
||||
var usedPercent = float32(this.TotalMemorySize()*100) / float32(capacityBytes)
|
||||
var lfuFreePercent = this.policy.MemoryLFUFreePercent
|
||||
if lfuFreePercent <= 0 {
|
||||
lfuFreePercent = 5
|
||||
}
|
||||
if capacityBytes > 0 {
|
||||
if lfuFreePercent < 100 {
|
||||
if usedPercent >= 100-lfuFreePercent {
|
||||
startLFU = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清理过期
|
||||
var purgeCount = this.policy.MemoryAutoPurgeCount
|
||||
if purgeCount <= 0 {
|
||||
@@ -431,6 +435,23 @@ func (this *MemoryStorage) purgeLoop() {
|
||||
})
|
||||
|
||||
// LFU
|
||||
// 计算是否应该开启LFU清理
|
||||
var capacityBytes = this.policy.CapacityBytes()
|
||||
var startLFU = false
|
||||
|
||||
var usedPercent = float32(this.TotalMemorySize()*100) / float32(capacityBytes)
|
||||
var lfuFreePercent = this.policy.MemoryLFUFreePercent
|
||||
if lfuFreePercent <= 0 {
|
||||
lfuFreePercent = 5
|
||||
}
|
||||
if capacityBytes > 0 {
|
||||
if lfuFreePercent < 100 {
|
||||
if usedPercent >= 100-lfuFreePercent {
|
||||
startLFU = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if startLFU {
|
||||
var total, _ = this.list.Count()
|
||||
if total > 0 {
|
||||
@@ -463,34 +484,20 @@ func (this *MemoryStorage) purgeLoop() {
|
||||
// 开始Flush任务
|
||||
func (this *MemoryStorage) startFlush() {
|
||||
var statCount = 0
|
||||
var writeDelayMS float64 = 0
|
||||
|
||||
for key := range this.dirtyChan {
|
||||
statCount++
|
||||
|
||||
if statCount == 100 {
|
||||
statCount = 0
|
||||
|
||||
if protectingLoadWhenDump {
|
||||
loadStat, err := load.Avg()
|
||||
if err == nil && loadStat != nil {
|
||||
if loadStat.Load1 > 10 {
|
||||
writeDelayMS = 100
|
||||
} else if loadStat.Load1 > 3 {
|
||||
writeDelayMS = 50
|
||||
} else if loadStat.Load1 > 2 {
|
||||
writeDelayMS = 10
|
||||
} else {
|
||||
writeDelayMS = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.flushItem(key)
|
||||
|
||||
if writeDelayMS > 0 {
|
||||
time.Sleep(time.Duration(writeDelayMS) * time.Millisecond)
|
||||
if fsutils.IsInExtremelyHighLoad {
|
||||
time.Sleep(1 * time.Second)
|
||||
} else if fsutils.IsInHighLoad {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -506,9 +513,20 @@ func (this *MemoryStorage) flushItem(key string) {
|
||||
item, ok := this.valuesMap[hash]
|
||||
this.locker.RUnlock()
|
||||
|
||||
// 从内存中移除,并确保无论如何都会执行
|
||||
defer func() {
|
||||
_ = this.Delete(key)
|
||||
|
||||
// 重用内存,前提是确保内存不再被引用
|
||||
if enableFragmentPool && ok && item.IsDone && !item.isReferring && len(item.BodyValue) > 0 {
|
||||
SharedFragmentMemoryPool.Put(item.BodyValue)
|
||||
}
|
||||
}()
|
||||
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if !item.IsDone {
|
||||
remotelogs.Error("CACHE", "flush items failed: open writer failed: item has not been done")
|
||||
return
|
||||
@@ -517,6 +535,16 @@ func (this *MemoryStorage) flushItem(key string) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否在列表中,防止未加入列表时就开始flush
|
||||
isInList, err := this.list.Exist(types.String(hash))
|
||||
if err != nil {
|
||||
remotelogs.Error("CACHE", "flush items failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
if !isInList {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
writer, err := this.parentStorage.OpenFlushWriter(key, item.ExpiresAt, item.Status, len(item.HeaderValue), int64(len(item.BodyValue)))
|
||||
if err != nil {
|
||||
if !CanIgnoreErr(err) {
|
||||
@@ -554,26 +582,37 @@ func (this *MemoryStorage) flushItem(key string) {
|
||||
HeaderSize: writer.HeaderSize(),
|
||||
BodySize: writer.BodySize(),
|
||||
})
|
||||
|
||||
// 从内存中移除
|
||||
_ = this.Delete(key)
|
||||
}
|
||||
|
||||
func (this *MemoryStorage) memoryCapacityBytes() int64 {
|
||||
var maxSystemBytes = SharedManager.MaxSystemMemoryBytesPerStorage()
|
||||
if this.policy == nil {
|
||||
return 0
|
||||
}
|
||||
c1 := int64(0)
|
||||
if this.policy.Capacity != nil {
|
||||
c1 = this.policy.Capacity.Bytes()
|
||||
return maxSystemBytes
|
||||
}
|
||||
|
||||
if SharedManager.MaxMemoryCapacity != nil {
|
||||
c2 := SharedManager.MaxMemoryCapacity.Bytes()
|
||||
if c2 > 0 {
|
||||
return c2
|
||||
var capacityBytes = SharedManager.MaxMemoryCapacity.Bytes()
|
||||
if capacityBytes > 0 {
|
||||
if capacityBytes > maxSystemBytes {
|
||||
return maxSystemBytes
|
||||
}
|
||||
|
||||
return capacityBytes
|
||||
}
|
||||
}
|
||||
return c1
|
||||
|
||||
var capacity = this.policy.Capacity // copy
|
||||
if capacity != nil {
|
||||
var capacityBytes = capacity.Bytes()
|
||||
if capacityBytes > 0 {
|
||||
if capacityBytes > maxSystemBytes {
|
||||
return maxSystemBytes
|
||||
}
|
||||
return capacityBytes
|
||||
}
|
||||
}
|
||||
|
||||
return maxSystemBytes
|
||||
}
|
||||
|
||||
func (this *MemoryStorage) deleteWithoutLocker(key string) error {
|
||||
|
||||
@@ -3,6 +3,7 @@ package caches
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"runtime"
|
||||
@@ -271,6 +272,10 @@ func TestMemoryStorage_Purge(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMemoryStorage_Expire(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{
|
||||
MemoryAutoPurgeInterval: 5,
|
||||
}, nil)
|
||||
|
||||
@@ -4,11 +4,11 @@ package caches
|
||||
|
||||
import "strings"
|
||||
|
||||
// 获取 ranges 文件路径
|
||||
func partialRangesFilePath(path string) string {
|
||||
// PartialRangesFilePath 获取 ranges 文件路径
|
||||
func PartialRangesFilePath(path string) string {
|
||||
// ranges路径
|
||||
var dotIndex = strings.LastIndex(path, ".")
|
||||
var rangePath = ""
|
||||
var rangePath string
|
||||
if dotIndex < 0 {
|
||||
rangePath = path + "@ranges.cache"
|
||||
} else {
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestParseHost(t *testing.T) {
|
||||
func TestUintString(t *testing.T) {
|
||||
t.Log(strconv.FormatUint(xxhash.Sum64String("https://goedge.cn/"), 10))
|
||||
t.Log(strconv.FormatUint(123456789, 10))
|
||||
t.Log(fmt.Sprintf("%d", 1234567890123))
|
||||
t.Logf("%d", 1234567890123)
|
||||
}
|
||||
|
||||
func BenchmarkUint_String(b *testing.B) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package caches
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"os"
|
||||
@@ -42,7 +43,9 @@ func NewFileWriter(storage StorageInterface, rawWriter *os.File, key string, exp
|
||||
|
||||
// WriteHeader 写入数据
|
||||
func (this *FileWriter) WriteHeader(data []byte) (n int, err error) {
|
||||
fsutils.WriteBegin()
|
||||
n, err = this.rawWriter.Write(data)
|
||||
fsutils.WriteEnd()
|
||||
this.headerSize += int64(n)
|
||||
if err != nil {
|
||||
_ = this.Discard()
|
||||
@@ -72,21 +75,30 @@ func (this *FileWriter) WriteHeaderLength(headerLength int) error {
|
||||
|
||||
// Write 写入数据
|
||||
func (this *FileWriter) Write(data []byte) (n int, err error) {
|
||||
n, err = this.rawWriter.Write(data)
|
||||
this.bodySize += int64(n)
|
||||
|
||||
if this.maxSize > 0 && this.bodySize > this.maxSize {
|
||||
err = ErrEntityTooLarge
|
||||
|
||||
if this.storage != nil {
|
||||
this.storage.IgnoreKey(this.key)
|
||||
// split LARGE data
|
||||
var l = len(data)
|
||||
if l > (2 << 20) {
|
||||
var offset = 0
|
||||
const bufferSize = 256 << 10
|
||||
for {
|
||||
var end = offset + bufferSize
|
||||
if end > l {
|
||||
end = l
|
||||
}
|
||||
n1, err1 := this.write(data[offset:end])
|
||||
n += n1
|
||||
if err1 != nil {
|
||||
return n, err1
|
||||
}
|
||||
if end >= l {
|
||||
return n, nil
|
||||
}
|
||||
offset = end
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
_ = this.Discard()
|
||||
}
|
||||
return
|
||||
// write NORMAL size data
|
||||
return this.write(data)
|
||||
}
|
||||
|
||||
// WriteAt 在指定位置写入数据
|
||||
@@ -126,18 +138,24 @@ func (this *FileWriter) Close() error {
|
||||
|
||||
err := this.WriteHeaderLength(types.Int(this.headerSize))
|
||||
if err != nil {
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
_ = os.Remove(path)
|
||||
return err
|
||||
}
|
||||
err = this.WriteBodyLength(this.bodySize)
|
||||
if err != nil {
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
_ = os.Remove(path)
|
||||
return err
|
||||
}
|
||||
|
||||
fsutils.WriteBegin()
|
||||
err = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
if err != nil {
|
||||
_ = os.Remove(path)
|
||||
} else if strings.HasSuffix(path, FileTmpSuffix) {
|
||||
@@ -156,7 +174,9 @@ func (this *FileWriter) Discard() error {
|
||||
this.endFunc()
|
||||
})
|
||||
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
|
||||
err := os.Remove(this.rawWriter.Name())
|
||||
return err
|
||||
@@ -182,3 +202,23 @@ func (this *FileWriter) Key() string {
|
||||
func (this *FileWriter) ItemType() ItemType {
|
||||
return ItemTypeFile
|
||||
}
|
||||
|
||||
func (this *FileWriter) write(data []byte) (n int, err error) {
|
||||
fsutils.WriteBegin()
|
||||
n, err = this.rawWriter.Write(data)
|
||||
fsutils.WriteEnd()
|
||||
this.bodySize += int64(n)
|
||||
|
||||
if this.maxSize > 0 && this.bodySize > this.maxSize {
|
||||
err = ErrEntityTooLarge
|
||||
|
||||
if this.storage != nil {
|
||||
this.storage.IgnoreKey(this.key, this.maxSize)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
_ = this.Discard()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package caches
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/cespare/xxhash"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MemoryWriter struct {
|
||||
@@ -16,29 +16,51 @@ type MemoryWriter struct {
|
||||
bodySize int64
|
||||
status int
|
||||
isDirty bool
|
||||
maxSize int64
|
||||
|
||||
expectedBodySize int64
|
||||
maxSize int64
|
||||
|
||||
hash uint64
|
||||
item *MemoryItem
|
||||
endFunc func()
|
||||
endFunc func(valueItem *MemoryItem)
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64, status int, isDirty bool, maxSize int64, endFunc func()) *MemoryWriter {
|
||||
w := &MemoryWriter{
|
||||
storage: memoryStorage,
|
||||
key: key,
|
||||
expiredAt: expiredAt,
|
||||
item: &MemoryItem{
|
||||
ExpiresAt: expiredAt,
|
||||
ModifiedAt: time.Now().Unix(),
|
||||
Status: status,
|
||||
},
|
||||
status: status,
|
||||
isDirty: isDirty,
|
||||
maxSize: maxSize,
|
||||
endFunc: endFunc,
|
||||
func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64, status int, isDirty bool, expectedBodySize int64, maxSize int64, endFunc func(valueItem *MemoryItem)) *MemoryWriter {
|
||||
var valueItem = &MemoryItem{
|
||||
ExpiresAt: expiredAt,
|
||||
ModifiedAt: fasttime.Now().Unix(),
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
var w = &MemoryWriter{
|
||||
storage: memoryStorage,
|
||||
key: key,
|
||||
expiredAt: expiredAt,
|
||||
item: valueItem,
|
||||
status: status,
|
||||
isDirty: isDirty,
|
||||
expectedBodySize: expectedBodySize,
|
||||
maxSize: maxSize,
|
||||
endFunc: endFunc,
|
||||
}
|
||||
|
||||
w.hash = w.calculateHash(key)
|
||||
|
||||
return w
|
||||
@@ -53,17 +75,32 @@ func (this *MemoryWriter) WriteHeader(data []byte) (n int, err error) {
|
||||
|
||||
// Write 写入数据
|
||||
func (this *MemoryWriter) Write(data []byte) (n int, err error) {
|
||||
this.bodySize += int64(len(data))
|
||||
this.item.BodyValue = append(this.item.BodyValue, data...)
|
||||
var l = len(data)
|
||||
if l == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if this.item.IsPrepared {
|
||||
if this.item.WriteOffset+int64(l) > this.expectedBodySize {
|
||||
err = ErrWritingUnavailable
|
||||
return
|
||||
}
|
||||
copy(this.item.BodyValue[this.item.WriteOffset:], data)
|
||||
this.item.WriteOffset += int64(l)
|
||||
} else {
|
||||
this.item.BodyValue = append(this.item.BodyValue, data...)
|
||||
}
|
||||
|
||||
this.bodySize += int64(l)
|
||||
|
||||
// 检查尺寸
|
||||
if this.maxSize > 0 && this.bodySize > this.maxSize {
|
||||
err = ErrEntityTooLarge
|
||||
this.storage.IgnoreKey(this.key)
|
||||
return len(data), err
|
||||
this.storage.IgnoreKey(this.key, this.maxSize)
|
||||
return l, err
|
||||
}
|
||||
|
||||
return len(data), nil
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// WriteAt 在指定位置写入数据
|
||||
@@ -87,7 +124,7 @@ func (this *MemoryWriter) BodySize() int64 {
|
||||
func (this *MemoryWriter) Close() error {
|
||||
// 需要在Locker之外
|
||||
defer this.once.Do(func() {
|
||||
this.endFunc()
|
||||
this.endFunc(this.item)
|
||||
})
|
||||
|
||||
if this.item == nil {
|
||||
@@ -96,30 +133,48 @@ func (this *MemoryWriter) Close() error {
|
||||
|
||||
this.storage.locker.Lock()
|
||||
this.item.IsDone = true
|
||||
this.storage.valuesMap[this.hash] = this.item
|
||||
var err error
|
||||
if this.isDirty {
|
||||
if this.storage.parentStorage != nil {
|
||||
this.storage.valuesMap[this.hash] = this.item
|
||||
|
||||
select {
|
||||
case this.storage.dirtyChan <- this.key:
|
||||
default:
|
||||
// remove from values map
|
||||
delete(this.storage.valuesMap, this.hash)
|
||||
|
||||
err = ErrWritingQueueFull
|
||||
}
|
||||
} else {
|
||||
this.storage.valuesMap[this.hash] = this.item
|
||||
}
|
||||
} else {
|
||||
this.storage.valuesMap[this.hash] = this.item
|
||||
}
|
||||
|
||||
this.storage.locker.Unlock()
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Discard 丢弃
|
||||
func (this *MemoryWriter) Discard() error {
|
||||
// 需要在Locker之外
|
||||
defer this.once.Do(func() {
|
||||
this.endFunc()
|
||||
this.endFunc(this.item)
|
||||
})
|
||||
|
||||
this.storage.locker.Lock()
|
||||
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()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ package caches
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"io"
|
||||
"os"
|
||||
@@ -42,7 +43,7 @@ func NewPartialFileWriter(rawWriter *os.File, key string, expiredAt int64, metaH
|
||||
isPartial: isPartial,
|
||||
bodyOffset: bodyOffset,
|
||||
ranges: ranges,
|
||||
rangePath: partialRangesFilePath(rawWriter.Name()),
|
||||
rangePath: PartialRangesFilePath(rawWriter.Name()),
|
||||
metaHeaderSize: metaHeaderSize,
|
||||
metaBodySize: metaBodySize,
|
||||
}
|
||||
@@ -53,7 +54,9 @@ func (this *PartialFileWriter) WriteHeader(data []byte) (n int, err error) {
|
||||
if !this.isNew {
|
||||
return
|
||||
}
|
||||
fsutils.WriteBegin()
|
||||
n, err = this.rawWriter.Write(data)
|
||||
fsutils.WriteEnd()
|
||||
this.headerSize += int64(n)
|
||||
if err != nil {
|
||||
_ = this.Discard()
|
||||
@@ -62,7 +65,9 @@ func (this *PartialFileWriter) WriteHeader(data []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (this *PartialFileWriter) AppendHeader(data []byte) error {
|
||||
fsutils.WriteBegin()
|
||||
_, err := this.rawWriter.Write(data)
|
||||
fsutils.WriteEnd()
|
||||
if err != nil {
|
||||
_ = this.Discard()
|
||||
} else {
|
||||
@@ -99,7 +104,9 @@ func (this *PartialFileWriter) WriteHeaderLength(headerLength int) error {
|
||||
|
||||
// Write 写入数据
|
||||
func (this *PartialFileWriter) Write(data []byte) (n int, err error) {
|
||||
fsutils.WriteBegin()
|
||||
n, err = this.rawWriter.Write(data)
|
||||
fsutils.WriteEnd()
|
||||
this.bodySize += int64(n)
|
||||
if err != nil {
|
||||
_ = this.Discard()
|
||||
@@ -128,7 +135,9 @@ func (this *PartialFileWriter) WriteAt(offset int64, data []byte) error {
|
||||
this.bodyOffset = SizeMeta + int64(keyLength) + this.headerSize
|
||||
}
|
||||
|
||||
fsutils.WriteBegin()
|
||||
_, err := this.rawWriter.WriteAt(data, this.bodyOffset+offset)
|
||||
fsutils.WriteEnd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -172,7 +181,9 @@ func (this *PartialFileWriter) Close() error {
|
||||
this.ranges.BodySize = this.bodySize
|
||||
err := this.ranges.WriteToFile(this.rangePath)
|
||||
if err != nil {
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
this.remove()
|
||||
return err
|
||||
}
|
||||
@@ -181,19 +192,25 @@ func (this *PartialFileWriter) Close() error {
|
||||
if this.isNew {
|
||||
err = this.WriteHeaderLength(types.Int(this.headerSize))
|
||||
if err != nil {
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
this.remove()
|
||||
return err
|
||||
}
|
||||
err = this.WriteBodyLength(this.bodySize)
|
||||
if err != nil {
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
this.remove()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fsutils.WriteBegin()
|
||||
err = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
if err != nil {
|
||||
this.remove()
|
||||
}
|
||||
@@ -207,7 +224,9 @@ func (this *PartialFileWriter) Discard() error {
|
||||
this.endFunc()
|
||||
})
|
||||
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
|
||||
_ = os.Remove(this.rangePath)
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !plus || !linux
|
||||
|
||||
package compressions
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -49,3 +50,45 @@ func TestBrotliReader(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBrotliReader(b *testing.B) {
|
||||
data, err := os.ReadFile("./reader_brotli.go")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
var buf = bytes.NewBuffer([]byte{})
|
||||
writer, err := compressions.NewBrotliWriter(buf, 5)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_, err = writer.Write(data)
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
var compressedData = buf.Bytes()
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
reader, readerErr := compressions.NewBrotliReader(bytes.NewBuffer(compressedData))
|
||||
if readerErr != nil {
|
||||
b.Fatal(readerErr)
|
||||
}
|
||||
var readBuf = make([]byte, 1024)
|
||||
for {
|
||||
_, readErr := reader.Read(readBuf)
|
||||
if readErr != nil {
|
||||
if readErr != io.EOF {
|
||||
b.Fatal(readErr)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
closeErr := reader.Close()
|
||||
if closeErr != nil {
|
||||
b.Fatal(closeErr)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type ContentEncoding = string
|
||||
@@ -58,3 +59,32 @@ func NewWriter(writer io.Writer, compressType serverconfigs.HTTPCompressionType,
|
||||
}
|
||||
return nil, errors.New("invalid compression type '" + compressType + "'")
|
||||
}
|
||||
|
||||
// SupportEncoding 检查是否支持某个编码
|
||||
func SupportEncoding(encoding string) bool {
|
||||
return encoding == ContentEncodingBr ||
|
||||
encoding == ContentEncodingGzip ||
|
||||
encoding == ContentEncodingDeflate ||
|
||||
encoding == ContentEncodingZSTD
|
||||
}
|
||||
|
||||
// WrapHTTPResponse 包装http.Response对象
|
||||
func WrapHTTPResponse(resp *http.Response) {
|
||||
if resp == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var contentEncoding = resp.Header.Get("Content-Encoding")
|
||||
if len(contentEncoding) == 0 || !SupportEncoding(contentEncoding) {
|
||||
return
|
||||
}
|
||||
|
||||
reader, err := NewReader(resp.Body, contentEncoding)
|
||||
if err != nil {
|
||||
// unable to decode, we ignore the error
|
||||
return
|
||||
}
|
||||
resp.Header.Del("Content-Encoding")
|
||||
resp.Header.Del("Content-Length")
|
||||
resp.Body = reader
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build !plus || !linux
|
||||
|
||||
package compressions
|
||||
|
||||
@@ -27,7 +28,7 @@ func newBrotliWriter(writer io.Writer, level int) (*BrotliWriter, error) {
|
||||
return &BrotliWriter{
|
||||
writer: brotli.NewWriterOptions(writer, brotli.WriterOptions{
|
||||
Quality: level,
|
||||
LGWin: 13, // TODO 在全局设置里可以设置此值
|
||||
LGWin: 14, // TODO 在全局设置里可以设置此值
|
||||
}),
|
||||
level: level,
|
||||
}, nil
|
||||
|
||||
@@ -19,6 +19,10 @@ func NewZSTDWriter(writer io.Writer, level int) (Writer, error) {
|
||||
}
|
||||
|
||||
func newZSTDWriter(writer io.Writer, level int) (Writer, error) {
|
||||
if level < 0 {
|
||||
level = 0
|
||||
}
|
||||
|
||||
var zstdLevel = zstd.EncoderLevelFromZstd(level)
|
||||
|
||||
zstdWriter, err := zstd.NewWriter(writer, zstd.WithEncoderLevel(zstdLevel))
|
||||
|
||||
@@ -9,6 +9,24 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewZSTDWriter_Level0(t *testing.T) {
|
||||
var buf = &bytes.Buffer{}
|
||||
writer, err := compressions.NewZSTDWriter(buf, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var originData = []byte(strings.Repeat("Hello", 1024))
|
||||
_, err = writer.Write(originData)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("origin data:", len(originData), "result:", buf.Len())
|
||||
}
|
||||
|
||||
func TestNewZSTDWriter(t *testing.T) {
|
||||
var buf = &bytes.Buffer{}
|
||||
writer, err := compressions.NewZSTDWriter(buf, 10)
|
||||
|
||||
@@ -1,38 +1,81 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
)
|
||||
|
||||
// APIConfig 节点API配置
|
||||
const ConfigFileName = "api_node.yaml"
|
||||
const oldConfigFileName = "api.yaml"
|
||||
|
||||
type APIConfig struct {
|
||||
RPC struct {
|
||||
Endpoints []string `yaml:"endpoints" json:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
|
||||
} `yaml:"rpc" json:"rpc"`
|
||||
NodeId string `yaml:"nodeId" json:"nodeId"`
|
||||
Secret string `yaml:"secret" json:"secret"`
|
||||
OldRPC struct {
|
||||
Endpoints []string `yaml:"endpoints,omitempty" json:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate,omitempty" json:"disableUpdate"`
|
||||
} `yaml:"rpc,omitempty" json:"rpc"`
|
||||
|
||||
RPCEndpoints []string `yaml:"rpc.endpoints,flow" json:"rpc.endpoints"`
|
||||
RPCDisableUpdate bool `yaml:"rpc.disableUpdate" json:"rpc.disableUpdate"`
|
||||
NodeId string `yaml:"nodeId" json:"nodeId"`
|
||||
Secret string `yaml:"secret" json:"secret"`
|
||||
}
|
||||
|
||||
func NewAPIConfig() *APIConfig {
|
||||
return &APIConfig{}
|
||||
}
|
||||
|
||||
func (this *APIConfig) Init() error {
|
||||
// compatible with old
|
||||
if len(this.RPCEndpoints) == 0 && len(this.OldRPC.Endpoints) > 0 {
|
||||
this.RPCEndpoints = this.OldRPC.Endpoints
|
||||
this.RPCDisableUpdate = this.OldRPC.DisableUpdate
|
||||
}
|
||||
|
||||
if len(this.RPCEndpoints) == 0 {
|
||||
return errors.New("no valid 'rpc.endpoints'")
|
||||
}
|
||||
|
||||
if len(this.NodeId) == 0 {
|
||||
return errors.New("'nodeId' required")
|
||||
}
|
||||
if len(this.Secret) == 0 {
|
||||
return errors.New("'secret' required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadAPIConfig() (*APIConfig, error) {
|
||||
data, err := os.ReadFile(Tea.ConfigFile("api.yaml"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, filename := range []string{ConfigFileName, oldConfigFileName} {
|
||||
data, err := os.ReadFile(Tea.ConfigFile(filename))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := &APIConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var config = &APIConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
return nil, errors.New("init error: " + err.Error())
|
||||
}
|
||||
|
||||
// 自动生成新的配置文件
|
||||
if filename == oldConfigFileName {
|
||||
config.OldRPC.Endpoints = nil
|
||||
_ = config.WriteFile(Tea.ConfigFile(ConfigFileName))
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
return nil, errors.New("no config file '" + ConfigFileName + "' found")
|
||||
}
|
||||
|
||||
// WriteFile 保存到文件
|
||||
|
||||
@@ -3,6 +3,7 @@ package configs_test
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
_ "github.com/iwind/TeaGo/bootstrap"
|
||||
"gopkg.in/yaml.v3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -12,4 +13,10 @@ func TestLoadAPIConfig(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("%+v", config)
|
||||
|
||||
configData, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(configData))
|
||||
}
|
||||
|
||||
@@ -1,11 +1,57 @@
|
||||
package configs
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"gopkg.in/yaml.v3"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ClusterConfig 集群配置
|
||||
type ClusterConfig struct {
|
||||
RPC struct {
|
||||
OldRPC struct {
|
||||
Endpoints []string `yaml:"endpoints" json:"endpoints"`
|
||||
DisableUpdate bool `yaml:"disableUpdate" json:"disableUpdate"`
|
||||
} `yaml:"rpc" json:"rpc"`
|
||||
} `yaml:"rpc,omitempty" json:"rpc"`
|
||||
|
||||
RPCEndpoints []string `yaml:"rpc.endpoints,flow" json:"rpc.endpoints"`
|
||||
RPCDisableUpdate bool `yaml:"rpc.disableUpdate" json:"rpc.disableUpdate"`
|
||||
|
||||
ClusterId string `yaml:"clusterId" json:"clusterId"`
|
||||
Secret string `yaml:"secret" json:"secret"`
|
||||
}
|
||||
|
||||
func (this *ClusterConfig) Init() error {
|
||||
// compatible with old
|
||||
if len(this.RPCEndpoints) == 0 && len(this.OldRPC.Endpoints) > 0 {
|
||||
this.RPCEndpoints = this.OldRPC.Endpoints
|
||||
this.RPCDisableUpdate = this.OldRPC.DisableUpdate
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadClusterConfig() (*ClusterConfig, error) {
|
||||
for _, filename := range []string{"api_cluster.yaml", "cluster.yaml"} {
|
||||
data, err := os.ReadFile(Tea.ConfigFile(filename))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config = &ClusterConfig{}
|
||||
err = yaml.Unmarshal(data, config)
|
||||
if err != nil {
|
||||
return config, err
|
||||
}
|
||||
|
||||
err = config.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
28
internal/configs/cluster_config_test.go
Normal file
28
internal/configs/cluster_config_test.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package configs_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/configs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"gopkg.in/yaml.v3"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadClusterConfig(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
config, err := configs.LoadClusterConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("%+v", config)
|
||||
|
||||
configData, err := yaml.Marshal(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(string(configData))
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
package conns
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
@@ -10,14 +11,14 @@ import (
|
||||
var SharedMap = NewMap()
|
||||
|
||||
type Map struct {
|
||||
m map[string]map[int]net.Conn // ip => { port => Conn }
|
||||
m map[string]map[string]net.Conn // ip => { network_port => Conn }
|
||||
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
func NewMap() *Map {
|
||||
return &Map{
|
||||
m: map[string]map[int]net.Conn{},
|
||||
m: map[string]map[string]net.Conn{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,21 +26,19 @@ func (this *Map) Add(conn net.Conn) {
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr)
|
||||
|
||||
key, ip, ok := this.connAddr(conn)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var ip = tcpAddr.IP.String()
|
||||
var port = tcpAddr.Port
|
||||
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
connMap, ok := this.m[ip]
|
||||
if !ok {
|
||||
this.m[ip] = map[int]net.Conn{port: conn}
|
||||
this.m[ip] = map[string]net.Conn{key: conn}
|
||||
} else {
|
||||
connMap[port] = conn
|
||||
connMap[key] = conn
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,14 +46,11 @@ func (this *Map) Remove(conn net.Conn) {
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
tcpAddr, ok := conn.RemoteAddr().(*net.TCPAddr)
|
||||
key, ip, ok := this.connAddr(conn)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var ip = tcpAddr.IP.String()
|
||||
var port = tcpAddr.Port
|
||||
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
@@ -62,7 +58,7 @@ func (this *Map) Remove(conn net.Conn) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
delete(connMap, port)
|
||||
delete(connMap, key)
|
||||
|
||||
if len(connMap) == 0 {
|
||||
delete(this.m, ip)
|
||||
@@ -121,3 +117,24 @@ func (this *Map) AllConns() []net.Conn {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (this *Map) connAddr(conn net.Conn) (key string, ip string, ok bool) {
|
||||
if conn == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var addr = conn.RemoteAddr()
|
||||
switch realAddr := addr.(type) {
|
||||
case *net.TCPAddr:
|
||||
return addr.Network() + types.String(realAddr.Port), realAddr.IP.String(), true
|
||||
case *net.UDPAddr:
|
||||
return addr.Network() + types.String(realAddr.Port), realAddr.IP.String(), true
|
||||
default:
|
||||
var s = addr.String()
|
||||
host, port, err := net.SplitHostPort(s)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return addr.Network() + port, host, true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.0.1"
|
||||
Version = "1.3.3"
|
||||
|
||||
ProductName = "Edge Node"
|
||||
ProcessName = "edge-node"
|
||||
@@ -14,5 +14,6 @@ const (
|
||||
// SystemdServiceName systemd
|
||||
SystemdServiceName = "edge-node"
|
||||
|
||||
AccessLogSockName = "edge-node.accesslog.sock"
|
||||
AccessLogSockName = "edge-node.accesslog"
|
||||
CacheGarbageSockName = "edge-node.cache.garbage"
|
||||
)
|
||||
|
||||
@@ -22,8 +22,6 @@ var (
|
||||
|
||||
IsQuiting = false // 是否正在退出
|
||||
EnableDBStat = false // 是否开启本地数据库统计
|
||||
|
||||
DiskIsFast = false // 是否为高速硬盘
|
||||
)
|
||||
|
||||
// 检查是否为主程序
|
||||
|
||||
@@ -19,7 +19,6 @@ func TestAES128CFBMethod_Encrypt(t *testing.T) {
|
||||
dst = dst[:len(src)]
|
||||
t.Log("dst:", string(dst))
|
||||
|
||||
src = make([]byte, len(src))
|
||||
src, err = method.Decrypt(dst)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -54,7 +53,6 @@ func TestAES128CFBMethod_Encrypt2(t *testing.T) {
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
a := []byte{1}
|
||||
_, err = method.Decrypt(a)
|
||||
if err != nil {
|
||||
@@ -64,7 +62,6 @@ func TestAES128CFBMethod_Encrypt2(t *testing.T) {
|
||||
|
||||
for _, dst := range sources {
|
||||
dst2 := append([]byte{}, dst...)
|
||||
src2 := make([]byte, len(dst2))
|
||||
src2, err := method.Decrypt(dst2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
||||
@@ -3,10 +3,11 @@ package events
|
||||
type Event = string
|
||||
|
||||
const (
|
||||
EventStart Event = "start" // start loading
|
||||
EventLoaded Event = "loaded" // first load
|
||||
EventQuit Event = "quit" // quit node gracefully
|
||||
EventReload Event = "reload" // reload config
|
||||
EventTerminated Event = "terminated" // process terminated
|
||||
EventNFTablesReady Event = "nftablesReady" // nftables ready
|
||||
EventStart Event = "start" // start loading
|
||||
EventLoaded Event = "loaded" // first load
|
||||
EventQuit Event = "quit" // quit node gracefully
|
||||
EventReload Event = "reload" // reload config
|
||||
EventTerminated Event = "terminated" // process terminated
|
||||
EventNFTablesReady Event = "nftablesReady" // nftables ready
|
||||
EventReloadSomeServers Event = "reloadSomeServers" // reload some servers
|
||||
)
|
||||
|
||||
@@ -24,6 +24,17 @@ func On(event Event, callback func()) {
|
||||
OnKey(event, nil, callback)
|
||||
}
|
||||
|
||||
func OnEvents(events []Event, callback func()) {
|
||||
for _, event := range events {
|
||||
On(event, callback)
|
||||
}
|
||||
}
|
||||
|
||||
func OnClose(callback func()) {
|
||||
On(EventQuit, callback)
|
||||
On(EventTerminated, callback)
|
||||
}
|
||||
|
||||
// OnKey 使用Key增加事件回调
|
||||
func OnKey(event Event, key interface{}, callback func()) {
|
||||
if key == nil {
|
||||
|
||||
@@ -9,8 +9,8 @@ func TestOn(t *testing.T) {
|
||||
type User struct {
|
||||
name string
|
||||
}
|
||||
var u = &User{}
|
||||
var u2 = &User{}
|
||||
var u = &User{name: "lily"}
|
||||
var u2 = &User{name: "lucy"}
|
||||
|
||||
events.On("hello", func() {
|
||||
t.Log("world")
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/ddosconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
@@ -92,7 +93,7 @@ func (this *DDoSProtectionManager) Apply(config *ddosconfigs.ProtectionConfig) e
|
||||
// 对比配置
|
||||
configJSON, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return errors.New("encode config to json failed: " + err.Error())
|
||||
return fmt.Errorf("encode config to json failed: %w", err)
|
||||
}
|
||||
if !allowIPListChanged && bytes.Equal(this.lastConfig, configJSON) {
|
||||
return nil
|
||||
@@ -188,7 +189,7 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
for _, filter := range nftablesFilters {
|
||||
chain, oldRules, err := this.getRules(filter)
|
||||
if err != nil {
|
||||
return errors.New("get old rules failed: " + err.Error())
|
||||
return fmt.Errorf("get old rules failed: %w", err)
|
||||
}
|
||||
|
||||
var protocol = filter.protocol()
|
||||
@@ -273,7 +274,7 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
// 先清空所有相关规则
|
||||
err = this.removeOldTCPRules(chain, oldRules)
|
||||
if err != nil {
|
||||
return errors.New("delete old rules failed: " + err.Error())
|
||||
return fmt.Errorf("delete old rules failed: %w", err)
|
||||
}
|
||||
|
||||
// 添加新规则
|
||||
@@ -281,9 +282,9 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
if maxConnections > 0 {
|
||||
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "count", "over", types.String(maxConnections), "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "maxConnections", types.String(maxConnections)}))
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +294,7 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,14 +306,14 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
} else {
|
||||
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsMinutelyRate)+"/minute burst "+types.String(newConnectionsMinutelyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsRate", "0"}))
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,14 +326,14 @@ func (this *DDoSProtectionManager) addTCPRules(tcpConfig *ddosconfigs.TCPConfig)
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
} else {
|
||||
var cmd = executils.NewTimeoutCmd(10*time.Second, nftExe, "add", "rule", protocol, filter.Name, nftablesChainName, "tcp", "dport", types.String(port), "ct", "state", "new", "meter", "meter-"+protocol+"-"+types.String(port)+"-new-connections-secondly-rate", "{ "+protocol+" saddr limit rate over "+types.String(newConnectionsSecondlyRate)+"/second burst "+types.String(newConnectionsSecondlyRate+3)+" packets }" /**"add", "@deny_set", "{"+protocol+" saddr}",**/, "counter", "drop", "comment", this.encodeUserData([]string{"tcp", types.String(port), "newConnectionsSecondlyRate", "0"}))
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("add nftables rule '" + cmd.String() + "' failed: " + err.Error() + " (" + cmd.Stderr() + ")")
|
||||
return fmt.Errorf("add nftables rule '%s' failed: %w (%s)", cmd.String(), err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -498,11 +499,11 @@ func (this *DDoSProtectionManager) getTable(filter *nftablesTableDefinition) (*n
|
||||
func (this *DDoSProtectionManager) getRules(filter *nftablesTableDefinition) (*nftables.Chain, []*nftables.Rule, error) {
|
||||
table, err := this.getTable(filter)
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("get table failed: " + err.Error())
|
||||
return nil, nil, fmt.Errorf("get table failed: %w", err)
|
||||
}
|
||||
chain, err := table.GetChain(nftablesChainName)
|
||||
if err != nil {
|
||||
return nil, nil, errors.New("get chain failed: " + err.Error())
|
||||
return nil, nil, fmt.Errorf("get chain failed: %w", err)
|
||||
}
|
||||
rules, err := chain.GetRules()
|
||||
return chain, rules, err
|
||||
@@ -538,7 +539,7 @@ func (this *DDoSProtectionManager) updateAllowIPList(allIPList []string) error {
|
||||
// 不存在则删除
|
||||
err = set.DeleteIPElement(ip)
|
||||
if err != nil {
|
||||
return errors.New("delete ip element '" + ip + "' failed: " + err.Error())
|
||||
return fmt.Errorf("delete ip element '%s' failed: %w", ip, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -556,7 +557,7 @@ func (this *DDoSProtectionManager) updateAllowIPList(allIPList []string) error {
|
||||
// 不存在则添加
|
||||
err = set.AddIPElement(ip, nil, false)
|
||||
if err != nil {
|
||||
return errors.New("add ip '" + ip + "' failed: " + err.Error())
|
||||
return fmt.Errorf("add ip '%s' failed: %w", ip, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
package firewalls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -32,7 +31,7 @@ func NewFirewalld() *Firewalld {
|
||||
cmdQueue: make(chan *firewalldCmd, 4096),
|
||||
}
|
||||
|
||||
path, err := exec.LookPath("firewall-cmd")
|
||||
path, err := executils.LookPath("firewall-cmd")
|
||||
if err == nil && len(path) > 0 {
|
||||
var cmd = executils.NewTimeoutCmd(3*time.Second, path, "--state")
|
||||
err := cmd.Run()
|
||||
@@ -195,7 +194,7 @@ func (this *Firewalld) DropSourceIP(ip string, timeoutSeconds int, async bool) e
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("run command failed '" + cmd.String() + "': " + err.Error())
|
||||
return fmt.Errorf("run command failed '%s': %w", cmd.String(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package firewalls
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
@@ -88,11 +89,15 @@ type blockIPItem struct {
|
||||
}
|
||||
|
||||
func NewNFTablesFirewall() (*NFTablesFirewall, error) {
|
||||
conn, err := nftables.NewConn()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var firewall = &NFTablesFirewall{
|
||||
conn: nftables.NewConn(),
|
||||
conn: conn,
|
||||
dropIPQueue: make(chan *blockIPItem, 4096),
|
||||
}
|
||||
err := firewall.init()
|
||||
err = firewall.init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -145,10 +150,10 @@ func (this *NFTablesFirewall) init() error {
|
||||
table, err = this.conn.AddIPv6Table(tableDef.Name)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.New("create table '" + tableDef.Name + "' failed: " + err.Error())
|
||||
return fmt.Errorf("create table '%s' failed: %w", tableDef.Name, err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("get table '" + tableDef.Name + "' failed: " + err.Error())
|
||||
return fmt.Errorf("get table '%s' failed: %w", tableDef.Name, err)
|
||||
}
|
||||
}
|
||||
if table == nil {
|
||||
@@ -162,10 +167,10 @@ func (this *NFTablesFirewall) init() error {
|
||||
if nftables.IsNotFound(err) {
|
||||
chain, err = table.AddAcceptChain(chainName)
|
||||
if err != nil {
|
||||
return errors.New("create chain '" + chainName + "' failed: " + err.Error())
|
||||
return fmt.Errorf("create chain '%s' failed: %w", chainName, err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("get chain '" + chainName + "' failed: " + err.Error())
|
||||
return fmt.Errorf("get chain '%s' failed: %w", chainName, err)
|
||||
}
|
||||
}
|
||||
if chain == nil {
|
||||
@@ -180,7 +185,7 @@ func (this *NFTablesFirewall) init() error {
|
||||
_, err = chain.AddAcceptInterfaceRule("lo", loRuleName)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.New("add 'lo' rule failed: " + err.Error())
|
||||
return fmt.Errorf("add 'lo' rule failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,10 +208,10 @@ func (this *NFTablesFirewall) init() error {
|
||||
HasTimeout: true,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.New("create set '" + setName + "' failed: " + err.Error())
|
||||
return fmt.Errorf("create set '%s' failed: %w", setName, err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("get set '" + setName + "' failed: " + err.Error())
|
||||
return fmt.Errorf("get set '%s' failed: %w", setName, err)
|
||||
}
|
||||
}
|
||||
if set == nil {
|
||||
@@ -255,10 +260,10 @@ func (this *NFTablesFirewall) init() error {
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return errors.New("add rule failed: " + err.Error())
|
||||
return fmt.Errorf("add rule failed: %w", err)
|
||||
}
|
||||
} else {
|
||||
return errors.New("get rule failed: " + err.Error())
|
||||
return fmt.Errorf("get rule failed: %w", err)
|
||||
}
|
||||
}
|
||||
if rule == nil {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package nftables
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build linux
|
||||
|
||||
package nftables
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package nftables_test
|
||||
|
||||
@@ -11,7 +10,10 @@ import (
|
||||
)
|
||||
|
||||
func getIPv4Chain(t *testing.T) *nftables.Chain {
|
||||
var conn = nftables.NewConn()
|
||||
conn, err := nftables.NewConn()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
table, err := conn.GetTable("test_ipv4", nftables.TableFamilyIPv4)
|
||||
if err != nil {
|
||||
if err == nftables.ErrTableNotFound {
|
||||
|
||||
@@ -15,10 +15,14 @@ type Conn struct {
|
||||
rawConn *nft.Conn
|
||||
}
|
||||
|
||||
func NewConn() *Conn {
|
||||
return &Conn{
|
||||
rawConn: &nft.Conn{},
|
||||
func NewConn() (*Conn, error) {
|
||||
conn, err := nft.New()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Conn{
|
||||
rawConn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *Conn) Raw() *nft.Conn {
|
||||
|
||||
@@ -6,12 +6,13 @@ package nftables_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"os/exec"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConn_Test(t *testing.T) {
|
||||
_, err := exec.LookPath("nft")
|
||||
_, err := executils.LookPath("nft")
|
||||
if err == nil {
|
||||
t.Log("ok")
|
||||
return
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
|
||||
package nftables
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrTableNotFound = errors.New("table not found")
|
||||
var ErrChainNotFound = errors.New("chain not found")
|
||||
@@ -15,5 +18,5 @@ func IsNotFound(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
return err == ErrTableNotFound || err == ErrChainNotFound || err == ErrSetNotFound || err == ErrRuleNotFound
|
||||
return err == ErrTableNotFound || err == ErrChainNotFound || err == ErrSetNotFound || err == ErrRuleNotFound || strings.Contains(err.Error(), "no such file or directory")
|
||||
}
|
||||
|
||||
65
internal/firewalls/nftables/expration.go
Normal file
65
internal/firewalls/nftables/expration.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Expiration struct {
|
||||
m map[string]time.Time // key => expires time
|
||||
|
||||
lastGCAt int64
|
||||
|
||||
locker sync.RWMutex
|
||||
}
|
||||
|
||||
func NewExpiration() *Expiration {
|
||||
return &Expiration{
|
||||
m: map[string]time.Time{},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Expiration) AddUnsafe(key []byte, expires time.Time) {
|
||||
this.m[string(key)] = expires
|
||||
}
|
||||
|
||||
func (this *Expiration) Add(key []byte, expires time.Time) {
|
||||
this.locker.Lock()
|
||||
this.m[string(key)] = expires
|
||||
this.gc()
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *Expiration) Remove(key []byte) {
|
||||
this.locker.Lock()
|
||||
delete(this.m, string(key))
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
func (this *Expiration) Contains(key []byte) bool {
|
||||
this.locker.RLock()
|
||||
expires, ok := this.m[string(key)]
|
||||
if ok && expires.Year() > 2000 && time.Now().After(expires) {
|
||||
ok = false
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
func (this *Expiration) gc() {
|
||||
// we won't gc too frequently
|
||||
var currentTime = time.Now().Unix()
|
||||
if this.lastGCAt >= currentTime {
|
||||
return
|
||||
}
|
||||
this.lastGCAt = currentTime
|
||||
|
||||
var now = time.Now().Add(-10 * time.Second) // gc elements expired before 10 seconds ago
|
||||
for key, expires := range this.m {
|
||||
if expires.Year() > 2000 && now.After(expires) {
|
||||
delete(this.m, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
59
internal/firewalls/nftables/expration_test.go
Normal file
59
internal/firewalls/nftables/expration_test.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package nftables_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/firewalls/nftables"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestExpiration_Add(t *testing.T) {
|
||||
var expiration = nftables.NewExpiration()
|
||||
{
|
||||
expiration.Add([]byte{'a', 'b', 'c'}, time.Now())
|
||||
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
|
||||
}
|
||||
{
|
||||
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(1*time.Second))
|
||||
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
|
||||
}
|
||||
{
|
||||
expiration.Add([]byte{'a', 'b', 'c'}, time.Time{})
|
||||
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
|
||||
}
|
||||
{
|
||||
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(-1*time.Second))
|
||||
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
|
||||
}
|
||||
{
|
||||
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(-10*time.Second))
|
||||
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
|
||||
}
|
||||
{
|
||||
expiration.Add([]byte{'a', 'b', 'c'}, time.Now().Add(1*time.Second))
|
||||
expiration.Remove([]byte{'a', 'b', 'c'})
|
||||
t.Log(expiration.Contains([]byte{'a', 'b', 'c'}))
|
||||
}
|
||||
{
|
||||
expiration.Add(net.ParseIP("10.254.0.75").To4(), time.Now())
|
||||
t.Log(expiration.Contains(net.ParseIP("10.254.0.75").To4()))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNewExpiration(b *testing.B) {
|
||||
var expiration = nftables.NewExpiration()
|
||||
for i := 0; i < 10_000; i++ {
|
||||
expiration.Add([]byte(types.String(types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255)))), time.Now().Add(3600*time.Second))
|
||||
}
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
expiration.Add([]byte(types.String(types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255))+"."+types.String(rands.Int(0, 255)))), time.Now().Add(3600*time.Second))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build linux
|
||||
|
||||
package nftables
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build linux
|
||||
|
||||
package nftables
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
@@ -12,7 +13,6 @@ import (
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
@@ -54,7 +54,7 @@ func init() {
|
||||
|
||||
// NftExePath 查找nftables可执行文件路径
|
||||
func NftExePath() string {
|
||||
path, _ := exec.LookPath("nft")
|
||||
path, _ := executils.LookPath("nft")
|
||||
if len(path) > 0 {
|
||||
return path
|
||||
}
|
||||
@@ -85,21 +85,21 @@ func (this *Installer) Install() error {
|
||||
}
|
||||
|
||||
// 检查是否已经存在
|
||||
if len(NftExePath()) > 0 {
|
||||
if len(NftExePath()) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var cmd *executils.Cmd
|
||||
|
||||
// check dnf
|
||||
dnfExe, err := exec.LookPath("dnf")
|
||||
dnfExe, err := executils.LookPath("dnf")
|
||||
if err == nil {
|
||||
cmd = executils.NewCmd(dnfExe, "-y", "install", "nftables")
|
||||
}
|
||||
|
||||
// check apt
|
||||
if cmd == nil {
|
||||
aptExe, err := exec.LookPath("apt")
|
||||
aptExe, err := executils.LookPath("apt")
|
||||
if err == nil {
|
||||
cmd = executils.NewCmd(aptExe, "install", "nftables")
|
||||
}
|
||||
@@ -107,7 +107,7 @@ func (this *Installer) Install() error {
|
||||
|
||||
// check yum
|
||||
if cmd == nil {
|
||||
yumExe, err := exec.LookPath("yum")
|
||||
yumExe, err := executils.LookPath("yum")
|
||||
if err == nil {
|
||||
cmd = executils.NewCmd(yumExe, "-y", "install", "nftables")
|
||||
}
|
||||
@@ -121,7 +121,7 @@ func (this *Installer) Install() error {
|
||||
cmd.WithStderr()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New(err.Error() + ": " + cmd.Stderr())
|
||||
return fmt.Errorf("%w: %s", err, cmd.Stderr())
|
||||
}
|
||||
|
||||
remotelogs.Println("NFTABLES", "installed nftables with command '"+cmd.String()+"' successfully")
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build linux
|
||||
|
||||
package nftables
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package nftables
|
||||
|
||||
@@ -35,17 +34,25 @@ type Set struct {
|
||||
conn *Conn
|
||||
rawSet *nft.Set
|
||||
batch *SetBatch
|
||||
|
||||
expiration *Expiration
|
||||
}
|
||||
|
||||
func NewSet(conn *Conn, rawSet *nft.Set) *Set {
|
||||
return &Set{
|
||||
conn: conn,
|
||||
rawSet: rawSet,
|
||||
var set = &Set{
|
||||
conn: conn,
|
||||
rawSet: rawSet,
|
||||
expiration: nil,
|
||||
batch: &SetBatch{
|
||||
conn: conn,
|
||||
rawSet: rawSet,
|
||||
},
|
||||
}
|
||||
|
||||
// retrieve set elements to improve "delete" speed
|
||||
set.initElements()
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (this *Set) Raw() *nft.Set {
|
||||
@@ -57,11 +64,21 @@ func (this *Set) Name() string {
|
||||
}
|
||||
|
||||
func (this *Set) AddElement(key []byte, options *ElementOptions, overwrite bool) error {
|
||||
// check if already exists
|
||||
if this.expiration != nil && !overwrite && this.expiration.Contains(key) {
|
||||
return nil
|
||||
}
|
||||
|
||||
var expiresTime = time.Time{}
|
||||
var rawElement = nft.SetElement{
|
||||
Key: key,
|
||||
}
|
||||
if options != nil {
|
||||
rawElement.Timeout = options.Timeout
|
||||
|
||||
if options.Timeout > 0 {
|
||||
expiresTime = time.UnixMilli(time.Now().UnixMilli() + options.Timeout.Milliseconds())
|
||||
}
|
||||
}
|
||||
err := this.conn.Raw().SetAddElements(this.rawSet, []nft.SetElement{
|
||||
rawElement,
|
||||
@@ -71,9 +88,19 @@ func (this *Set) AddElement(key []byte, options *ElementOptions, overwrite bool)
|
||||
}
|
||||
|
||||
err = this.conn.Commit()
|
||||
if err != nil {
|
||||
if err == nil {
|
||||
if this.expiration != nil {
|
||||
this.expiration.Add(key, expiresTime)
|
||||
}
|
||||
} else {
|
||||
var isFileExistsErr = strings.Contains(err.Error(), "file exists")
|
||||
if !overwrite && isFileExistsErr {
|
||||
// ignore file exists error
|
||||
return nil
|
||||
}
|
||||
|
||||
// retry if exists
|
||||
if overwrite && strings.Contains(err.Error(), "file exists") {
|
||||
if overwrite && isFileExistsErr {
|
||||
deleteErr := this.conn.Raw().SetDeleteElements(this.rawSet, []nft.SetElement{
|
||||
{
|
||||
Key: key,
|
||||
@@ -85,6 +112,11 @@ func (this *Set) AddElement(key []byte, options *ElementOptions, overwrite bool)
|
||||
})
|
||||
if err == nil {
|
||||
err = this.conn.Commit()
|
||||
if err == nil {
|
||||
if this.expiration != nil {
|
||||
this.expiration.Add(key, expiresTime)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,6 +139,11 @@ func (this *Set) AddIPElement(ip string, options *ElementOptions, overwrite bool
|
||||
}
|
||||
|
||||
func (this *Set) DeleteElement(key []byte) error {
|
||||
// if set element does not exist, we return immediately
|
||||
if this.expiration != nil && !this.expiration.Contains(key) {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := this.conn.Raw().SetDeleteElements(this.rawSet, []nft.SetElement{
|
||||
{
|
||||
Key: key,
|
||||
@@ -116,9 +153,17 @@ func (this *Set) DeleteElement(key []byte) error {
|
||||
return err
|
||||
}
|
||||
err = this.conn.Commit()
|
||||
if err != nil {
|
||||
if err == nil {
|
||||
if this.expiration != nil {
|
||||
this.expiration.Remove(key)
|
||||
}
|
||||
} else {
|
||||
if strings.Contains(err.Error(), "no such file or directory") {
|
||||
err = nil
|
||||
|
||||
if this.expiration != nil {
|
||||
this.expiration.Remove(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build linux
|
||||
|
||||
package nftables
|
||||
|
||||
|
||||
8
internal/firewalls/nftables/set_ext.go
Normal file
8
internal/firewalls/nftables/set_ext.go
Normal file
@@ -0,0 +1,8 @@
|
||||
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build linux && !plus
|
||||
|
||||
package nftables
|
||||
|
||||
func (this *Set) initElements() {
|
||||
// NOT IMPLEMENTED
|
||||
}
|
||||
@@ -34,7 +34,7 @@ func getIPv4Set(t *testing.T) *nftables.Set {
|
||||
|
||||
func TestSet_AddElement(t *testing.T) {
|
||||
var set = getIPv4Set(t)
|
||||
err := set.AddElement(net.ParseIP("192.168.2.31").To4(), &nftables.ElementOptions{Timeout: 86400 * time.Second})
|
||||
err := set.AddElement(net.ParseIP("192.168.2.31").To4(), &nftables.ElementOptions{Timeout: 86400 * time.Second}, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package nftables
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package nftables_test
|
||||
|
||||
@@ -10,7 +9,10 @@ import (
|
||||
)
|
||||
|
||||
func getIPv4Table(t *testing.T) *nftables.Table {
|
||||
var conn = nftables.NewConn()
|
||||
conn, err := nftables.NewConn()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
table, err := conn.GetTable("test_ipv4", nftables.TableFamilyIPv4)
|
||||
if err != nil {
|
||||
if err == nftables.ErrTableNotFound {
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package goman
|
||||
package goman_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
New(func() {
|
||||
goman.New(func() {
|
||||
t.Log("Hello")
|
||||
|
||||
t.Log(List())
|
||||
t.Log(goman.List())
|
||||
})
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
t.Log(List())
|
||||
t.Log(goman.List())
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
func TestNewWithArgs(t *testing.T) {
|
||||
NewWithArgs(func(args ...interface{}) {
|
||||
goman.NewWithArgs(func(args ...interface{}) {
|
||||
t.Log(args[0], args[1])
|
||||
}, 1, 2)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
52
internal/goman/task_group.go
Normal file
52
internal/goman/task_group.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package goman
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"runtime"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type TaskGroup struct {
|
||||
semi chan zero.Zero
|
||||
wg *sync.WaitGroup
|
||||
locker *sync.RWMutex
|
||||
}
|
||||
|
||||
func NewTaskGroup() *TaskGroup {
|
||||
var concurrent = runtime.NumCPU()
|
||||
if concurrent <= 1 {
|
||||
concurrent = 2
|
||||
}
|
||||
return &TaskGroup{
|
||||
semi: make(chan zero.Zero, concurrent),
|
||||
wg: &sync.WaitGroup{},
|
||||
locker: &sync.RWMutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *TaskGroup) Run(f func()) {
|
||||
this.wg.Add(1)
|
||||
go func() {
|
||||
defer this.wg.Done()
|
||||
|
||||
this.semi <- zero.Zero{}
|
||||
|
||||
f()
|
||||
|
||||
<-this.semi
|
||||
}()
|
||||
}
|
||||
|
||||
func (this *TaskGroup) Wait() {
|
||||
this.wg.Wait()
|
||||
}
|
||||
|
||||
func (this *TaskGroup) Lock() {
|
||||
this.locker.Lock()
|
||||
}
|
||||
|
||||
func (this *TaskGroup) Unlock() {
|
||||
this.locker.Unlock()
|
||||
}
|
||||
30
internal/goman/task_group_test.go
Normal file
30
internal/goman/task_group_test.go
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package goman_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewTaskGroup(t *testing.T) {
|
||||
var group = goman.NewTaskGroup()
|
||||
var m = map[int]bool{}
|
||||
|
||||
for i := 0; i < runtime.NumCPU()*2; i++ {
|
||||
var index = i
|
||||
group.Run(func() {
|
||||
t.Log("task", index)
|
||||
|
||||
group.Lock()
|
||||
_, ok := m[index]
|
||||
if ok {
|
||||
t.Error("duplicated:", index)
|
||||
}
|
||||
m[index] = true
|
||||
group.Unlock()
|
||||
})
|
||||
}
|
||||
group.Wait()
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
@@ -82,7 +81,7 @@ func (this *FirewalldAction) runActionSingleIP(action string, listType IPListTyp
|
||||
path := this.config.Path
|
||||
var err error
|
||||
if len(path) == 0 {
|
||||
path, err = exec.LookPath("firewall-cmd")
|
||||
path, err = executils.LookPath("firewall-cmd")
|
||||
if err != nil {
|
||||
if this.firewalldNotFound {
|
||||
return nil
|
||||
@@ -148,7 +147,7 @@ func (this *FirewalldAction) runActionSingleIP(action string, listType IPListTyp
|
||||
cmd.WithStderr()
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New(err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("%w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,11 +3,16 @@ package iplibrary
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHTTPAPIAction_AddItem(t *testing.T) {
|
||||
action := NewHTTPAPIAction()
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var action = NewHTTPAPIAction()
|
||||
action.config = &firewallconfigs.FirewallActionHTTPAPIConfig{
|
||||
URL: "http://127.0.0.1:2345/post",
|
||||
TimeoutSeconds: 0,
|
||||
@@ -24,7 +29,11 @@ func TestHTTPAPIAction_AddItem(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHTTPAPIAction_DeleteItem(t *testing.T) {
|
||||
action := NewHTTPAPIAction()
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var action = NewHTTPAPIAction()
|
||||
action.config = &firewallconfigs.FirewallActionHTTPAPIConfig{
|
||||
URL: "http://127.0.0.1:2345/post",
|
||||
TimeoutSeconds: 0,
|
||||
|
||||
@@ -2,11 +2,11 @@ package iplibrary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -55,7 +55,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
|
||||
// 创建ipset
|
||||
{
|
||||
path, err := exec.LookPath("ipset")
|
||||
path, err := executils.LookPath("ipset")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -71,7 +71,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
if err != nil {
|
||||
var output = cmd.Stderr()
|
||||
if !strings.Contains(output, "already exists") {
|
||||
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("create ipset '%s': %w, output: %s", listName, err, output)
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
if err != nil {
|
||||
var output = cmd.Stderr()
|
||||
if !strings.Contains(output, "already exists") {
|
||||
return errors.New("create ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("create ipset '%s': %w, output: %s", listName, err, output)
|
||||
} else {
|
||||
err = nil
|
||||
}
|
||||
@@ -99,7 +99,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
|
||||
// firewalld
|
||||
if this.config.AutoAddToFirewalld {
|
||||
path, err := exec.LookPath("firewall-cmd")
|
||||
path, err := executils.LookPath("firewall-cmd")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
if strings.Contains(output, "NAME_CONFLICT") {
|
||||
err = nil
|
||||
} else {
|
||||
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("firewall-cmd add ipset '%s': %w, output: %s", listName, err, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +135,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
if strings.Contains(output, "NAME_CONFLICT") {
|
||||
err = nil
|
||||
} else {
|
||||
return errors.New("firewall-cmd add ipset '" + listName + "': " + err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("firewall-cmd add ipset '%s': %w, output: %s", listName, err, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,7 +149,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("firewall-cmd add rich rule '%s': %w, output: %s", listName, err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("firewall-cmd add rich rule '" + listName + "': " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("firewall-cmd add rich rule '%s': %w, output: %s", listName, err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,14 +172,14 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("firewall-cmd reload: " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("firewall-cmd reload: %w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// iptables
|
||||
if this.config.AutoAddToIPTables {
|
||||
path, err := exec.LookPath("iptables")
|
||||
path, err := executils.LookPath("iptables")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -201,7 +201,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("iptables add rule: %w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,7 +222,7 @@ func (this *IPSetAction) Init(config *firewallconfigs.FirewallActionConfig) erro
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New("iptables add rule: " + err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("iptables add rule: %w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,7 +284,7 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
|
||||
return nil
|
||||
}
|
||||
|
||||
var listName = ""
|
||||
var listName string
|
||||
var isIPv6 = strings.Contains(item.IpFrom, ":")
|
||||
|
||||
switch listType {
|
||||
@@ -311,7 +311,7 @@ func (this *IPSetAction) runActionSingleIP(action string, listType IPListType, i
|
||||
var path = this.config.Path
|
||||
var err error
|
||||
if len(path) == 0 {
|
||||
path, err = exec.LookPath("ipset")
|
||||
path, err = executils.LookPath("ipset")
|
||||
if err != nil {
|
||||
// 找不到ipset命令错误只提示一次
|
||||
if this.ipsetNotfound {
|
||||
|
||||
@@ -4,13 +4,19 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"github.com/iwind/TeaGo/maps"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIPSetAction_Init(t *testing.T) {
|
||||
action := iplibrary.NewIPSetAction()
|
||||
_, lookupErr := executils.LookPath("iptables")
|
||||
if lookupErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var action = iplibrary.NewIPSetAction()
|
||||
err := action.Init(&firewallconfigs.FirewallActionConfig{
|
||||
Params: maps.Map{
|
||||
"path": "/usr/bin/iptables",
|
||||
@@ -25,6 +31,11 @@ func TestIPSetAction_Init(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIPSetAction_AddItem(t *testing.T) {
|
||||
_, lookupErr := executils.LookPath("iptables")
|
||||
if lookupErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var action = iplibrary.NewIPSetAction()
|
||||
action.SetConfig(&firewallconfigs.FirewallActionIPSetConfig{
|
||||
Path: "/usr/bin/iptables",
|
||||
@@ -84,7 +95,12 @@ func TestIPSetAction_AddItem(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIPSetAction_DeleteItem(t *testing.T) {
|
||||
action := iplibrary.NewIPSetAction()
|
||||
_, lookupErr := executils.LookPath("firewalld")
|
||||
if lookupErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var action = iplibrary.NewIPSetAction()
|
||||
err := action.Init(&firewallconfigs.FirewallActionConfig{
|
||||
Params: maps.Map{
|
||||
"path": "/usr/bin/firewalld",
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -87,7 +86,7 @@ func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType
|
||||
var path = this.config.Path
|
||||
var err error
|
||||
if len(path) == 0 {
|
||||
path, err = exec.LookPath("iptables")
|
||||
path, err = executils.LookPath("iptables")
|
||||
if err != nil {
|
||||
if this.iptablesNotFound {
|
||||
return nil
|
||||
@@ -129,7 +128,7 @@ func (this *IPTablesAction) runActionSingleIP(action string, listType IPListType
|
||||
if strings.Contains(output, "No chain/target/match") {
|
||||
err = nil
|
||||
} else {
|
||||
return errors.New(err.Error() + ", output: " + output)
|
||||
return fmt.Errorf("%w, output: %s", err, output)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -3,12 +3,18 @@ package iplibrary
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
executils "github.com/TeaOSLab/EdgeNode/internal/utils/exec"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIPTablesAction_AddItem(t *testing.T) {
|
||||
action := NewIPTablesAction()
|
||||
_, lookupErr := executils.LookPath("iptables")
|
||||
if lookupErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var action = NewIPTablesAction()
|
||||
action.config = &firewallconfigs.FirewallActionIPTablesConfig{
|
||||
Path: "/usr/bin/iptables",
|
||||
}
|
||||
@@ -40,7 +46,12 @@ func TestIPTablesAction_AddItem(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIPTablesAction_DeleteItem(t *testing.T) {
|
||||
action := NewIPTablesAction()
|
||||
_, lookupErr := executils.LookPath("firewalld")
|
||||
if lookupErr != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var action = NewIPTablesAction()
|
||||
action.config = &firewallconfigs.FirewallActionIPTablesConfig{
|
||||
Path: "/usr/bin/firewalld",
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ func (this *ActionManager) UpdateActions(actions []*firewallconfigs.FirewallActi
|
||||
continue
|
||||
}
|
||||
|
||||
instances, _ := this.eventMap[action.EventLevel]
|
||||
var instances = this.eventMap[action.EventLevel]
|
||||
instances = append(instances, instance)
|
||||
this.eventMap[action.EventLevel] = instances
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
func TestActionManager_UpdateActions(t *testing.T) {
|
||||
manager := NewActionManager()
|
||||
var manager = NewActionManager()
|
||||
manager.UpdateActions([]*firewallconfigs.FirewallActionConfig{
|
||||
{
|
||||
Id: 1,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
@@ -62,7 +61,7 @@ func (this *ScriptAction) runAction(action string, listType IPListType, item *pb
|
||||
cmd.WithStderr()
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return errors.New(err.Error() + ", output: " + cmd.Stderr())
|
||||
return fmt.Errorf("%w, output: %s", err, cmd.Stderr())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,11 +3,16 @@ package iplibrary
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestScriptAction_AddItem(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
action := NewScriptAction()
|
||||
action.config = &firewallconfigs.FirewallActionScriptConfig{
|
||||
Path: "/tmp/ip-item.sh",
|
||||
@@ -27,6 +32,10 @@ func TestScriptAction_AddItem(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestScriptAction_DeleteItem(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
action := NewScriptAction()
|
||||
action.config = &firewallconfigs.FirewallActionScriptConfig{
|
||||
Path: "/tmp/ip-item.sh",
|
||||
|
||||
@@ -2,6 +2,7 @@ package iplibrary
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"runtime"
|
||||
"testing"
|
||||
@@ -75,8 +76,14 @@ func TestIPItem_Contains(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIPItem_Memory(t *testing.T) {
|
||||
var isSingleTest = testutils.IsSingleTesting()
|
||||
|
||||
var list = NewIPList()
|
||||
for i := 0; i < 2_000_000; i ++ {
|
||||
var count = 100
|
||||
if isSingleTest {
|
||||
count = 2_000_000
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
list.Add(&IPItem{
|
||||
Type: "ip",
|
||||
Id: uint64(i),
|
||||
@@ -87,7 +94,9 @@ func TestIPItem_Memory(t *testing.T) {
|
||||
})
|
||||
}
|
||||
t.Log("waiting")
|
||||
time.Sleep(10 * time.Second)
|
||||
if isSingleTest {
|
||||
time.Sleep(10 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIPItem_Contains(b *testing.B) {
|
||||
@@ -105,4 +114,3 @@ func BenchmarkIPItem_Contains(b *testing.B) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user