Compare commits
99 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f5088958e | ||
|
|
eff928d988 | ||
|
|
b1e1c3ebbf | ||
|
|
c8f440e24c | ||
|
|
6c5f4e0705 | ||
|
|
73e6b04026 | ||
|
|
0b09de3378 | ||
|
|
69b1355cfe | ||
|
|
459f8a8215 | ||
|
|
a58b9a1fda | ||
|
|
6cdcceda86 | ||
|
|
1cf0f13f7d | ||
|
|
b1b450bb50 | ||
|
|
a7ad2cea8f | ||
|
|
c9dac96366 | ||
|
|
5b9ffe1225 | ||
|
|
b965080bb0 | ||
|
|
ed49d74874 | ||
|
|
91f817d2d2 | ||
|
|
96cb816200 | ||
|
|
bf28505d91 | ||
|
|
ad558cc0e8 | ||
|
|
0065543e96 | ||
|
|
56219d5082 | ||
|
|
ffac717219 | ||
|
|
8794bf5676 | ||
|
|
7130154bc8 | ||
|
|
2b01162ab4 | ||
|
|
3ce30e36bb | ||
|
|
3e183ac824 | ||
|
|
e1d9e72df0 | ||
|
|
bbb71db0d0 | ||
|
|
7310b601d6 | ||
|
|
7220c53ced | ||
|
|
234887cc1d | ||
|
|
fedc4262a0 | ||
|
|
267ac99d65 | ||
|
|
140d3bbf59 | ||
|
|
ae82310b92 | ||
|
|
484b56492b | ||
|
|
167bb2df29 | ||
|
|
a4d58ffc40 | ||
|
|
64a186b0e7 | ||
|
|
e0bed33cc5 | ||
|
|
5e7ea9a884 | ||
|
|
4bdd248f99 | ||
|
|
0789b3d0d5 | ||
|
|
27c61ca0d4 | ||
|
|
7141add68e | ||
|
|
7e11ee98a9 | ||
|
|
cdb6134a3b | ||
|
|
3a9fec5196 | ||
|
|
0396cae524 | ||
|
|
04c503d644 | ||
|
|
f526665633 | ||
|
|
fe069762bb | ||
|
|
62f8ed63be | ||
|
|
4fb1e414be | ||
|
|
4cc6a4ff31 | ||
|
|
6026d8c3bd | ||
|
|
dc9cff541e | ||
|
|
b1151479e0 | ||
|
|
7bb808c08d | ||
|
|
200718d7dc | ||
|
|
d75afa9850 | ||
|
|
8d72e5fdd1 | ||
|
|
1fe15d4e3c | ||
|
|
e87ea9d802 | ||
|
|
b15b5cfb8f | ||
|
|
c9eb577c06 | ||
|
|
ece596d7b9 | ||
|
|
58b6d7848a | ||
|
|
c4bb92433d | ||
|
|
f6e5201cc3 | ||
|
|
b645c76a07 | ||
|
|
27c56119ae | ||
|
|
e034c31333 | ||
|
|
94ada46abc | ||
|
|
4148681bb8 | ||
|
|
e5c5234be8 | ||
|
|
750aafdea1 | ||
|
|
4d3c214d7d | ||
|
|
a5950cc91b | ||
|
|
4d88629707 | ||
|
|
98441ccd3a | ||
|
|
2a44283016 | ||
|
|
db9463bdb1 | ||
|
|
d2e9c8c10f | ||
|
|
b4995868c9 | ||
|
|
10319ab48f | ||
|
|
6146a829ed | ||
|
|
947ef4eb78 | ||
|
|
4cfd48f0c4 | ||
|
|
f1d0984031 | ||
|
|
f119690ab7 | ||
|
|
a77134a601 | ||
|
|
707ed03f1e | ||
|
|
07080b13ee | ||
|
|
f78f9136d0 |
@@ -71,4 +71,6 @@ linters:
|
||||
- forcetypeassert
|
||||
- whitespace
|
||||
- noctx
|
||||
- rowserrcheck
|
||||
- rowserrcheck
|
||||
- tagliatelle
|
||||
- protogetter
|
||||
@@ -11,4 +11,4 @@ go run -tags=${TAG} ../cmd/edge-node/main.go stop
|
||||
|
||||
# reference: https://pkg.go.dev/cmd/go/internal/test
|
||||
go clean -testcache
|
||||
go test -timeout 10s -tags="${TAG}" -cover ../...
|
||||
go test -timeout 60s -tags="${TAG}" -cover ../...
|
||||
39
go.mod
39
go.mod
@@ -6,7 +6,6 @@ replace (
|
||||
github.com/TeaOSLab/EdgeCommon => ../EdgeCommon
|
||||
github.com/dchest/captcha => github.com/iwind/captcha v0.0.0-20231130092438-ae985686ed84
|
||||
github.com/fsnotify/fsnotify => github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4
|
||||
github.com/google/nftables => github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -16,21 +15,21 @@ require (
|
||||
github.com/aws/aws-sdk-go v1.44.279
|
||||
github.com/baidubce/bce-sdk-go v0.9.170
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670
|
||||
github.com/cespare/xxhash v1.1.0
|
||||
github.com/cespare/xxhash/v2 v2.2.0
|
||||
github.com/cockroachdb/pebble v1.1.0
|
||||
github.com/dchest/captcha v0.0.0-00010101000000-000000000000
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/google/nftables v0.1.0
|
||||
github.com/google/nftables v0.2.0
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible
|
||||
github.com/iwind/TeaGo v0.0.0-20230630104525-161f0b32996d
|
||||
github.com/iwind/TeaGo v0.0.0-20240411075713-6c1fc9aca7b6
|
||||
github.com/iwind/gofcgi v0.0.0-20210528023741-a92711d45f11
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
|
||||
github.com/iwind/gowebp v0.0.0-20240109104518-489f3429f5c5
|
||||
github.com/klauspost/compress v1.17.7
|
||||
github.com/klauspost/compress v1.17.8
|
||||
github.com/mattn/go-sqlite3 v1.14.17
|
||||
github.com/mdlayher/netlink v1.7.1
|
||||
github.com/mdlayher/netlink v1.7.2
|
||||
github.com/miekg/dns v1.1.43
|
||||
github.com/mssola/useragent v1.0.0
|
||||
github.com/pires/go-proxyproto v0.6.1
|
||||
@@ -39,10 +38,10 @@ require (
|
||||
github.com/shirou/gopsutil/v3 v3.22.2
|
||||
github.com/tdewolff/minify/v2 v2.20.19
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.41
|
||||
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81
|
||||
golang.org/x/image v0.13.0
|
||||
golang.org/x/net v0.22.0
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f
|
||||
golang.org/x/image v0.15.0
|
||||
golang.org/x/net v0.24.0
|
||||
golang.org/x/sys v0.19.0
|
||||
google.golang.org/grpc v1.62.1
|
||||
google.golang.org/protobuf v1.33.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@@ -54,32 +53,25 @@ require (
|
||||
github.com/clbanning/mxj v1.8.4 // indirect
|
||||
github.com/cockroachdb/errors v1.11.1 // indirect
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
|
||||
github.com/cockroachdb/pebble v1.1.0 // indirect
|
||||
github.com/cockroachdb/redact v1.1.5 // indirect
|
||||
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/getsentry/sentry-go v0.27.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/glog v1.2.0 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/flatbuffers v24.3.7+incompatible // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-querystring v1.0.0 // indirect
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/native v1.0.0 // indirect
|
||||
github.com/josharian/native v1.1.0 // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mdlayher/socket v0.4.0 // indirect
|
||||
github.com/mdlayher/socket v0.5.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.4.3 // indirect
|
||||
github.com/mozillazg/go-httpheader v0.2.1 // indirect
|
||||
github.com/onsi/ginkgo/v2 v2.16.0 // indirect
|
||||
@@ -95,13 +87,12 @@ require (
|
||||
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
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.uber.org/mock v0.4.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/mod v0.16.0 // indirect
|
||||
golang.org/x/sync v0.6.0 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.19.0 // indirect
|
||||
golang.org/x/tools v0.20.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
|
||||
)
|
||||
|
||||
174
go.sum
174
go.sum
@@ -1,9 +1,5 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
|
||||
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
@@ -17,16 +13,12 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670 h1:FQPKKjDhzG0T4ew6dm6MGrXb4PRAi8ZmTuYuxcF62BM=
|
||||
github.com/biessek/golang-ico v0.0.0-20180326222316-d348d9ea4670/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.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
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/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/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
|
||||
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
|
||||
github.com/cockroachdb/errors v1.11.1 h1:xSEW75zKaKCWzR3OfxXUxgrk/NtT4G1MiOv5lWZazG8=
|
||||
github.com/cockroachdb/errors v1.11.1/go.mod h1:8MUxA3Gi6b25tYlFEBGLf+D8aISL+M4MIpiWMSNRfxw=
|
||||
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
|
||||
@@ -41,21 +33,12 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
|
||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
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/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
|
||||
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
@@ -72,34 +55,10 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEe
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
|
||||
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
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.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.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/flatbuffers v24.3.7+incompatible h1:BxGUkIQnOciBu33bd5BdvqY8Qvo0O/GR4SPhh7x9Ed0=
|
||||
github.com/google/flatbuffers v24.3.7+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
|
||||
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.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
@@ -108,16 +67,17 @@ github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASu
|
||||
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/nftables v0.2.0 h1:PbJwaBmbVLzpeldoeUKGkE2RjstrjPKMl6oLrfEJ6/8=
|
||||
github.com/google/nftables v0.2.0/go.mod h1:Beg6V6zZ3oEn0JuiUQ4wqwuyqqzasOltcoXPtgLbFp4=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7 h1:y3N7Bm7Y9/CtpiVkw/ZWj6lSlDF3F74SfKwfTCer72Q=
|
||||
github.com/google/pprof v0.0.0-20240227163752-401108e1b7e7/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible h1:XRAk4HBDLCYEdPLWtKf5iZhOi7lfx17aY0oSO9+mcg8=
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-obs v3.23.4+incompatible/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/TeaGo v0.0.0-20240411075713-6c1fc9aca7b6 h1:dS3pTxrLlDQxdoxSUcHkHnr3LHpsBIXv8v2/xw65RN8=
|
||||
github.com/iwind/TeaGo v0.0.0-20240411075713-6c1fc9aca7b6/go.mod h1:SfqVbWyIPdVflyA6lMgicZzsoGS8pyeLiTRe8/CIpGI=
|
||||
github.com/iwind/captcha v0.0.0-20231130092438-ae985686ed84 h1:/RtK8t22a/YFkBWiEwxS+JWcDmxAKsu+r+p00c36K0Q=
|
||||
github.com/iwind/captcha v0.0.0-20231130092438-ae985686ed84/go.mod h1:7zoElIawLp7GUMLcj54K9kbw+jEyvz2K0FDdRRYhvWo=
|
||||
github.com/iwind/fsnotify v1.5.2-0.20220817040843-193be2051ff4 h1:PKtXlgNHJhdwl5ozio7KRV3n0SckMw+8ZC2NCpRSv8U=
|
||||
@@ -128,26 +88,20 @@ github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/iwind/gowebp v0.0.0-20240109104518-489f3429f5c5 h1:tA0HEDQJ/FM847wc7kVpSgkTfMF1LervEmd2UZQr3Po=
|
||||
github.com/iwind/gowebp v0.0.0-20240109104518-489f3429f5c5/go.mod h1:AYyXDhbbD7q9N6rJff2jrE7pGupaiyvtv3YeyIAQLXk=
|
||||
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4 h1:RPAH9Sj9l/20zH5zU5/iJGszfwPq6eLjoiC/n/asulA=
|
||||
github.com/iwind/nftables v0.0.0-20230419014751-9f023a644ad4/go.mod h1:7OLL+86wZKfBnAJxNxmdcZ0ebbgdp/A28fcagx9oJqA=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/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/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
|
||||
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e h1:LvL4XsI70QxOGHed6yhQtAU34Kx3Qq2wwBzGFKY8zKk=
|
||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
|
||||
github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
@@ -160,23 +114,18 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
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/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||
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/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
|
||||
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
|
||||
github.com/mdlayher/socket v0.5.0 h1:ilICZmJcQz70vrWVes1MFera4jGiWNocSkykwwoy3XI=
|
||||
github.com/mdlayher/socket v0.5.0/go.mod h1:WkcBFfvyG8QENs5+hfQPl1X6Jpd2yeLIYgrGFmJiJxI=
|
||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
|
||||
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
@@ -185,6 +134,8 @@ github.com/onsi/ginkgo/v2 v2.16.0 h1:7q1w9frJDzninhXxjZd+Y/x54XNjG/UlRLIYPZafsPM
|
||||
github.com/onsi/ginkgo/v2 v2.16.0/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw=
|
||||
github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
@@ -196,7 +147,6 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF
|
||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
|
||||
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos=
|
||||
github.com/prometheus/client_model v0.6.0/go.mod h1:NTQHnmxFpouOD0DpvP4XujX3CdOAGQPoaGhyTchlyt8=
|
||||
github.com/prometheus/common v0.51.0 h1:vT5R9NAlW4V6k8Wruk7ikrHaHRsrPbduM/cKTOdQM/k=
|
||||
@@ -212,27 +162,18 @@ github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1
|
||||
github.com/quic-go/quic-go v0.42.0 h1:uSfdap0eveIl8KXnipv9K7nlwZ5IqLlYOpJ58u5utpM=
|
||||
github.com/quic-go/quic-go v0.42.0/go.mod h1:132kz4kL3F9vxhW3CtQJLDVwcFe5wdWeJXXijhsO57M=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/shirou/gopsutil/v3 v3.22.2 h1:wCrArWFkHYIdDxx/FSfF5RB4dpJYW6t7rcp3+zL8uks=
|
||||
github.com/shirou/gopsutil/v3 v3.22.2/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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tdewolff/minify/v2 v2.20.19 h1:tX0SR0LUrIqGoLjXnkIzRSIbKJ7PaNnSENLD4CyH6Xo=
|
||||
github.com/tdewolff/minify/v2 v2.20.19/go.mod h1:ulkFoeAVWMLEyjuDz1ZIWOA31g5aWOawCFRp9R/MudM=
|
||||
github.com/tdewolff/parse/v2 v2.7.12 h1:tgavkHc2ZDEQVKy1oWxwIyh5bP4F5fEh/JmBwPP/3LQ=
|
||||
@@ -255,8 +196,6 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec
|
||||
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.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
|
||||
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -265,51 +204,35 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
|
||||
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.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f h1:3CW0unweImhOzd5FmYuRsD4Y4oQFKZIjAnKbjV4WIrw=
|
||||
golang.org/x/exp v0.0.0-20240314144324-c7f7c6466f7f/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
|
||||
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81 h1:6R2FC06FonbXQ8pK11/PDFY6N6LWlf9KlzibaCapmqc=
|
||||
golang.org/x/exp v0.0.0-20240318143956-a85f2c67cd81/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||
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-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/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY=
|
||||
golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
|
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
|
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-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.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
|
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
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.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -323,10 +246,9 @@ golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-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-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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=
|
||||
@@ -341,44 +263,21 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-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.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-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/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-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||
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.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk=
|
||||
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
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/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -388,7 +287,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
@@ -396,5 +294,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
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=
|
||||
|
||||
@@ -3,7 +3,6 @@ package apps
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/files"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
@@ -41,7 +40,7 @@ func (this *LogWriter) Init() {
|
||||
this.c = make(chan string, 1024)
|
||||
|
||||
// 异步写入文件
|
||||
var maxFileSize = 128 * sizes.M // 文件最大尺寸,超出此尺寸则清空
|
||||
var maxFileSize int64 = 128 << 20 // 文件最大尺寸,超出此尺寸则清空
|
||||
if fp != nil {
|
||||
goman.New(func() {
|
||||
var totalSize int64 = 0
|
||||
|
||||
@@ -6,13 +6,14 @@ import "errors"
|
||||
|
||||
// 常用的几个错误
|
||||
var (
|
||||
ErrNotFound = errors.New("cache not found")
|
||||
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")
|
||||
ErrServerIsBusy = errors.New("server is busy")
|
||||
ErrNotFound = errors.New("cache not found")
|
||||
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")
|
||||
ErrServerIsBusy = errors.New("server is busy")
|
||||
ErrUnexpectedContentLength = errors.New("unexpected content length")
|
||||
)
|
||||
|
||||
// CapacityError 容量错误
|
||||
@@ -45,3 +46,12 @@ func CanIgnoreErr(err error) bool {
|
||||
var capacityErr *CapacityError
|
||||
return errors.As(err, &capacityErr)
|
||||
}
|
||||
|
||||
func IsCapacityError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var capacityErr *CapacityError
|
||||
return errors.As(err, &capacityErr)
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import (
|
||||
"fmt"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"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"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"net"
|
||||
@@ -64,7 +64,7 @@ func (this *SQLiteFileListDB) Open(dbPath string) error {
|
||||
|
||||
// 动态调整Cache值
|
||||
var cacheSize = 512
|
||||
var memoryGB = utils.SystemMemoryGB()
|
||||
var memoryGB = memutils.SystemMemoryGB()
|
||||
if memoryGB >= 1 {
|
||||
cacheSize = 256 * memoryGB
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
package caches
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"math/big"
|
||||
"sync"
|
||||
@@ -46,7 +46,7 @@ func NewSQLiteFileListHashMap() *SQLiteFileListHashMap {
|
||||
|
||||
func (this *SQLiteFileListHashMap) Load(db *SQLiteFileListDB) error {
|
||||
// 如果系统内存过小,我们不缓存
|
||||
if utils.SystemMemoryGB() < 3 {
|
||||
if memutils.SystemMemoryGB() < 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ func (this *KVFileList) Add(hash string, item *Item) error {
|
||||
}
|
||||
|
||||
// Exist 检查内容是否存在
|
||||
func (this *KVFileList) Exist(hash string) (bool, error) {
|
||||
func (this *KVFileList) Exist(hash string) (bool, int64, error) {
|
||||
return this.getStore(hash).ExistItem(hash)
|
||||
}
|
||||
|
||||
|
||||
@@ -90,23 +90,23 @@ func (this *KVListFileStore) AddItem(hash string, item *Item) error {
|
||||
return this.itemsTable.Set(hash, item)
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) ExistItem(hash string) (bool, error) {
|
||||
func (this *KVListFileStore) ExistItem(hash string) (bool, int64, error) {
|
||||
if !this.isReady() {
|
||||
return false, nil
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
item, err := this.itemsTable.Get(hash)
|
||||
if err != nil {
|
||||
if kvstore.IsKeyNotFound(err) {
|
||||
return false, nil
|
||||
if kvstore.IsNotFound(err) {
|
||||
return false, -1, nil
|
||||
}
|
||||
return false, err
|
||||
return false, -1, err
|
||||
}
|
||||
if item == nil {
|
||||
return false, nil
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
return item.ExpiresAt >= fasttime.NewFastTime().Unix(), nil
|
||||
return item.ExpiresAt > fasttime.Now().Unix(), item.HeaderSize + item.BodySize, nil
|
||||
}
|
||||
|
||||
func (this *KVListFileStore) ExistQuickItem(hash string) (bool, error) {
|
||||
|
||||
@@ -18,14 +18,6 @@ import (
|
||||
|
||||
var testingKVList *caches.KVFileList
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
m.Run()
|
||||
|
||||
if testingKVList != nil {
|
||||
_ = testingKVList.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func testOpenKVFileList(t *testing.T) *caches.KVFileList {
|
||||
var list = caches.NewKVFileList(Tea.Root + "/data/stores/cache-stores")
|
||||
err := list.Init()
|
||||
@@ -38,11 +30,18 @@ func testOpenKVFileList(t *testing.T) *caches.KVFileList {
|
||||
}
|
||||
|
||||
func TestNewKVFileList(t *testing.T) {
|
||||
_ = testOpenKVFileList(t)
|
||||
var list = testOpenKVFileList(t)
|
||||
err := list.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVFileList_Add(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.Add(stringutil.Md5("123456"), &caches.Item{
|
||||
Type: caches.ItemTypeFile,
|
||||
@@ -67,6 +66,9 @@ func TestKVFileList_Add_Many(t *testing.T) {
|
||||
}
|
||||
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
const start = 0
|
||||
const count = 1_000_000
|
||||
@@ -113,6 +115,9 @@ func TestKVFileList_Add_Many_Suffix(t *testing.T) {
|
||||
}
|
||||
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
const start = 0
|
||||
const count = 1000
|
||||
@@ -155,11 +160,15 @@ func TestKVFileList_Add_Many_Suffix(t *testing.T) {
|
||||
|
||||
func TestKVFileList_Exist(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
for _, hash := range []string{
|
||||
stringutil.Md5("123456"),
|
||||
stringutil.Md5("654321"),
|
||||
} {
|
||||
b, err := list.Exist(hash)
|
||||
b, _, err := list.Exist(hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -169,6 +178,10 @@ func TestKVFileList_Exist(t *testing.T) {
|
||||
|
||||
func TestKVFileList_ExistQuick(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
for _, hash := range []string{
|
||||
stringutil.Md5("123456"),
|
||||
stringutil.Md5("654321"),
|
||||
@@ -183,6 +196,10 @@ func TestKVFileList_ExistQuick(t *testing.T) {
|
||||
|
||||
func TestKVFileList_Remove(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
for _, hash := range []string{
|
||||
stringutil.Md5("123456"),
|
||||
stringutil.Md5("654321"),
|
||||
@@ -196,6 +213,10 @@ func TestKVFileList_Remove(t *testing.T) {
|
||||
|
||||
func TestKVFileList_CleanAll(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.CleanAll()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -208,6 +229,10 @@ func TestKVFileList_Inspect(t *testing.T) {
|
||||
}
|
||||
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
err := list.TestInspect(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -216,6 +241,10 @@ func TestKVFileList_Inspect(t *testing.T) {
|
||||
|
||||
func TestKVFileList_Purge(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
count, err := list.Purge(4_000, func(hash string) error {
|
||||
//t.Log("hash:", hash)
|
||||
@@ -229,6 +258,10 @@ func TestKVFileList_Purge(t *testing.T) {
|
||||
|
||||
func TestKVFileList_PurgeLFU(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
err := list.PurgeLFU(20000, func(hash string) error {
|
||||
t.Log("hash:", hash)
|
||||
@@ -242,6 +275,10 @@ func TestKVFileList_PurgeLFU(t *testing.T) {
|
||||
|
||||
func TestKVFileList_Count(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
count, err := list.Count()
|
||||
if err != nil {
|
||||
@@ -252,6 +289,10 @@ func TestKVFileList_Count(t *testing.T) {
|
||||
|
||||
func TestKVFileList_Stat(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
stat, err := list.Stat(func(hash string) bool {
|
||||
return true
|
||||
@@ -264,6 +305,10 @@ func TestKVFileList_Stat(t *testing.T) {
|
||||
|
||||
func TestKVFileList_CleanPrefix(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
|
||||
defer func() {
|
||||
@@ -279,6 +324,10 @@ func TestKVFileList_CleanPrefix(t *testing.T) {
|
||||
|
||||
func TestKVFileList_CleanMatchPrefix(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
|
||||
defer func() {
|
||||
@@ -294,6 +343,10 @@ func TestKVFileList_CleanMatchPrefix(t *testing.T) {
|
||||
|
||||
func TestKVFileList_CleanMatchKey(t *testing.T) {
|
||||
var list = testOpenKVFileList(t)
|
||||
defer func() {
|
||||
_ = list.Close()
|
||||
}()
|
||||
|
||||
var before = time.Now()
|
||||
|
||||
defer func() {
|
||||
@@ -322,7 +375,7 @@ func BenchmarkKVFileList_Exist(b *testing.B) {
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, existErr := list.Exist(stringutil.Md5(strconv.Itoa(rand.Int() % 2_000_000)))
|
||||
_, _, existErr := list.Exist(stringutil.Md5(strconv.Itoa(rand.Int() % 2_000_000)))
|
||||
if existErr != nil {
|
||||
b.Fatal(existErr)
|
||||
}
|
||||
|
||||
@@ -130,26 +130,26 @@ func (this *SQLiteFileList) Add(hash string, item *Item) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *SQLiteFileList) Exist(hash string) (bool, error) {
|
||||
func (this *SQLiteFileList) Exist(hash string) (bool, int64, error) {
|
||||
var db = this.GetDB(hash)
|
||||
|
||||
if !db.IsReady() {
|
||||
return false, nil
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
// 如果Hash列表里不存在,那么必然不存在
|
||||
if !db.hashMap.Exist(hash) {
|
||||
return false, nil
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
var item = this.memoryCache.Read(hash)
|
||||
if item != nil {
|
||||
return true, nil
|
||||
return true, -1, nil
|
||||
}
|
||||
|
||||
var row = db.existsByHashStmt.QueryRow(hash, time.Now().Unix())
|
||||
if row.Err() != nil {
|
||||
return false, nil
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
var expiredAt int64
|
||||
@@ -158,15 +158,15 @@ func (this *SQLiteFileList) Exist(hash string) (bool, error) {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
return false, err
|
||||
return false, -1, err
|
||||
}
|
||||
|
||||
if expiredAt < fasttime.Now().Unix() {
|
||||
return false, nil
|
||||
if expiredAt <= fasttime.Now().Unix() {
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
this.memoryCache.Write(hash, zero.Zero{}, this.maxExpiresAtForMemoryCache(expiredAt))
|
||||
return true, nil
|
||||
return true, -1, nil
|
||||
}
|
||||
|
||||
func (this *SQLiteFileList) ExistQuick(hash string) (isReady bool, found bool) {
|
||||
|
||||
@@ -140,7 +140,7 @@ func TestFileList_Exist(t *testing.T) {
|
||||
}()
|
||||
{
|
||||
var hash = stringutil.Md5("123456")
|
||||
exists, err := list.Exist(hash)
|
||||
exists, _, err := list.Exist(hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -148,7 +148,7 @@ func TestFileList_Exist(t *testing.T) {
|
||||
}
|
||||
{
|
||||
var hash = stringutil.Md5("http://edge.teaos.cn/1234561")
|
||||
exists, err := list.Exist(hash)
|
||||
exists, _, err := list.Exist(hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -208,7 +208,7 @@ func TestFileList_Exist_Many_DB(t *testing.T) {
|
||||
countLocker.Unlock()
|
||||
|
||||
var list = listSlice[rands.Int(0, len(listSlice)-1)]
|
||||
_, _ = list.Exist(hash)
|
||||
_, _, _ = list.Exist(hash)
|
||||
default:
|
||||
return
|
||||
}
|
||||
@@ -442,6 +442,6 @@ func BenchmarkFileList_Exist(b *testing.B) {
|
||||
}
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = list.Exist("f0eb5b87e0b0041f3917002c0707475f" + types.String(i))
|
||||
_, _, _ = list.Exist("f0eb5b87e0b0041f3917002c0707475f" + types.String(i))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ type ListInterface interface {
|
||||
Add(hash string, item *Item) error
|
||||
|
||||
// Exist 检查内容是否存在
|
||||
Exist(hash string) (bool, error)
|
||||
Exist(hash string) (ok bool, size int64, err error)
|
||||
|
||||
// CleanPrefix 清除某个前缀的缓存
|
||||
CleanPrefix(prefix string) error
|
||||
|
||||
@@ -89,21 +89,21 @@ func (this *MemoryList) Add(hash string, item *Item) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *MemoryList) Exist(hash string) (bool, error) {
|
||||
func (this *MemoryList) Exist(hash string) (bool, int64, error) {
|
||||
this.locker.RLock()
|
||||
defer this.locker.RUnlock()
|
||||
|
||||
prefix := this.prefix(hash)
|
||||
itemMap, ok := this.itemMaps[prefix]
|
||||
if !ok {
|
||||
return false, nil
|
||||
return false, -1, nil
|
||||
}
|
||||
item, ok := itemMap[hash]
|
||||
if !ok {
|
||||
return false, nil
|
||||
return false, -1, nil
|
||||
}
|
||||
|
||||
return !item.IsExpired(), nil
|
||||
return !item.IsExpired(), -1, nil
|
||||
}
|
||||
|
||||
// CleanPrefix 根据前缀进行清除
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -317,7 +317,7 @@ func BenchmarkMemoryList(b *testing.B) {
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_, _ = list.Exist(types.String("a" + types.String(rands.Int(1, 10000))))
|
||||
_, _, _ = list.Exist(types.String("a" + types.String(rands.Int(1, 10000))))
|
||||
_ = list.Add("a"+types.String(rands.Int(1, 100000)), &caches.Item{})
|
||||
_, _ = list.Purge(1000, func(hash string) error {
|
||||
return nil
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
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"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
"github.com/iwind/TeaGo/lists"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"golang.org/x/sys/unix"
|
||||
@@ -149,7 +149,7 @@ func (this *Manager) UpdatePolicies(newPolicies []*serverconfigs.HTTPCachePolicy
|
||||
}
|
||||
|
||||
this.CountFileStorages = 0
|
||||
this.CountFileStorages = 0
|
||||
this.CountMemoryStorages = 0
|
||||
for _, storage := range this.storageMap {
|
||||
_, isFileStorage := storage.(*FileStorage)
|
||||
this.CountMemoryStorages++
|
||||
@@ -299,7 +299,7 @@ func (this *Manager) MaxSystemMemoryBytesPerStorage() int64 {
|
||||
count = 1
|
||||
}
|
||||
|
||||
var resultBytes = int64(utils.SystemMemoryBytes()) / 3 / int64(count) // 1/3 of the system memory
|
||||
var resultBytes = int64(memutils.SystemMemoryBytes()) / 3 / int64(count) // 1/3 of the system memory
|
||||
if resultBytes < 1<<30 {
|
||||
resultBytes = 1 << 30
|
||||
}
|
||||
|
||||
@@ -1,375 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
enableFragmentPool = false
|
||||
minMemoryFragmentPoolItemSize = 8 << 10
|
||||
maxMemoryFragmentPoolItemSize = 128 << 20
|
||||
maxItemsInMemoryFragmentPoolBucket = 1024
|
||||
memoryFragmentPoolBucketSegmentSize = 512 << 10
|
||||
maxMemoryFragmentPoolItemAgeSeconds = 60
|
||||
)
|
||||
|
||||
var SharedFragmentMemoryPool *MemoryFragmentPool
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
SharedFragmentMemoryPool = NewMemoryFragmentPool()
|
||||
|
||||
goman.New(func() {
|
||||
var ticker = time.NewTicker(200 * time.Millisecond)
|
||||
for range ticker.C {
|
||||
for i := 0; i < 10; i++ { // skip N empty buckets
|
||||
var isEmpty = SharedFragmentMemoryPool.GCNextBucket()
|
||||
if !isEmpty {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type MemoryFragmentPoolItem struct {
|
||||
Bytes []byte
|
||||
|
||||
size int64
|
||||
createdAt int64
|
||||
|
||||
Refs int32
|
||||
}
|
||||
|
||||
func (this *MemoryFragmentPoolItem) IsExpired() bool {
|
||||
return this.createdAt < fasttime.Now().Unix()-maxMemoryFragmentPoolItemAgeSeconds
|
||||
}
|
||||
|
||||
func (this *MemoryFragmentPoolItem) Reset() {
|
||||
this.Bytes = nil
|
||||
}
|
||||
|
||||
func (this *MemoryFragmentPoolItem) IsAvailable() bool {
|
||||
return atomic.AddInt32(&this.Refs, 1) == 1
|
||||
}
|
||||
|
||||
// MemoryFragmentPool memory fragments management
|
||||
type MemoryFragmentPool struct {
|
||||
bucketMaps []map[uint64]*MemoryFragmentPoolItem // [ { id => Zero }, ... ]
|
||||
countBuckets int
|
||||
gcBucketIndex int
|
||||
|
||||
mu sync.RWMutex
|
||||
|
||||
id uint64
|
||||
totalMemory int64
|
||||
|
||||
isOk bool
|
||||
capacity int64
|
||||
|
||||
debugMode bool
|
||||
countGet uint64
|
||||
countNew uint64
|
||||
}
|
||||
|
||||
// NewMemoryFragmentPool create new fragment memory pool
|
||||
func NewMemoryFragmentPool() *MemoryFragmentPool {
|
||||
var pool = &MemoryFragmentPool{}
|
||||
pool.init()
|
||||
return pool
|
||||
}
|
||||
|
||||
func (this *MemoryFragmentPool) init() {
|
||||
var capacity = int64(utils.SystemMemoryGB()) << 30 / 16
|
||||
if capacity > 256<<20 {
|
||||
this.isOk = true
|
||||
this.capacity = capacity
|
||||
|
||||
this.bucketMaps = []map[uint64]*MemoryFragmentPoolItem{}
|
||||
for i := 0; i < maxMemoryFragmentPoolItemSize/memoryFragmentPoolBucketSegmentSize+1; i++ {
|
||||
this.bucketMaps = append(this.bucketMaps, map[uint64]*MemoryFragmentPoolItem{})
|
||||
}
|
||||
this.countBuckets = len(this.bucketMaps)
|
||||
}
|
||||
|
||||
// print statistics for debug
|
||||
if len(os.Getenv("GOEDGE_DEBUG_MEMORY_FRAGMENT_POOL")) > 0 {
|
||||
this.debugMode = true
|
||||
|
||||
go func() {
|
||||
var maxRounds = 10_000
|
||||
var ticker = time.NewTicker(10 * time.Second)
|
||||
for range ticker.C {
|
||||
logs.Println("reused:", this.countGet, "created:", this.countNew, "fragments:", this.Len(), "memory:", this.totalMemory>>20, "MB")
|
||||
|
||||
maxRounds--
|
||||
if maxRounds <= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// Get try to get a bytes object
|
||||
func (this *MemoryFragmentPool) Get(expectSize int64) (resultBytes []byte, ok bool) {
|
||||
if !this.isOk {
|
||||
return
|
||||
}
|
||||
|
||||
if expectSize <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// DO NOT check min segment size
|
||||
|
||||
this.mu.RLock()
|
||||
|
||||
var bucketIndex = this.bucketIndexForSize(expectSize)
|
||||
var resultItemId uint64
|
||||
const maxSearchingBuckets = 20
|
||||
for i := bucketIndex; i <= bucketIndex+maxSearchingBuckets; i++ {
|
||||
resultBytes, resultItemId, ok = this.findItemInMap(this.bucketMaps[i], expectSize)
|
||||
if ok {
|
||||
this.mu.RUnlock()
|
||||
|
||||
// remove from bucket
|
||||
this.mu.Lock()
|
||||
delete(this.bucketMaps[i], resultItemId)
|
||||
this.mu.Unlock()
|
||||
|
||||
return
|
||||
}
|
||||
if i >= this.countBuckets {
|
||||
break
|
||||
}
|
||||
}
|
||||
this.mu.RUnlock()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Put a bytes object to specified bucket
|
||||
func (this *MemoryFragmentPool) Put(data []byte) (ok bool) {
|
||||
if !this.isOk {
|
||||
return
|
||||
}
|
||||
|
||||
var l = int64(cap(data)) // MUST be 'cap' instead of 'len'
|
||||
|
||||
if l < minMemoryFragmentPoolItemSize || l > maxMemoryFragmentPoolItemSize {
|
||||
return
|
||||
}
|
||||
|
||||
if atomic.LoadInt64(&this.totalMemory) >= this.capacity {
|
||||
return
|
||||
}
|
||||
|
||||
var itemId = atomic.AddUint64(&this.id, 1)
|
||||
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
|
||||
var bucketMap = this.bucketMaps[this.bucketIndexForSize(l)]
|
||||
if len(bucketMap) >= maxItemsInMemoryFragmentPoolBucket {
|
||||
return
|
||||
}
|
||||
|
||||
atomic.AddInt64(&this.totalMemory, l)
|
||||
|
||||
bucketMap[itemId] = &MemoryFragmentPoolItem{
|
||||
Bytes: data,
|
||||
size: l,
|
||||
createdAt: fasttime.Now().Unix(),
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GC fully GC
|
||||
func (this *MemoryFragmentPool) GC() {
|
||||
if !this.isOk {
|
||||
return
|
||||
}
|
||||
|
||||
var totalMemory = atomic.LoadInt64(&this.totalMemory)
|
||||
if totalMemory < this.capacity {
|
||||
return
|
||||
}
|
||||
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
|
||||
var garbageSize = totalMemory * 1 / 10 // 10%
|
||||
|
||||
// remove expired
|
||||
for _, bucketMap := range this.bucketMaps {
|
||||
for itemId, item := range bucketMap {
|
||||
if item.IsExpired() {
|
||||
delete(bucketMap, itemId)
|
||||
item.Reset()
|
||||
atomic.AddInt64(&this.totalMemory, -item.size)
|
||||
|
||||
garbageSize -= item.size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove others
|
||||
if garbageSize > 0 {
|
||||
for _, bucketMap := range this.bucketMaps {
|
||||
for itemId, item := range bucketMap {
|
||||
delete(bucketMap, itemId)
|
||||
item.Reset()
|
||||
atomic.AddInt64(&this.totalMemory, -item.size)
|
||||
|
||||
garbageSize -= item.size
|
||||
if garbageSize <= 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GCNextBucket gc one bucket
|
||||
func (this *MemoryFragmentPool) GCNextBucket() (isEmpty bool) {
|
||||
if !this.isOk {
|
||||
return
|
||||
}
|
||||
|
||||
var itemIds = []uint64{}
|
||||
|
||||
// find
|
||||
this.mu.RLock()
|
||||
|
||||
var bucketIndex = this.gcBucketIndex
|
||||
var bucketMap = this.bucketMaps[bucketIndex]
|
||||
isEmpty = len(bucketMap) == 0
|
||||
if isEmpty {
|
||||
this.mu.RUnlock()
|
||||
|
||||
// move to next bucket index
|
||||
bucketIndex++
|
||||
if bucketIndex >= this.countBuckets {
|
||||
bucketIndex = 0
|
||||
}
|
||||
this.gcBucketIndex = bucketIndex
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
for itemId, item := range bucketMap {
|
||||
if item.IsExpired() {
|
||||
itemIds = append(itemIds, itemId)
|
||||
}
|
||||
}
|
||||
|
||||
this.mu.RUnlock()
|
||||
|
||||
// remove
|
||||
if len(itemIds) > 0 {
|
||||
this.mu.Lock()
|
||||
for _, itemId := range itemIds {
|
||||
item, ok := bucketMap[itemId]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if !item.IsAvailable() {
|
||||
continue
|
||||
}
|
||||
delete(bucketMap, itemId)
|
||||
item.Reset()
|
||||
atomic.AddInt64(&this.totalMemory, -item.size)
|
||||
}
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// move to next bucket index
|
||||
bucketIndex++
|
||||
if bucketIndex >= this.countBuckets {
|
||||
bucketIndex = 0
|
||||
}
|
||||
this.gcBucketIndex = bucketIndex
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (this *MemoryFragmentPool) SetCapacity(capacity int64) {
|
||||
this.capacity = capacity
|
||||
}
|
||||
|
||||
func (this *MemoryFragmentPool) TotalSize() int64 {
|
||||
return atomic.LoadInt64(&this.totalMemory)
|
||||
}
|
||||
|
||||
func (this *MemoryFragmentPool) Len() int {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
var count = 0
|
||||
for _, bucketMap := range this.bucketMaps {
|
||||
count += len(bucketMap)
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
func (this *MemoryFragmentPool) IncreaseNew() {
|
||||
if this.isOk && this.debugMode {
|
||||
atomic.AddUint64(&this.countNew, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *MemoryFragmentPool) bucketIndexForSize(size int64) int {
|
||||
return int(size / memoryFragmentPoolBucketSegmentSize)
|
||||
}
|
||||
|
||||
func (this *MemoryFragmentPool) findItemInMap(bucketMap map[uint64]*MemoryFragmentPoolItem, expectSize int64) (resultBytes []byte, resultItemId uint64, ok bool) {
|
||||
if len(bucketMap) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for itemId, item := range bucketMap {
|
||||
if item.size >= expectSize {
|
||||
// check if is referred
|
||||
if !item.IsAvailable() {
|
||||
continue
|
||||
}
|
||||
|
||||
// return result
|
||||
if item.size != expectSize {
|
||||
resultBytes = item.Bytes[:expectSize]
|
||||
} else {
|
||||
resultBytes = item.Bytes
|
||||
}
|
||||
|
||||
// reset old item
|
||||
item.Reset()
|
||||
atomic.AddInt64(&this.totalMemory, -item.size)
|
||||
|
||||
resultItemId = itemId
|
||||
|
||||
if this.debugMode {
|
||||
atomic.AddUint64(&this.countGet, 1)
|
||||
}
|
||||
|
||||
ok = true
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,313 +0,0 @@
|
||||
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
"runtime"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewMemoryFragmentPool(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var pool = caches.NewMemoryFragmentPool()
|
||||
for i := 0; i < 3000; i++ {
|
||||
ok := pool.Put(make([]byte, 2<<20))
|
||||
if !ok {
|
||||
t.Log("finished at", i)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
t.Log(pool.TotalSize()>>20, "MB", pool.Len(), "items")
|
||||
|
||||
{
|
||||
r, ok := pool.Get(1 << 20)
|
||||
a.IsTrue(ok)
|
||||
a.IsTrue(len(r) == 1<<20)
|
||||
}
|
||||
|
||||
{
|
||||
r, ok := pool.Get(2 << 20)
|
||||
a.IsTrue(ok)
|
||||
a.IsTrue(len(r) == 2<<20)
|
||||
}
|
||||
|
||||
{
|
||||
r, ok := pool.Get(4 << 20)
|
||||
a.IsFalse(ok)
|
||||
a.IsTrue(len(r) == 0)
|
||||
}
|
||||
|
||||
t.Log(pool.TotalSize()>>20, "MB", pool.Len(), "items")
|
||||
}
|
||||
|
||||
func TestNewMemoryFragmentPool_LargeBucket(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var pool = caches.NewMemoryFragmentPool()
|
||||
{
|
||||
pool.Put(make([]byte, 128<<20+1))
|
||||
a.IsTrue(pool.Len() == 0)
|
||||
}
|
||||
|
||||
{
|
||||
pool.Put(make([]byte, 128<<20))
|
||||
a.IsTrue(pool.Len() == 1)
|
||||
|
||||
pool.Get(118 << 20)
|
||||
a.IsTrue(pool.Len() == 0)
|
||||
}
|
||||
|
||||
{
|
||||
pool.Put(make([]byte, 128<<20))
|
||||
a.IsTrue(pool.Len() == 1)
|
||||
|
||||
pool.Get(110 << 20)
|
||||
a.IsTrue(pool.Len() == 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryFragmentPool_Get_Exactly(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var pool = caches.NewMemoryFragmentPool()
|
||||
{
|
||||
pool.Put(make([]byte, 129<<20))
|
||||
a.IsTrue(pool.Len() == 0)
|
||||
}
|
||||
|
||||
{
|
||||
pool.Put(make([]byte, 4<<20))
|
||||
a.IsTrue(pool.Len() == 1)
|
||||
}
|
||||
|
||||
{
|
||||
pool.Get(4 << 20)
|
||||
a.IsTrue(pool.Len() == 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryFragmentPool_Get_Round(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var pool = caches.NewMemoryFragmentPool()
|
||||
{
|
||||
pool.Put(make([]byte, 8<<20))
|
||||
pool.Put(make([]byte, 8<<20))
|
||||
pool.Put(make([]byte, 8<<20))
|
||||
a.IsTrue(pool.Len() == 3)
|
||||
}
|
||||
|
||||
{
|
||||
resultBytes, ok := pool.Get(3 << 20)
|
||||
a.IsTrue(pool.Len() == 2)
|
||||
if ok {
|
||||
pool.Put(resultBytes)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
pool.Get(2 << 20)
|
||||
a.IsTrue(pool.Len() == 2)
|
||||
}
|
||||
|
||||
{
|
||||
pool.Get(1 << 20)
|
||||
a.IsTrue(pool.Len() == 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryFragmentPool_GC(t *testing.T) {
|
||||
var pool = caches.NewMemoryFragmentPool()
|
||||
pool.SetCapacity(32 << 20)
|
||||
for i := 0; i < 16; i++ {
|
||||
pool.Put(make([]byte, 4<<20))
|
||||
}
|
||||
var before = time.Now()
|
||||
pool.GC()
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
t.Log(pool.Len())
|
||||
}
|
||||
|
||||
func TestMemoryFragmentPool_Memory(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var pool = caches.NewMemoryFragmentPool()
|
||||
|
||||
testutils.StartMemoryStats(t, func() {
|
||||
t.Log(pool.Len(), "items")
|
||||
})
|
||||
|
||||
var sampleData = bytes.Repeat([]byte{'A'}, 16<<20)
|
||||
|
||||
var countNew = 0
|
||||
for i := 0; i < 1000; i++ {
|
||||
cacheData, ok := pool.Get(16 << 20)
|
||||
if ok {
|
||||
copy(cacheData, sampleData)
|
||||
pool.Put(cacheData)
|
||||
} else {
|
||||
countNew++
|
||||
var data = make([]byte, 16<<20)
|
||||
copy(data, sampleData)
|
||||
pool.Put(data)
|
||||
}
|
||||
}
|
||||
|
||||
t.Log("count new:", countNew)
|
||||
t.Log("count remains:", pool.Len())
|
||||
|
||||
time.Sleep(10 * time.Minute)
|
||||
}
|
||||
|
||||
func TestMemoryFragmentPool_GCNextBucket(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var pool = caches.NewMemoryFragmentPool()
|
||||
for i := 0; i < 1000; i++ {
|
||||
pool.Put(make([]byte, rands.Int(0, 100)<<20))
|
||||
}
|
||||
|
||||
var lastLen int
|
||||
for {
|
||||
pool.GCNextBucket()
|
||||
var currentLen = pool.Len()
|
||||
if lastLen == currentLen {
|
||||
continue
|
||||
}
|
||||
lastLen = currentLen
|
||||
|
||||
t.Log(currentLen, "items", pool.TotalSize(), "bytes", timeutil.Format("H:i:s"))
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if currentLen == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMemoryFragmentPoolItem(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var m = map[int]*caches.MemoryFragmentPoolItem{}
|
||||
m[1] = &caches.MemoryFragmentPoolItem{
|
||||
Refs: 0,
|
||||
}
|
||||
var item = m[1]
|
||||
a.IsTrue(item.Refs == 0)
|
||||
a.IsTrue(atomic.AddInt32(&item.Refs, 1) == 1)
|
||||
|
||||
for _, item2 := range m {
|
||||
t.Log(item2)
|
||||
a.IsTrue(atomic.AddInt32(&item2.Refs, 1) == 2)
|
||||
}
|
||||
|
||||
t.Log(m)
|
||||
}
|
||||
|
||||
func BenchmarkMemoryFragmentPool_Get_HIT(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
var pool = caches.NewMemoryFragmentPool()
|
||||
for i := 0; i < 3000; i++ {
|
||||
ok := pool.Put(make([]byte, 2<<20))
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
data, ok := pool.Get(2 << 20)
|
||||
if ok {
|
||||
pool.Put(data)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMemoryFragmentPool_Get_TOTALLY_MISSING(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
var pool = caches.NewMemoryFragmentPool()
|
||||
for i := 0; i < 3000; i++ {
|
||||
ok := pool.Put(make([]byte, 2<<20+100))
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
data, ok := pool.Get(2<<20 + 200)
|
||||
if ok {
|
||||
pool.Put(data)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMemoryPool_Get_HIT_MISSING(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
var pool = caches.NewMemoryFragmentPool()
|
||||
for i := 0; i < 3000; i++ {
|
||||
ok := pool.Put(make([]byte, rands.Int(2, 32)<<20))
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
data, ok := pool.Get(4 << 20)
|
||||
if ok {
|
||||
pool.Put(data)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkMemoryFragmentPool_GC(b *testing.B) {
|
||||
runtime.GOMAXPROCS(4)
|
||||
|
||||
var pool = caches.NewMemoryFragmentPool()
|
||||
pool.SetCapacity(1 << 30)
|
||||
for i := 0; i < 2_000; i++ {
|
||||
pool.Put(make([]byte, 1<<20))
|
||||
}
|
||||
|
||||
var mu = sync.Mutex{}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
mu.Lock()
|
||||
for i := 0; i < 100; i++ {
|
||||
pool.GCNextBucket()
|
||||
}
|
||||
mu.Unlock()
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -5,8 +5,8 @@ package caches
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -43,7 +43,7 @@ func NewOpenFileCache(maxCount int) (*OpenFileCache, error) {
|
||||
maxCount: maxCount,
|
||||
poolMap: map[string]*OpenFilePool{},
|
||||
poolList: linkedlist.NewList[*OpenFilePool](),
|
||||
capacitySize: (int64(utils.SystemMemoryGB()) << 30) / 16,
|
||||
capacitySize: (int64(memutils.SystemMemoryGB()) << 30) / 16,
|
||||
}
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
@@ -64,6 +64,8 @@ func NewOpenFileCache(maxCount int) (*OpenFileCache, error) {
|
||||
}
|
||||
|
||||
func (this *OpenFileCache) Get(filename string) *OpenFile {
|
||||
filename = filepath.Clean(filename)
|
||||
|
||||
this.locker.RLock()
|
||||
pool, ok := this.poolMap[filename]
|
||||
this.locker.RUnlock()
|
||||
@@ -85,6 +87,8 @@ func (this *OpenFileCache) Get(filename string) *OpenFile {
|
||||
}
|
||||
|
||||
func (this *OpenFileCache) Put(filename string, file *OpenFile) {
|
||||
filename = filepath.Clean(filename)
|
||||
|
||||
if file.size > maxOpenFileSize {
|
||||
return
|
||||
}
|
||||
@@ -119,6 +123,8 @@ func (this *OpenFileCache) Put(filename string, file *OpenFile) {
|
||||
}
|
||||
|
||||
func (this *OpenFileCache) Close(filename string) {
|
||||
filename = filepath.Clean(filename)
|
||||
|
||||
this.locker.Lock()
|
||||
|
||||
pool, ok := this.poolMap[filename]
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
@@ -70,7 +69,7 @@ func NewPartialRangesFromJSON(data []byte) (*PartialRanges, error) {
|
||||
|
||||
// NewPartialRangesFromFile 从文件中加载范围信息
|
||||
func NewPartialRangesFromFile(path string) (*PartialRanges, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
data, err := SharedPartialRangesQueue.Get(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -172,7 +171,8 @@ func (this *PartialRanges) Bytes() []byte {
|
||||
|
||||
// WriteToFile 写入到文件中
|
||||
func (this *PartialRanges) WriteToFile(path string) error {
|
||||
return os.WriteFile(path, this.Bytes(), 0666)
|
||||
SharedPartialRangesQueue.Put(path, this.Bytes())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Max 获取最大位置
|
||||
@@ -188,6 +188,11 @@ func (this *PartialRanges) Reset() {
|
||||
this.Ranges = [][2]int64{}
|
||||
}
|
||||
|
||||
// IsCompleted 是否已下载完整
|
||||
func (this *PartialRanges) IsCompleted() bool {
|
||||
return len(this.Ranges) == 1 && this.Ranges[0][0] == 0 && this.Ranges[0][1] == this.BodySize-1
|
||||
}
|
||||
|
||||
func (this *PartialRanges) merge(index int) {
|
||||
// forward
|
||||
var lastIndex = index
|
||||
|
||||
144
internal/caches/partial_ranges_queue.go
Normal file
144
internal/caches/partial_ranges_queue.go
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var SharedPartialRangesQueue = NewPartialRangesQueue()
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
SharedPartialRangesQueue.Start()
|
||||
}
|
||||
|
||||
const partialRangesQueueSharding = 8
|
||||
|
||||
// PartialRangesQueue ranges file writing queue
|
||||
type PartialRangesQueue struct {
|
||||
m [partialRangesQueueSharding]map[string][]byte // { filename => data, ... }
|
||||
|
||||
c chan string // filename1, ...
|
||||
mu [partialRangesQueueSharding]*sync.RWMutex
|
||||
}
|
||||
|
||||
// NewPartialRangesQueue Create new queue
|
||||
func NewPartialRangesQueue() *PartialRangesQueue {
|
||||
var queueSize = 512
|
||||
var memGB = memutils.SystemMemoryGB()
|
||||
if memGB > 16 {
|
||||
queueSize = 8 << 10
|
||||
} else if memGB > 8 {
|
||||
queueSize = 4 << 10
|
||||
} else if memGB > 4 {
|
||||
queueSize = 2 << 10
|
||||
} else if memGB > 2 {
|
||||
queueSize = 1 << 10
|
||||
}
|
||||
|
||||
var m = [partialRangesQueueSharding]map[string][]byte{}
|
||||
var muList = [partialRangesQueueSharding]*sync.RWMutex{}
|
||||
for i := 0; i < partialRangesQueueSharding; i++ {
|
||||
muList[i] = &sync.RWMutex{}
|
||||
m[i] = map[string][]byte{}
|
||||
}
|
||||
|
||||
return &PartialRangesQueue{
|
||||
mu: muList,
|
||||
m: m,
|
||||
c: make(chan string, queueSize),
|
||||
}
|
||||
}
|
||||
|
||||
// Start the queue
|
||||
func (this *PartialRangesQueue) Start() {
|
||||
goman.New(func() {
|
||||
this.Dump()
|
||||
})
|
||||
}
|
||||
|
||||
// Put ranges data to filename
|
||||
func (this *PartialRangesQueue) Put(filename string, data []byte) {
|
||||
var index = this.indexForKey(filename)
|
||||
|
||||
this.mu[index].Lock()
|
||||
this.m[index][filename] = data
|
||||
this.mu[index].Unlock()
|
||||
|
||||
// always wait to finish
|
||||
this.c <- filename
|
||||
}
|
||||
|
||||
// Get ranges data from filename
|
||||
func (this *PartialRangesQueue) Get(filename string) ([]byte, error) {
|
||||
var index = this.indexForKey(filename)
|
||||
|
||||
this.mu[index].RLock()
|
||||
data, ok := this.m[index][filename]
|
||||
this.mu[index].RUnlock()
|
||||
|
||||
if ok {
|
||||
return data, nil
|
||||
}
|
||||
|
||||
return os.ReadFile(filename)
|
||||
}
|
||||
|
||||
// Delete ranges filename
|
||||
func (this *PartialRangesQueue) Delete(filename string) {
|
||||
var index = this.indexForKey(filename)
|
||||
|
||||
this.mu[index].Lock()
|
||||
delete(this.m[index], filename)
|
||||
this.mu[index].Unlock()
|
||||
}
|
||||
|
||||
// Dump ranges to filename from memory
|
||||
func (this *PartialRangesQueue) Dump() {
|
||||
for filename := range this.c {
|
||||
var index = this.indexForKey(filename)
|
||||
|
||||
this.mu[index].Lock()
|
||||
data, ok := this.m[index][filename]
|
||||
if ok {
|
||||
delete(this.m[index], filename)
|
||||
}
|
||||
this.mu[index].Unlock()
|
||||
|
||||
if !ok || len(data) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
err := os.WriteFile(filename, data, 0666)
|
||||
if err != nil {
|
||||
remotelogs.Println("PARTIAL_RANGES_QUEUE", "write file '"+filename+"' failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Len count all files
|
||||
func (this *PartialRangesQueue) Len() int {
|
||||
var count int
|
||||
|
||||
for i := 0; i < partialRangesQueueSharding; i++ {
|
||||
this.mu[i].RLock()
|
||||
count += len(this.m[i])
|
||||
this.mu[i].RUnlock()
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func (this *PartialRangesQueue) indexForKey(filename string) int {
|
||||
return int(fnv.HashString(filename) % partialRangesQueueSharding)
|
||||
}
|
||||
31
internal/caches/partial_ranges_queue_test.go
Normal file
31
internal/caches/partial_ranges_queue_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package caches_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewPartialRangesQueue(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var queue = caches.NewPartialRangesQueue()
|
||||
queue.Put("a", []byte{1, 2, 3})
|
||||
t.Log("add 'a':", queue.Len())
|
||||
t.Log(queue.Get("a"))
|
||||
a.IsTrue(queue.Len() == 1)
|
||||
|
||||
queue.Put("a", nil)
|
||||
t.Log("add 'a':", queue.Len())
|
||||
a.IsTrue(queue.Len() == 1)
|
||||
|
||||
queue.Put("b", nil)
|
||||
t.Log("add 'b':", queue.Len())
|
||||
a.IsTrue(queue.Len() == 2)
|
||||
|
||||
queue.Delete("a")
|
||||
t.Log("delete 'a':", queue.Len())
|
||||
a.IsTrue(queue.Len() == 1)
|
||||
}
|
||||
18
internal/caches/reader_file_mmap.go
Normal file
18
internal/caches/reader_file_mmap.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !plus
|
||||
|
||||
package caches
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
type MMAPFileReader struct {
|
||||
FileReader
|
||||
}
|
||||
|
||||
func (this *MMAPFileReader) CopyBodyTo(writer io.Writer) (int, error) {
|
||||
// stub
|
||||
return 0, errors.New("not implemented")
|
||||
}
|
||||
@@ -34,7 +34,7 @@ func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
|
||||
this.header = this.openFile.header
|
||||
}
|
||||
|
||||
isOk := false
|
||||
var isOk = false
|
||||
|
||||
if autoDiscard {
|
||||
defer func() {
|
||||
@@ -54,9 +54,9 @@ func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
|
||||
var buf = this.meta
|
||||
if len(buf) == 0 {
|
||||
buf = make([]byte, SizeMeta)
|
||||
ok, err := this.readToBuff(this.fp, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
ok, readErr := this.readToBuff(this.fp, buf)
|
||||
if readErr != nil {
|
||||
return readErr
|
||||
}
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
@@ -73,10 +73,10 @@ func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
|
||||
this.status = status
|
||||
|
||||
// URL
|
||||
urlLength := binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus : SizeExpiresAt+SizeStatus+SizeURLLength])
|
||||
var urlLength = binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus : SizeExpiresAt+SizeStatus+SizeURLLength])
|
||||
|
||||
// header
|
||||
headerSize := int(binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus+SizeURLLength : SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength]))
|
||||
var headerSize = int(binary.BigEndian.Uint32(buf[SizeExpiresAt+SizeStatus+SizeURLLength : SizeExpiresAt+SizeStatus+SizeURLLength+SizeHeaderLength]))
|
||||
if headerSize == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func (this *PartialFileReader) InitAutoDiscard(autoDiscard bool) error {
|
||||
if this.openFileCache != nil && len(this.header) == 0 {
|
||||
if headerSize > 0 && headerSize <= 512 {
|
||||
this.header = make([]byte, headerSize)
|
||||
_, err := this.fp.Seek(this.headerOffset, io.SeekStart)
|
||||
_, err = this.fp.Seek(this.headerOffset, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -140,7 +140,13 @@ func (this *PartialFileReader) Ranges() *PartialRanges {
|
||||
return this.ranges
|
||||
}
|
||||
|
||||
func (this *PartialFileReader) IsCompleted() bool {
|
||||
return this.ranges != nil && this.ranges.IsCompleted()
|
||||
}
|
||||
|
||||
func (this *PartialFileReader) discard() error {
|
||||
SharedPartialRangesQueue.Delete(this.rangePath)
|
||||
_ = os.Remove(this.rangePath)
|
||||
|
||||
return this.FileReader.discard()
|
||||
}
|
||||
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/sizes"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
@@ -54,16 +54,30 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
|
||||
HotItemSize = 1024 // 热点数据数量
|
||||
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||
FileToMemoryMaxSize = 32 * sizes.M // 可以从文件写入到内存的最大文件尺寸
|
||||
FileStorageMaxIgnoreKeys = 32768 // 最大可忽略的键值数(尺寸过大的键值)
|
||||
HotItemSize = 1024 // 热点数据数量
|
||||
HotItemLifeSeconds int64 = 3600 // 热点数据生命周期
|
||||
FileTmpSuffix = ".tmp"
|
||||
DefaultMinDiskFreeSpace uint64 = 5 << 30 // 当前磁盘最小剩余空间
|
||||
DefaultStaleCacheSeconds = 1200 // 过时缓存留存时间
|
||||
HashKeyLength = 32
|
||||
)
|
||||
|
||||
var FileToMemoryMaxSize int64 = 32 << 20 // 可以从文件写入到内存的最大文件尺寸
|
||||
|
||||
func init() {
|
||||
var availableMemoryGB = memutils.AvailableMemoryGB()
|
||||
if availableMemoryGB > 64 {
|
||||
FileToMemoryMaxSize = 512 << 20
|
||||
} else if availableMemoryGB > 32 {
|
||||
FileToMemoryMaxSize = 256 << 20
|
||||
} else if availableMemoryGB > 16 {
|
||||
FileToMemoryMaxSize = 128 << 20
|
||||
} else if availableMemoryGB > 8 {
|
||||
FileToMemoryMaxSize = 64 << 20
|
||||
}
|
||||
}
|
||||
|
||||
var sharedWritingFileKeyMap = map[string]zero.Zero{} // key => bool
|
||||
var sharedWritingFileKeyLocker = sync.Mutex{}
|
||||
|
||||
@@ -125,7 +139,7 @@ func (this *FileStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolic
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
var oldOptions = &serverconfigs.HTTPFileCacheStorage{}
|
||||
var oldOptions = serverconfigs.NewHTTPFileCacheStorage()
|
||||
err = json.Unmarshal(oldOptionsJSON, oldOptions)
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -135,7 +149,7 @@ func (this *FileStorage) CanUpdatePolicy(newPolicy *serverconfigs.HTTPCachePolic
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
var newOptions = &serverconfigs.HTTPFileCacheStorage{}
|
||||
var newOptions = serverconfigs.NewHTTPFileCacheStorage()
|
||||
err = json.Unmarshal(newOptionsJSON, newOptions)
|
||||
if err != nil {
|
||||
return false
|
||||
@@ -158,7 +172,7 @@ func (this *FileStorage) UpdatePolicy(newPolicy *serverconfigs.HTTPCachePolicy)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var newOptions = &serverconfigs.HTTPFileCacheStorage{}
|
||||
var newOptions = serverconfigs.NewHTTPFileCacheStorage()
|
||||
err = json.Unmarshal(newOptionsJSON, newOptions)
|
||||
if err != nil {
|
||||
remotelogs.Error("CACHE", "update policy '"+types.String(this.policy.Id)+"' failed: decode options failed: "+err.Error())
|
||||
@@ -223,7 +237,7 @@ func (this *FileStorage) Init() error {
|
||||
var before = time.Now()
|
||||
|
||||
// 配置
|
||||
var options = &serverconfigs.HTTPFileCacheStorage{}
|
||||
var options = serverconfigs.NewHTTPFileCacheStorage()
|
||||
optionsJSON, err := json.Marshal(this.policy.Options)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -297,14 +311,19 @@ func (this *FileStorage) Init() error {
|
||||
var totalSize = this.TotalDiskSize()
|
||||
var cost = time.Since(before).Seconds() * 1000
|
||||
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))
|
||||
if totalSize > (1 << 30) {
|
||||
sizeMB = fmt.Sprintf("%.3f GiB", float64(totalSize)/(1<<30))
|
||||
} else if totalSize > (1 << 20) {
|
||||
sizeMB = fmt.Sprintf("%.3f MiB", float64(totalSize)/(1<<20))
|
||||
} else if totalSize > (1 << 10) {
|
||||
sizeMB = fmt.Sprintf("%.3f KiB", float64(totalSize)/(1<<10))
|
||||
}
|
||||
remotelogs.Println("CACHE", "init policy "+types.String(this.policy.Id)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, size: "+sizeMB)
|
||||
|
||||
var mmapTag = "disabled"
|
||||
if this.options.EnableMMAP {
|
||||
mmapTag = "enabled"
|
||||
}
|
||||
remotelogs.Println("CACHE", "init policy "+types.String(this.policy.Id)+" from '"+this.options.Dir+"', cost: "+fmt.Sprintf("%.2f", cost)+" ms, size: "+sizeMB+", mmap: "+mmapTag)
|
||||
}()
|
||||
|
||||
// 初始化list
|
||||
@@ -360,17 +379,29 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
|
||||
hash, path, _ := this.keyPath(key)
|
||||
|
||||
// 检查文件记录是否已过期
|
||||
var estimatedSize int64
|
||||
if !useStale {
|
||||
exists, err := this.list.Exist(hash)
|
||||
exists, filesize, err := this.list.Exist(hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
estimatedSize = filesize
|
||||
}
|
||||
|
||||
// 尝试通过MMAP读取
|
||||
if estimatedSize > 0 {
|
||||
reader, err := this.tryMMAPReader(isPartial, estimatedSize, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if reader != nil {
|
||||
return reader, nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO 尝试使用mmap加快读取速度
|
||||
var isOk = false
|
||||
var openFile *OpenFile
|
||||
var openFileCache = this.openFileCache // 因为中间可能有修改,所以先赋值再获取
|
||||
@@ -378,6 +409,7 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
|
||||
openFile = openFileCache.Get(path)
|
||||
}
|
||||
var fp *os.File
|
||||
|
||||
var err error
|
||||
if openFile == nil {
|
||||
fp, err = os.OpenFile(path, os.O_RDONLY, 0444)
|
||||
@@ -409,6 +441,7 @@ func (this *FileStorage) openReader(key string, allowMemory bool, useStale bool,
|
||||
fileReader.openFileCache = openFileCache
|
||||
reader = fileReader
|
||||
}
|
||||
|
||||
err = reader.Init()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -459,7 +492,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
maxMemorySize = maxSize
|
||||
}
|
||||
var memoryStorage = this.memoryStorage
|
||||
if !fsutils.DiskIsExtremelyFast() && !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) {
|
||||
if !isFlushing && !isPartial && memoryStorage != nil && ((bodySize > 0 && bodySize < maxMemorySize) || bodySize < 0) {
|
||||
writer, err := memoryStorage.OpenWriter(key, expiredAt, status, headerSize, bodySize, maxMemorySize, false)
|
||||
if err == nil {
|
||||
return writer, nil
|
||||
@@ -469,6 +502,10 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
if errors.Is(err, ErrWritingQueueFull) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if IsCapacityError(err) && bodySize > 0 && memoryStorage.totalDirtySize > (128<<20) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 是否正在写入
|
||||
@@ -549,7 +586,7 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
var partialRanges *PartialRanges
|
||||
if isPartial {
|
||||
// 数据库中是否存在
|
||||
existsCacheItem, _ := this.list.Exist(hash)
|
||||
existsCacheItem, _, _ := this.list.Exist(hash)
|
||||
if existsCacheItem {
|
||||
readerFp, err := os.OpenFile(tmpPath, os.O_RDONLY, 0444)
|
||||
if err == nil {
|
||||
@@ -588,7 +625,6 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
writer, err := os.OpenFile(tmpPath, flags, 0666)
|
||||
fsutils.WriteEnd()
|
||||
if err != nil {
|
||||
// TODO 检查在各个系统中的稳定性
|
||||
if os.IsNotExist(err) {
|
||||
_ = os.MkdirAll(dir, 0777)
|
||||
|
||||
@@ -622,6 +658,11 @@ func (this *FileStorage) openWriter(key string, expiredAt int64, status int, hea
|
||||
return nil, fmt.Errorf("%w (003)", ErrFileIsWriting)
|
||||
}
|
||||
|
||||
// 关闭
|
||||
if openFileCache != nil {
|
||||
openFileCache.Close(cachePath)
|
||||
}
|
||||
|
||||
var metaBodySize int64 = -1
|
||||
var metaHeaderSize = -1
|
||||
if isNewCreated {
|
||||
@@ -1236,6 +1277,7 @@ func (this *FileStorage) hotLoop() {
|
||||
}
|
||||
|
||||
var buf = utils.BytePool16k.Get()
|
||||
|
||||
defer utils.BytePool16k.Put(buf)
|
||||
for _, item := range result[:size] {
|
||||
reader, err := this.openReader(item.Key, false, false, false)
|
||||
@@ -1277,8 +1319,8 @@ func (this *FileStorage) hotLoop() {
|
||||
continue
|
||||
}
|
||||
|
||||
err = reader.ReadHeader(buf, func(n int) (goNext bool, err error) {
|
||||
_, err = writer.WriteHeader(buf[:n])
|
||||
err = reader.ReadHeader(buf.Bytes, func(n int) (goNext bool, err error) {
|
||||
_, err = writer.WriteHeader(buf.Bytes[:n])
|
||||
return
|
||||
})
|
||||
if err != nil {
|
||||
@@ -1287,10 +1329,10 @@ func (this *FileStorage) hotLoop() {
|
||||
continue
|
||||
}
|
||||
|
||||
err = reader.ReadBody(buf, func(n int) (goNext bool, err error) {
|
||||
err = reader.ReadBody(buf.Bytes, func(n int) (goNext bool, err error) {
|
||||
goNext = true
|
||||
if n > 0 {
|
||||
_, err = writer.Write(buf[:n])
|
||||
_, err = writer.Write(buf.Bytes[:n])
|
||||
if err != nil {
|
||||
goNext = false
|
||||
}
|
||||
@@ -1398,7 +1440,7 @@ func (this *FileStorage) increaseHit(key string, hash string, reader Reader) {
|
||||
|
||||
// 增加到热点
|
||||
// 这里不收录缓存尺寸过大的文件
|
||||
if memoryStorage != nil && reader.BodySize() > 0 && reader.BodySize() < 128*sizes.M {
|
||||
if memoryStorage != nil && reader.BodySize() > 0 && reader.BodySize() < (128<<20) {
|
||||
this.hotMapLocker.Lock()
|
||||
hotItem, ok := this.hotMap[key]
|
||||
|
||||
@@ -1444,6 +1486,7 @@ func (this *FileStorage) removeCacheFile(path string) error {
|
||||
_, statErr := os.Stat(partialPath)
|
||||
if statErr == nil {
|
||||
_ = os.Remove(partialPath)
|
||||
SharedPartialRangesQueue.Delete(partialPath)
|
||||
}
|
||||
}
|
||||
return err
|
||||
|
||||
9
internal/caches/storage_file_ext.go
Normal file
9
internal/caches/storage_file_ext.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
//go:build !plus
|
||||
|
||||
package caches
|
||||
|
||||
func (this *FileStorage) tryMMAPReader(isPartial bool, estimatedSize int64, path string) (Reader, error) {
|
||||
// stub
|
||||
return nil, nil
|
||||
}
|
||||
@@ -664,7 +664,7 @@ func TestFileStorage_ScanGarbageCaches(t *testing.T) {
|
||||
func BenchmarkFileStorage_Read(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
_ = utils.SetRLimit(1024 * 1024)
|
||||
_ = utils.SetRLimit(1 << 20)
|
||||
|
||||
var storage = NewFileStorage(&serverconfigs.HTTPCachePolicy{
|
||||
Id: 1,
|
||||
|
||||
@@ -9,9 +9,10 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"math"
|
||||
"runtime"
|
||||
@@ -54,7 +55,9 @@ type MemoryStorage struct {
|
||||
|
||||
purgeTicker *utils.Ticker
|
||||
|
||||
usedSize int64
|
||||
usedSize int64
|
||||
totalDirtySize int64
|
||||
|
||||
writingKeyMap map[string]zero.Zero // key => bool
|
||||
|
||||
ignoreKeys *setutils.FixedSet
|
||||
@@ -66,7 +69,7 @@ func NewMemoryStorage(policy *serverconfigs.HTTPCachePolicy, parentStorage Stora
|
||||
|
||||
if parentStorage != nil {
|
||||
if queueSize <= 0 {
|
||||
queueSize = utils.SystemMemoryGB() * 100_000
|
||||
queueSize = memutils.SystemMemoryGB() * 100_000
|
||||
}
|
||||
|
||||
dirtyChan = make(chan string, queueSize)
|
||||
@@ -99,10 +102,19 @@ func (this *MemoryStorage) Init() error {
|
||||
|
||||
// 启动定时Flush memory to disk任务
|
||||
if this.parentStorage != nil {
|
||||
// TODO 应该根据磁盘性能决定线程数
|
||||
// TODO 线程数应该可以在缓存策略和节点中设定
|
||||
var threads = runtime.NumCPU()
|
||||
|
||||
var threads = 2
|
||||
var numCPU = runtime.NumCPU()
|
||||
if fsutils.DiskIsExtremelyFast() {
|
||||
if numCPU >= 8 {
|
||||
threads = 8
|
||||
} else {
|
||||
threads = 4
|
||||
}
|
||||
} else if fsutils.DiskIsFast() {
|
||||
if numCPU >= 4 {
|
||||
threads = 4
|
||||
}
|
||||
}
|
||||
for i := 0; i < threads; i++ {
|
||||
goman.New(func() {
|
||||
this.startFlush()
|
||||
@@ -118,7 +130,7 @@ func (this *MemoryStorage) OpenReader(key string, useStale bool, isPartial bool)
|
||||
var hash = this.hash(key)
|
||||
|
||||
// check if exists in list
|
||||
exists, _ := this.list.Exist(types.String(hash))
|
||||
exists, _, _ := this.list.Exist(types.String(hash))
|
||||
if !exists {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
@@ -202,7 +214,7 @@ func (this *MemoryStorage) openWriter(key string, expiresAt int64, status int, h
|
||||
item, ok := this.valuesMap[hash]
|
||||
if ok && !item.IsExpired() {
|
||||
var hashString = types.String(hash)
|
||||
exists, _ := this.list.Exist(hashString)
|
||||
exists, _, _ := this.list.Exist(hashString)
|
||||
if !exists {
|
||||
// remove from values map
|
||||
delete(this.valuesMap, hash)
|
||||
@@ -330,6 +342,9 @@ func (this *MemoryStorage) Stop() {
|
||||
close(this.dirtyChan)
|
||||
}
|
||||
|
||||
this.usedSize = 0
|
||||
this.totalDirtySize = 0
|
||||
|
||||
_ = this.list.Close()
|
||||
|
||||
this.locker.Unlock()
|
||||
@@ -496,14 +511,20 @@ func (this *MemoryStorage) startFlush() {
|
||||
|
||||
if fsutils.IsInExtremelyHighLoad {
|
||||
time.Sleep(1 * time.Second)
|
||||
} else if fsutils.IsInHighLoad {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 单次Flush任务
|
||||
func (this *MemoryStorage) flushItem(key string) {
|
||||
func (this *MemoryStorage) flushItem(fullKey string) {
|
||||
sizeString, key, found := strings.Cut(fullKey, "@")
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
atomic.AddInt64(&this.totalDirtySize, -types.Int64(sizeString))
|
||||
}()
|
||||
|
||||
if this.parentStorage == nil {
|
||||
return
|
||||
}
|
||||
@@ -516,11 +537,6 @@ func (this *MemoryStorage) flushItem(key string) {
|
||||
// 从内存中移除,并确保无论如何都会执行
|
||||
defer func() {
|
||||
_ = this.Delete(key)
|
||||
|
||||
// 重用内存,前提是确保内存不再被引用
|
||||
if enableFragmentPool && ok && item.IsDone && !item.isReferring && len(item.BodyValue) > 0 {
|
||||
SharedFragmentMemoryPool.Put(item.BodyValue)
|
||||
}
|
||||
}()
|
||||
|
||||
if !ok {
|
||||
@@ -536,13 +552,28 @@ func (this *MemoryStorage) flushItem(key string) {
|
||||
}
|
||||
|
||||
// 检查是否在列表中,防止未加入列表时就开始flush
|
||||
isInList, err := this.list.Exist(types.String(hash))
|
||||
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)
|
||||
for i := 0; i < 1000; i++ {
|
||||
isInList, _, err = this.list.Exist(types.String(hash))
|
||||
if err != nil {
|
||||
remotelogs.Error("CACHE", "flush items failed: "+err.Error())
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
if isInList {
|
||||
break
|
||||
}
|
||||
time.Sleep(1 * time.Millisecond)
|
||||
}
|
||||
if !isInList {
|
||||
// discard
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
writer, err := this.parentStorage.OpenFlushWriter(key, item.ExpiresAt, item.Status, len(item.HeaderValue), int64(len(item.BodyValue)))
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
@@ -381,3 +382,31 @@ func BenchmarkValuesMap(b *testing.B) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkNewMemoryStorage(b *testing.B) {
|
||||
var storage = NewMemoryStorage(&serverconfigs.HTTPCachePolicy{}, nil)
|
||||
|
||||
var data = bytes.Repeat([]byte{'A'}, 1024)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
func() {
|
||||
writer, err := storage.OpenWriter("abc"+strconv.Itoa(rand.Int()), time.Now().Unix()+60, 200, -1, -1, -1, false)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
_, _ = writer.WriteHeader([]byte("Header"))
|
||||
_, _ = writer.Write(data)
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ package caches_test
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/caches"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
@@ -136,6 +136,13 @@ func (this *FileWriter) Close() error {
|
||||
|
||||
var path = this.rawWriter.Name()
|
||||
|
||||
// check content length
|
||||
if this.metaBodySize > 0 && this.bodySize != this.metaBodySize {
|
||||
_ = this.rawWriter.Close()
|
||||
_ = os.Remove(path)
|
||||
return ErrUnexpectedContentLength
|
||||
}
|
||||
|
||||
err := this.WriteHeaderLength(types.Int(this.headerSize))
|
||||
if err != nil {
|
||||
fsutils.WriteBegin()
|
||||
|
||||
@@ -3,8 +3,10 @@ package caches
|
||||
import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type MemoryWriter struct {
|
||||
@@ -32,27 +34,11 @@ func NewMemoryWriter(memoryStorage *MemoryStorage, key string, expiredAt int64,
|
||||
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()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if expectedBodySize > 0 {
|
||||
valueItem.BodyValue = make([]byte, 0, expectedBodySize)
|
||||
}
|
||||
if expectedBodySize > 0 {
|
||||
valueItem.BodyValue = make([]byte, 0, expectedBodySize)
|
||||
}
|
||||
|
||||
var w = &MemoryWriter{
|
||||
storage: memoryStorage,
|
||||
key: key,
|
||||
@@ -135,6 +121,14 @@ func (this *MemoryWriter) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// check content length
|
||||
if this.expectedBodySize > 0 && this.bodySize != this.expectedBodySize {
|
||||
this.storage.locker.Lock()
|
||||
delete(this.storage.valuesMap, this.hash)
|
||||
this.storage.locker.Unlock()
|
||||
return ErrUnexpectedContentLength
|
||||
}
|
||||
|
||||
this.storage.locker.Lock()
|
||||
this.item.IsDone = true
|
||||
var err error
|
||||
@@ -143,7 +137,8 @@ func (this *MemoryWriter) Close() error {
|
||||
this.storage.valuesMap[this.hash] = this.item
|
||||
|
||||
select {
|
||||
case this.storage.dirtyChan <- this.key:
|
||||
case this.storage.dirtyChan <- types.String(this.bodySize) + "@" + this.key:
|
||||
atomic.AddInt64(&this.storage.totalDirtySize, this.bodySize)
|
||||
default:
|
||||
// remove from values map
|
||||
delete(this.storage.valuesMap, this.hash)
|
||||
@@ -171,14 +166,6 @@ func (this *MemoryWriter) Discard() error {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -127,6 +127,32 @@ func (this *PartialFileWriter) WriteAt(offset int64, data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// prevent extending too much space in a single writing
|
||||
var maxOffset = this.ranges.Max()
|
||||
if offset-maxOffset > 16<<20 {
|
||||
var maxExtendSize int64 = 32 << 20
|
||||
if fsutils.DiskIsExtremelyFast() {
|
||||
maxExtendSize = 128 << 20
|
||||
} else if fsutils.DiskIsFast() {
|
||||
maxExtendSize = 64 << 20
|
||||
}
|
||||
if offset-maxOffset > maxExtendSize {
|
||||
stat, err := this.rawWriter.Stat()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// extend min size to prepare for file tail
|
||||
const extendSizePerStep = 8 << 20
|
||||
if stat.Size()+extendSizePerStep <= this.bodyOffset+offset+int64(len(data)) {
|
||||
fsutils.WriteBegin()
|
||||
_ = this.rawWriter.Truncate(stat.Size() + extendSizePerStep)
|
||||
fsutils.WriteEnd()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if this.bodyOffset == 0 {
|
||||
var keyLength = 0
|
||||
if this.ranges.Version == 0 { // 以往的版本包含有Key
|
||||
@@ -228,6 +254,7 @@ func (this *PartialFileWriter) Discard() error {
|
||||
_ = this.rawWriter.Close()
|
||||
fsutils.WriteEnd()
|
||||
|
||||
SharedPartialRangesQueue.Delete(this.rangePath)
|
||||
_ = os.Remove(this.rangePath)
|
||||
|
||||
err := os.Remove(this.rawWriter.Name())
|
||||
@@ -261,5 +288,7 @@ func (this *PartialFileWriter) IsNew() bool {
|
||||
|
||||
func (this *PartialFileWriter) remove() {
|
||||
_ = os.Remove(this.rawWriter.Name())
|
||||
|
||||
SharedPartialRangesQueue.Delete(this.rangePath)
|
||||
_ = os.Remove(this.rangePath)
|
||||
}
|
||||
|
||||
14
internal/compressions/errors.go
Normal file
14
internal/compressions/errors.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package compressions
|
||||
|
||||
import "errors"
|
||||
|
||||
var ErrIsBusy = errors.New("the system is busy for compression")
|
||||
|
||||
func CanIgnore(err error) bool {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
return errors.Is(err, ErrIsBusy)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ type Reader interface {
|
||||
Reset(reader io.Reader) error
|
||||
RawClose() error
|
||||
Close() error
|
||||
IncreaseHit() uint32
|
||||
|
||||
SetPool(pool *ReaderPool)
|
||||
ResetFinish()
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
|
||||
package compressions
|
||||
|
||||
import "sync/atomic"
|
||||
|
||||
type BaseReader struct {
|
||||
pool *ReaderPool
|
||||
|
||||
isFinished bool
|
||||
hits uint32
|
||||
}
|
||||
|
||||
func (this *BaseReader) SetPool(pool *ReaderPool) {
|
||||
@@ -13,8 +16,11 @@ func (this *BaseReader) SetPool(pool *ReaderPool) {
|
||||
}
|
||||
|
||||
func (this *BaseReader) Finish(obj Reader) error {
|
||||
if this.isFinished {
|
||||
return nil
|
||||
}
|
||||
err := obj.RawClose()
|
||||
if err == nil && this.pool != nil && !this.isFinished {
|
||||
if err == nil && this.pool != nil {
|
||||
this.pool.Put(obj)
|
||||
}
|
||||
this.isFinished = true
|
||||
@@ -24,3 +30,7 @@ func (this *BaseReader) Finish(obj Reader) error {
|
||||
func (this *BaseReader) ResetFinish() {
|
||||
this.isFinished = false
|
||||
}
|
||||
|
||||
func (this *BaseReader) IncreaseHit() uint32 {
|
||||
return atomic.AddUint32(&this.hits, 1)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"io"
|
||||
)
|
||||
|
||||
const maxReadHits = 1 << 20
|
||||
|
||||
type ReaderPool struct {
|
||||
c chan Reader
|
||||
newFunc func(reader io.Reader) (Reader, error)
|
||||
@@ -49,6 +51,11 @@ func (this *ReaderPool) Get(parentReader io.Reader) (Reader, error) {
|
||||
}
|
||||
|
||||
func (this *ReaderPool) Put(reader Reader) {
|
||||
if reader.IncreaseHit() > maxReadHits {
|
||||
// do nothing to discard it
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case this.c <- reader:
|
||||
default:
|
||||
|
||||
@@ -4,7 +4,6 @@ package compressions
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -15,11 +14,8 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedBrotliReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
|
||||
|
||||
sharedBrotliReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) {
|
||||
return newBrotliReader(reader)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ package compressions
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -15,11 +14,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedDeflateReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
|
||||
sharedDeflateReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) {
|
||||
return newDeflateReader(reader)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ package compressions
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -15,11 +14,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedGzipReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
|
||||
sharedGzipReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) {
|
||||
return newGzipReader(reader)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ package compressions
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -15,11 +14,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedZSTDReaderPool = NewReaderPool(maxSize, func(reader io.Reader) (Reader, error) {
|
||||
sharedZSTDReaderPool = NewReaderPool(CalculatePoolSize(), func(reader io.Reader) (Reader, error) {
|
||||
return newZSTDReader(reader)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ func NewZSTDReader(reader io.Reader) (Reader, error) {
|
||||
}
|
||||
|
||||
func newZSTDReader(reader io.Reader) (Reader, error) {
|
||||
r, err := zstd.NewReader(reader)
|
||||
r, err := zstd.NewReader(reader, zstd.WithDecoderMaxWindow(256<<20))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ package compressions
|
||||
import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type ContentEncoding = string
|
||||
@@ -88,3 +90,31 @@ func WrapHTTPResponse(resp *http.Response) {
|
||||
resp.Header.Del("Content-Length")
|
||||
resp.Body = reader
|
||||
}
|
||||
|
||||
// 系统CPU线程数
|
||||
var countCPU = runtime.NumCPU()
|
||||
|
||||
// GenerateCompressLevel 根据系统资源自动生成压缩级别
|
||||
func GenerateCompressLevel(minLevel int, maxLevel int) (level int) {
|
||||
if countCPU < 16 {
|
||||
return minLevel
|
||||
}
|
||||
|
||||
if countCPU < 32 {
|
||||
return min(3, maxLevel)
|
||||
}
|
||||
|
||||
return min(5, maxLevel)
|
||||
}
|
||||
|
||||
// CalculatePoolSize 计算Pool尺寸
|
||||
func CalculatePoolSize() int {
|
||||
var maxSize = memutils.SystemMemoryGB() * 32
|
||||
if maxSize == 0 {
|
||||
maxSize = 128
|
||||
}
|
||||
if maxSize > 2048 {
|
||||
maxSize = 2048
|
||||
}
|
||||
return maxSize
|
||||
}
|
||||
|
||||
27
internal/compressions/utils_test.go
Normal file
27
internal/compressions/utils_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package compressions_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateCompressLevel(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
t.Log(compressions.GenerateCompressLevel(0, 10))
|
||||
t.Log(compressions.GenerateCompressLevel(1, 10))
|
||||
t.Log(compressions.GenerateCompressLevel(1, 4))
|
||||
|
||||
{
|
||||
var level = compressions.GenerateCompressLevel(1, 2)
|
||||
t.Log(level)
|
||||
a.IsTrue(level >= 1 && level <= 2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculatePoolSize(t *testing.T) {
|
||||
t.Log(compressions.CalculatePoolSize())
|
||||
}
|
||||
@@ -11,6 +11,7 @@ type Writer interface {
|
||||
RawClose() error
|
||||
Close() error
|
||||
Level() int
|
||||
IncreaseHit() uint32
|
||||
|
||||
SetPool(pool *WriterPool)
|
||||
ResetFinish()
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
|
||||
package compressions
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type BaseWriter struct {
|
||||
pool *WriterPool
|
||||
|
||||
isFinished bool
|
||||
|
||||
hits uint32
|
||||
}
|
||||
|
||||
func (this *BaseWriter) SetPool(pool *WriterPool) {
|
||||
@@ -13,8 +19,11 @@ func (this *BaseWriter) SetPool(pool *WriterPool) {
|
||||
}
|
||||
|
||||
func (this *BaseWriter) Finish(obj Writer) error {
|
||||
if this.isFinished {
|
||||
return nil
|
||||
}
|
||||
err := obj.RawClose()
|
||||
if err == nil && this.pool != nil && !this.isFinished {
|
||||
if err == nil && this.pool != nil {
|
||||
this.pool.Put(obj)
|
||||
}
|
||||
this.isFinished = true
|
||||
@@ -24,3 +33,7 @@ func (this *BaseWriter) Finish(obj Writer) error {
|
||||
func (this *BaseWriter) ResetFinish() {
|
||||
this.isFinished = false
|
||||
}
|
||||
|
||||
func (this *BaseWriter) IncreaseHit() uint32 {
|
||||
return atomic.AddUint32(&this.hits, 1)
|
||||
}
|
||||
|
||||
@@ -19,12 +19,8 @@ func NewBrotliWriter(writer io.Writer, level int) (Writer, error) {
|
||||
return sharedBrotliWriterPool.Get(writer, level)
|
||||
}
|
||||
|
||||
func newBrotliWriter(writer io.Writer, level int) (*BrotliWriter, error) {
|
||||
if level <= 0 {
|
||||
level = brotli.BestSpeed
|
||||
} else if level > brotli.BestCompression {
|
||||
level = brotli.BestCompression
|
||||
}
|
||||
func newBrotliWriter(writer io.Writer) (*BrotliWriter, error) {
|
||||
var level = GenerateCompressLevel(brotli.BestSpeed, brotli.BestCompression)
|
||||
return &BrotliWriter{
|
||||
writer: brotli.NewWriterOptions(writer, brotli.WriterOptions{
|
||||
Quality: level,
|
||||
|
||||
@@ -18,12 +18,8 @@ func NewDeflateWriter(writer io.Writer, level int) (Writer, error) {
|
||||
return sharedDeflateWriterPool.Get(writer, level)
|
||||
}
|
||||
|
||||
func newDeflateWriter(writer io.Writer, level int) (Writer, error) {
|
||||
if level <= 0 {
|
||||
level = flate.BestSpeed
|
||||
} else if level > flate.BestCompression {
|
||||
level = flate.BestCompression
|
||||
}
|
||||
func newDeflateWriter(writer io.Writer) (Writer, error) {
|
||||
var level = GenerateCompressLevel(flate.BestSpeed, flate.BestCompression)
|
||||
|
||||
flateWriter, err := flate.NewWriter(writer, level)
|
||||
if err != nil {
|
||||
|
||||
@@ -18,12 +18,8 @@ func NewGzipWriter(writer io.Writer, level int) (Writer, error) {
|
||||
return sharedGzipWriterPool.Get(writer, level)
|
||||
}
|
||||
|
||||
func newGzipWriter(writer io.Writer, level int) (Writer, error) {
|
||||
if level <= 0 {
|
||||
level = gzip.BestSpeed
|
||||
} else if level > gzip.BestCompression {
|
||||
level = gzip.BestCompression
|
||||
}
|
||||
func newGzipWriter(writer io.Writer) (Writer, error) {
|
||||
var level = GenerateCompressLevel(gzip.BestSpeed, gzip.BestCompression)
|
||||
|
||||
gzipWriter, err := gzip.NewWriterLevel(writer, level)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,38 +3,58 @@
|
||||
package compressions
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
const maxWriterHits = 1 << 20
|
||||
|
||||
var isBusy = false
|
||||
|
||||
func init() {
|
||||
if !teaconst.IsMain {
|
||||
return
|
||||
}
|
||||
|
||||
goman.New(func() {
|
||||
var ticker = time.NewTicker(100 * time.Millisecond)
|
||||
for range ticker.C {
|
||||
if isBusy {
|
||||
isBusy = false
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func IsBusy() bool {
|
||||
return isBusy
|
||||
}
|
||||
|
||||
type WriterPool struct {
|
||||
m map[int]chan Writer // level => chan Writer
|
||||
c chan Writer // level => chan Writer
|
||||
newFunc func(writer io.Writer, level int) (Writer, error)
|
||||
}
|
||||
|
||||
func NewWriterPool(maxSize int, maxLevel int, newFunc func(writer io.Writer, level int) (Writer, error)) *WriterPool {
|
||||
func NewWriterPool(maxSize int, newFunc func(writer io.Writer, level int) (Writer, error)) *WriterPool {
|
||||
if maxSize <= 0 {
|
||||
maxSize = 1024
|
||||
}
|
||||
|
||||
var m = map[int]chan Writer{}
|
||||
for i := 0; i <= maxLevel; i++ {
|
||||
m[i] = make(chan Writer, maxSize)
|
||||
}
|
||||
|
||||
return &WriterPool{
|
||||
m: m,
|
||||
c: make(chan Writer, maxSize),
|
||||
newFunc: newFunc,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *WriterPool) Get(parentWriter io.Writer, level int) (Writer, error) {
|
||||
c, ok := this.m[level]
|
||||
if !ok {
|
||||
c = this.m[0]
|
||||
if isBusy {
|
||||
return nil, ErrIsBusy
|
||||
}
|
||||
|
||||
select {
|
||||
case writer := <-c:
|
||||
case writer := <-this.c:
|
||||
writer.Reset(parentWriter)
|
||||
writer.ResetFinish()
|
||||
return writer, nil
|
||||
@@ -49,13 +69,14 @@ func (this *WriterPool) Get(parentWriter io.Writer, level int) (Writer, error) {
|
||||
}
|
||||
|
||||
func (this *WriterPool) Put(writer Writer) {
|
||||
var level = writer.Level()
|
||||
c, ok := this.m[level]
|
||||
if !ok {
|
||||
c = this.m[0]
|
||||
if writer.IncreaseHit() > maxWriterHits {
|
||||
// do nothing to discard it
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case c <- writer:
|
||||
case this.c <- writer:
|
||||
default:
|
||||
isBusy = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ package compressions
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/andybalholm/brotli"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -16,11 +14,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedBrotliWriterPool = NewWriterPool(maxSize, brotli.BestCompression, func(writer io.Writer, level int) (Writer, error) {
|
||||
return newBrotliWriter(writer, level)
|
||||
sharedBrotliWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) {
|
||||
return newBrotliWriter(writer)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
package compressions
|
||||
|
||||
import (
|
||||
"compress/flate"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -16,11 +14,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedDeflateWriterPool = NewWriterPool(maxSize, flate.BestCompression, func(writer io.Writer, level int) (Writer, error) {
|
||||
return newDeflateWriter(writer, level)
|
||||
sharedDeflateWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) {
|
||||
return newDeflateWriter(writer)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
package compressions
|
||||
|
||||
import (
|
||||
"compress/gzip"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -16,11 +14,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedGzipWriterPool = NewWriterPool(maxSize, gzip.BestCompression, func(writer io.Writer, level int) (Writer, error) {
|
||||
return newGzipWriter(writer, level)
|
||||
sharedGzipWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) {
|
||||
return newGzipWriter(writer)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ package compressions
|
||||
|
||||
import (
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"io"
|
||||
)
|
||||
|
||||
@@ -16,11 +14,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
|
||||
var maxSize = utils.SystemMemoryGB() * 256
|
||||
if maxSize == 0 {
|
||||
maxSize = 256
|
||||
}
|
||||
sharedZSTDWriterPool = NewWriterPool(maxSize, int(zstd.SpeedBestCompression), func(writer io.Writer, level int) (Writer, error) {
|
||||
return newZSTDWriter(writer, level)
|
||||
sharedZSTDWriterPool = NewWriterPool(CalculatePoolSize(), func(writer io.Writer, level int) (Writer, error) {
|
||||
return newZSTDWriter(writer)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,21 +18,18 @@ func NewZSTDWriter(writer io.Writer, level int) (Writer, error) {
|
||||
return sharedZSTDWriterPool.Get(writer, level)
|
||||
}
|
||||
|
||||
func newZSTDWriter(writer io.Writer, level int) (Writer, error) {
|
||||
if level < 0 {
|
||||
level = 0
|
||||
}
|
||||
func newZSTDWriter(writer io.Writer) (Writer, error) {
|
||||
var level = 1
|
||||
var zstdLevel = zstd.SpeedFastest
|
||||
|
||||
var zstdLevel = zstd.EncoderLevelFromZstd(level)
|
||||
|
||||
zstdWriter, err := zstd.NewWriter(writer, zstd.WithEncoderLevel(zstdLevel))
|
||||
zstdWriter, err := zstd.NewWriter(writer, zstd.WithEncoderLevel(zstdLevel), zstd.WithWindowSize(16<<10), zstd.WithLowerEncoderMem(true))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ZSTDWriter{
|
||||
writer: zstdWriter,
|
||||
level: level,
|
||||
writer: zstdWriter,
|
||||
level: level,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
67
internal/conns/map_test_test.go
Normal file
67
internal/conns/map_test_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package conns_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"net"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type testConn struct {
|
||||
net.Conn
|
||||
|
||||
addr net.Addr
|
||||
}
|
||||
|
||||
func (this *testConn) Read(b []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
func (this *testConn) Write(b []byte) (n int, err error) {
|
||||
return
|
||||
}
|
||||
func (this *testConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
func (this *testConn) LocalAddr() net.Addr {
|
||||
return &net.TCPAddr{
|
||||
IP: net.ParseIP(testutils.RandIP()),
|
||||
Port: 1234,
|
||||
}
|
||||
}
|
||||
func (this *testConn) RemoteAddr() net.Addr {
|
||||
if this.addr != nil {
|
||||
return this.addr
|
||||
}
|
||||
this.addr = &net.TCPAddr{
|
||||
IP: net.ParseIP(testutils.RandIP()),
|
||||
Port: 1234,
|
||||
}
|
||||
return this.addr
|
||||
}
|
||||
func (this *testConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
func (this *testConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
func (this *testConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func BenchmarkMap_Add(b *testing.B) {
|
||||
runtime.GOMAXPROCS(512)
|
||||
|
||||
var m = conns.NewMap()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var conn = &testConn{}
|
||||
m.Add(conn)
|
||||
m.Remove(conn)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.3.4"
|
||||
Version = "1.3.6"
|
||||
|
||||
ProductName = "Edge Node"
|
||||
ProcessName = "edge-node"
|
||||
@@ -17,5 +17,5 @@ const (
|
||||
AccessLogSockName = "edge-node.accesslog"
|
||||
CacheGarbageSockName = "edge-node.cache.garbage"
|
||||
|
||||
EnableKVCacheStore = false // determine store cache keys in KVStore or sqlite
|
||||
EnableKVCacheStore = true // determine store cache keys in KVStore or sqlite
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ package firewalls
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/conns"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
@@ -386,12 +386,12 @@ func (this *NFTablesFirewall) DropSourceIP(ip string, timeoutSeconds int, async
|
||||
// 再次尝试关闭连接
|
||||
defer conns.SharedMap.CloseIPConns(ip)
|
||||
|
||||
var ipLong = configutils.IPString2Long(ip)
|
||||
if strings.Contains(ip, ":") { // ipv6
|
||||
if len(this.denyIPv6Sets) == 0 {
|
||||
return errors.New("ipv6 ip set not found")
|
||||
}
|
||||
return this.denyIPv6Sets[ipLong%uint64(len(this.denyIPv6Sets))].AddElement(data.To16(), &nftables.ElementOptions{
|
||||
var setIndex = iputils.ParseIP(ip).Mod(len(this.denyIPv6Sets))
|
||||
return this.denyIPv6Sets[setIndex].AddElement(data.To16(), &nftables.ElementOptions{
|
||||
Timeout: time.Duration(timeoutSeconds) * time.Second,
|
||||
}, false)
|
||||
}
|
||||
@@ -400,7 +400,8 @@ func (this *NFTablesFirewall) DropSourceIP(ip string, timeoutSeconds int, async
|
||||
if len(this.denyIPv4Sets) == 0 {
|
||||
return errors.New("ipv4 ip set not found")
|
||||
}
|
||||
return this.denyIPv4Sets[ipLong%uint64(len(this.denyIPv4Sets))].AddElement(data.To4(), &nftables.ElementOptions{
|
||||
var setIndex = iputils.ParseIP(ip).Mod(len(this.denyIPv4Sets))
|
||||
return this.denyIPv4Sets[setIndex].AddElement(data.To4(), &nftables.ElementOptions{
|
||||
Timeout: time.Duration(timeoutSeconds) * time.Second,
|
||||
}, false)
|
||||
}
|
||||
@@ -412,10 +413,10 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
|
||||
return errors.New("invalid ip '" + ip + "'")
|
||||
}
|
||||
|
||||
var ipLong = configutils.IPString2Long(ip)
|
||||
if strings.Contains(ip, ":") { // ipv6
|
||||
var setIndex = iputils.ParseIP(ip).Mod(len(this.denyIPv6Sets))
|
||||
if len(this.denyIPv6Sets) > 0 {
|
||||
err := this.denyIPv6Sets[ipLong%uint64(len(this.denyIPv6Sets))].DeleteElement(data.To16())
|
||||
err := this.denyIPv6Sets[setIndex].DeleteElement(data.To16())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -433,7 +434,8 @@ func (this *NFTablesFirewall) RemoveSourceIP(ip string) error {
|
||||
|
||||
// ipv4
|
||||
if len(this.denyIPv4Sets) > 0 {
|
||||
err := this.denyIPv4Sets[ipLong%uint64(len(this.denyIPv4Sets))].DeleteElement(data.To4())
|
||||
var setIndex = iputils.ParseIP(ip).Mod(len(this.denyIPv4Sets))
|
||||
err := this.denyIPv4Sets[setIndex].DeleteElement(data.To4())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
)
|
||||
|
||||
@@ -14,36 +15,37 @@ const (
|
||||
|
||||
// IPItem IP条目
|
||||
type IPItem struct {
|
||||
Type string `json:"type"`
|
||||
Id uint64 `json:"id"`
|
||||
IPFrom uint64 `json:"ipFrom"`
|
||||
IPTo uint64 `json:"ipTo"`
|
||||
Type string `json:"type"`
|
||||
Id uint64 `json:"id"`
|
||||
IPFrom []byte `json:"ipFrom"`
|
||||
IPTo []byte `json:"ipTo"`
|
||||
|
||||
ExpiredAt int64 `json:"expiredAt"`
|
||||
EventLevel string `json:"eventLevel"`
|
||||
}
|
||||
|
||||
// Contains 检查是否包含某个IP
|
||||
func (this *IPItem) Contains(ip uint64) bool {
|
||||
func (this *IPItem) Contains(ipBytes []byte) bool {
|
||||
switch this.Type {
|
||||
case IPItemTypeIPv4:
|
||||
return this.containsIPv4(ip)
|
||||
return this.containsIP(ipBytes)
|
||||
case IPItemTypeIPv6:
|
||||
return this.containsIPv6(ip)
|
||||
return this.containsIP(ipBytes)
|
||||
case IPItemTypeAll:
|
||||
return this.containsAll()
|
||||
default:
|
||||
return this.containsIPv4(ip)
|
||||
return this.containsIP(ipBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否包含某个IPv4
|
||||
func (this *IPItem) containsIPv4(ip uint64) bool {
|
||||
if this.IPTo == 0 {
|
||||
if this.IPFrom != ip {
|
||||
// 检查是否包含某个
|
||||
func (this *IPItem) containsIP(ipBytes []byte) bool {
|
||||
if IsZero(this.IPTo) {
|
||||
if iputils.CompareBytes(this.IPFrom, ipBytes) != 0 {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
if this.IPFrom > ip || this.IPTo < ip {
|
||||
if iputils.CompareBytes(this.IPFrom, ipBytes) > 0 || iputils.CompareBytes(this.IPTo, ipBytes) < 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -53,17 +55,6 @@ func (this *IPItem) containsIPv4(ip uint64) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查是否包含某个IPv6
|
||||
func (this *IPItem) containsIPv6(ip uint64) bool {
|
||||
if this.IPFrom != ip {
|
||||
return false
|
||||
}
|
||||
if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// 检查是否包所有IP
|
||||
func (this *IPItem) containsAll() bool {
|
||||
if this.ExpiredAt > 0 && this.ExpiredAt < fasttime.Now().Unix() {
|
||||
|
||||
@@ -1,98 +1,104 @@
|
||||
package iplibrary
|
||||
package iplibrary_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIPItem_Contains(t *testing.T) {
|
||||
a := assert.NewAssertion(t)
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
{
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.100"),
|
||||
IPTo: 0,
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.100"),
|
||||
IPTo: nil,
|
||||
ExpiredAt: 0,
|
||||
}
|
||||
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.100")))
|
||||
a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.100")))
|
||||
}
|
||||
|
||||
{
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.100"),
|
||||
IPTo: 0,
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.100"),
|
||||
IPTo: nil,
|
||||
ExpiredAt: time.Now().Unix() + 1,
|
||||
}
|
||||
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.100")))
|
||||
a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.100")))
|
||||
}
|
||||
|
||||
{
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.100"),
|
||||
IPTo: 0,
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.100"),
|
||||
IPTo: nil,
|
||||
ExpiredAt: time.Now().Unix() - 1,
|
||||
}
|
||||
a.IsFalse(item.Contains(utils.IP2Long("192.168.1.100")))
|
||||
a.IsFalse(item.Contains(iputils.ToBytes("192.168.1.100")))
|
||||
}
|
||||
{
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.100"),
|
||||
IPTo: 0,
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.100"),
|
||||
IPTo: nil,
|
||||
ExpiredAt: 0,
|
||||
}
|
||||
a.IsFalse(item.Contains(utils.IP2Long("192.168.1.101")))
|
||||
a.IsFalse(item.Contains(iputils.ToBytes("192.168.1.101")))
|
||||
}
|
||||
|
||||
{
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPTo: utils.IP2Long("192.168.1.101"),
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
IPTo: iputils.ToBytes("192.168.1.101"),
|
||||
ExpiredAt: 0,
|
||||
}
|
||||
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.100")))
|
||||
a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.100")))
|
||||
}
|
||||
|
||||
{
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPTo: utils.IP2Long("192.168.1.100"),
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
IPTo: iputils.ToBytes("192.168.1.100"),
|
||||
ExpiredAt: 0,
|
||||
}
|
||||
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.100")))
|
||||
a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.100")))
|
||||
}
|
||||
|
||||
{
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPTo: utils.IP2Long("192.168.1.101"),
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
IPTo: iputils.ToBytes("192.168.1.101"),
|
||||
ExpiredAt: 0,
|
||||
}
|
||||
a.IsTrue(item.Contains(utils.IP2Long("192.168.1.1")))
|
||||
a.IsTrue(item.Contains(iputils.ToBytes("192.168.1.1")))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPItem_Memory(t *testing.T) {
|
||||
var isSingleTest = testutils.IsSingleTesting()
|
||||
|
||||
var list = NewIPList()
|
||||
var list = iplibrary.NewIPList()
|
||||
var count = 100
|
||||
if isSingleTest {
|
||||
count = 2_000_000
|
||||
}
|
||||
for i := 0; i < count; i++ {
|
||||
list.Add(&IPItem{
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Type: "ip",
|
||||
Id: uint64(i),
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPTo: 0,
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
IPTo: nil,
|
||||
ExpiredAt: time.Now().Unix(),
|
||||
EventLevel: "",
|
||||
})
|
||||
}
|
||||
|
||||
runtime.GC()
|
||||
|
||||
t.Log("waiting")
|
||||
if isSingleTest {
|
||||
time.Sleep(10 * time.Second)
|
||||
@@ -102,15 +108,18 @@ func TestIPItem_Memory(t *testing.T) {
|
||||
func BenchmarkIPItem_Contains(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
item := &IPItem{
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPTo: utils.IP2Long("192.168.1.101"),
|
||||
var item = &iplibrary.IPItem{
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
IPTo: iputils.ToBytes("192.168.1.101"),
|
||||
ExpiredAt: 0,
|
||||
}
|
||||
ip := utils.IP2Long("192.168.1.1")
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < 10_000; j++ {
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
var ip = iputils.ToBytes("192.168.1." + strconv.Itoa(rand.Int()%255))
|
||||
item.Contains(ip)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/expires"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"sort"
|
||||
@@ -12,26 +12,32 @@ var GlobalBlackIPList = NewIPList()
|
||||
var GlobalWhiteIPList = NewIPList()
|
||||
|
||||
// IPList IP名单
|
||||
// TODO IP名单可以分片关闭,这样让每一片的数据量减少,查询更快
|
||||
// TODO 对ipMap进行分区
|
||||
type IPList struct {
|
||||
isDeleted bool
|
||||
|
||||
itemsMap map[uint64]*IPItem // id => item
|
||||
sortedItems []*IPItem
|
||||
itemsMap map[uint64]*IPItem // id => item
|
||||
|
||||
sortedRangeItems []*IPItem
|
||||
ipMap map[string]*IPItem // ipFrom => IPItem
|
||||
bufferItemsMap map[uint64]*IPItem // id => IPItem
|
||||
|
||||
allItemsMap map[uint64]*IPItem // id => item
|
||||
|
||||
expireList *expires.List
|
||||
|
||||
locker sync.RWMutex
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func NewIPList() *IPList {
|
||||
list := &IPList{
|
||||
itemsMap: map[uint64]*IPItem{},
|
||||
allItemsMap: map[uint64]*IPItem{},
|
||||
var list = &IPList{
|
||||
itemsMap: map[uint64]*IPItem{},
|
||||
bufferItemsMap: map[uint64]*IPItem{},
|
||||
allItemsMap: map[uint64]*IPItem{},
|
||||
ipMap: map[string]*IPItem{},
|
||||
}
|
||||
|
||||
expireList := expires.NewList()
|
||||
var expireList = expires.NewList()
|
||||
expireList.OnGC(func(itemId uint64) {
|
||||
list.Delete(itemId)
|
||||
})
|
||||
@@ -44,64 +50,66 @@ func (this *IPList) Add(item *IPItem) {
|
||||
return
|
||||
}
|
||||
|
||||
this.addItem(item, true)
|
||||
this.addItem(item, true, true)
|
||||
}
|
||||
|
||||
// AddDelay 延迟添加,需要手工调用Sort()函数
|
||||
func (this *IPList) AddDelay(item *IPItem) {
|
||||
if this.isDeleted {
|
||||
if this.isDeleted || item == nil {
|
||||
return
|
||||
}
|
||||
|
||||
this.addItem(item, false)
|
||||
if !IsZero(item.IPTo) {
|
||||
this.mu.Lock()
|
||||
this.bufferItemsMap[item.Id] = item
|
||||
this.mu.Unlock()
|
||||
} else {
|
||||
this.addItem(item, true, true)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *IPList) Sort() {
|
||||
this.locker.Lock()
|
||||
this.sortItems()
|
||||
this.locker.Unlock()
|
||||
this.mu.Lock()
|
||||
this.sortRangeItems(false)
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
func (this *IPList) Delete(itemId uint64) {
|
||||
this.locker.Lock()
|
||||
this.mu.Lock()
|
||||
this.deleteItem(itemId)
|
||||
this.locker.Unlock()
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
// Contains 判断是否包含某个IP
|
||||
func (this *IPList) Contains(ip uint64) bool {
|
||||
func (this *IPList) Contains(ipBytes []byte) bool {
|
||||
if this.isDeleted {
|
||||
return false
|
||||
}
|
||||
|
||||
this.locker.RLock()
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
|
||||
if len(this.allItemsMap) > 0 {
|
||||
this.locker.RUnlock()
|
||||
return true
|
||||
}
|
||||
|
||||
var item = this.lookupIP(ip)
|
||||
|
||||
this.locker.RUnlock()
|
||||
|
||||
var item = this.lookupIP(ipBytes)
|
||||
return item != nil
|
||||
}
|
||||
|
||||
// ContainsExpires 判断是否包含某个IP
|
||||
func (this *IPList) ContainsExpires(ip uint64) (expiresAt int64, ok bool) {
|
||||
func (this *IPList) ContainsExpires(ipBytes []byte) (expiresAt int64, ok bool) {
|
||||
if this.isDeleted {
|
||||
return
|
||||
}
|
||||
|
||||
this.locker.RLock()
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
|
||||
if len(this.allItemsMap) > 0 {
|
||||
this.locker.RUnlock()
|
||||
return 0, true
|
||||
}
|
||||
|
||||
var item = this.lookupIP(ip)
|
||||
|
||||
this.locker.RUnlock()
|
||||
var item = this.lookupIP(ipBytes)
|
||||
|
||||
if item == nil {
|
||||
return
|
||||
@@ -119,7 +127,10 @@ func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found b
|
||||
if len(ipStrings) == 0 {
|
||||
return
|
||||
}
|
||||
this.locker.RLock()
|
||||
|
||||
this.mu.RLock()
|
||||
defer this.mu.RUnlock()
|
||||
|
||||
if len(this.allItemsMap) > 0 {
|
||||
for _, allItem := range this.allItemsMap {
|
||||
item = allItem
|
||||
@@ -127,7 +138,6 @@ func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found b
|
||||
}
|
||||
|
||||
if item != nil {
|
||||
this.locker.RUnlock()
|
||||
found = true
|
||||
return
|
||||
}
|
||||
@@ -136,14 +146,12 @@ func (this *IPList) ContainsIPStrings(ipStrings []string) (item *IPItem, found b
|
||||
if len(ipString) == 0 {
|
||||
continue
|
||||
}
|
||||
item = this.lookupIP(utils.IP2Long(ipString))
|
||||
item = this.lookupIP(iputils.ToBytes(ipString))
|
||||
if item != nil {
|
||||
this.locker.RUnlock()
|
||||
found = true
|
||||
return
|
||||
}
|
||||
}
|
||||
this.locker.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -151,7 +159,27 @@ func (this *IPList) SetDeleted() {
|
||||
this.isDeleted = true
|
||||
}
|
||||
|
||||
func (this *IPList) addItem(item *IPItem, sortable bool) {
|
||||
func (this *IPList) SortedRangeItems() []*IPItem {
|
||||
return this.sortedRangeItems
|
||||
}
|
||||
|
||||
func (this *IPList) IPMap() map[string]*IPItem {
|
||||
return this.ipMap
|
||||
}
|
||||
|
||||
func (this *IPList) ItemsMap() map[uint64]*IPItem {
|
||||
return this.itemsMap
|
||||
}
|
||||
|
||||
func (this *IPList) AllItemsMap() map[uint64]*IPItem {
|
||||
return this.allItemsMap
|
||||
}
|
||||
|
||||
func (this *IPList) BufferItemsMap() map[uint64]*IPItem {
|
||||
return this.bufferItemsMap
|
||||
}
|
||||
|
||||
func (this *IPList) addItem(item *IPItem, lock bool, sortable bool) {
|
||||
if item == nil {
|
||||
return
|
||||
}
|
||||
@@ -160,20 +188,29 @@ func (this *IPList) addItem(item *IPItem, sortable bool) {
|
||||
return
|
||||
}
|
||||
|
||||
if item.IPFrom == 0 && item.IPTo == 0 {
|
||||
var shouldSort bool
|
||||
|
||||
if iputils.CompareBytes(item.IPFrom, item.IPTo) == 0 {
|
||||
item.IPTo = nil
|
||||
}
|
||||
|
||||
if IsZero(item.IPFrom) && IsZero(item.IPTo) {
|
||||
if item.Type != IPItemTypeAll {
|
||||
return
|
||||
}
|
||||
} else if item.IPTo > 0 {
|
||||
if item.IPFrom > item.IPTo {
|
||||
} else if !IsZero(item.IPTo) {
|
||||
if iputils.CompareBytes(item.IPFrom, item.IPTo) > 0 {
|
||||
item.IPFrom, item.IPTo = item.IPTo, item.IPFrom
|
||||
} else if item.IPFrom == 0 {
|
||||
} else if IsZero(item.IPFrom) {
|
||||
item.IPFrom = item.IPTo
|
||||
item.IPTo = 0
|
||||
item.IPTo = nil
|
||||
}
|
||||
}
|
||||
|
||||
this.locker.Lock()
|
||||
if lock {
|
||||
this.mu.Lock()
|
||||
defer this.mu.Unlock()
|
||||
}
|
||||
|
||||
// 是否已经存在
|
||||
_, ok := this.itemsMap[item.Id]
|
||||
@@ -184,51 +221,72 @@ func (this *IPList) addItem(item *IPItem, sortable bool) {
|
||||
this.itemsMap[item.Id] = item
|
||||
|
||||
// 展开
|
||||
if item.IPFrom > 0 {
|
||||
this.sortedItems = append(this.sortedItems, item)
|
||||
} else {
|
||||
if item.Type == IPItemTypeAll {
|
||||
this.allItemsMap[item.Id] = item
|
||||
} else if !IsZero(item.IPFrom) {
|
||||
if !IsZero(item.IPTo) {
|
||||
this.sortedRangeItems = append(this.sortedRangeItems, item)
|
||||
shouldSort = true
|
||||
} else {
|
||||
this.ipMap[ToHex(item.IPFrom)] = item
|
||||
}
|
||||
}
|
||||
|
||||
if item.ExpiredAt > 0 {
|
||||
this.expireList.Add(item.Id, item.ExpiredAt)
|
||||
}
|
||||
|
||||
if sortable {
|
||||
this.sortItems()
|
||||
if shouldSort && sortable {
|
||||
this.sortRangeItems(true)
|
||||
}
|
||||
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
// 对列表进行排序
|
||||
func (this *IPList) sortItems() {
|
||||
sort.Slice(this.sortedItems, func(i, j int) bool {
|
||||
var item1 = this.sortedItems[i]
|
||||
var item2 = this.sortedItems[j]
|
||||
if item1.IPFrom == item2.IPFrom {
|
||||
return item1.IPTo < item2.IPTo
|
||||
func (this *IPList) sortRangeItems(force bool) {
|
||||
if len(this.bufferItemsMap) > 0 {
|
||||
for _, item := range this.bufferItemsMap {
|
||||
this.addItem(item, false, false)
|
||||
}
|
||||
return item1.IPFrom < item2.IPFrom
|
||||
})
|
||||
this.bufferItemsMap = map[uint64]*IPItem{}
|
||||
force = true
|
||||
}
|
||||
|
||||
if force {
|
||||
sort.Slice(this.sortedRangeItems, func(i, j int) bool {
|
||||
var item1 = this.sortedRangeItems[i]
|
||||
var item2 = this.sortedRangeItems[j]
|
||||
if iputils.CompareBytes(item1.IPFrom, item2.IPFrom) == 0 {
|
||||
return iputils.CompareBytes(item1.IPTo, item2.IPTo) < 0
|
||||
}
|
||||
return iputils.CompareBytes(item1.IPFrom, item2.IPFrom) < 0
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 不加锁的情况下查找Item
|
||||
func (this *IPList) lookupIP(ip uint64) *IPItem {
|
||||
if len(this.sortedItems) == 0 {
|
||||
func (this *IPList) lookupIP(ipBytes []byte) *IPItem {
|
||||
{
|
||||
item, ok := this.ipMap[ToHex(ipBytes)]
|
||||
if ok && (item.ExpiredAt == 0 || item.ExpiredAt > fasttime.Now().Unix()) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
if len(this.sortedRangeItems) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var count = len(this.sortedItems)
|
||||
var count = len(this.sortedRangeItems)
|
||||
var resultIndex = -1
|
||||
sort.Search(count, func(i int) bool {
|
||||
var item = this.sortedItems[i]
|
||||
if item.IPFrom < ip {
|
||||
if item.IPTo >= ip {
|
||||
var item = this.sortedRangeItems[i]
|
||||
var cmp = iputils.CompareBytes(item.IPFrom, ipBytes)
|
||||
if cmp < 0 {
|
||||
if iputils.CompareBytes(item.IPTo, ipBytes) >= 0 {
|
||||
resultIndex = i
|
||||
}
|
||||
return false
|
||||
} else if item.IPFrom == ip {
|
||||
} else if cmp == 0 {
|
||||
resultIndex = i
|
||||
return false
|
||||
}
|
||||
@@ -239,36 +297,54 @@ func (this *IPList) lookupIP(ip uint64) *IPItem {
|
||||
return nil
|
||||
}
|
||||
|
||||
return this.sortedItems[resultIndex]
|
||||
var item = this.sortedRangeItems[resultIndex]
|
||||
if item.ExpiredAt == 0 || item.ExpiredAt > fasttime.Now().Unix() {
|
||||
return item
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 在不加锁的情况下删除某个Item
|
||||
// 将会被别的方法引用,切记不能加锁
|
||||
func (this *IPList) deleteItem(itemId uint64) {
|
||||
_, ok := this.itemsMap[itemId]
|
||||
if !ok {
|
||||
// 从buffer中删除
|
||||
delete(this.bufferItemsMap, itemId)
|
||||
|
||||
// 从all items中删除
|
||||
_, ok := this.allItemsMap[itemId]
|
||||
if ok {
|
||||
delete(this.allItemsMap, itemId)
|
||||
}
|
||||
|
||||
// 检查是否存在
|
||||
oldItem, existsOld := this.itemsMap[itemId]
|
||||
if !existsOld {
|
||||
return
|
||||
}
|
||||
|
||||
// 从ipMap中删除
|
||||
if IsZero(oldItem.IPTo) {
|
||||
var ipHex = ToHex(oldItem.IPFrom)
|
||||
ipItem, ok := this.ipMap[ipHex]
|
||||
if ok && ipItem.Id == itemId {
|
||||
delete(this.ipMap, ipHex)
|
||||
}
|
||||
}
|
||||
|
||||
delete(this.itemsMap, itemId)
|
||||
|
||||
// 是否为All Item
|
||||
_, ok = this.allItemsMap[itemId]
|
||||
if ok {
|
||||
delete(this.allItemsMap, itemId)
|
||||
return
|
||||
}
|
||||
|
||||
// 删除排序中的Item
|
||||
var index = -1
|
||||
for itemIndex, item := range this.sortedItems {
|
||||
if item.Id == itemId {
|
||||
index = itemIndex
|
||||
break
|
||||
if !IsZero(oldItem.IPTo) {
|
||||
var index = -1
|
||||
for itemIndex, item := range this.sortedRangeItems {
|
||||
if item.Id == itemId {
|
||||
index = itemIndex
|
||||
break
|
||||
}
|
||||
}
|
||||
if index >= 0 {
|
||||
copy(this.sortedRangeItems[index:], this.sortedRangeItems[index+1:])
|
||||
this.sortedRangeItems = this.sortedRangeItems[:len(this.sortedRangeItems)-1]
|
||||
}
|
||||
}
|
||||
if index >= 0 {
|
||||
copy(this.sortedItems[index:], this.sortedItems[index+1:])
|
||||
this.sortedItems = this.sortedItems[:len(this.sortedItems)-1]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,305 +1,13 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
import "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
|
||||
type IPListDB struct {
|
||||
db *dbs.DB
|
||||
|
||||
itemTableName string
|
||||
versionTableName string
|
||||
|
||||
deleteExpiredItemsStmt *dbs.Stmt
|
||||
deleteItemStmt *dbs.Stmt
|
||||
insertItemStmt *dbs.Stmt
|
||||
selectItemsStmt *dbs.Stmt
|
||||
selectMaxItemVersionStmt *dbs.Stmt
|
||||
|
||||
selectVersionStmt *dbs.Stmt
|
||||
updateVersionStmt *dbs.Stmt
|
||||
|
||||
cleanTicker *time.Ticker
|
||||
|
||||
dir string
|
||||
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
func NewIPListDB() (*IPListDB, error) {
|
||||
var db = &IPListDB{
|
||||
itemTableName: "ipItems",
|
||||
versionTableName: "versions",
|
||||
dir: filepath.Clean(Tea.Root + "/data"),
|
||||
cleanTicker: time.NewTicker(24 * time.Hour),
|
||||
}
|
||||
err := db.init()
|
||||
return db, err
|
||||
}
|
||||
|
||||
func (this *IPListDB) init() error {
|
||||
// 检查目录是否存在
|
||||
_, err := os.Stat(this.dir)
|
||||
if err != nil {
|
||||
err = os.MkdirAll(this.dir, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remotelogs.Println("IP_LIST_DB", "create data dir '"+this.dir+"'")
|
||||
}
|
||||
|
||||
var path = this.dir + "/ip_list.db"
|
||||
|
||||
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
//_, err = db.Exec("VACUUM")
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
this.db = db
|
||||
|
||||
// 恢复数据库
|
||||
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
|
||||
if len(recoverEnv) > 0 {
|
||||
for _, indexName := range []string{"ip_list_itemId", "ip_list_expiredAt"} {
|
||||
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化数据库
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"listId" integer DEFAULT 0,
|
||||
"listType" varchar(32),
|
||||
"isGlobal" integer(1) DEFAULT 0,
|
||||
"type" varchar(16),
|
||||
"itemId" integer DEFAULT 0,
|
||||
"ipFrom" varchar(64) DEFAULT 0,
|
||||
"ipTo" varchar(64) DEFAULT 0,
|
||||
"expiredAt" integer DEFAULT 0,
|
||||
"eventLevel" varchar(32),
|
||||
"isDeleted" integer(1) DEFAULT 0,
|
||||
"version" integer DEFAULT 0,
|
||||
"nodeId" integer DEFAULT 0,
|
||||
"serverId" integer DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "ip_list_itemId"
|
||||
ON "` + this.itemTableName + `" (
|
||||
"itemId" ASC
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "ip_list_expiredAt"
|
||||
ON "` + this.itemTableName + `" (
|
||||
"expiredAt" ASC
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.versionTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"version" integer DEFAULT 0
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化SQL语句
|
||||
this.deleteExpiredItemsStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "expiredAt">0 AND "expiredAt"<?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.deleteItemStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "itemId"=?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.insertItemStmt, err = this.db.Prepare(`INSERT INTO "` + this.itemTableName + `" ("listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectItemsStmt, err = this.db.Prepare(`SELECT "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId" FROM "` + this.itemTableName + `" WHERE isDeleted=0 ORDER BY "version" ASC, "itemId" ASC LIMIT ?, ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectMaxItemVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.itemTableName + `" ORDER BY "id" DESC LIMIT 1`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.versionTableName + `" LIMIT 1`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.updateVersionStmt, err = this.db.Prepare(`REPLACE INTO "` + this.versionTableName + `" ("id", "version") VALUES (1, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.db = db
|
||||
|
||||
goman.New(func() {
|
||||
events.OnClose(func() {
|
||||
_ = this.Close()
|
||||
this.cleanTicker.Stop()
|
||||
})
|
||||
|
||||
for range this.cleanTicker.C {
|
||||
err := this.DeleteExpiredItems()
|
||||
if err != nil {
|
||||
remotelogs.Error("IP_LIST_DB", "clean expired items failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteExpiredItems 删除过期的条目
|
||||
func (this *IPListDB) DeleteExpiredItems() error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := this.deleteExpiredItemsStmt.Exec(time.Now().Unix() - 7*86400)
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *IPListDB) AddItem(item *pb.IPItem) error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := this.deleteItemStmt.Exec(item.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果是删除,则不再创建新记录
|
||||
if item.IsDeleted {
|
||||
return this.UpdateMaxVersion(item.Version)
|
||||
}
|
||||
|
||||
_, err = this.insertItemStmt.Exec(item.ListId, item.ListType, item.IsGlobal, item.Type, item.Id, item.IpFrom, item.IpTo, item.ExpiredAt, item.EventLevel, item.IsDeleted, item.Version, item.NodeId, item.ServerId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return this.UpdateMaxVersion(item.Version)
|
||||
}
|
||||
|
||||
func (this *IPListDB) ReadItems(offset int64, size int64) (items []*pb.IPItem, err error) {
|
||||
if this.isClosed {
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := this.selectItemsStmt.Query(offset, size)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
// "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId"
|
||||
var pbItem = &pb.IPItem{}
|
||||
err = rows.Scan(&pbItem.ListId, &pbItem.ListType, &pbItem.IsGlobal, &pbItem.Type, &pbItem.Id, &pbItem.IpFrom, &pbItem.IpTo, &pbItem.ExpiredAt, &pbItem.EventLevel, &pbItem.IsDeleted, &pbItem.Version, &pbItem.NodeId, &pbItem.ServerId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, pbItem)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ReadMaxVersion 读取当前最大版本号
|
||||
func (this *IPListDB) ReadMaxVersion() int64 {
|
||||
if this.isClosed {
|
||||
return 0
|
||||
}
|
||||
|
||||
// from version table
|
||||
{
|
||||
var row = this.selectVersionStmt.QueryRow()
|
||||
if row == nil {
|
||||
return 0
|
||||
}
|
||||
var version int64
|
||||
err := row.Scan(&version)
|
||||
if err == nil {
|
||||
return version
|
||||
}
|
||||
}
|
||||
|
||||
// from items table
|
||||
{
|
||||
var row = this.selectMaxItemVersionStmt.QueryRow()
|
||||
if row == nil {
|
||||
return 0
|
||||
}
|
||||
var version int64
|
||||
err := row.Scan(&version)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateMaxVersion 修改版本号
|
||||
func (this *IPListDB) UpdateMaxVersion(version int64) error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := this.updateVersionStmt.Exec(version)
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *IPListDB) Close() error {
|
||||
this.isClosed = true
|
||||
|
||||
if this.db != nil {
|
||||
for _, stmt := range []*dbs.Stmt{
|
||||
this.deleteExpiredItemsStmt,
|
||||
this.deleteItemStmt,
|
||||
this.insertItemStmt,
|
||||
this.selectItemsStmt,
|
||||
this.selectMaxItemVersionStmt, // ipItems table
|
||||
|
||||
this.selectVersionStmt, // versions table
|
||||
this.updateVersionStmt,
|
||||
} {
|
||||
if stmt != nil {
|
||||
_ = stmt.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return this.db.Close()
|
||||
}
|
||||
return nil
|
||||
type IPListDB interface {
|
||||
Name() string
|
||||
DeleteExpiredItems() error
|
||||
ReadMaxVersion() (int64, error)
|
||||
ReadItems(offset int64, size int64) (items []*pb.IPItem, goNext bool, err error)
|
||||
AddItem(item *pb.IPItem) error
|
||||
}
|
||||
|
||||
233
internal/iplibrary/ip_list_kv.go
Normal file
233
internal/iplibrary/ip_list_kv.go
Normal file
@@ -0,0 +1,233 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/kvstore"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type KVIPList struct {
|
||||
ipTable *kvstore.Table[*pb.IPItem]
|
||||
versionsTable *kvstore.Table[int64]
|
||||
|
||||
encoder *IPItemEncoder[*pb.IPItem]
|
||||
|
||||
cleanTicker *time.Ticker
|
||||
|
||||
isClosed bool
|
||||
|
||||
offsetItemKey string
|
||||
}
|
||||
|
||||
func NewKVIPList() (*KVIPList, error) {
|
||||
var db = &KVIPList{
|
||||
cleanTicker: time.NewTicker(24 * time.Hour),
|
||||
encoder: &IPItemEncoder[*pb.IPItem]{},
|
||||
}
|
||||
err := db.init()
|
||||
return db, err
|
||||
}
|
||||
|
||||
func (this *KVIPList) init() error {
|
||||
store, storeErr := kvstore.DefaultStore()
|
||||
if storeErr != nil {
|
||||
return storeErr
|
||||
}
|
||||
db, dbErr := store.NewDB("ip_list")
|
||||
if dbErr != nil {
|
||||
return dbErr
|
||||
}
|
||||
|
||||
{
|
||||
table, err := kvstore.NewTable[*pb.IPItem]("ip_items", this.encoder)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.ipTable = table
|
||||
|
||||
err = table.AddFields("expiresAt")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db.AddTable(table)
|
||||
}
|
||||
|
||||
{
|
||||
table, err := kvstore.NewTable[int64]("versions", kvstore.NewIntValueEncoder[int64]())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.versionsTable = table
|
||||
db.AddTable(table)
|
||||
}
|
||||
|
||||
goman.New(func() {
|
||||
events.OnClose(func() {
|
||||
_ = this.Close()
|
||||
this.cleanTicker.Stop()
|
||||
})
|
||||
|
||||
idles.RunTicker(this.cleanTicker, func() {
|
||||
if this.isClosed {
|
||||
return
|
||||
}
|
||||
deleteErr := this.DeleteExpiredItems()
|
||||
if deleteErr != nil {
|
||||
remotelogs.Error("IP_LIST_DB", "clean expired items failed: "+deleteErr.Error())
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name 数据库名称代号
|
||||
func (this *KVIPList) Name() string {
|
||||
return "kvstore"
|
||||
}
|
||||
|
||||
// DeleteExpiredItems 删除过期的条目
|
||||
func (this *KVIPList) DeleteExpiredItems() error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
for {
|
||||
var found bool
|
||||
var currentTime = fasttime.Now().Unix()
|
||||
err := this.ipTable.
|
||||
Query().
|
||||
FieldAsc("expiresAt").
|
||||
ForUpdate().
|
||||
Limit(1000).
|
||||
FindAll(func(tx *kvstore.Tx[*pb.IPItem], item kvstore.Item[*pb.IPItem]) (goNext bool, err error) {
|
||||
if !item.Value.IsDeleted && item.Value.ExpiredAt == 0 { // never expires
|
||||
return kvstore.Skip()
|
||||
}
|
||||
if item.Value.ExpiredAt < currentTime-7*86400 /** keep for 7 days **/ {
|
||||
err = tx.Delete(item.Key)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
found = true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
found = false
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVIPList) AddItem(item *pb.IPItem) error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 先删除
|
||||
var key = this.encoder.EncodeKey(item)
|
||||
err := this.ipTable.Delete(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果是删除,则不再创建新记录
|
||||
if item.IsDeleted {
|
||||
return this.UpdateMaxVersion(item.Version)
|
||||
}
|
||||
|
||||
err = this.ipTable.Set(key, item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return this.UpdateMaxVersion(item.Version)
|
||||
}
|
||||
|
||||
func (this *KVIPList) ReadItems(offset int64, size int64) (items []*pb.IPItem, goNextLoop bool, err error) {
|
||||
if this.isClosed {
|
||||
return
|
||||
}
|
||||
|
||||
err = this.ipTable.
|
||||
Query().
|
||||
Offset(this.offsetItemKey).
|
||||
Limit(int(size)).
|
||||
FindAll(func(tx *kvstore.Tx[*pb.IPItem], item kvstore.Item[*pb.IPItem]) (goNext bool, err error) {
|
||||
this.offsetItemKey = item.Key
|
||||
goNextLoop = true
|
||||
|
||||
if !item.Value.IsDeleted {
|
||||
items = append(items, item.Value)
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ReadMaxVersion 读取当前最大版本号
|
||||
func (this *KVIPList) ReadMaxVersion() (int64, error) {
|
||||
if this.isClosed {
|
||||
return 0, errors.New("database has been closed")
|
||||
}
|
||||
|
||||
version, err := this.versionsTable.Get("version")
|
||||
if err != nil {
|
||||
if kvstore.IsNotFound(err) {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
// UpdateMaxVersion 修改版本号
|
||||
func (this *KVIPList) UpdateMaxVersion(version int64) error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
return this.versionsTable.Set("version", version)
|
||||
}
|
||||
|
||||
func (this *KVIPList) TestInspect(t *testing.T) error {
|
||||
return this.ipTable.
|
||||
Query().
|
||||
FindAll(func(tx *kvstore.Tx[*pb.IPItem], item kvstore.Item[*pb.IPItem]) (goNext bool, err error) {
|
||||
if len(item.Key) != 8 {
|
||||
return false, errors.New("invalid key '" + item.Key + "'")
|
||||
}
|
||||
|
||||
t.Log(binary.BigEndian.Uint64([]byte(item.Key)), "=>", item.Value)
|
||||
return true, nil
|
||||
})
|
||||
}
|
||||
|
||||
// Flush to disk
|
||||
func (this *KVIPList) Flush() error {
|
||||
return this.ipTable.DB().Store().Flush()
|
||||
}
|
||||
|
||||
func (this *KVIPList) Close() error {
|
||||
this.isClosed = true
|
||||
return nil
|
||||
}
|
||||
55
internal/iplibrary/ip_list_kv_objects.go
Normal file
55
internal/iplibrary/ip_list_kv_objects.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"math"
|
||||
)
|
||||
|
||||
type IPItemEncoder[T interface{ *pb.IPItem }] struct {
|
||||
}
|
||||
|
||||
func NewIPItemEncoder[T interface{ *pb.IPItem }]() *IPItemEncoder[T] {
|
||||
return &IPItemEncoder[T]{}
|
||||
}
|
||||
|
||||
func (this *IPItemEncoder[T]) Encode(value T) ([]byte, error) {
|
||||
return proto.Marshal(any(value).(*pb.IPItem))
|
||||
}
|
||||
|
||||
func (this *IPItemEncoder[T]) EncodeField(value T, fieldName string) ([]byte, error) {
|
||||
switch fieldName {
|
||||
case "expiresAt":
|
||||
var expiresAt = any(value).(*pb.IPItem).ExpiredAt
|
||||
if expiresAt < 0 || expiresAt > int64(math.MaxUint32) {
|
||||
expiresAt = 0
|
||||
}
|
||||
var b = make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(b, uint32(expiresAt))
|
||||
return b, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("field '" + fieldName + "' not found")
|
||||
}
|
||||
|
||||
func (this *IPItemEncoder[T]) Decode(valueBytes []byte) (value T, err error) {
|
||||
var item = &pb.IPItem{}
|
||||
err = proto.Unmarshal(valueBytes, item)
|
||||
value = item
|
||||
return
|
||||
}
|
||||
|
||||
// EncodeKey generate key for ip item
|
||||
func (this *IPItemEncoder[T]) EncodeKey(item *pb.IPItem) string {
|
||||
var b = make([]byte, 8)
|
||||
if item.Id < 0 {
|
||||
item.Id = 0
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint64(b, uint64(item.Id))
|
||||
return string(b)
|
||||
}
|
||||
221
internal/iplibrary/ip_list_kv_test.go
Normal file
221
internal/iplibrary/ip_list_kv_test.go
Normal file
@@ -0,0 +1,221 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package iplibrary_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestKVIPList_AddItem(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = kv.Flush()
|
||||
}()
|
||||
|
||||
{
|
||||
err = kv.AddItem(&pb.IPItem{
|
||||
Id: 1,
|
||||
IpFrom: "192.168.1.101",
|
||||
IpTo: "",
|
||||
Version: 1,
|
||||
ExpiredAt: fasttime.NewFastTime().Unix() + 60,
|
||||
ListId: 1,
|
||||
IsDeleted: false,
|
||||
ListType: "white",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
err = kv.AddItem(&pb.IPItem{
|
||||
Id: 2,
|
||||
IpFrom: "192.168.1.102",
|
||||
IpTo: "",
|
||||
Version: 2,
|
||||
ExpiredAt: fasttime.NewFastTime().Unix() + 60,
|
||||
ListId: 1,
|
||||
IsDeleted: false,
|
||||
ListType: "white",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
err = kv.AddItem(&pb.IPItem{
|
||||
Id: 3,
|
||||
IpFrom: "192.168.1.103",
|
||||
IpTo: "",
|
||||
Version: 3,
|
||||
ExpiredAt: fasttime.NewFastTime().Unix() + 60,
|
||||
ListId: 1,
|
||||
IsDeleted: false,
|
||||
ListType: "white",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVIPList_AddItems_Many(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = kv.Flush()
|
||||
}()
|
||||
|
||||
var count = 2
|
||||
var from = 1
|
||||
if testutils.IsSingleTesting() {
|
||||
count = 2_000_000
|
||||
}
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
t.Logf("cost: %.2f s", time.Since(before).Seconds())
|
||||
}()
|
||||
|
||||
for i := from; i <= from+count; i++ {
|
||||
err = kv.AddItem(&pb.IPItem{
|
||||
Id: int64(i),
|
||||
IpFrom: testutils.RandIP(),
|
||||
IpTo: "",
|
||||
Version: int64(i),
|
||||
ExpiredAt: fasttime.NewFastTime().Unix() + 86400,
|
||||
ListId: 1,
|
||||
IsDeleted: false,
|
||||
ListType: "white",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVIPList_DeleteExpiredItems(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = kv.Flush()
|
||||
}()
|
||||
|
||||
err = kv.DeleteExpiredItems()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVIPList_UpdateMaxVersion(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = kv.Flush()
|
||||
}()
|
||||
|
||||
err = kv.UpdateMaxVersion(101)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
maxVersion, err := kv.ReadMaxVersion()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log("version:", maxVersion)
|
||||
}
|
||||
|
||||
func TestKVIPList_ReadMaxVersion(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
maxVersion, err := kv.ReadMaxVersion()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Log("version:", maxVersion)
|
||||
}
|
||||
|
||||
func TestKVIPList_ReadItems(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for {
|
||||
items, goNext, readErr := kv.ReadItems(0, 2)
|
||||
if readErr != nil {
|
||||
t.Fatal(readErr)
|
||||
}
|
||||
t.Log("====")
|
||||
for _, item := range items {
|
||||
t.Log(item.Id)
|
||||
}
|
||||
|
||||
if !goNext {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVIPList_CountItems(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var count int
|
||||
var m = map[int64]zero.Zero{}
|
||||
for {
|
||||
items, goNext, readErr := kv.ReadItems(0, 1000)
|
||||
if readErr != nil {
|
||||
t.Fatal(readErr)
|
||||
}
|
||||
for _, item := range items {
|
||||
count++
|
||||
m[item.Id] = zero.Zero{}
|
||||
}
|
||||
|
||||
if !goNext {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.Log("count:", count, "len:", len(m))
|
||||
}
|
||||
|
||||
func TestKVIPList_Inspect(t *testing.T) {
|
||||
kv, err := iplibrary.NewKVIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = kv.TestInspect(t)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
313
internal/iplibrary/ip_list_sqlite.go
Normal file
313
internal/iplibrary/ip_list_sqlite.go
Normal file
@@ -0,0 +1,313 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/events"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SQLiteIPList struct {
|
||||
db *dbs.DB
|
||||
|
||||
itemTableName string
|
||||
versionTableName string
|
||||
|
||||
deleteExpiredItemsStmt *dbs.Stmt
|
||||
deleteItemStmt *dbs.Stmt
|
||||
insertItemStmt *dbs.Stmt
|
||||
selectItemsStmt *dbs.Stmt
|
||||
selectMaxItemVersionStmt *dbs.Stmt
|
||||
|
||||
selectVersionStmt *dbs.Stmt
|
||||
updateVersionStmt *dbs.Stmt
|
||||
|
||||
cleanTicker *time.Ticker
|
||||
|
||||
dir string
|
||||
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
func NewSQLiteIPList() (*SQLiteIPList, error) {
|
||||
var db = &SQLiteIPList{
|
||||
itemTableName: "ipItems",
|
||||
versionTableName: "versions",
|
||||
dir: filepath.Clean(Tea.Root + "/data"),
|
||||
cleanTicker: time.NewTicker(24 * time.Hour),
|
||||
}
|
||||
err := db.init()
|
||||
return db, err
|
||||
}
|
||||
|
||||
func (this *SQLiteIPList) init() error {
|
||||
// 检查目录是否存在
|
||||
_, err := os.Stat(this.dir)
|
||||
if err != nil {
|
||||
err = os.MkdirAll(this.dir, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remotelogs.Println("IP_LIST_DB", "create data dir '"+this.dir+"'")
|
||||
}
|
||||
|
||||
var path = this.dir + "/ip_list.db"
|
||||
|
||||
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
//_, err = db.Exec("VACUUM")
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
this.db = db
|
||||
|
||||
// 恢复数据库
|
||||
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
|
||||
if len(recoverEnv) > 0 {
|
||||
for _, indexName := range []string{"ip_list_itemId", "ip_list_expiredAt"} {
|
||||
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化数据库
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.itemTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"listId" integer DEFAULT 0,
|
||||
"listType" varchar(32),
|
||||
"isGlobal" integer(1) DEFAULT 0,
|
||||
"type" varchar(16),
|
||||
"itemId" integer DEFAULT 0,
|
||||
"ipFrom" varchar(64) DEFAULT 0,
|
||||
"ipTo" varchar(64) DEFAULT 0,
|
||||
"expiredAt" integer DEFAULT 0,
|
||||
"eventLevel" varchar(32),
|
||||
"isDeleted" integer(1) DEFAULT 0,
|
||||
"version" integer DEFAULT 0,
|
||||
"nodeId" integer DEFAULT 0,
|
||||
"serverId" integer DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "ip_list_itemId"
|
||||
ON "` + this.itemTableName + `" (
|
||||
"itemId" ASC
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "ip_list_expiredAt"
|
||||
ON "` + this.itemTableName + `" (
|
||||
"expiredAt" ASC
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.versionTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"version" integer DEFAULT 0
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 初始化SQL语句
|
||||
this.deleteExpiredItemsStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "expiredAt">0 AND "expiredAt"<?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.deleteItemStmt, err = this.db.Prepare(`DELETE FROM "` + this.itemTableName + `" WHERE "itemId"=?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.insertItemStmt, err = this.db.Prepare(`INSERT INTO "` + this.itemTableName + `" ("listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectItemsStmt, err = this.db.Prepare(`SELECT "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId" FROM "` + this.itemTableName + `" WHERE isDeleted=0 ORDER BY "version" ASC, "itemId" ASC LIMIT ?, ?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectMaxItemVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.itemTableName + `" ORDER BY "id" DESC LIMIT 1`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.selectVersionStmt, err = this.db.Prepare(`SELECT "version" FROM "` + this.versionTableName + `" LIMIT 1`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.updateVersionStmt, err = this.db.Prepare(`REPLACE INTO "` + this.versionTableName + `" ("id", "version") VALUES (1, ?)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.db = db
|
||||
|
||||
goman.New(func() {
|
||||
events.OnClose(func() {
|
||||
_ = this.Close()
|
||||
this.cleanTicker.Stop()
|
||||
})
|
||||
|
||||
idles.RunTicker(this.cleanTicker, func() {
|
||||
deleteErr := this.DeleteExpiredItems()
|
||||
if deleteErr != nil {
|
||||
remotelogs.Error("IP_LIST_DB", "clean expired items failed: "+deleteErr.Error())
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name 数据库名称代号
|
||||
func (this *SQLiteIPList) Name() string {
|
||||
return "sqlite"
|
||||
}
|
||||
|
||||
// DeleteExpiredItems 删除过期的条目
|
||||
func (this *SQLiteIPList) DeleteExpiredItems() error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := this.deleteExpiredItemsStmt.Exec(time.Now().Unix() - 7*86400)
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *SQLiteIPList) AddItem(item *pb.IPItem) error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := this.deleteItemStmt.Exec(item.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 如果是删除,则不再创建新记录
|
||||
if item.IsDeleted {
|
||||
return this.UpdateMaxVersion(item.Version)
|
||||
}
|
||||
|
||||
_, err = this.insertItemStmt.Exec(item.ListId, item.ListType, item.IsGlobal, item.Type, item.Id, item.IpFrom, item.IpTo, item.ExpiredAt, item.EventLevel, item.IsDeleted, item.Version, item.NodeId, item.ServerId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return this.UpdateMaxVersion(item.Version)
|
||||
}
|
||||
|
||||
func (this *SQLiteIPList) ReadItems(offset int64, size int64) (items []*pb.IPItem, goNext bool, err error) {
|
||||
if this.isClosed {
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := this.selectItemsStmt.Query(offset, size)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
for rows.Next() {
|
||||
// "listId", "listType", "isGlobal", "type", "itemId", "ipFrom", "ipTo", "expiredAt", "eventLevel", "isDeleted", "version", "nodeId", "serverId"
|
||||
var pbItem = &pb.IPItem{}
|
||||
err = rows.Scan(&pbItem.ListId, &pbItem.ListType, &pbItem.IsGlobal, &pbItem.Type, &pbItem.Id, &pbItem.IpFrom, &pbItem.IpTo, &pbItem.ExpiredAt, &pbItem.EventLevel, &pbItem.IsDeleted, &pbItem.Version, &pbItem.NodeId, &pbItem.ServerId)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
items = append(items, pbItem)
|
||||
}
|
||||
|
||||
goNext = int64(len(items)) == size
|
||||
return
|
||||
}
|
||||
|
||||
// ReadMaxVersion 读取当前最大版本号
|
||||
func (this *SQLiteIPList) ReadMaxVersion() (int64, error) {
|
||||
if this.isClosed {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// from version table
|
||||
{
|
||||
var row = this.selectVersionStmt.QueryRow()
|
||||
if row == nil {
|
||||
return 0, nil
|
||||
}
|
||||
var version int64
|
||||
err := row.Scan(&version)
|
||||
if err == nil {
|
||||
return version, nil
|
||||
}
|
||||
}
|
||||
|
||||
// from items table
|
||||
{
|
||||
var row = this.selectMaxItemVersionStmt.QueryRow()
|
||||
if row == nil {
|
||||
return 0, nil
|
||||
}
|
||||
var version int64
|
||||
err := row.Scan(&version)
|
||||
if err != nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return version, nil
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateMaxVersion 修改版本号
|
||||
func (this *SQLiteIPList) UpdateMaxVersion(version int64) error {
|
||||
if this.isClosed {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := this.updateVersionStmt.Exec(version)
|
||||
return err
|
||||
}
|
||||
|
||||
func (this *SQLiteIPList) Close() error {
|
||||
this.isClosed = true
|
||||
|
||||
if this.db != nil {
|
||||
for _, stmt := range []*dbs.Stmt{
|
||||
this.deleteExpiredItemsStmt,
|
||||
this.deleteItemStmt,
|
||||
this.insertItemStmt,
|
||||
this.selectItemsStmt,
|
||||
this.selectMaxItemVersionStmt, // ipItems table
|
||||
|
||||
this.selectVersionStmt, // versions table
|
||||
this.updateVersionStmt,
|
||||
} {
|
||||
if stmt != nil {
|
||||
_ = stmt.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return this.db.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIPListDB_AddItem(t *testing.T) {
|
||||
db, err := iplibrary.NewIPListDB()
|
||||
func TestSQLiteIPList_AddItem(t *testing.T) {
|
||||
db, err := iplibrary.NewSQLiteIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -58,8 +58,8 @@ func TestIPListDB_AddItem(t *testing.T) {
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
func TestIPListDB_ReadItems(t *testing.T) {
|
||||
db, err := iplibrary.NewIPListDB()
|
||||
func TestSQLiteIPList_ReadItems(t *testing.T) {
|
||||
db, err := iplibrary.NewSQLiteIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -71,15 +71,16 @@ func TestIPListDB_ReadItems(t *testing.T) {
|
||||
_ = db.Close()
|
||||
}()
|
||||
|
||||
items, err := db.ReadItems(0, 2)
|
||||
items, goNext, err := db.ReadItems(0, 2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("goNext:", goNext)
|
||||
logs.PrintAsJSON(items, t)
|
||||
}
|
||||
|
||||
func TestIPListDB_ReadMaxVersion(t *testing.T) {
|
||||
db, err := iplibrary.NewIPListDB()
|
||||
func TestSQLiteIPList_ReadMaxVersion(t *testing.T) {
|
||||
db, err := iplibrary.NewSQLiteIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -89,8 +90,8 @@ func TestIPListDB_ReadMaxVersion(t *testing.T) {
|
||||
t.Log(db.ReadMaxVersion())
|
||||
}
|
||||
|
||||
func TestIPListDB_UpdateMaxVersion(t *testing.T) {
|
||||
db, err := iplibrary.NewIPListDB()
|
||||
func TestSQLiteIPList_UpdateMaxVersion(t *testing.T) {
|
||||
db, err := iplibrary.NewSQLiteIPList()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
package iplibrary
|
||||
package iplibrary_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"fmt"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/rands"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
@@ -14,216 +19,278 @@ import (
|
||||
)
|
||||
|
||||
func TestIPList_Add_Empty(t *testing.T) {
|
||||
ipList := NewIPList()
|
||||
ipList.Add(&IPItem{
|
||||
var ipList = iplibrary.NewIPList()
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
})
|
||||
logs.PrintAsJSON(ipList.itemsMap, t)
|
||||
logs.PrintAsJSON(ipList.allItemsMap, t)
|
||||
logs.PrintAsJSON(ipList.ItemsMap(), t)
|
||||
logs.PrintAsJSON(ipList.AllItemsMap(), t)
|
||||
logs.PrintAsJSON(ipList.IPMap(), t)
|
||||
}
|
||||
|
||||
func TestIPList_Add_One(t *testing.T) {
|
||||
ipList := NewIPList()
|
||||
ipList.Add(&IPItem{
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var ipList = iplibrary.NewIPList()
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
})
|
||||
ipList.Add(&IPItem{
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 2,
|
||||
IPTo: utils.IP2Long("192.168.1.2"),
|
||||
IPTo: iputils.ToBytes("192.168.1.2"),
|
||||
})
|
||||
ipList.Add(&IPItem{
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 3,
|
||||
IPFrom: utils.IP2Long("192.168.0.2"),
|
||||
IPFrom: iputils.ToBytes("192.168.0.2"),
|
||||
})
|
||||
ipList.Add(&IPItem{
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 4,
|
||||
IPFrom: utils.IP2Long("192.168.0.2"),
|
||||
IPTo: utils.IP2Long("192.168.0.1"),
|
||||
IPFrom: iputils.ToBytes("192.168.0.2"),
|
||||
IPTo: iputils.ToBytes("192.168.0.1"),
|
||||
})
|
||||
ipList.Add(&IPItem{
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 5,
|
||||
IPFrom: utils.IP2Long("2001:db8:0:1::101"),
|
||||
IPFrom: iputils.ToBytes("2001:db8:0:1::101"),
|
||||
})
|
||||
ipList.Add(&IPItem{
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 6,
|
||||
IPFrom: 0,
|
||||
IPFrom: nil,
|
||||
Type: "all",
|
||||
})
|
||||
t.Log("===items===")
|
||||
logs.PrintAsJSON(ipList.itemsMap, t)
|
||||
logs.PrintAsJSON(ipList.ItemsMap(), t)
|
||||
|
||||
t.Log("===sorted items===")
|
||||
logs.PrintAsJSON(ipList.sortedItems, t)
|
||||
logs.PrintAsJSON(ipList.SortedRangeItems(), t)
|
||||
|
||||
t.Log("===all items===")
|
||||
logs.PrintAsJSON(ipList.allItemsMap, t) // ip => items
|
||||
a.IsTrue(len(ipList.AllItemsMap()) == 1)
|
||||
logs.PrintAsJSON(ipList.AllItemsMap(), t) // ip => items
|
||||
|
||||
t.Log("===ip items===")
|
||||
logs.PrintAsJSON(ipList.IPMap())
|
||||
}
|
||||
|
||||
func TestIPList_Update(t *testing.T) {
|
||||
ipList := NewIPList()
|
||||
ipList.Add(&IPItem{
|
||||
var ipList = iplibrary.NewIPList()
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
})
|
||||
/**ipList.Add(&IPItem{
|
||||
|
||||
t.Log("===before===")
|
||||
logs.PrintAsJSON(ipList.ItemsMap(), t)
|
||||
logs.PrintAsJSON(ipList.SortedRangeItems(), t)
|
||||
logs.PrintAsJSON(ipList.IPMap(), t)
|
||||
|
||||
/**ipList.Add(&iplibrary.IPItem{
|
||||
Id: 2,
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
})**/
|
||||
ipList.Add(&IPItem{
|
||||
Id: 1,
|
||||
IPTo: utils.IP2Long("192.168.1.2"),
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
//IPFrom: 123,
|
||||
IPTo: iputils.ToBytes("192.168.1.2"),
|
||||
})
|
||||
logs.PrintAsJSON(ipList.itemsMap, t)
|
||||
logs.PrintAsJSON(ipList.sortedItems, t)
|
||||
|
||||
t.Log("===after===")
|
||||
logs.PrintAsJSON(ipList.ItemsMap(), t)
|
||||
logs.PrintAsJSON(ipList.SortedRangeItems(), t)
|
||||
logs.PrintAsJSON(ipList.IPMap(), t)
|
||||
}
|
||||
|
||||
func TestIPList_Update_AllItems(t *testing.T) {
|
||||
ipList := NewIPList()
|
||||
ipList.Add(&IPItem{
|
||||
var ipList = iplibrary.NewIPList()
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
Type: IPItemTypeAll,
|
||||
IPFrom: 0,
|
||||
Type: iplibrary.IPItemTypeAll,
|
||||
IPFrom: nil,
|
||||
})
|
||||
ipList.Add(&IPItem{
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
IPTo: 0,
|
||||
IPTo: nil,
|
||||
})
|
||||
t.Log("===items map===")
|
||||
logs.PrintAsJSON(ipList.itemsMap, t)
|
||||
logs.PrintAsJSON(ipList.ItemsMap(), t)
|
||||
t.Log("===all items map===")
|
||||
logs.PrintAsJSON(ipList.allItemsMap, t)
|
||||
logs.PrintAsJSON(ipList.AllItemsMap(), t)
|
||||
t.Log("===ip map===")
|
||||
logs.PrintAsJSON(ipList.IPMap())
|
||||
}
|
||||
|
||||
func TestIPList_Add_Range(t *testing.T) {
|
||||
ipList := NewIPList()
|
||||
ipList.Add(&IPItem{
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var ipList = iplibrary.NewIPList()
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPTo: utils.IP2Long("192.168.2.1"),
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
IPTo: iputils.ToBytes("192.168.2.1"),
|
||||
})
|
||||
ipList.Add(&IPItem{
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 2,
|
||||
IPTo: utils.IP2Long("192.168.1.2"),
|
||||
IPTo: iputils.ToBytes("192.168.1.2"),
|
||||
})
|
||||
t.Log(len(ipList.itemsMap), "ips")
|
||||
logs.PrintAsJSON(ipList.itemsMap, t)
|
||||
logs.PrintAsJSON(ipList.allItemsMap, t)
|
||||
}
|
||||
|
||||
func TestIPList_Add_Overflow(t *testing.T) {
|
||||
a := assert.NewAssertion(t)
|
||||
|
||||
ipList := NewIPList()
|
||||
ipList.Add(&IPItem{
|
||||
Id: 1,
|
||||
IPFrom: utils.IP2Long("192.168.1.1"),
|
||||
IPTo: utils.IP2Long("192.169.255.1"),
|
||||
ipList.Add(&iplibrary.IPItem{
|
||||
Id: 3,
|
||||
IPFrom: iputils.ToBytes("192.168.0.1"),
|
||||
IPTo: iputils.ToBytes("192.168.0.2"),
|
||||
})
|
||||
t.Log(len(ipList.itemsMap), "ips")
|
||||
a.IsTrue(len(ipList.itemsMap) <= 65535)
|
||||
|
||||
a.IsTrue(len(ipList.SortedRangeItems()) == 2)
|
||||
|
||||
t.Log(len(ipList.ItemsMap()), "ips")
|
||||
t.Log("===items map===")
|
||||
logs.PrintAsJSON(ipList.ItemsMap(), t)
|
||||
t.Log("===sorted range items===")
|
||||
logs.PrintAsJSON(ipList.SortedRangeItems())
|
||||
t.Log("===all items map===")
|
||||
logs.PrintAsJSON(ipList.AllItemsMap(), t)
|
||||
|
||||
t.Log("===ip map===")
|
||||
logs.PrintAsJSON(ipList.IPMap(), t)
|
||||
}
|
||||
|
||||
func TestNewIPList_Memory(t *testing.T) {
|
||||
list := NewIPList()
|
||||
var list = iplibrary.NewIPList()
|
||||
|
||||
for i := 0; i < 200_0000; i++ {
|
||||
list.Add(&IPItem{
|
||||
IPFrom: 1,
|
||||
IPTo: 2,
|
||||
var count = 100
|
||||
if testutils.IsSingleTesting() {
|
||||
count = 2_000_000
|
||||
}
|
||||
var stat1 = testutils.ReadMemoryStat()
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
list.AddDelay(&iplibrary.IPItem{
|
||||
Id: uint64(i),
|
||||
IPFrom: iputils.ToBytes(testutils.RandIP()),
|
||||
IPTo: iputils.ToBytes(testutils.RandIP()),
|
||||
ExpiredAt: time.Now().Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
t.Log("ok")
|
||||
list.Sort()
|
||||
|
||||
runtime.GC()
|
||||
|
||||
var stat2 = testutils.ReadMemoryStat()
|
||||
t.Log((stat2.HeapInuse-stat1.HeapInuse)>>20, "MB")
|
||||
}
|
||||
|
||||
func TestIPList_Contains(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
list := NewIPList()
|
||||
var list = iplibrary.NewIPList()
|
||||
for i := 0; i < 255; i++ {
|
||||
list.AddDelay(&IPItem{
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: uint64(i),
|
||||
IPFrom: utils.IP2Long(strconv.Itoa(i) + ".168.0.1"),
|
||||
IPTo: utils.IP2Long(strconv.Itoa(i) + ".168.255.1"),
|
||||
IPFrom: iputils.ToBytes(strconv.Itoa(i) + ".168.0.1"),
|
||||
IPTo: iputils.ToBytes(strconv.Itoa(i) + ".168.255.1"),
|
||||
ExpiredAt: 0,
|
||||
})
|
||||
}
|
||||
for i := 0; i < 255; i++ {
|
||||
list.AddDelay(&IPItem{
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: uint64(1000 + i),
|
||||
IPFrom: utils.IP2Long("192.167.2." + strconv.Itoa(i)),
|
||||
IPFrom: iputils.ToBytes("192.167.2." + strconv.Itoa(i)),
|
||||
})
|
||||
}
|
||||
list.Sort()
|
||||
t.Log(len(list.itemsMap), "ip")
|
||||
|
||||
before := time.Now()
|
||||
a.IsTrue(list.Contains(utils.IP2Long("192.168.1.100")))
|
||||
a.IsTrue(list.Contains(utils.IP2Long("192.168.2.100")))
|
||||
a.IsFalse(list.Contains(utils.IP2Long("192.169.3.100")))
|
||||
a.IsFalse(list.Contains(utils.IP2Long("192.167.3.100")))
|
||||
a.IsTrue(list.Contains(utils.IP2Long("192.167.2.100")))
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 10000,
|
||||
IPFrom: iputils.ToBytes("::1"),
|
||||
})
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 10001,
|
||||
IPFrom: iputils.ToBytes("::2"),
|
||||
IPTo: iputils.ToBytes("::5"),
|
||||
})
|
||||
|
||||
t.Log(len(list.ItemsMap()), "ip")
|
||||
|
||||
var before = time.Now()
|
||||
a.IsTrue(list.Contains(iputils.ToBytes("192.168.1.100")))
|
||||
a.IsTrue(list.Contains(iputils.ToBytes("192.168.2.100")))
|
||||
a.IsFalse(list.Contains(iputils.ToBytes("192.169.3.100")))
|
||||
a.IsFalse(list.Contains(iputils.ToBytes("192.167.3.100")))
|
||||
a.IsTrue(list.Contains(iputils.ToBytes("192.167.2.100")))
|
||||
a.IsTrue(list.Contains(iputils.ToBytes("::1")))
|
||||
a.IsTrue(list.Contains(iputils.ToBytes("::3")))
|
||||
a.IsFalse(list.Contains(iputils.ToBytes("::8")))
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}
|
||||
|
||||
func TestIPList_Contains_Many(t *testing.T) {
|
||||
list := NewIPList()
|
||||
var list = iplibrary.NewIPList()
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
list.AddDelay(&IPItem{
|
||||
list.AddDelay(&iplibrary.IPItem{
|
||||
Id: uint64(i),
|
||||
IPFrom: utils.IP2Long(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255))),
|
||||
IPTo: utils.IP2Long(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255))),
|
||||
IPFrom: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255))),
|
||||
IPTo: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255))),
|
||||
ExpiredAt: 0,
|
||||
})
|
||||
}
|
||||
list.Sort()
|
||||
t.Log(len(list.itemsMap), "ip")
|
||||
|
||||
before := time.Now()
|
||||
_ = list.Contains(utils.IP2Long("192.168.1.100"))
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
var before = time.Now()
|
||||
list.Sort()
|
||||
t.Log("sort cost:", time.Since(before).Seconds()*1000, "ms")
|
||||
t.Log(len(list.ItemsMap()), "ip")
|
||||
|
||||
before = time.Now()
|
||||
_ = list.Contains(iputils.ToBytes("192.168.1.100"))
|
||||
t.Log("contains cost:", time.Since(before).Seconds()*1000, "ms")
|
||||
}
|
||||
|
||||
func TestIPList_ContainsAll(t *testing.T) {
|
||||
list := NewIPList()
|
||||
list.Add(&IPItem{
|
||||
Id: 1,
|
||||
Type: "all",
|
||||
IPFrom: 0,
|
||||
})
|
||||
b := list.Contains(utils.IP2Long("192.168.1.1"))
|
||||
if b {
|
||||
t.Log(b)
|
||||
} else {
|
||||
t.Fatal("'b' should be true")
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
{
|
||||
var list = iplibrary.NewIPList()
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
Type: "all",
|
||||
IPFrom: nil,
|
||||
})
|
||||
var b = list.Contains(iputils.ToBytes("192.168.1.1"))
|
||||
a.IsTrue(b)
|
||||
|
||||
list.Delete(1)
|
||||
|
||||
b = list.Contains(iputils.ToBytes("192.168.1.1"))
|
||||
a.IsFalse(b)
|
||||
}
|
||||
|
||||
list.Delete(1)
|
||||
{
|
||||
var list = iplibrary.NewIPList()
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
Type: "all",
|
||||
IPFrom: iputils.ToBytes("0.0.0.0"),
|
||||
})
|
||||
var b = list.Contains(iputils.ToBytes("192.168.1.1"))
|
||||
a.IsTrue(b)
|
||||
|
||||
b = list.Contains(utils.IP2Long("192.168.1.1"))
|
||||
if !b {
|
||||
t.Log(b)
|
||||
} else {
|
||||
t.Fatal("'b' should be false")
|
||||
list.Delete(1)
|
||||
|
||||
b = list.Contains(iputils.ToBytes("192.168.1.1"))
|
||||
a.IsFalse(b)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestIPList_ContainsIPStrings(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
list := NewIPList()
|
||||
var list = iplibrary.NewIPList()
|
||||
for i := 0; i < 255; i++ {
|
||||
list.Add(&IPItem{
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: uint64(i),
|
||||
IPFrom: utils.IP2Long(strconv.Itoa(i) + ".168.0.1"),
|
||||
IPTo: utils.IP2Long(strconv.Itoa(i) + ".168.255.1"),
|
||||
IPFrom: iputils.ToBytes(strconv.Itoa(i) + ".168.0.1"),
|
||||
IPTo: iputils.ToBytes(strconv.Itoa(i) + ".168.255.1"),
|
||||
ExpiredAt: 0,
|
||||
})
|
||||
}
|
||||
t.Log(len(list.itemsMap), "ip")
|
||||
t.Log(len(list.ItemsMap()), "ip")
|
||||
|
||||
{
|
||||
item, ok := list.ContainsIPStrings([]string{"192.168.1.100"})
|
||||
@@ -238,85 +305,191 @@ func TestIPList_ContainsIPStrings(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIPList_Delete(t *testing.T) {
|
||||
list := NewIPList()
|
||||
list.Add(&IPItem{
|
||||
var list = iplibrary.NewIPList()
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
IPFrom: utils.IP2Long("192.168.0.1"),
|
||||
IPFrom: iputils.ToBytes("192.168.0.1"),
|
||||
ExpiredAt: 0,
|
||||
})
|
||||
list.Add(&IPItem{
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 2,
|
||||
IPFrom: utils.IP2Long("192.168.0.1"),
|
||||
IPFrom: iputils.ToBytes("192.168.0.1"),
|
||||
ExpiredAt: 0,
|
||||
})
|
||||
t.Log("===BEFORE===")
|
||||
logs.PrintAsJSON(list.itemsMap, t)
|
||||
logs.PrintAsJSON(list.allItemsMap, t)
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 3,
|
||||
IPFrom: iputils.ToBytes("192.168.1.1"),
|
||||
IPTo: iputils.ToBytes("192.168.2.1"),
|
||||
ExpiredAt: 0,
|
||||
})
|
||||
t.Log("===before===")
|
||||
logs.PrintAsJSON(list.ItemsMap(), t)
|
||||
logs.PrintAsJSON(list.AllItemsMap(), t)
|
||||
logs.PrintAsJSON(list.SortedRangeItems())
|
||||
logs.PrintAsJSON(list.IPMap(), t)
|
||||
|
||||
{
|
||||
var found bool
|
||||
for _, item := range list.SortedRangeItems() {
|
||||
if item.Id == 3 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("should be found")
|
||||
}
|
||||
}
|
||||
|
||||
list.Delete(1)
|
||||
|
||||
t.Log("===AFTER===")
|
||||
logs.PrintAsJSON(list.itemsMap, t)
|
||||
logs.PrintAsJSON(list.allItemsMap, t)
|
||||
t.Log("===after===")
|
||||
logs.PrintAsJSON(list.ItemsMap(), t)
|
||||
logs.PrintAsJSON(list.AllItemsMap(), t)
|
||||
logs.PrintAsJSON(list.SortedRangeItems())
|
||||
logs.PrintAsJSON(list.IPMap(), t)
|
||||
|
||||
list.Delete(3)
|
||||
|
||||
{
|
||||
var found bool
|
||||
for _, item := range list.SortedRangeItems() {
|
||||
if item.Id == 3 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
t.Fatal("should be not found")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGC(t *testing.T) {
|
||||
list := NewIPList()
|
||||
list.Add(&IPItem{
|
||||
func TestIPList_GC(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var list = iplibrary.NewIPList()
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 1,
|
||||
IPFrom: utils.IP2Long("192.168.1.100"),
|
||||
IPTo: utils.IP2Long("192.168.1.101"),
|
||||
IPFrom: iputils.ToBytes("192.168.1.100"),
|
||||
IPTo: iputils.ToBytes("192.168.1.101"),
|
||||
ExpiredAt: time.Now().Unix() + 1,
|
||||
})
|
||||
list.Add(&IPItem{
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Id: 2,
|
||||
IPFrom: utils.IP2Long("192.168.1.102"),
|
||||
IPTo: utils.IP2Long("192.168.1.103"),
|
||||
IPFrom: iputils.ToBytes("192.168.1.102"),
|
||||
IPTo: iputils.ToBytes("192.168.1.103"),
|
||||
ExpiredAt: 0,
|
||||
})
|
||||
logs.PrintAsJSON(list.itemsMap, t)
|
||||
logs.PrintAsJSON(list.allItemsMap, t)
|
||||
logs.PrintAsJSON(list.ItemsMap(), t)
|
||||
logs.PrintAsJSON(list.AllItemsMap(), t)
|
||||
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
t.Log("===AFTER GC===")
|
||||
logs.PrintAsJSON(list.itemsMap, t)
|
||||
logs.PrintAsJSON(list.sortedItems, t)
|
||||
logs.PrintAsJSON(list.ItemsMap(), t)
|
||||
logs.PrintAsJSON(list.SortedRangeItems(), t)
|
||||
|
||||
a.IsTrue(len(list.ItemsMap()) == 1)
|
||||
a.IsTrue(len(list.SortedRangeItems()) == 1)
|
||||
}
|
||||
|
||||
func TestTooManyLists(t *testing.T) {
|
||||
func TestManyLists(t *testing.T) {
|
||||
debug.SetMaxThreads(20)
|
||||
|
||||
var lists = []*IPList{}
|
||||
var lists = []*iplibrary.IPList{}
|
||||
var locker = &sync.Mutex{}
|
||||
for i := 0; i < 1000; i++ {
|
||||
locker.Lock()
|
||||
lists = append(lists, NewIPList())
|
||||
lists = append(lists, iplibrary.NewIPList())
|
||||
locker.Unlock()
|
||||
}
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(3 * time.Second)
|
||||
}
|
||||
t.Log(runtime.NumGoroutine())
|
||||
t.Log(len(lists), "lists")
|
||||
}
|
||||
|
||||
func BenchmarkIPList_Add(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
var list = iplibrary.NewIPList()
|
||||
for i := 1; i < 200_000; i++ {
|
||||
list.AddDelay(&iplibrary.IPItem{
|
||||
Id: uint64(i),
|
||||
IPFrom: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
|
||||
IPTo: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
|
||||
ExpiredAt: time.Now().Unix() + 60,
|
||||
})
|
||||
}
|
||||
|
||||
list.Sort()
|
||||
|
||||
b.Log(len(list.ItemsMap()), "ip")
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
var ip = fmt.Sprintf("%d.%d.%d.%d", rand.Int()%255, rand.Int()%255, rand.Int()%255, rand.Int()%255)
|
||||
list.Add(&iplibrary.IPItem{
|
||||
Type: "",
|
||||
Id: uint64(i % 1_000_000),
|
||||
IPFrom: iputils.ToBytes(ip),
|
||||
IPTo: nil,
|
||||
ExpiredAt: fasttime.Now().Unix() + 3600,
|
||||
EventLevel: "",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIPList_Contains(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
var list = NewIPList()
|
||||
for i := 1; i < 200_000; i++ {
|
||||
list.AddDelay(&IPItem{
|
||||
var list = iplibrary.NewIPList()
|
||||
for i := 1; i < 1_000_000; i++ {
|
||||
var item = &iplibrary.IPItem{
|
||||
Id: uint64(i),
|
||||
IPFrom: utils.IP2Long(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
|
||||
IPTo: utils.IP2Long(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
|
||||
IPFrom: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
|
||||
ExpiredAt: time.Now().Unix() + 60,
|
||||
})
|
||||
}
|
||||
if i%100 == 0 {
|
||||
item.IPTo = iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1")
|
||||
}
|
||||
list.Add(item)
|
||||
}
|
||||
list.Sort()
|
||||
|
||||
b.Log(len(list.itemsMap), "ip")
|
||||
//b.Log(len(list.ItemsMap()), "ip")
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = list.Contains(utils.IP2Long("192.168.1.100"))
|
||||
}
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
_ = list.Contains(iputils.ToBytes(testutils.RandIP()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkIPList_Sort(b *testing.B) {
|
||||
var list = iplibrary.NewIPList()
|
||||
for i := 0; i < 1_000_000; i++ {
|
||||
var item = &iplibrary.IPItem{
|
||||
Id: uint64(i),
|
||||
IPFrom: iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1"),
|
||||
ExpiredAt: time.Now().Unix() + 60,
|
||||
}
|
||||
|
||||
if i%100 == 0 {
|
||||
item.IPTo = iputils.ToBytes(strconv.Itoa(rands.Int(0, 255)) + "." + strconv.Itoa(rands.Int(0, 255)) + ".0.1")
|
||||
}
|
||||
|
||||
list.AddDelay(item)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
list.Sort()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
)
|
||||
|
||||
@@ -24,25 +25,25 @@ func AllowIP(ip string, serverId int64) (canGoNext bool, inAllowList bool, expir
|
||||
}
|
||||
}
|
||||
|
||||
var ipLong = utils.IP2Long(ip)
|
||||
if ipLong == 0 {
|
||||
var ipBytes = iputils.ToBytes(ip)
|
||||
if IsZero(ipBytes) {
|
||||
return false, false, 0
|
||||
}
|
||||
|
||||
// check white lists
|
||||
if GlobalWhiteIPList.Contains(ipLong) {
|
||||
if GlobalWhiteIPList.Contains(ipBytes) {
|
||||
return true, true, 0
|
||||
}
|
||||
|
||||
if serverId > 0 {
|
||||
var list = SharedServerListManager.FindWhiteList(serverId, false)
|
||||
if list != nil && list.Contains(ipLong) {
|
||||
if list != nil && list.Contains(ipBytes) {
|
||||
return true, true, 0
|
||||
}
|
||||
}
|
||||
|
||||
// check black lists
|
||||
expiresAt, ok := GlobalBlackIPList.ContainsExpires(ipLong)
|
||||
expiresAt, ok := GlobalBlackIPList.ContainsExpires(ipBytes)
|
||||
if ok {
|
||||
return false, false, expiresAt
|
||||
}
|
||||
@@ -50,7 +51,7 @@ func AllowIP(ip string, serverId int64) (canGoNext bool, inAllowList bool, expir
|
||||
if serverId > 0 {
|
||||
var list = SharedServerListManager.FindBlackList(serverId, false)
|
||||
if list != nil {
|
||||
expiresAt, ok = list.ContainsExpires(ipLong)
|
||||
expiresAt, ok = list.ContainsExpires(ipBytes)
|
||||
if ok {
|
||||
return false, false, expiresAt
|
||||
}
|
||||
@@ -62,13 +63,13 @@ func AllowIP(ip string, serverId int64) (canGoNext bool, inAllowList bool, expir
|
||||
|
||||
// IsInWhiteList 检查IP是否在白名单中
|
||||
func IsInWhiteList(ip string) bool {
|
||||
var ipLong = utils.IP2Long(ip)
|
||||
if ipLong == 0 {
|
||||
var ipBytes = iputils.ToBytes(ip)
|
||||
if IsZero(ipBytes) {
|
||||
return false
|
||||
}
|
||||
|
||||
// check white lists
|
||||
return GlobalWhiteIPList.Contains(ipLong)
|
||||
return GlobalWhiteIPList.Contains(ipBytes)
|
||||
}
|
||||
|
||||
// AllowIPStrings 检查一组IP是否被允许访问
|
||||
@@ -84,3 +85,15 @@ func AllowIPStrings(ipStrings []string, serverId int64) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func IsZero(ipBytes []byte) bool {
|
||||
return len(ipBytes) == 0
|
||||
}
|
||||
|
||||
func ToHex(b []byte) string {
|
||||
if len(b) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ func TestIPIsAllowed(t *testing.T) {
|
||||
}
|
||||
|
||||
var manager = NewIPListManager()
|
||||
manager.init()
|
||||
manager.Init()
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package iplibrary
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
@@ -8,10 +9,13 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/waf"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
@@ -35,9 +39,9 @@ func init() {
|
||||
|
||||
var ticker = time.NewTicker(24 * time.Hour)
|
||||
goman.New(func() {
|
||||
for range ticker.C {
|
||||
idles.RunTicker(ticker, func() {
|
||||
SharedIPListManager.DeleteExpiredItems()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -45,13 +49,13 @@ func init() {
|
||||
type IPListManager struct {
|
||||
ticker *time.Ticker
|
||||
|
||||
db *IPListDB
|
||||
db IPListDB
|
||||
|
||||
lastVersion int64
|
||||
fetchPageSize int64
|
||||
|
||||
listMap map[int64]*IPList
|
||||
locker sync.Mutex
|
||||
mu sync.RWMutex
|
||||
|
||||
isFirstTime bool
|
||||
}
|
||||
@@ -65,10 +69,10 @@ func NewIPListManager() *IPListManager {
|
||||
}
|
||||
|
||||
func (this *IPListManager) Start() {
|
||||
this.init()
|
||||
this.Init()
|
||||
|
||||
// 第一次读取
|
||||
err := this.loop()
|
||||
err := this.Loop()
|
||||
if err != nil {
|
||||
remotelogs.ErrorObject("IP_LIST_MANAGER", err)
|
||||
}
|
||||
@@ -83,7 +87,7 @@ func (this *IPListManager) Start() {
|
||||
case <-this.ticker.C:
|
||||
case <-IPListUpdateNotify:
|
||||
}
|
||||
err := this.loop()
|
||||
err = this.Loop()
|
||||
if err != nil {
|
||||
countErrors++
|
||||
|
||||
@@ -108,9 +112,20 @@ func (this *IPListManager) Stop() {
|
||||
}
|
||||
}
|
||||
|
||||
func (this *IPListManager) init() {
|
||||
func (this *IPListManager) Init() {
|
||||
// 从数据库中当中读取数据
|
||||
db, err := NewIPListDB()
|
||||
// 检查sqlite文件是否存在,以便决定使用sqlite还是kv
|
||||
var sqlitePath = Tea.Root + "/data/ip_list.db"
|
||||
_, sqliteErr := os.Stat(sqlitePath)
|
||||
|
||||
var db IPListDB
|
||||
var err error
|
||||
if sqliteErr == nil || !teaconst.EnableKVCacheStore {
|
||||
db, err = NewSQLiteIPList()
|
||||
} else {
|
||||
db, err = NewKVIPList()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
remotelogs.Error("IP_LIST_MANAGER", "create ip list local database failed: "+err.Error())
|
||||
} else {
|
||||
@@ -120,22 +135,28 @@ func (this *IPListManager) init() {
|
||||
_ = db.DeleteExpiredItems()
|
||||
|
||||
// 本地数据库中最大版本号
|
||||
this.lastVersion = db.ReadMaxVersion()
|
||||
this.lastVersion, err = db.ReadMaxVersion()
|
||||
if err != nil {
|
||||
remotelogs.Error("IP_LIST_MANAGER", "find max version failed: "+err.Error())
|
||||
this.lastVersion = 0
|
||||
}
|
||||
remotelogs.Println("IP_LIST_MANAGER", "starting from '"+db.Name()+"' version '"+types.String(this.lastVersion)+"' ...")
|
||||
|
||||
// 从本地数据库中加载
|
||||
var offset int64 = 0
|
||||
var size int64 = 2_000
|
||||
|
||||
var tr = trackers.Begin("IP_LIST_MANAGER:load")
|
||||
defer tr.End()
|
||||
|
||||
for {
|
||||
items, err := db.ReadItems(offset, size)
|
||||
items, goNext, readErr := db.ReadItems(offset, size)
|
||||
var l = len(items)
|
||||
if err != nil {
|
||||
remotelogs.Error("IP_LIST_MANAGER", "read ip list from local database failed: "+err.Error())
|
||||
if readErr != nil {
|
||||
remotelogs.Error("IP_LIST_MANAGER", "read ip list from local database failed: "+readErr.Error())
|
||||
} else {
|
||||
if l == 0 {
|
||||
break
|
||||
}
|
||||
this.processItems(items, false)
|
||||
if int64(l) < size {
|
||||
if !goNext {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -144,7 +165,7 @@ func (this *IPListManager) init() {
|
||||
}
|
||||
}
|
||||
|
||||
func (this *IPListManager) loop() error {
|
||||
func (this *IPListManager) Loop() error {
|
||||
// 是否同步IP名单
|
||||
nodeConfig, _ := nodeconfigs.SharedNodeConfig()
|
||||
if nodeConfig != nil && !nodeConfig.EnableIPLists {
|
||||
@@ -213,9 +234,10 @@ func (this *IPListManager) fetch() (hasNext bool, err error) {
|
||||
}
|
||||
|
||||
func (this *IPListManager) FindList(listId int64) *IPList {
|
||||
this.locker.Lock()
|
||||
this.mu.RLock()
|
||||
var list = this.listMap[listId]
|
||||
this.locker.Unlock()
|
||||
this.mu.RUnlock()
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
@@ -225,6 +247,10 @@ func (this *IPListManager) DeleteExpiredItems() {
|
||||
}
|
||||
}
|
||||
|
||||
func (this *IPListManager) ListMap() map[int64]*IPList {
|
||||
return this.listMap
|
||||
}
|
||||
|
||||
// 处理IP条目
|
||||
func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
|
||||
var changedLists = map[*IPList]zero.Zero{}
|
||||
@@ -251,15 +277,15 @@ func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
|
||||
list = GlobalWhiteIPList
|
||||
}
|
||||
} else { // 其他List
|
||||
this.locker.Lock()
|
||||
this.mu.Lock()
|
||||
list = this.listMap[item.ListId]
|
||||
this.locker.Unlock()
|
||||
this.mu.Unlock()
|
||||
}
|
||||
if list == nil {
|
||||
list = NewIPList()
|
||||
this.locker.Lock()
|
||||
this.mu.Lock()
|
||||
this.listMap[item.ListId] = list
|
||||
this.locker.Unlock()
|
||||
this.mu.Unlock()
|
||||
}
|
||||
|
||||
changedLists[list] = zero.New()
|
||||
@@ -281,8 +307,8 @@ func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
|
||||
list.AddDelay(&IPItem{
|
||||
Id: uint64(item.Id),
|
||||
Type: item.Type,
|
||||
IPFrom: utils.IP2Long(item.IpFrom),
|
||||
IPTo: utils.IP2Long(item.IpTo),
|
||||
IPFrom: iputils.ToBytes(item.IpFrom),
|
||||
IPTo: iputils.ToBytes(item.IpTo),
|
||||
ExpiredAt: item.ExpiredAt,
|
||||
EventLevel: item.EventLevel,
|
||||
})
|
||||
@@ -294,8 +320,10 @@ func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
|
||||
}
|
||||
}
|
||||
|
||||
for changedList := range changedLists {
|
||||
changedList.Sort()
|
||||
if len(changedLists) > 0 {
|
||||
for changedList := range changedLists {
|
||||
changedList.Sort()
|
||||
}
|
||||
}
|
||||
|
||||
if fromRemote {
|
||||
@@ -308,9 +336,14 @@ func (this *IPListManager) processItems(items []*pb.IPItem, fromRemote bool) {
|
||||
|
||||
// 调试IP信息
|
||||
func (this *IPListManager) debugItem(item *pb.IPItem) {
|
||||
var ipRange = item.IpFrom
|
||||
if len(item.IpTo) > 0 {
|
||||
ipRange += " - " + item.IpTo
|
||||
}
|
||||
|
||||
if item.IsDeleted {
|
||||
remotelogs.Debug("IP_ITEM_DEBUG", "delete '"+item.IpFrom+"'")
|
||||
remotelogs.Debug("IP_ITEM_DEBUG", "delete '"+ipRange+"'")
|
||||
} else {
|
||||
remotelogs.Debug("IP_ITEM_DEBUG", "add '"+item.IpFrom+"'")
|
||||
remotelogs.Debug("IP_ITEM_DEBUG", "add '"+ipRange+"'")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package iplibrary
|
||||
package iplibrary_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/iputils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/iplibrary"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"testing"
|
||||
@@ -13,11 +14,11 @@ func TestIPListManager_init(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
var manager = NewIPListManager()
|
||||
manager.init()
|
||||
t.Log(manager.listMap)
|
||||
t.Log(SharedServerListManager.blackMap)
|
||||
logs.PrintAsJSON(GlobalBlackIPList.sortedItems, t)
|
||||
var manager = iplibrary.NewIPListManager()
|
||||
manager.Init()
|
||||
t.Log(manager.ListMap())
|
||||
t.Log(iplibrary.SharedServerListManager.BlackMap())
|
||||
logs.PrintAsJSON(iplibrary.GlobalBlackIPList.SortedRangeItems(), t)
|
||||
}
|
||||
|
||||
func TestIPListManager_check(t *testing.T) {
|
||||
@@ -25,15 +26,15 @@ func TestIPListManager_check(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
var manager = NewIPListManager()
|
||||
manager.init()
|
||||
var manager = iplibrary.NewIPListManager()
|
||||
manager.Init()
|
||||
|
||||
var before = time.Now()
|
||||
defer func() {
|
||||
t.Log(time.Since(before).Seconds()*1000, "ms")
|
||||
}()
|
||||
t.Log(SharedServerListManager.FindBlackList(23, true).Contains(utils.IP2Long("127.0.0.2")))
|
||||
t.Log(GlobalBlackIPList.Contains(utils.IP2Long("127.0.0.6")))
|
||||
t.Log(iplibrary.SharedServerListManager.FindBlackList(23, true).Contains(iputils.ToBytes("127.0.0.2")))
|
||||
t.Log(iplibrary.GlobalBlackIPList.Contains(iputils.ToBytes("127.0.0.6")))
|
||||
}
|
||||
|
||||
func TestIPListManager_loop(t *testing.T) {
|
||||
@@ -41,9 +42,9 @@ func TestIPListManager_loop(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
var manager = NewIPListManager()
|
||||
var manager = iplibrary.NewIPListManager()
|
||||
manager.Start()
|
||||
err := manager.loop()
|
||||
err := manager.Loop()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -59,3 +59,7 @@ func (this *ServerListManager) FindBlackList(serverId int64, autoCreate bool) *I
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *ServerListManager) BlackMap() map[int64]*IPList {
|
||||
return this.blackMap
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ func init() {
|
||||
type Manager struct {
|
||||
isQuiting bool
|
||||
|
||||
taskMap map[int64]*Task // itemId => *Task
|
||||
categoryTaskMap map[string][]*Task // category => []*Task
|
||||
taskMap map[int64]Task // itemId => Task
|
||||
categoryTaskMap map[string][]Task // category => []Task
|
||||
locker sync.RWMutex
|
||||
|
||||
hasHTTPMetrics bool
|
||||
@@ -37,8 +37,8 @@ type Manager struct {
|
||||
|
||||
func NewManager() *Manager {
|
||||
return &Manager{
|
||||
taskMap: map[int64]*Task{},
|
||||
categoryTaskMap: map[string][]*Task{},
|
||||
taskMap: map[int64]Task{},
|
||||
categoryTaskMap: map[string][]Task{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,11 +64,20 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC_MANAGER", "stop task '"+strconv.FormatInt(itemId, 10)+"' failed: "+err.Error())
|
||||
}
|
||||
|
||||
// deleted
|
||||
if newItem != nil && !newItem.IsOn {
|
||||
deleteErr := task.Delete()
|
||||
if deleteErr != nil {
|
||||
remotelogs.Error("METRIC_MANAGER", "delete task '"+strconv.FormatInt(itemId, 10)+"' failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
delete(this.taskMap, itemId)
|
||||
} else { // 更新已存在的
|
||||
if newItem.Version != task.item.Version {
|
||||
if newItem.Version != task.Item().Version {
|
||||
remotelogs.Println("METRIC_MANAGER", "update task '"+strconv.FormatInt(itemId, 10)+"'")
|
||||
task.item = newItem
|
||||
task.SetItem(newItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,7 +90,14 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
|
||||
_, ok := this.taskMap[newItem.Id]
|
||||
if !ok {
|
||||
remotelogs.Println("METRIC_MANAGER", "start task '"+strconv.FormatInt(newItem.Id, 10)+"'")
|
||||
task := NewTask(newItem)
|
||||
var task Task
|
||||
|
||||
if CheckSQLiteDB(newItem.Id) || !teaconst.EnableKVCacheStore {
|
||||
task = NewSQLiteTask(newItem)
|
||||
} else {
|
||||
task = NewKVTask(newItem)
|
||||
}
|
||||
|
||||
err := task.Init()
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC_MANAGER", "initialized task failed: "+err.Error())
|
||||
@@ -100,13 +116,13 @@ func (this *Manager) Update(items []*serverconfigs.MetricItemConfig) {
|
||||
this.hasHTTPMetrics = false
|
||||
this.hasTCPMetrics = false
|
||||
this.hasUDPMetrics = false
|
||||
this.categoryTaskMap = map[string][]*Task{}
|
||||
this.categoryTaskMap = map[string][]Task{}
|
||||
for _, task := range this.taskMap {
|
||||
var tasks = this.categoryTaskMap[task.item.Category]
|
||||
var tasks = this.categoryTaskMap[task.Item().Category]
|
||||
tasks = append(tasks, task)
|
||||
this.categoryTaskMap[task.item.Category] = tasks
|
||||
this.categoryTaskMap[task.Item().Category] = tasks
|
||||
|
||||
switch task.item.Category {
|
||||
switch task.Item().Category {
|
||||
case serverconfigs.MetricItemCategoryHTTP:
|
||||
this.hasHTTPMetrics = true
|
||||
case serverconfigs.MetricItemCategoryTCP:
|
||||
@@ -144,6 +160,10 @@ func (this *Manager) HasUDPMetrics() bool {
|
||||
return this.hasUDPMetrics
|
||||
}
|
||||
|
||||
func (this *Manager) TaskMap() map[int64]Task {
|
||||
return this.taskMap
|
||||
}
|
||||
|
||||
// Quit 退出管理器
|
||||
func (this *Manager) Quit() {
|
||||
this.isQuiting = true
|
||||
@@ -154,6 +174,6 @@ func (this *Manager) Quit() {
|
||||
for _, task := range this.taskMap {
|
||||
_ = task.Stop()
|
||||
}
|
||||
this.taskMap = map[int64]*Task{}
|
||||
this.taskMap = map[int64]Task{}
|
||||
this.locker.Unlock()
|
||||
}
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package metrics
|
||||
package metrics_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/metrics"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewManager(t *testing.T) {
|
||||
var manager = NewManager()
|
||||
var manager = metrics.NewManager()
|
||||
{
|
||||
manager.Update([]*serverconfigs.MetricItemConfig{})
|
||||
for _, task := range manager.taskMap {
|
||||
t.Log(task.item.Id)
|
||||
for _, task := range manager.TaskMap() {
|
||||
t.Log(task.Item().Id)
|
||||
}
|
||||
}
|
||||
{
|
||||
@@ -28,8 +29,8 @@ func TestNewManager(t *testing.T) {
|
||||
Id: 3,
|
||||
},
|
||||
})
|
||||
for _, task := range manager.taskMap {
|
||||
t.Log("task:", task.item.Id)
|
||||
for _, task := range manager.TaskMap() {
|
||||
t.Log("task:", task.Item().Id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +44,8 @@ func TestNewManager(t *testing.T) {
|
||||
Id: 2,
|
||||
},
|
||||
})
|
||||
for _, task := range manager.taskMap {
|
||||
t.Log("task:", task.item.Id)
|
||||
for _, task := range manager.TaskMap() {
|
||||
t.Log("task:", task.Item().Id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +57,8 @@ func TestNewManager(t *testing.T) {
|
||||
Version: 1,
|
||||
},
|
||||
})
|
||||
for _, task := range manager.taskMap {
|
||||
t.Log("task:", task.item.Id)
|
||||
for _, task := range manager.TaskMap() {
|
||||
t.Log("task:", task.Item().Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,120 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
byteutils "github.com/TeaOSLab/EdgeNode/internal/utils/byte"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Stat struct {
|
||||
ServerId int64
|
||||
Keys []string
|
||||
Hash string
|
||||
Value int64
|
||||
Time string
|
||||
ServerId int64 `json:"serverId"`
|
||||
Keys []string `json:"keys"`
|
||||
Hash string `json:"hash"`
|
||||
Value int64 `json:"value"`
|
||||
Time string `json:"time"`
|
||||
}
|
||||
|
||||
func SumStat(serverId int64, keys []string, time string, version int32, itemId int64) string {
|
||||
func UniqueKey(serverId int64, keys []string, time string, version int32, itemId int64) string {
|
||||
var keysData = strings.Join(keys, "$EDGE$")
|
||||
return strconv.FormatUint(fnv.HashString(strconv.FormatInt(serverId, 10)+"@"+keysData+"@"+time+"@"+strconv.Itoa(int(version))+"@"+strconv.FormatInt(itemId, 10)), 10)
|
||||
}
|
||||
|
||||
func (this *Stat) UniqueKey(version int32, itemId int64) string {
|
||||
return UniqueKey(this.ServerId, this.Keys, this.Time, version, itemId)
|
||||
}
|
||||
|
||||
func (this *Stat) FullKey(version int32, itemId int64) string {
|
||||
return this.Time + "_" + string(int32ToBigEndian(version)) + this.UniqueKey(version, itemId)
|
||||
}
|
||||
|
||||
func (this *Stat) EncodeValueKey(version int32) string {
|
||||
if this.Value < 0 {
|
||||
this.Value = 0
|
||||
}
|
||||
|
||||
return string(byteutils.Concat([]byte(this.Time), []byte{'_'}, int32ToBigEndian(version), int64ToBigEndian(this.ServerId), int64ToBigEndian(this.Value), []byte(this.Hash)))
|
||||
}
|
||||
|
||||
func (this *Stat) EncodeSumKey(version int32) string {
|
||||
return string(byteutils.Concat([]byte(this.Time), []byte{'_'}, int32ToBigEndian(version), int64ToBigEndian(this.ServerId)))
|
||||
}
|
||||
|
||||
func DecodeValueKey(valueKey string) (serverId int64, timeString string, version int32, value int64, hash string, err error) {
|
||||
var b = []byte(valueKey)
|
||||
var timeIndex = bytes.Index(b, []byte{'_'})
|
||||
if timeIndex < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
timeString = string(b[:timeIndex])
|
||||
b = b[timeIndex+1:]
|
||||
|
||||
if len(b) < 20+1 {
|
||||
err = errors.New("invalid value key")
|
||||
return
|
||||
}
|
||||
|
||||
version = int32(binary.BigEndian.Uint32(b[0:4]))
|
||||
serverId = int64(binary.BigEndian.Uint64(b[4:12]))
|
||||
value = int64(binary.BigEndian.Uint64(b[12:20]))
|
||||
hash = string(b[20:])
|
||||
return
|
||||
}
|
||||
|
||||
func DecodeSumKey(sumKey string) (serverId int64, timeString string, version int32, err error) {
|
||||
var b = []byte(sumKey)
|
||||
var timeIndex = bytes.Index(b, []byte{'_'})
|
||||
if timeIndex < 0 {
|
||||
return
|
||||
}
|
||||
|
||||
timeString = string(b[:timeIndex])
|
||||
b = b[timeIndex+1:]
|
||||
|
||||
if len(b) < 12 {
|
||||
err = errors.New("invalid sum key")
|
||||
return
|
||||
}
|
||||
|
||||
version = int32(binary.BigEndian.Uint32(b[:4]))
|
||||
serverId = int64(binary.BigEndian.Uint64(b[4:12]))
|
||||
return
|
||||
}
|
||||
|
||||
func EncodeSumValue(count uint64, total uint64) []byte {
|
||||
var result [16]byte
|
||||
binary.BigEndian.PutUint64(result[:8], count)
|
||||
binary.BigEndian.PutUint64(result[8:], total)
|
||||
return result[:]
|
||||
}
|
||||
|
||||
func DecodeSumValue(data []byte) (count uint64, total uint64) {
|
||||
if len(data) != 16 {
|
||||
return
|
||||
}
|
||||
count = binary.BigEndian.Uint64(data[:8])
|
||||
total = binary.BigEndian.Uint64(data[8:])
|
||||
return
|
||||
}
|
||||
|
||||
func int64ToBigEndian(i int64) []byte {
|
||||
if i < 0 {
|
||||
i = 0
|
||||
}
|
||||
var b = make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(b, uint64(i))
|
||||
return b
|
||||
}
|
||||
|
||||
func int32ToBigEndian(i int32) []byte {
|
||||
if i < 0 {
|
||||
i = 0
|
||||
}
|
||||
var b = make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(b, uint32(i))
|
||||
return b
|
||||
}
|
||||
|
||||
69
internal/metrics/stat_test.go
Normal file
69
internal/metrics/stat_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package metrics_test
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/metrics"
|
||||
"github.com/iwind/TeaGo/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestStat_EncodeValueKey(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var stat = &metrics.Stat{
|
||||
ServerId: 1,
|
||||
Keys: []string{"${remoteAddr}"},
|
||||
Hash: "123456",
|
||||
Value: 123,
|
||||
Time: "20240101",
|
||||
}
|
||||
|
||||
var valueKey = stat.EncodeValueKey(100)
|
||||
t.Log(valueKey)
|
||||
|
||||
serverId, timeString, version, value, hash, err := metrics.DecodeValueKey(valueKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(serverId, timeString, value, version, hash)
|
||||
a.IsTrue(serverId == 1)
|
||||
a.IsTrue(timeString == "20240101")
|
||||
a.IsTrue(value == 123)
|
||||
a.IsTrue(version == 100)
|
||||
a.IsTrue(hash == "123456")
|
||||
}
|
||||
|
||||
func TestStat_EncodeSumKey(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var stat = &metrics.Stat{
|
||||
ServerId: 1,
|
||||
Keys: []string{"${remoteAddr}"},
|
||||
Hash: "123456",
|
||||
Value: 123,
|
||||
Time: "20240101",
|
||||
}
|
||||
var sumKey = stat.EncodeSumKey(100)
|
||||
t.Log(sumKey)
|
||||
|
||||
serverId, timeString, version, err := metrics.DecodeSumKey(sumKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(serverId, timeString, version)
|
||||
a.IsTrue(serverId == 1)
|
||||
a.IsTrue(timeString == "20240101")
|
||||
a.IsTrue(version == 100)
|
||||
}
|
||||
|
||||
func TestStat_EncodeSumValue(t *testing.T) {
|
||||
var a = assert.NewAssertion(t)
|
||||
|
||||
var b = metrics.EncodeSumValue(123, 456)
|
||||
t.Log(b)
|
||||
|
||||
count, sum := metrics.DecodeSumValue(b)
|
||||
a.IsTrue(count == 123)
|
||||
a.IsTrue(sum == 456)
|
||||
}
|
||||
@@ -14,7 +14,7 @@ func BenchmarkSumStat(b *testing.B) {
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
metrics.SumStat(1, []string{"1.2.3.4"}, timeutil.Format("Ymd"), 1, 1)
|
||||
metrics.UniqueKey(1, []string{"1.2.3.4"}, timeutil.Format("Ymd"), 1, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,544 +1,21 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const MaxQueueSize = 256 // TODO 可以配置,可以在单个任务里配置
|
||||
|
||||
// Task 单个指标任务
|
||||
// 数据库存储:
|
||||
//
|
||||
// data/
|
||||
// metric.$ID.db
|
||||
// stats
|
||||
// id, keys, value, time, serverId, hash
|
||||
// 原理:
|
||||
// 添加或者有变更时 isUploaded = false
|
||||
// 上传时检查 isUploaded 状态
|
||||
// 只上传每个服务中排序最前面的 N 个数据
|
||||
type Task struct {
|
||||
item *serverconfigs.MetricItemConfig
|
||||
isLoaded bool
|
||||
|
||||
db *dbs.DB
|
||||
statTableName string
|
||||
isStopped bool
|
||||
|
||||
cleanTicker *utils.Ticker
|
||||
uploadTicker *utils.Ticker
|
||||
|
||||
cleanVersion int32
|
||||
|
||||
insertStatStmt *dbs.Stmt
|
||||
deleteByVersionStmt *dbs.Stmt
|
||||
deleteByExpiresTimeStmt *dbs.Stmt
|
||||
selectTopStmt *dbs.Stmt
|
||||
sumStmt *dbs.Stmt
|
||||
|
||||
serverIdMap map[int64]zero.Zero // 所有的服务Ids
|
||||
timeMap map[string]zero.Zero // time => bool
|
||||
serverIdMapLocker sync.Mutex
|
||||
|
||||
statsMap map[string]*Stat // 待写入队列,hash => *Stat
|
||||
statsLocker sync.RWMutex
|
||||
statsTicker *utils.Ticker
|
||||
}
|
||||
|
||||
// NewTask 获取新任务
|
||||
func NewTask(item *serverconfigs.MetricItemConfig) *Task {
|
||||
return &Task{
|
||||
item: item,
|
||||
serverIdMap: map[int64]zero.Zero{},
|
||||
timeMap: map[string]zero.Zero{},
|
||||
statsMap: map[string]*Stat{},
|
||||
}
|
||||
}
|
||||
|
||||
// Init 初始化
|
||||
func (this *Task) Init() error {
|
||||
this.statTableName = "stats"
|
||||
|
||||
// 检查目录是否存在
|
||||
var dir = Tea.Root + "/data"
|
||||
_, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
err = os.MkdirAll(dir, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remotelogs.Println("METRIC", "create data dir '"+dir+"'")
|
||||
}
|
||||
|
||||
var path = dir + "/metric." + types.String(this.item.Id) + ".db"
|
||||
|
||||
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
this.db = db
|
||||
|
||||
// 恢复数据库
|
||||
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
|
||||
if len(recoverEnv) > 0 {
|
||||
for _, indexName := range []string{"serverId", "hash"} {
|
||||
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
|
||||
}
|
||||
}
|
||||
|
||||
if teaconst.EnableDBStat {
|
||||
this.db.EnableStat(true)
|
||||
}
|
||||
|
||||
//创建统计表
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.statTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"hash" varchar(32),
|
||||
"keys" varchar(1024),
|
||||
"value" real DEFAULT 0,
|
||||
"time" varchar(32),
|
||||
"serverId" integer DEFAULT 0,
|
||||
"version" integer DEFAULT 0,
|
||||
"isUploaded" integer DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "serverId"
|
||||
ON "` + this.statTableName + `" (
|
||||
"serverId" ASC,
|
||||
"version" ASC
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "hash"
|
||||
ON "` + this.statTableName + `" (
|
||||
"hash" ASC
|
||||
);`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// insert stat stmt
|
||||
this.insertStatStmt, err = db.Prepare(`INSERT INTO "stats" ("serverId", "hash", "keys", "value", "time", "version", "isUploaded") VALUES (?, ?, ?, ?, ?, ?, 0) ON CONFLICT("hash") DO UPDATE SET "value"="value"+?, "isUploaded"=0`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete by version
|
||||
this.deleteByVersionStmt, err = db.Prepare(`DELETE FROM "` + this.statTableName + `" WHERE "version"<?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete by expires time
|
||||
this.deleteByExpiresTimeStmt, err = db.Prepare(`DELETE FROM "` + this.statTableName + `" WHERE "time"<?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// select topN stmt
|
||||
this.selectTopStmt, err = db.Prepare(`SELECT "id", "hash", "keys", "value", "isUploaded" FROM "` + this.statTableName + `" WHERE "serverId"=? AND "version"=? AND time=? ORDER BY "value" DESC LIMIT 20`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// sum stmt
|
||||
this.sumStmt, err = db.Prepare(`SELECT COUNT(*), IFNULL(SUM(value), 0) FROM "` + this.statTableName + `" WHERE "serverId"=? AND "version"=? AND time=?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 所有的服务IDs
|
||||
err = this.loadServerIdMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.isLoaded = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start 启动任务
|
||||
func (this *Task) Start() error {
|
||||
// 读取数据
|
||||
this.statsTicker = utils.NewTicker(1 * time.Minute)
|
||||
goman.New(func() {
|
||||
for this.statsTicker.Next() {
|
||||
var tr = trackers.Begin("[METRIC]DUMP_STATS_TO_LOCAL_DATABASE")
|
||||
|
||||
this.statsLocker.Lock()
|
||||
var statsMap = this.statsMap
|
||||
this.statsMap = map[string]*Stat{}
|
||||
this.statsLocker.Unlock()
|
||||
|
||||
for _, stat := range statsMap {
|
||||
err := this.InsertStat(stat)
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC", "insert stat failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
tr.End()
|
||||
}
|
||||
})
|
||||
|
||||
// 清理
|
||||
this.cleanTicker = utils.NewTicker(24 * time.Hour)
|
||||
goman.New(func() {
|
||||
for this.cleanTicker.Next() {
|
||||
var tr = trackers.Begin("[METRIC]CLEAN_EXPIRED")
|
||||
err := this.CleanExpired()
|
||||
tr.End()
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC", "clean expired stats failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 上传
|
||||
this.uploadTicker = utils.NewTicker(this.item.UploadDuration())
|
||||
goman.New(func() {
|
||||
for this.uploadTicker.Next() {
|
||||
var tr = trackers.Begin("[METRIC]UPLOAD_STATS")
|
||||
err := this.Upload(1 * time.Second)
|
||||
tr.End()
|
||||
if err != nil && !rpc.IsConnError(err) {
|
||||
remotelogs.Error("METRIC", "upload stats failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add 添加数据
|
||||
func (this *Task) Add(obj MetricInterface) {
|
||||
if this.isStopped || !this.isLoaded {
|
||||
return
|
||||
}
|
||||
|
||||
var keys = []string{}
|
||||
for _, key := range this.item.Keys {
|
||||
var k = obj.MetricKey(key)
|
||||
|
||||
// 忽略499状态
|
||||
if key == "${status}" && k == "499" {
|
||||
return
|
||||
}
|
||||
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
v, ok := obj.MetricValue(this.item.Value)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var hash = SumStat(obj.MetricServerId(), keys, this.item.CurrentTime(), this.item.Version, this.item.Id)
|
||||
var countItems int
|
||||
this.statsLocker.RLock()
|
||||
oldStat, ok := this.statsMap[hash]
|
||||
if !ok {
|
||||
countItems = len(this.statsMap)
|
||||
}
|
||||
this.statsLocker.RUnlock()
|
||||
if ok {
|
||||
atomic.AddInt64(&oldStat.Value, 1)
|
||||
} else {
|
||||
// 防止过载
|
||||
if countItems < MaxQueueSize {
|
||||
this.statsLocker.Lock()
|
||||
this.statsMap[hash] = &Stat{
|
||||
ServerId: obj.MetricServerId(),
|
||||
Keys: keys,
|
||||
Value: v,
|
||||
Time: this.item.CurrentTime(),
|
||||
Hash: hash,
|
||||
}
|
||||
this.statsLocker.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop 停止任务
|
||||
func (this *Task) Stop() error {
|
||||
this.isStopped = true
|
||||
|
||||
if this.cleanTicker != nil {
|
||||
this.cleanTicker.Stop()
|
||||
}
|
||||
if this.uploadTicker != nil {
|
||||
this.uploadTicker.Stop()
|
||||
}
|
||||
if this.statsTicker != nil {
|
||||
this.statsTicker.Stop()
|
||||
}
|
||||
|
||||
_ = this.insertStatStmt.Close()
|
||||
_ = this.deleteByVersionStmt.Close()
|
||||
_ = this.deleteByExpiresTimeStmt.Close()
|
||||
_ = this.selectTopStmt.Close()
|
||||
_ = this.sumStmt.Close()
|
||||
|
||||
if this.db != nil {
|
||||
_ = this.db.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertStat 写入数据
|
||||
func (this *Task) InsertStat(stat *Stat) error {
|
||||
if this.isStopped {
|
||||
return nil
|
||||
}
|
||||
if stat == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.serverIdMapLocker.Lock()
|
||||
this.serverIdMap[stat.ServerId] = zero.New()
|
||||
this.timeMap[stat.Time] = zero.New()
|
||||
this.serverIdMapLocker.Unlock()
|
||||
|
||||
keyData, err := json.Marshal(stat.Keys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = this.insertStatStmt.Exec(stat.ServerId, stat.Hash, keyData, stat.Value, stat.Time, this.item.Version, stat.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanExpired 清理数据
|
||||
func (this *Task) CleanExpired() error {
|
||||
if this.isStopped {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 清除低版本数据
|
||||
if this.cleanVersion < this.item.Version {
|
||||
_, err := this.deleteByVersionStmt.Exec(this.item.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.cleanVersion = this.item.Version
|
||||
}
|
||||
|
||||
// 清除过期的数据
|
||||
_, err := this.deleteByExpiresTimeStmt.Exec(this.item.LocalExpiresTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upload 上传数据
|
||||
func (this *Task) Upload(pauseDuration time.Duration) error {
|
||||
if this.isStopped {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.serverIdMapLocker.Lock()
|
||||
|
||||
// 服务IDs
|
||||
var serverIds []int64
|
||||
for serverId := range this.serverIdMap {
|
||||
serverIds = append(serverIds, serverId)
|
||||
}
|
||||
this.serverIdMap = map[int64]zero.Zero{} // 清空数据
|
||||
|
||||
// 时间
|
||||
var times = []string{}
|
||||
for t := range this.timeMap {
|
||||
times = append(times, t)
|
||||
}
|
||||
this.timeMap = map[string]zero.Zero{} // 清空数据
|
||||
|
||||
this.serverIdMapLocker.Unlock()
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, serverId := range serverIds {
|
||||
for _, currentTime := range times {
|
||||
idStrings, err := func(serverId int64, currentTime string) (ids []string, err error) {
|
||||
var t = trackers.Begin("[METRIC]SELECT_TOP_STMT")
|
||||
rows, err := this.selectTopStmt.Query(serverId, this.item.Version, currentTime)
|
||||
t.End()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var isClosed bool
|
||||
defer func() {
|
||||
if isClosed {
|
||||
return
|
||||
}
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
var pbStats []*pb.UploadingMetricStat
|
||||
for rows.Next() {
|
||||
var pbStat = &pb.UploadingMetricStat{}
|
||||
// "id", "hash", "keys", "value", "isUploaded"
|
||||
var isUploaded int
|
||||
var keysData []byte
|
||||
err = rows.Scan(&pbStat.Id, &pbStat.Hash, &keysData, &pbStat.Value, &isUploaded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO 先不判断是否已经上传,需要改造API进行配合
|
||||
/**if isUploaded == 1 {
|
||||
continue
|
||||
}**/
|
||||
if len(keysData) > 0 {
|
||||
err = json.Unmarshal(keysData, &pbStat.Keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
pbStats = append(pbStats, pbStat)
|
||||
ids = append(ids, strconv.FormatInt(pbStat.Id, 10))
|
||||
}
|
||||
|
||||
// 提前关闭
|
||||
_ = rows.Close()
|
||||
isClosed = true
|
||||
|
||||
// 上传
|
||||
if len(pbStats) > 0 {
|
||||
// 计算总和
|
||||
count, total, err := this.sum(serverId, currentTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = rpcClient.MetricStatRPC.UploadMetricStats(rpcClient.Context(), &pb.UploadMetricStatsRequest{
|
||||
MetricStats: pbStats,
|
||||
Time: currentTime,
|
||||
ServerId: serverId,
|
||||
ItemId: this.item.Id,
|
||||
Version: this.item.Version,
|
||||
Count: count,
|
||||
Total: float32(total),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}(serverId, currentTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(idStrings) > 0 {
|
||||
// 设置为已上传
|
||||
// TODO 先不判断是否已经上传,需要改造API进行配合
|
||||
/**_, err = this.db.Exec(`UPDATE "` + this.statTableName + `" SET isUploaded=1 WHERE id IN (` + strings.Join(idStrings, ",") + `)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}**/
|
||||
}
|
||||
}
|
||||
|
||||
// 休息一下,防止短时间内上传数据过多
|
||||
if pauseDuration > 0 {
|
||||
time.Sleep(pauseDuration)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 加载服务ID
|
||||
func (this *Task) loadServerIdMap() error {
|
||||
{
|
||||
rows, err := this.db.Query(`SELECT DISTINCT "serverId" FROM `+this.statTableName+" WHERE version=?", this.item.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
var serverId int64
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&serverId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.serverIdMapLocker.Lock()
|
||||
this.serverIdMap[serverId] = zero.New()
|
||||
this.serverIdMapLocker.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
rows, err := this.db.Query(`SELECT DISTINCT "time" FROM `+this.statTableName+" WHERE version=?", this.item.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
var timeString string
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&timeString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.serverIdMapLocker.Lock()
|
||||
this.timeMap[timeString] = zero.New()
|
||||
this.serverIdMapLocker.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 计算数量和综合
|
||||
func (this *Task) sum(serverId int64, time string) (count int64, total float64, err error) {
|
||||
rows, err := this.sumStmt.Query(serverId, this.item.Version, time)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
if rows.Next() {
|
||||
err = rows.Scan(&count, &total)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
return
|
||||
type Task interface {
|
||||
Init() error
|
||||
Item() *serverconfigs.MetricItemConfig
|
||||
SetItem(item *serverconfigs.MetricItemConfig)
|
||||
Add(obj MetricInterface)
|
||||
InsertStat(stat *Stat) error
|
||||
Upload(pauseDuration time.Duration) error
|
||||
Start() error
|
||||
Stop() error
|
||||
Delete() error
|
||||
CleanExpired() error
|
||||
}
|
||||
|
||||
75
internal/metrics/task_base.go
Normal file
75
internal/metrics/task_base.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type BaseTask struct {
|
||||
itemConfig *serverconfigs.MetricItemConfig
|
||||
isLoaded bool
|
||||
isStopped bool
|
||||
|
||||
statsMap map[string]*Stat // 待写入队列,hash => *Stat
|
||||
statsLocker sync.RWMutex
|
||||
}
|
||||
|
||||
// Add 添加数据
|
||||
func (this *BaseTask) Add(obj MetricInterface) {
|
||||
if this.isStopped || !this.isLoaded {
|
||||
return
|
||||
}
|
||||
|
||||
var keys = []string{}
|
||||
for _, key := range this.itemConfig.Keys {
|
||||
var k = obj.MetricKey(key)
|
||||
|
||||
// 忽略499状态
|
||||
if key == "${status}" && k == "499" {
|
||||
return
|
||||
}
|
||||
|
||||
keys = append(keys, k)
|
||||
}
|
||||
|
||||
v, ok := obj.MetricValue(this.itemConfig.Value)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var hash = UniqueKey(obj.MetricServerId(), keys, this.itemConfig.CurrentTime(), this.itemConfig.Version, this.itemConfig.Id)
|
||||
var countItems int
|
||||
this.statsLocker.RLock()
|
||||
oldStat, ok := this.statsMap[hash]
|
||||
if !ok {
|
||||
countItems = len(this.statsMap)
|
||||
}
|
||||
this.statsLocker.RUnlock()
|
||||
if ok {
|
||||
atomic.AddInt64(&oldStat.Value, 1)
|
||||
} else {
|
||||
// 防止过载
|
||||
if countItems < MaxQueueSize {
|
||||
this.statsLocker.Lock()
|
||||
this.statsMap[hash] = &Stat{
|
||||
ServerId: obj.MetricServerId(),
|
||||
Keys: keys,
|
||||
Value: v,
|
||||
Time: this.itemConfig.CurrentTime(),
|
||||
Hash: hash,
|
||||
}
|
||||
this.statsLocker.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *BaseTask) Item() *serverconfigs.MetricItemConfig {
|
||||
return this.itemConfig
|
||||
}
|
||||
|
||||
func (this *BaseTask) SetItem(itemConfig *serverconfigs.MetricItemConfig) {
|
||||
this.itemConfig = itemConfig
|
||||
}
|
||||
538
internal/metrics/task_kv.go
Normal file
538
internal/metrics/task_kv.go
Normal file
@@ -0,0 +1,538 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
byteutils "github.com/TeaOSLab/EdgeNode/internal/utils/byte"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/kvstore"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/cockroachdb/pebble"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TODO sumValues不用每次insertStat的时候都保存
|
||||
|
||||
// KVTask KV存储实现的任务管理
|
||||
type KVTask struct {
|
||||
BaseTask
|
||||
|
||||
itemsTable *kvstore.Table[*Stat] // hash => *Stat
|
||||
valuesTable *kvstore.Table[[]byte] // time_version_serverId_value_hash => []byte(nil)
|
||||
sumTable *kvstore.Table[[]byte] // time_version_serverId => [count][total]
|
||||
|
||||
serverTimeMap map[string]zero.Zero // 有变更的网站 serverId_time => Zero
|
||||
serverIdMapLocker sync.Mutex
|
||||
|
||||
statsTicker *utils.Ticker
|
||||
cleanTicker *time.Ticker
|
||||
uploadTicker *utils.Ticker
|
||||
|
||||
valuesCacheMap map[string]int64 // hash => value
|
||||
}
|
||||
|
||||
func NewKVTask(itemConfig *serverconfigs.MetricItemConfig) *KVTask {
|
||||
return &KVTask{
|
||||
BaseTask: BaseTask{
|
||||
itemConfig: itemConfig,
|
||||
statsMap: map[string]*Stat{},
|
||||
},
|
||||
|
||||
serverTimeMap: map[string]zero.Zero{},
|
||||
valuesCacheMap: map[string]int64{},
|
||||
}
|
||||
}
|
||||
|
||||
func (this *KVTask) Init() error {
|
||||
store, err := kvstore.DefaultStore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db, err := store.NewDB("metrics" + types.String(this.itemConfig.Id))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
{
|
||||
table, tableErr := kvstore.NewTable[*Stat]("items", &ItemEncoder[*Stat]{})
|
||||
if tableErr != nil {
|
||||
return tableErr
|
||||
}
|
||||
db.AddTable(table)
|
||||
this.itemsTable = table
|
||||
}
|
||||
|
||||
{
|
||||
table, tableErr := kvstore.NewTable[[]byte]("values", kvstore.NewNilValueEncoder())
|
||||
if tableErr != nil {
|
||||
return tableErr
|
||||
}
|
||||
db.AddTable(table)
|
||||
this.valuesTable = table
|
||||
}
|
||||
|
||||
{
|
||||
table, tableErr := kvstore.NewTable[[]byte]("sum_values", kvstore.NewBytesValueEncoder())
|
||||
if tableErr != nil {
|
||||
return tableErr
|
||||
}
|
||||
db.AddTable(table)
|
||||
this.sumTable = table
|
||||
}
|
||||
|
||||
// 所有的服务IDs
|
||||
err = this.loadServerIdMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.isLoaded = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVTask) InsertStat(stat *Stat) error {
|
||||
if this.isStopped {
|
||||
return nil
|
||||
}
|
||||
if stat == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var version = this.itemConfig.Version
|
||||
|
||||
this.serverIdMapLocker.Lock()
|
||||
this.serverTimeMap[types.String(stat.ServerId)+"_"+stat.Time] = zero.New()
|
||||
this.serverIdMapLocker.Unlock()
|
||||
|
||||
if len(stat.Hash) == 0 {
|
||||
stat.Hash = stat.UniqueKey(version, this.itemConfig.Id)
|
||||
}
|
||||
|
||||
var isNew bool
|
||||
var newValue = stat.Value
|
||||
|
||||
// insert or update
|
||||
{
|
||||
var statKey = stat.FullKey(version, this.itemConfig.Id)
|
||||
oldStat, err := this.itemsTable.Get(statKey)
|
||||
var oldValue int64
|
||||
if err != nil {
|
||||
if !kvstore.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
isNew = true
|
||||
} else {
|
||||
oldValue = oldStat.Value
|
||||
|
||||
// delete old value from valuesTable
|
||||
err = this.valuesTable.Delete(oldStat.EncodeValueKey(version))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
oldValue += stat.Value
|
||||
stat.Value = oldValue
|
||||
err = this.itemsTable.Set(statKey, stat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// insert value into valuesTable
|
||||
err = this.valuesTable.Insert(stat.EncodeValueKey(version), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// sum
|
||||
{
|
||||
var sumKey = stat.EncodeSumKey(version)
|
||||
sumResult, err := this.sumTable.Get(sumKey)
|
||||
var count uint64
|
||||
var total uint64
|
||||
if err != nil {
|
||||
if !kvstore.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
count, total = DecodeSumValue(sumResult)
|
||||
}
|
||||
|
||||
if isNew {
|
||||
count++
|
||||
}
|
||||
total += uint64(newValue)
|
||||
|
||||
err = this.sumTable.Set(sumKey, EncodeSumValue(count, total))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVTask) Upload(pauseDuration time.Duration) error {
|
||||
var uploadTr = trackers.Begin("METRIC:UPLOAD_STATS")
|
||||
defer uploadTr.End()
|
||||
|
||||
if this.isStopped {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.serverIdMapLocker.Lock()
|
||||
|
||||
// 服务IDs
|
||||
var serverTimeMap = this.serverTimeMap
|
||||
this.serverTimeMap = map[string]zero.Zero{} // 清空数据
|
||||
|
||||
this.serverIdMapLocker.Unlock()
|
||||
|
||||
if len(serverTimeMap) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 控制缓存map不要太长
|
||||
if len(this.valuesCacheMap) > 4096 {
|
||||
var newMap = map[string]int64{}
|
||||
var countElements int
|
||||
for k, v := range this.valuesCacheMap {
|
||||
newMap[k] = v
|
||||
countElements++
|
||||
if countElements >= 2048 {
|
||||
break
|
||||
}
|
||||
}
|
||||
this.valuesCacheMap = newMap
|
||||
}
|
||||
|
||||
// 开始上传
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var totalCount int
|
||||
|
||||
for serverTime := range serverTimeMap {
|
||||
count, uploadErr := func(serverTime string) (int, error) {
|
||||
serverIdString, timeString, found := strings.Cut(serverTime, "_")
|
||||
if !found {
|
||||
return 0, nil
|
||||
}
|
||||
var serverId = types.Int64(serverIdString)
|
||||
if serverId <= 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return this.uploadServerStats(rpcClient, serverId, timeString)
|
||||
}(serverTime)
|
||||
if uploadErr != nil {
|
||||
return uploadErr
|
||||
}
|
||||
|
||||
totalCount += count
|
||||
|
||||
// 休息一下,防止短时间内上传数据过多
|
||||
if pauseDuration > 0 && totalCount >= 100 {
|
||||
time.Sleep(pauseDuration)
|
||||
uploadTr.Add(-pauseDuration)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVTask) Start() error {
|
||||
// 读取数据
|
||||
this.statsTicker = utils.NewTicker(1 * time.Minute)
|
||||
if Tea.IsTesting() {
|
||||
this.statsTicker = utils.NewTicker(10 * time.Second)
|
||||
}
|
||||
goman.New(func() {
|
||||
for this.statsTicker.Next() {
|
||||
var tr = trackers.Begin("METRIC:DUMP_STATS_TO_LOCAL_DATABASE")
|
||||
|
||||
this.statsLocker.Lock()
|
||||
var statsMap = this.statsMap
|
||||
this.statsMap = map[string]*Stat{}
|
||||
this.statsLocker.Unlock()
|
||||
|
||||
for _, stat := range statsMap {
|
||||
err := this.InsertStat(stat)
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC", "insert stat failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
tr.End()
|
||||
}
|
||||
})
|
||||
|
||||
// 清理
|
||||
this.cleanTicker = time.NewTicker(24 * time.Hour)
|
||||
goman.New(func() {
|
||||
idles.RunTicker(this.cleanTicker, func() {
|
||||
var tr = trackers.Begin("METRIC:CLEAN_EXPIRED")
|
||||
err := this.CleanExpired()
|
||||
tr.End()
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC", "clean expired stats failed: "+err.Error())
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 上传
|
||||
this.uploadTicker = utils.NewTicker(this.itemConfig.UploadDuration())
|
||||
goman.New(func() {
|
||||
for this.uploadTicker.Next() {
|
||||
err := this.Upload(1 * time.Second)
|
||||
if err != nil && !rpc.IsConnError(err) {
|
||||
remotelogs.Error("METRIC", "upload stats failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVTask) Stop() error {
|
||||
this.isStopped = true
|
||||
|
||||
if this.cleanTicker != nil {
|
||||
this.cleanTicker.Stop()
|
||||
}
|
||||
if this.uploadTicker != nil {
|
||||
this.uploadTicker.Stop()
|
||||
}
|
||||
if this.statsTicker != nil {
|
||||
this.statsTicker.Stop()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVTask) Delete() error {
|
||||
this.isStopped = true
|
||||
|
||||
return this.itemsTable.DB().Truncate()
|
||||
}
|
||||
|
||||
func (this *KVTask) CleanExpired() error {
|
||||
if this.isStopped {
|
||||
return nil
|
||||
}
|
||||
|
||||
var versionBytes = int32ToBigEndian(this.itemConfig.Version)
|
||||
var expiresTime = this.itemConfig.LocalExpiresTime()
|
||||
|
||||
var rangeEnd = append([]byte(expiresTime+"_"), versionBytes...)
|
||||
rangeEnd = append(rangeEnd, 0xFF, 0xFF)
|
||||
|
||||
{
|
||||
err := this.itemsTable.DeleteRange("", string(rangeEnd))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
err := this.valuesTable.DeleteRange("", string(rangeEnd))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
err := this.sumTable.DeleteRange("", string(rangeEnd))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *KVTask) Flush() error {
|
||||
return this.itemsTable.DB().Store().Flush()
|
||||
}
|
||||
|
||||
func (this *KVTask) TestInspect(t *testing.T) {
|
||||
var db = this.itemsTable.DB()
|
||||
it, err := db.Store().RawDB().NewIter(&pebble.IterOptions{
|
||||
LowerBound: []byte(db.Namespace()),
|
||||
UpperBound: append([]byte(db.Namespace()), 0xFF),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_ = it.Close()
|
||||
}()
|
||||
|
||||
for it.First(); it.Valid(); it.Next() {
|
||||
valueBytes, valueErr := it.ValueAndErr()
|
||||
if valueErr != nil {
|
||||
t.Fatal(valueErr)
|
||||
}
|
||||
var key = string(it.Key()[len(db.Namespace())-1:])
|
||||
t.Log(key, "=>", string(valueBytes))
|
||||
if strings.HasPrefix(key, "$values$K$") {
|
||||
_, _, _, value, hash, _ := DecodeValueKey(key[len("$values$K$"):])
|
||||
t.Log(" |", hash, "=>", value)
|
||||
} else if strings.HasPrefix(key, "$sumValues$K$") {
|
||||
count, sum := DecodeSumValue(valueBytes)
|
||||
t.Log(" |", count, sum)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *KVTask) Truncate() error {
|
||||
var db = this.itemsTable.DB()
|
||||
err := db.Truncate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Store().Flush()
|
||||
}
|
||||
|
||||
func (this *KVTask) uploadServerStats(rpcClient *rpc.RPCClient, serverId int64, currentTime string) (countValues int, uploadErr error) {
|
||||
var pbStats []*pb.UploadingMetricStat
|
||||
var keepKeys []string
|
||||
|
||||
var prefix = string(byteutils.Concat([]byte(currentTime), []byte{'_'}, int32ToBigEndian(this.itemConfig.Version), int64ToBigEndian(serverId)))
|
||||
var newCachedKeys = map[string]int64{}
|
||||
queryErr := this.valuesTable.
|
||||
Query().
|
||||
Prefix(prefix).
|
||||
Desc().
|
||||
Limit(20).
|
||||
FindAll(func(tx *kvstore.Tx[[]byte], item kvstore.Item[[]byte]) (goNext bool, err error) {
|
||||
_, _, version, value, hash, decodeErr := DecodeValueKey(item.Key)
|
||||
if decodeErr != nil {
|
||||
return false, decodeErr
|
||||
}
|
||||
if value <= 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// value not changed for the key
|
||||
if this.valuesCacheMap[hash] == value {
|
||||
keepKeys = append(keepKeys, hash)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
newCachedKeys[hash] = value
|
||||
|
||||
stat, valueErr := this.itemsTable.Get(string(byteutils.Concat([]byte(currentTime), []byte{'_'}, int32ToBigEndian(version), []byte(hash))))
|
||||
if valueErr != nil {
|
||||
if kvstore.IsNotFound(valueErr) {
|
||||
return true, nil
|
||||
}
|
||||
return false, valueErr
|
||||
}
|
||||
if stat == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
pbStats = append(pbStats, &pb.UploadingMetricStat{
|
||||
Id: 0, // not used in node
|
||||
Hash: hash,
|
||||
Keys: stat.Keys,
|
||||
Value: float32(value),
|
||||
})
|
||||
|
||||
return true, nil
|
||||
})
|
||||
if queryErr != nil {
|
||||
return 0, queryErr
|
||||
}
|
||||
|
||||
// count & total
|
||||
var count, total uint64
|
||||
{
|
||||
sumValue, err := this.sumTable.Get(prefix)
|
||||
if err != nil {
|
||||
if kvstore.IsNotFound(err) {
|
||||
return 0, nil
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
count, total = DecodeSumValue(sumValue)
|
||||
}
|
||||
|
||||
_, err := rpcClient.MetricStatRPC.UploadMetricStats(rpcClient.Context(), &pb.UploadMetricStatsRequest{
|
||||
MetricStats: pbStats,
|
||||
Time: currentTime,
|
||||
ServerId: serverId,
|
||||
ItemId: this.itemConfig.Id,
|
||||
Version: this.itemConfig.Version,
|
||||
Count: int64(count),
|
||||
Total: float32(total),
|
||||
KeepKeys: keepKeys,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// put into cache map MUST be after uploading success
|
||||
for k, v := range newCachedKeys {
|
||||
this.valuesCacheMap[k] = v
|
||||
}
|
||||
|
||||
return len(pbStats), nil
|
||||
}
|
||||
|
||||
func (this *KVTask) loadServerIdMap() error {
|
||||
var offsetKey string
|
||||
var currentTime = this.itemConfig.CurrentTime()
|
||||
for {
|
||||
var found bool
|
||||
err := this.sumTable.
|
||||
Query().
|
||||
Limit(1000).
|
||||
Offset(offsetKey).
|
||||
FindAll(func(tx *kvstore.Tx[[]byte], item kvstore.Item[[]byte]) (goNext bool, err error) {
|
||||
offsetKey = item.Key
|
||||
found = true
|
||||
|
||||
serverId, timeString, version, decodeErr := DecodeSumKey(item.Key)
|
||||
if decodeErr != nil {
|
||||
return false, decodeErr
|
||||
}
|
||||
|
||||
if version != this.itemConfig.Version || timeString != currentTime {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
this.serverIdMapLocker.Lock()
|
||||
this.serverTimeMap[types.String(serverId)+"_"+timeString] = zero.New()
|
||||
this.serverIdMapLocker.Unlock()
|
||||
|
||||
return true, nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
24
internal/metrics/task_kv_objects.go
Normal file
24
internal/metrics/task_kv_objects.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type ItemEncoder[T interface{ *Stat }] struct {
|
||||
}
|
||||
|
||||
func (this *ItemEncoder[T]) Encode(value T) ([]byte, error) {
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
func (this *ItemEncoder[T]) EncodeField(value T, fieldName string) ([]byte, error) {
|
||||
return nil, errors.New("invalid field name '" + fieldName + "'")
|
||||
}
|
||||
|
||||
func (this *ItemEncoder[T]) Decode(valueBytes []byte) (value T, err error) {
|
||||
err = json.Unmarshal(valueBytes, &value)
|
||||
return
|
||||
}
|
||||
@@ -37,8 +37,8 @@ func (this *testObj) MetricCategory() string {
|
||||
return "http"
|
||||
}
|
||||
|
||||
func TestTask_Init(t *testing.T) {
|
||||
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{
|
||||
func TestKVTask_Init(t *testing.T) {
|
||||
var task = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
Category: "",
|
||||
@@ -57,8 +57,8 @@ func TestTask_Init(t *testing.T) {
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
func TestTask_Add(t *testing.T) {
|
||||
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{
|
||||
func TestKVTask_Add(t *testing.T) {
|
||||
var task = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
Category: "",
|
||||
@@ -80,15 +80,18 @@ func TestTask_Add(t *testing.T) {
|
||||
}()
|
||||
|
||||
task.Add(&testObj{ip: "127.0.0.2"})
|
||||
time.Sleep(1 * time.Second) // waiting for inserting
|
||||
|
||||
if testutils.IsSingleTesting() {
|
||||
time.Sleep(1 * time.Second) // waiting for inserting
|
||||
}
|
||||
}
|
||||
|
||||
func TestTask_Add_Many(t *testing.T) {
|
||||
func TestKVTask_Add_Many(t *testing.T) {
|
||||
if !testutils.IsSingleTesting() {
|
||||
return
|
||||
}
|
||||
|
||||
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{
|
||||
var task = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
Category: "",
|
||||
@@ -120,7 +123,7 @@ func TestTask_Add_Many(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTask_InsertStat(t *testing.T) {
|
||||
func TestKVTask_InsertStat(t *testing.T) {
|
||||
var item = &serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
@@ -131,11 +134,19 @@ func TestTask_InsertStat(t *testing.T) {
|
||||
Value: "${countRequest}",
|
||||
Version: 1,
|
||||
}
|
||||
var task = metrics.NewTask(item)
|
||||
var task = metrics.NewKVTask(item)
|
||||
err := task.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
err = task.Flush()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
err = task.Start()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -144,21 +155,50 @@ func TestTask_InsertStat(t *testing.T) {
|
||||
_ = task.Stop()
|
||||
}()
|
||||
|
||||
err = task.InsertStat(&metrics.Stat{
|
||||
ServerId: 1,
|
||||
Keys: []string{"127.0.0.1"},
|
||||
Hash: "",
|
||||
Value: 1,
|
||||
Time: item.CurrentTime(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
{
|
||||
err = task.InsertStat(&metrics.Stat{
|
||||
ServerId: 1,
|
||||
Keys: []string{"127.0.0.1"},
|
||||
Hash: "",
|
||||
Value: 1,
|
||||
Time: item.CurrentTime(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
t.Log("ok")
|
||||
|
||||
{
|
||||
err = task.InsertStat(&metrics.Stat{
|
||||
ServerId: 2,
|
||||
Keys: []string{"127.0.0.2"},
|
||||
Hash: "",
|
||||
Value: 3,
|
||||
Time: item.CurrentTime(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
err = task.InsertStat(&metrics.Stat{
|
||||
ServerId: 1,
|
||||
Keys: []string{"127.0.0.3"},
|
||||
Hash: "",
|
||||
Value: 2,
|
||||
Time: item.CurrentTime(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
TestKVTask_TestInspect(t)
|
||||
}
|
||||
|
||||
func TestTask_CleanExpired(t *testing.T) {
|
||||
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{
|
||||
func TestKVTask_CleanExpired(t *testing.T) {
|
||||
var task = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
Category: "",
|
||||
@@ -184,12 +224,18 @@ func TestTask_CleanExpired(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("ok")
|
||||
|
||||
defer func() {
|
||||
_ = task.Flush()
|
||||
}()
|
||||
|
||||
t.Log("=== inspect ===")
|
||||
task.TestInspect(t)
|
||||
}
|
||||
|
||||
func TestTask_Upload(t *testing.T) {
|
||||
var task = metrics.NewTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
func TestKVTask_Upload(t *testing.T) {
|
||||
var task = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 31,
|
||||
IsOn: false,
|
||||
Category: "",
|
||||
Period: 1,
|
||||
@@ -218,11 +264,51 @@ func TestTask_Upload(t *testing.T) {
|
||||
t.Log("ok")
|
||||
}
|
||||
|
||||
var testingTask *metrics.Task
|
||||
func TestKVTask_TestInspect(t *testing.T) {
|
||||
var task = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
Category: "",
|
||||
Period: 1,
|
||||
PeriodUnit: serverconfigs.MetricItemPeriodUnitDay,
|
||||
Keys: []string{"${remoteAddr}"},
|
||||
Value: "${countRequest}",
|
||||
Version: 1,
|
||||
})
|
||||
err := task.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
task.TestInspect(t)
|
||||
}
|
||||
|
||||
func TestKVTask_Truncate(t *testing.T) {
|
||||
var task = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
Category: "",
|
||||
Period: 1,
|
||||
PeriodUnit: serverconfigs.MetricItemPeriodUnitDay,
|
||||
Keys: []string{"${remoteAddr}"},
|
||||
Value: "${countRequest}",
|
||||
Version: 1,
|
||||
})
|
||||
err := task.Init()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if testutils.IsSingleTesting() {
|
||||
_ = task.Truncate()
|
||||
}
|
||||
}
|
||||
|
||||
var testingTask metrics.Task
|
||||
var testingTaskInitOnce = &sync.Once{}
|
||||
|
||||
func initTestingTask() {
|
||||
testingTask = metrics.NewTask(&serverconfigs.MetricItemConfig{
|
||||
testingTask = metrics.NewKVTask(&serverconfigs.MetricItemConfig{
|
||||
Id: 1,
|
||||
IsOn: false,
|
||||
Category: "tcp",
|
||||
@@ -243,7 +329,7 @@ func initTestingTask() {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkTask_Add(b *testing.B) {
|
||||
func BenchmarkKVTask_Add(b *testing.B) {
|
||||
runtime.GOMAXPROCS(1)
|
||||
|
||||
testingTaskInitOnce.Do(func() {
|
||||
506
internal/metrics/task_sqlite.go
Normal file
506
internal/metrics/task_sqlite.go
Normal file
@@ -0,0 +1,506 @@
|
||||
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
|
||||
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/trackers"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/dbs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/idles"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const MaxQueueSize = 256 // TODO 可以配置,可以在单个任务里配置
|
||||
|
||||
// SQLiteTask 单个指标任务
|
||||
// 数据库存储:
|
||||
//
|
||||
// data/
|
||||
// metric.$ID.db
|
||||
// stats
|
||||
// id, keys, value, time, serverId, hash
|
||||
// 原理:
|
||||
// 添加或者有变更时 isUploaded = false
|
||||
// 上传时检查 isUploaded 状态
|
||||
// 只上传每个服务中排序最前面的 N 个数据
|
||||
type SQLiteTask struct {
|
||||
BaseTask
|
||||
|
||||
db *dbs.DB
|
||||
statTableName string
|
||||
|
||||
cleanTicker *time.Ticker
|
||||
uploadTicker *utils.Ticker
|
||||
|
||||
cleanVersion int32
|
||||
|
||||
insertStatStmt *dbs.Stmt
|
||||
deleteByVersionStmt *dbs.Stmt
|
||||
deleteByExpiresTimeStmt *dbs.Stmt
|
||||
selectTopStmt *dbs.Stmt
|
||||
sumStmt *dbs.Stmt
|
||||
|
||||
serverIdMap map[int64]zero.Zero // 所有的服务Ids
|
||||
timeMap map[string]zero.Zero // time => bool
|
||||
serverIdMapLocker sync.Mutex
|
||||
|
||||
statsTicker *utils.Ticker
|
||||
}
|
||||
|
||||
// NewSQLiteTask 获取新任务
|
||||
func NewSQLiteTask(item *serverconfigs.MetricItemConfig) *SQLiteTask {
|
||||
return &SQLiteTask{
|
||||
BaseTask: BaseTask{
|
||||
itemConfig: item,
|
||||
statsMap: map[string]*Stat{},
|
||||
},
|
||||
|
||||
serverIdMap: map[int64]zero.Zero{},
|
||||
timeMap: map[string]zero.Zero{},
|
||||
}
|
||||
}
|
||||
|
||||
func CheckSQLiteDB(itemId int64) bool {
|
||||
var path = Tea.Root + "/data/metric." + types.String(itemId) + ".db"
|
||||
_, err := os.Stat(path)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Init 初始化
|
||||
func (this *SQLiteTask) Init() error {
|
||||
this.statTableName = "stats"
|
||||
|
||||
// 检查目录是否存在
|
||||
var dir = Tea.Root + "/data"
|
||||
_, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
err = os.MkdirAll(dir, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
remotelogs.Println("METRIC", "create data dir '"+dir+"'")
|
||||
}
|
||||
|
||||
var path = dir + "/metric." + types.String(this.itemConfig.Id) + ".db"
|
||||
|
||||
db, err := dbs.OpenWriter("file:" + path + "?cache=shared&mode=rwc&_journal_mode=WAL&_sync=" + dbs.SyncMode + "&_locking_mode=EXCLUSIVE")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
this.db = db
|
||||
|
||||
// 恢复数据库
|
||||
var recoverEnv, _ = os.LookupEnv("EdgeRecover")
|
||||
if len(recoverEnv) > 0 {
|
||||
for _, indexName := range []string{"serverId", "hash"} {
|
||||
_, _ = db.Exec(`REINDEX "` + indexName + `"`)
|
||||
}
|
||||
}
|
||||
|
||||
if teaconst.EnableDBStat {
|
||||
this.db.EnableStat(true)
|
||||
}
|
||||
|
||||
//创建统计表
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS "` + this.statTableName + `" (
|
||||
"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
"hash" varchar(32),
|
||||
"keys" varchar(1024),
|
||||
"value" real DEFAULT 0,
|
||||
"time" varchar(32),
|
||||
"serverId" integer DEFAULT 0,
|
||||
"version" integer DEFAULT 0,
|
||||
"isUploaded" integer DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "serverId"
|
||||
ON "` + this.statTableName + `" (
|
||||
"serverId" ASC,
|
||||
"version" ASC
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "hash"
|
||||
ON "` + this.statTableName + `" (
|
||||
"hash" ASC
|
||||
);`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// insert stat stmt
|
||||
this.insertStatStmt, err = db.Prepare(`INSERT INTO "stats" ("serverId", "hash", "keys", "value", "time", "version", "isUploaded") VALUES (?, ?, ?, ?, ?, ?, 0) ON CONFLICT("hash") DO UPDATE SET "value"="value"+?, "isUploaded"=0`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete by version
|
||||
this.deleteByVersionStmt, err = db.Prepare(`DELETE FROM "` + this.statTableName + `" WHERE "version"<?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// delete by expires time
|
||||
this.deleteByExpiresTimeStmt, err = db.Prepare(`DELETE FROM "` + this.statTableName + `" WHERE "time"<?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// select topN stmt
|
||||
this.selectTopStmt, err = db.Prepare(`SELECT "id", "hash", "keys", "value", "isUploaded" FROM "` + this.statTableName + `" WHERE "serverId"=? AND "version"=? AND time=? ORDER BY "value" DESC LIMIT 20`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// sum stmt
|
||||
this.sumStmt, err = db.Prepare(`SELECT COUNT(*), IFNULL(SUM(value), 0) FROM "` + this.statTableName + `" WHERE "serverId"=? AND "version"=? AND time=?`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 所有的服务IDs
|
||||
err = this.loadServerIdMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
this.isLoaded = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start 启动任务
|
||||
func (this *SQLiteTask) Start() error {
|
||||
// 读取数据
|
||||
this.statsTicker = utils.NewTicker(1 * time.Minute)
|
||||
goman.New(func() {
|
||||
for this.statsTicker.Next() {
|
||||
var tr = trackers.Begin("METRIC:DUMP_STATS_TO_LOCAL_DATABASE")
|
||||
|
||||
this.statsLocker.Lock()
|
||||
var statsMap = this.statsMap
|
||||
this.statsMap = map[string]*Stat{}
|
||||
this.statsLocker.Unlock()
|
||||
|
||||
for _, stat := range statsMap {
|
||||
err := this.InsertStat(stat)
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC", "insert stat failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
tr.End()
|
||||
}
|
||||
})
|
||||
|
||||
// 清理
|
||||
this.cleanTicker = time.NewTicker(24 * time.Hour)
|
||||
goman.New(func() {
|
||||
idles.RunTicker(this.cleanTicker, func() {
|
||||
var tr = trackers.Begin("METRIC:CLEAN_EXPIRED")
|
||||
err := this.CleanExpired()
|
||||
tr.End()
|
||||
if err != nil {
|
||||
remotelogs.Error("METRIC", "clean expired stats failed: "+err.Error())
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 上传
|
||||
this.uploadTicker = utils.NewTicker(this.itemConfig.UploadDuration())
|
||||
goman.New(func() {
|
||||
for this.uploadTicker.Next() {
|
||||
err := this.Upload(1 * time.Second)
|
||||
if err != nil && !rpc.IsConnError(err) {
|
||||
remotelogs.Error("METRIC", "upload stats failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop 停止任务
|
||||
func (this *SQLiteTask) Stop() error {
|
||||
this.isStopped = true
|
||||
|
||||
if this.cleanTicker != nil {
|
||||
this.cleanTicker.Stop()
|
||||
}
|
||||
if this.uploadTicker != nil {
|
||||
this.uploadTicker.Stop()
|
||||
}
|
||||
if this.statsTicker != nil {
|
||||
this.statsTicker.Stop()
|
||||
}
|
||||
|
||||
_ = this.insertStatStmt.Close()
|
||||
_ = this.deleteByVersionStmt.Close()
|
||||
_ = this.deleteByExpiresTimeStmt.Close()
|
||||
_ = this.selectTopStmt.Close()
|
||||
_ = this.sumStmt.Close()
|
||||
|
||||
if this.db != nil {
|
||||
_ = this.db.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *SQLiteTask) Delete() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// InsertStat 写入数据
|
||||
func (this *SQLiteTask) InsertStat(stat *Stat) error {
|
||||
if this.isStopped {
|
||||
return nil
|
||||
}
|
||||
if stat == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.serverIdMapLocker.Lock()
|
||||
this.serverIdMap[stat.ServerId] = zero.New()
|
||||
this.timeMap[stat.Time] = zero.New()
|
||||
this.serverIdMapLocker.Unlock()
|
||||
|
||||
keyData, err := json.Marshal(stat.Keys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = this.insertStatStmt.Exec(stat.ServerId, stat.Hash, keyData, stat.Value, stat.Time, this.itemConfig.Version, stat.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CleanExpired 清理数据
|
||||
func (this *SQLiteTask) CleanExpired() error {
|
||||
if this.isStopped {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 清除低版本数据
|
||||
if this.cleanVersion < this.itemConfig.Version {
|
||||
_, err := this.deleteByVersionStmt.Exec(this.itemConfig.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.cleanVersion = this.itemConfig.Version
|
||||
}
|
||||
|
||||
// 清除过期的数据
|
||||
_, err := this.deleteByExpiresTimeStmt.Exec(this.itemConfig.LocalExpiresTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Upload 上传数据
|
||||
func (this *SQLiteTask) Upload(pauseDuration time.Duration) error {
|
||||
var uploadTr = trackers.Begin("METRIC:UPLOAD_STATS")
|
||||
defer uploadTr.End()
|
||||
|
||||
if this.isStopped {
|
||||
return nil
|
||||
}
|
||||
|
||||
this.serverIdMapLocker.Lock()
|
||||
|
||||
// 服务IDs
|
||||
var serverIds []int64
|
||||
for serverId := range this.serverIdMap {
|
||||
serverIds = append(serverIds, serverId)
|
||||
}
|
||||
this.serverIdMap = map[int64]zero.Zero{} // 清空数据
|
||||
|
||||
// 时间
|
||||
var times = []string{}
|
||||
for t := range this.timeMap {
|
||||
times = append(times, t)
|
||||
}
|
||||
this.timeMap = map[string]zero.Zero{} // 清空数据
|
||||
|
||||
this.serverIdMapLocker.Unlock()
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, serverId := range serverIds {
|
||||
for _, currentTime := range times {
|
||||
idStrings, err := func(serverId int64, currentTime string) (ids []string, err error) {
|
||||
var t = trackers.Begin("METRIC:SELECT_TOP_STMT")
|
||||
rows, err := this.selectTopStmt.Query(serverId, this.itemConfig.Version, currentTime)
|
||||
t.End()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var isClosed bool
|
||||
defer func() {
|
||||
if isClosed {
|
||||
return
|
||||
}
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
var pbStats []*pb.UploadingMetricStat
|
||||
for rows.Next() {
|
||||
var pbStat = &pb.UploadingMetricStat{}
|
||||
// "id", "hash", "keys", "value", "isUploaded"
|
||||
var isUploaded int
|
||||
var keysData []byte
|
||||
err = rows.Scan(&pbStat.Id, &pbStat.Hash, &keysData, &pbStat.Value, &isUploaded)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO 先不判断是否已经上传,需要改造API进行配合
|
||||
/**if isUploaded == 1 {
|
||||
continue
|
||||
}**/
|
||||
if len(keysData) > 0 {
|
||||
err = json.Unmarshal(keysData, &pbStat.Keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
pbStats = append(pbStats, pbStat)
|
||||
ids = append(ids, strconv.FormatInt(pbStat.Id, 10))
|
||||
}
|
||||
|
||||
// 提前关闭
|
||||
_ = rows.Close()
|
||||
isClosed = true
|
||||
|
||||
// 上传
|
||||
if len(pbStats) > 0 {
|
||||
// 计算总和
|
||||
count, total, err := this.sum(serverId, currentTime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = rpcClient.MetricStatRPC.UploadMetricStats(rpcClient.Context(), &pb.UploadMetricStatsRequest{
|
||||
MetricStats: pbStats,
|
||||
Time: currentTime,
|
||||
ServerId: serverId,
|
||||
ItemId: this.itemConfig.Id,
|
||||
Version: this.itemConfig.Version,
|
||||
Count: count,
|
||||
Total: float32(total),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}(serverId, currentTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(idStrings) > 0 {
|
||||
// 设置为已上传
|
||||
// TODO 先不判断是否已经上传,需要改造API进行配合
|
||||
/**_, err = this.db.Exec(`UPDATE "` + this.statTableName + `" SET isUploaded=1 WHERE id IN (` + strings.Join(idStrings, ",") + `)`)
|
||||
if err != nil {
|
||||
return err
|
||||
}**/
|
||||
}
|
||||
}
|
||||
|
||||
// 休息一下,防止短时间内上传数据过多
|
||||
if pauseDuration > 0 {
|
||||
time.Sleep(pauseDuration)
|
||||
uploadTr.Add(-pauseDuration)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 加载服务ID
|
||||
func (this *SQLiteTask) loadServerIdMap() error {
|
||||
{
|
||||
rows, err := this.db.Query(`SELECT DISTINCT "serverId" FROM `+this.statTableName+" WHERE version=?", this.itemConfig.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
var serverId int64
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&serverId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.serverIdMapLocker.Lock()
|
||||
this.serverIdMap[serverId] = zero.New()
|
||||
this.serverIdMapLocker.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
rows, err := this.db.Query(`SELECT DISTINCT "time" FROM `+this.statTableName+" WHERE version=?", this.itemConfig.Version)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
|
||||
var timeString string
|
||||
for rows.Next() {
|
||||
err = rows.Scan(&timeString)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
this.serverIdMapLocker.Lock()
|
||||
this.timeMap[timeString] = zero.New()
|
||||
this.serverIdMapLocker.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 计算数量和综合
|
||||
func (this *SQLiteTask) sum(serverId int64, time string) (count int64, total float64, err error) {
|
||||
rows, err := this.sumStmt.Query(serverId, this.itemConfig.Version, time)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
defer func() {
|
||||
_ = rows.Close()
|
||||
}()
|
||||
if rows.Next() {
|
||||
err = rows.Scan(&count, &total)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -282,14 +282,14 @@ func (this *APIStream) handleStatCache(message *pb.NodeStreamMessage) error {
|
||||
}
|
||||
|
||||
sizeFormat := ""
|
||||
if stat.Size < 1024 {
|
||||
if stat.Size < (1 << 10) {
|
||||
sizeFormat = strconv.FormatInt(stat.Size, 10) + " Bytes"
|
||||
} else if stat.Size < 1024*1024 {
|
||||
sizeFormat = fmt.Sprintf("%.2f KB", float64(stat.Size)/1024)
|
||||
} else if stat.Size < 1024*1024*1024 {
|
||||
sizeFormat = fmt.Sprintf("%.2f MB", float64(stat.Size)/1024/1024)
|
||||
} else if stat.Size < (1 << 20) {
|
||||
sizeFormat = fmt.Sprintf("%.2f KiB", float64(stat.Size)/(1<<10))
|
||||
} else if stat.Size < (1 << 30) {
|
||||
sizeFormat = fmt.Sprintf("%.2f MiB", float64(stat.Size)/(1<<20))
|
||||
} else {
|
||||
sizeFormat = fmt.Sprintf("%.2f GB", float64(stat.Size)/1024/1024/1024)
|
||||
sizeFormat = fmt.Sprintf("%.2f GiB", float64(stat.Size)/(1<<30))
|
||||
}
|
||||
this.replyOk(message.RequestId, "size:"+sizeFormat+", count:"+strconv.Itoa(stat.Count))
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"strings"
|
||||
@@ -26,7 +27,7 @@ type HTTPAccessLogQueue struct {
|
||||
// NewHTTPAccessLogQueue 获取新对象
|
||||
func NewHTTPAccessLogQueue() *HTTPAccessLogQueue {
|
||||
// 队列中最大的值,超出此数量的访问日志会被丢弃
|
||||
var maxSize = 2_000 * (1 + utils.SystemMemoryGB()/2)
|
||||
var maxSize = 2_000 * (1 + memutils.SystemMemoryGB()/2)
|
||||
if maxSize > 20_000 {
|
||||
maxSize = 20_000
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
connutils "github.com/TeaOSLab/EdgeNode/internal/utils/conns"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"io"
|
||||
@@ -245,7 +246,9 @@ func (this *HTTPCacheTaskManager) fetchKey(key *pb.HTTPCacheTaskKey) error {
|
||||
}
|
||||
|
||||
// 读取内容,以便于生成缓存
|
||||
_, err = io.Copy(io.Discard, resp.Body)
|
||||
var buf = utils.BytePool16k.Get()
|
||||
_, err = io.CopyBuffer(io.Discard, resp.Body, buf.Bytes)
|
||||
utils.BytePool16k.Put(buf)
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
err = this.simplifyErr(err)
|
||||
|
||||
@@ -7,15 +7,17 @@ import (
|
||||
|
||||
// HTTPClient HTTP客户端
|
||||
type HTTPClient struct {
|
||||
rawClient *http.Client
|
||||
accessAt int64
|
||||
rawClient *http.Client
|
||||
accessAt int64
|
||||
isProxyProtocol bool
|
||||
}
|
||||
|
||||
// NewHTTPClient 获取新客户端对象
|
||||
func NewHTTPClient(rawClient *http.Client) *HTTPClient {
|
||||
func NewHTTPClient(rawClient *http.Client, isProxyProtocol bool) *HTTPClient {
|
||||
return &HTTPClient{
|
||||
rawClient: rawClient,
|
||||
accessAt: fasttime.Now().Unix(),
|
||||
rawClient: rawClient,
|
||||
accessAt: fasttime.Now().Unix(),
|
||||
isProxyProtocol: isProxyProtocol,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +36,11 @@ func (this *HTTPClient) AccessTime() int64 {
|
||||
return this.accessAt
|
||||
}
|
||||
|
||||
// IsProxyProtocol 判断是否为PROXY Protocol
|
||||
func (this *HTTPClient) IsProxyProtocol() bool {
|
||||
return this.isProxyProtocol
|
||||
}
|
||||
|
||||
// Close 关闭
|
||||
func (this *HTTPClient) Close() {
|
||||
this.rawClient.CloseIdleConnections()
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/pires/go-proxyproto"
|
||||
"golang.org/x/net/http2"
|
||||
"net"
|
||||
@@ -22,10 +23,11 @@ import (
|
||||
var SharedHTTPClientPool = NewHTTPClientPool()
|
||||
|
||||
const httpClientProxyProtocolTag = "@ProxyProtocol@"
|
||||
const maxHTTPRedirects = 8
|
||||
|
||||
// HTTPClientPool 客户端池
|
||||
type HTTPClientPool struct {
|
||||
clientsMap map[string]*HTTPClient // backend key => client
|
||||
clientsMap map[uint64]*HTTPClient // origin key => client
|
||||
|
||||
cleanTicker *time.Ticker
|
||||
|
||||
@@ -36,7 +38,7 @@ type HTTPClientPool struct {
|
||||
func NewHTTPClientPool() *HTTPClientPool {
|
||||
var pool = &HTTPClientPool{
|
||||
cleanTicker: time.NewTicker(1 * time.Hour),
|
||||
clientsMap: map[string]*HTTPClient{},
|
||||
clientsMap: map[uint64]*HTTPClient{},
|
||||
}
|
||||
|
||||
goman.New(func() {
|
||||
@@ -56,15 +58,38 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
||||
return nil, errors.New("origin addr should not be empty (originId:" + strconv.FormatInt(origin.Id, 10) + ")")
|
||||
}
|
||||
|
||||
var key = origin.UniqueKey() + "@" + originAddr
|
||||
if req == nil || req.RawReq == nil || req.RawReq.URL == nil {
|
||||
err = errors.New("invalid request url")
|
||||
return
|
||||
}
|
||||
var originHost = req.RawReq.URL.Host
|
||||
var urlPort = req.RawReq.URL.Port()
|
||||
if len(urlPort) == 0 {
|
||||
if req.RawReq.URL.Scheme == "http" {
|
||||
urlPort = "80"
|
||||
} else {
|
||||
urlPort = "443"
|
||||
}
|
||||
|
||||
originHost = originHost + ":" + urlPort
|
||||
}
|
||||
|
||||
var rawKey = origin.UniqueKey() + "@" + originAddr + "@" + originHost
|
||||
|
||||
// if we are under available ProxyProtocol, we add client ip to key to make every client unique
|
||||
var isProxyProtocol = false
|
||||
if proxyProtocol != nil && proxyProtocol.IsOn {
|
||||
key += httpClientProxyProtocolTag + req.requestRemoteAddr(true)
|
||||
rawKey += httpClientProxyProtocolTag + req.requestRemoteAddr(true)
|
||||
isProxyProtocol = true
|
||||
}
|
||||
|
||||
// follow redirects
|
||||
if followRedirects {
|
||||
rawKey += "@follow"
|
||||
}
|
||||
|
||||
var key = xxhash.Sum64String(rawKey)
|
||||
|
||||
var isLnRequest = origin.Id == 0
|
||||
|
||||
this.locker.RLock()
|
||||
@@ -142,20 +167,27 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
||||
|
||||
var transport = &HTTPClientTransport{
|
||||
Transport: &http.Transport{
|
||||
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
// 普通的连接
|
||||
conn, err := (&net.Dialer{
|
||||
Timeout: connectionTimeout,
|
||||
KeepAlive: 1 * time.Minute,
|
||||
}).DialContext(ctx, network, originAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) {
|
||||
var realAddr = originAddr
|
||||
|
||||
// for redirections
|
||||
if followRedirects && originHost != addr {
|
||||
realAddr = addr
|
||||
}
|
||||
|
||||
// 处理PROXY protocol
|
||||
err = this.handlePROXYProtocol(conn, req, proxyProtocol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// connect
|
||||
conn, dialErr := (&net.Dialer{
|
||||
Timeout: connectionTimeout,
|
||||
KeepAlive: 1 * time.Minute,
|
||||
}).DialContext(ctx, network, realAddr)
|
||||
if dialErr != nil {
|
||||
return nil, dialErr
|
||||
}
|
||||
|
||||
// handle PROXY protocol
|
||||
proxyErr := this.handlePROXYProtocol(conn, req, proxyProtocol)
|
||||
if proxyErr != nil {
|
||||
return nil, proxyErr
|
||||
}
|
||||
|
||||
return NewOriginConn(conn), nil
|
||||
@@ -181,25 +213,16 @@ func (this *HTTPClientPool) Client(req *HTTPRequest,
|
||||
Timeout: readTimeout,
|
||||
Transport: transport,
|
||||
CheckRedirect: func(targetReq *http.Request, via []*http.Request) error {
|
||||
// 是否跟随
|
||||
if followRedirects {
|
||||
var schemeIsSame = true
|
||||
for _, r := range via {
|
||||
if r.URL.Scheme != targetReq.URL.Scheme {
|
||||
schemeIsSame = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if schemeIsSame {
|
||||
return nil
|
||||
}
|
||||
// follow redirects
|
||||
if followRedirects && len(via) <= maxHTTPRedirects {
|
||||
return nil
|
||||
}
|
||||
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
|
||||
this.clientsMap[key] = NewHTTPClient(rawClient)
|
||||
this.clientsMap[key] = NewHTTPClient(rawClient, isProxyProtocol)
|
||||
|
||||
return rawClient, nil
|
||||
}
|
||||
@@ -209,14 +232,14 @@ func (this *HTTPClientPool) cleanClients() {
|
||||
for range this.cleanTicker.C {
|
||||
var nowTime = fasttime.Now().Unix()
|
||||
|
||||
var expiredKeys = []string{}
|
||||
var expiredKeys []uint64
|
||||
var expiredClients = []*HTTPClient{}
|
||||
|
||||
// lookup expired clients
|
||||
this.locker.RLock()
|
||||
for k, client := range this.clientsMap {
|
||||
if client.AccessTime() < nowTime-86400 ||
|
||||
(strings.Contains(k, httpClientProxyProtocolTag) && client.AccessTime() < nowTime-3600) { // 超过 N 秒没有调用就关闭
|
||||
(client.IsProxyProtocol() && client.AccessTime() < nowTime-3600) { // 超过 N 秒没有调用就关闭
|
||||
expiredKeys = append(expiredKeys, k)
|
||||
expiredClients = append(expiredClients, client)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/testutils"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -12,6 +13,13 @@ import (
|
||||
func TestHTTPClientPool_Client(t *testing.T) {
|
||||
var pool = NewHTTPClientPool()
|
||||
|
||||
rawReq, err := http.NewRequest(http.MethodGet, "https://example.com/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var req = &HTTPRequest{RawReq: rawReq}
|
||||
|
||||
{
|
||||
var origin = &serverconfigs.OriginConfig{
|
||||
Id: 1,
|
||||
@@ -23,14 +31,14 @@ func TestHTTPClientPool_Client(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
{
|
||||
client, err := pool.Client(nil, origin, origin.Addr.PickAddress(), nil, false)
|
||||
client, err := pool.Client(req, origin, origin.Addr.PickAddress(), nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("client:", client)
|
||||
}
|
||||
for i := 0; i < 10; i++ {
|
||||
client, err := pool.Client(nil, origin, origin.Addr.PickAddress(), nil, false)
|
||||
client, err := pool.Client(req, origin, origin.Addr.PickAddress(), nil, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ type HTTPRequest struct {
|
||||
isHealthCheck bool
|
||||
|
||||
// 共享参数
|
||||
// DO NOT change the variable after created
|
||||
nodeConfig *nodeconfigs.NodeConfig
|
||||
|
||||
// ln request
|
||||
@@ -453,6 +454,9 @@ func (this *HTTPRequest) doEnd() {
|
||||
|
||||
stats.SharedTrafficStatManager.Add(this.ReqServer.UserId, this.ReqServer.Id, this.ReqHost, totalBytes, cachedBytes, 1, countCached, countAttacks, attackBytes, countWebsocketConnections, this.ReqServer.ShouldCheckTrafficLimit(), this.ReqServer.PlanId())
|
||||
|
||||
// unique IP
|
||||
stats.SharedDAUManager.AddIP(this.ReqServer.Id, this.requestRemoteAddr(true))
|
||||
|
||||
// 指标
|
||||
if metrics.SharedManager.HasHTTPMetrics() {
|
||||
this.doMetricsResponse()
|
||||
|
||||
@@ -256,11 +256,9 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
if this.web.Compression != nil && this.web.Compression.IsOn {
|
||||
_, encoding, ok := this.web.Compression.MatchAcceptEncoding(this.RawReq.Header.Get("Accept-Encoding"))
|
||||
if ok {
|
||||
if reader == nil {
|
||||
reader, _ = storage.OpenReader(key+caches.SuffixCompression+encoding, useStale, false)
|
||||
if reader != nil {
|
||||
tags = append(tags, encoding)
|
||||
}
|
||||
reader, _ = storage.OpenReader(key+caches.SuffixCompression+encoding, useStale, false)
|
||||
if reader != nil {
|
||||
tags = append(tags, encoding)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,7 +277,7 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
}
|
||||
|
||||
if len(rangeHeader) > 0 {
|
||||
pReader, ranges := this.tryPartialReader(storage, key, useStale, rangeHeader)
|
||||
pReader, ranges := this.tryPartialReader(storage, key, useStale, rangeHeader, this.cacheRef.ForcePartialContent)
|
||||
if pReader != nil {
|
||||
isPartialCache = true
|
||||
reader = pReader
|
||||
@@ -342,8 +340,8 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
this.writer.SetSentHeaderBytes(reader.HeaderSize())
|
||||
var headerPool = this.bytePool(reader.HeaderSize())
|
||||
var headerBuf = headerPool.Get()
|
||||
err = reader.ReadHeader(headerBuf, func(n int) (goNext bool, readErr error) {
|
||||
headerData = append(headerData, headerBuf[:n]...)
|
||||
err = reader.ReadHeader(headerBuf.Bytes, func(n int) (goNext bool, readErr error) {
|
||||
headerData = append(headerData, headerBuf.Bytes[:n]...)
|
||||
for {
|
||||
var nIndex = bytes.Index(headerData, []byte{'\n'})
|
||||
if nIndex >= 0 {
|
||||
@@ -501,8 +499,8 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
|
||||
var pool = this.bytePool(fileSize)
|
||||
var bodyBuf = pool.Get()
|
||||
err = reader.ReadBodyRange(bodyBuf, ranges[0].Start(), ranges[0].End(), func(n int) (goNext bool, readErr error) {
|
||||
_, readErr = this.writer.Write(bodyBuf[:n])
|
||||
err = reader.ReadBodyRange(bodyBuf.Bytes, ranges[0].Start(), ranges[0].End(), func(n int) (goNext bool, readErr error) {
|
||||
_, readErr = this.writer.Write(bodyBuf.Bytes[:n])
|
||||
if readErr != nil {
|
||||
return false, errWritingToClient
|
||||
}
|
||||
@@ -557,8 +555,8 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
|
||||
var pool = this.bytePool(fileSize)
|
||||
var bodyBuf = pool.Get()
|
||||
err = reader.ReadBodyRange(bodyBuf, r.Start(), r.End(), func(n int) (goNext bool, readErr error) {
|
||||
_, readErr = this.writer.Write(bodyBuf[:n])
|
||||
err = reader.ReadBodyRange(bodyBuf.Bytes, r.Start(), r.End(), func(n int) (goNext bool, readErr error) {
|
||||
_, readErr = this.writer.Write(bodyBuf.Bytes[:n])
|
||||
if readErr != nil {
|
||||
return false, errWritingToClient
|
||||
}
|
||||
@@ -588,18 +586,27 @@ func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
|
||||
this.writer.Prepare(resp, fileSize, reader.Status(), false)
|
||||
this.writer.WriteHeader(reader.Status())
|
||||
|
||||
var pool = this.bytePool(fileSize)
|
||||
var bodyBuf = pool.Get()
|
||||
if storage.CanSendfile() {
|
||||
var pool = this.bytePool(fileSize)
|
||||
var bodyBuf = pool.Get()
|
||||
if fp, canSendFile := this.writer.canSendfile(); canSendFile {
|
||||
this.writer.sentBodyBytes, err = io.CopyBuffer(this.writer.rawWriter, fp, bodyBuf)
|
||||
this.writer.sentBodyBytes, err = io.CopyBuffer(this.writer.rawWriter, fp, bodyBuf.Bytes)
|
||||
} else {
|
||||
_, err = io.CopyBuffer(this.writer, resp.Body, bodyBuf)
|
||||
_, err = io.CopyBuffer(this.writer, resp.Body, bodyBuf.Bytes)
|
||||
}
|
||||
pool.Put(bodyBuf)
|
||||
} else {
|
||||
_, err = io.CopyBuffer(this.writer, resp.Body, bodyBuf)
|
||||
mmapReader, isMMAPReader := reader.(*caches.MMAPFileReader)
|
||||
if isMMAPReader {
|
||||
_, err = mmapReader.CopyBodyTo(this.writer)
|
||||
} else {
|
||||
var pool = this.bytePool(fileSize)
|
||||
var bodyBuf = pool.Get()
|
||||
_, err = io.CopyBuffer(this.writer, resp.Body, bodyBuf.Bytes)
|
||||
pool.Put(bodyBuf)
|
||||
}
|
||||
}
|
||||
pool.Put(bodyBuf)
|
||||
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
@@ -641,7 +648,7 @@ func (this *HTTPRequest) addExpiresHeader(expiresAt int64) {
|
||||
}
|
||||
|
||||
// 尝试读取区间缓存
|
||||
func (this *HTTPRequest) tryPartialReader(storage caches.StorageInterface, key string, useStale bool, rangeHeader string) (caches.Reader, []rangeutils.Range) {
|
||||
func (this *HTTPRequest) tryPartialReader(storage caches.StorageInterface, key string, useStale bool, rangeHeader string, forcePartialContent bool) (caches.Reader, []rangeutils.Range) {
|
||||
// 尝试读取Partial cache
|
||||
if len(rangeHeader) == 0 {
|
||||
return nil, nil
|
||||
@@ -669,15 +676,17 @@ func (this *HTTPRequest) tryPartialReader(storage caches.StorageInterface, key s
|
||||
}
|
||||
}()
|
||||
|
||||
// 检查是否已下载完整
|
||||
if !forcePartialContent &&
|
||||
len(ranges) > 0 &&
|
||||
ranges[0][1] < 0 &&
|
||||
!partialReader.IsCompleted() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// 检查范围
|
||||
//const maxFirstSpan = 16 << 20 // TODO 可以在缓存策略中设置此值
|
||||
// 这里 **切记不要** 为末尾位置指定一个中间值,因为部分软件客户端不支持
|
||||
for index, r := range ranges {
|
||||
// 没有指定结束位置时,自动指定一个
|
||||
/**if r.Start() >= 0 && r.End() == -1 {
|
||||
if partialReader.MaxLength() > r.Start()+maxFirstSpan {
|
||||
r[1] = r.Start() + maxFirstSpan
|
||||
}
|
||||
}**/
|
||||
r1, ok := r.Convert(partialReader.MaxLength())
|
||||
if !ok {
|
||||
return nil, nil
|
||||
|
||||
@@ -206,9 +206,9 @@ func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
|
||||
this.writer.WriteHeader(resp.StatusCode)
|
||||
|
||||
// 输出到客户端
|
||||
pool := this.bytePool(resp.ContentLength)
|
||||
buf := pool.Get()
|
||||
_, err = io.CopyBuffer(this.writer, resp.Body, buf)
|
||||
var pool = this.bytePool(resp.ContentLength)
|
||||
var buf = pool.Get()
|
||||
_, err = io.CopyBuffer(this.writer, resp.Body, buf.Bytes)
|
||||
pool.Put(buf)
|
||||
|
||||
closeErr := resp.Body.Close()
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
const (
|
||||
// AccessLogMaxRequestBodySize 访问日志存储的请求内容最大尺寸 TODO 此值应该可以在访问日志页设置
|
||||
AccessLogMaxRequestBodySize = 2 * 1024 * 1024
|
||||
AccessLogMaxRequestBodySize = 2 << 20
|
||||
)
|
||||
|
||||
// 日志
|
||||
|
||||
@@ -104,7 +104,7 @@ func (this *HTTPRequest) doPageLookup(pages []*serverconfigs.HTTPPageConfig, sta
|
||||
this.writer.WriteHeader(status)
|
||||
}
|
||||
var buf = utils.BytePool1k.Get()
|
||||
_, err = utils.CopyWithFilter(this.writer, fp, buf, func(p []byte) []byte {
|
||||
_, err = utils.CopyWithFilter(this.writer, fp, buf.Bytes, func(p []byte) []byte {
|
||||
return []byte(this.Format(string(p)))
|
||||
})
|
||||
utils.BytePool1k.Put(buf)
|
||||
|
||||
@@ -12,6 +12,11 @@ func (this *HTTPRequest) doCheckReferers() (shouldStop bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查URL
|
||||
if !this.web.Referers.MatchURL(this.URL()) {
|
||||
return
|
||||
}
|
||||
|
||||
var origin = this.RawReq.Header.Get("Origin")
|
||||
|
||||
const cacheSeconds = "3600" // 时间不能过长,防止修改设置后长期无法生效
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/compressions"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/utils/fnv"
|
||||
@@ -289,8 +290,44 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
this.RawReq.URL.Scheme = "http"
|
||||
}
|
||||
|
||||
// request origin with Accept-Encoding: gzip, ...
|
||||
var rawAcceptEncoding string
|
||||
var acceptEncodingChanged bool
|
||||
if this.nodeConfig != nil &&
|
||||
this.nodeConfig.GlobalServerConfig != nil &&
|
||||
this.nodeConfig.GlobalServerConfig.HTTPAll.RequestOriginsWithEncodings &&
|
||||
this.RawReq.ProtoAtLeast(1, 1) &&
|
||||
this.RawReq.Header != nil {
|
||||
rawAcceptEncoding = this.RawReq.Header.Get("Accept-Encoding")
|
||||
if len(rawAcceptEncoding) == 0 {
|
||||
this.RawReq.Header.Set("Accept-Encoding", "gzip")
|
||||
acceptEncodingChanged = true
|
||||
} else if strings.Index(rawAcceptEncoding, "gzip") < 0 {
|
||||
this.RawReq.Header.Set("Accept-Encoding", rawAcceptEncoding+", gzip")
|
||||
acceptEncodingChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
// 开始请求
|
||||
resp, requestErr = client.Do(this.RawReq)
|
||||
|
||||
// recover Accept-Encoding
|
||||
if acceptEncodingChanged {
|
||||
if len(rawAcceptEncoding) > 0 {
|
||||
this.RawReq.Header.Set("Accept-Encoding", rawAcceptEncoding)
|
||||
} else {
|
||||
this.RawReq.Header.Del("Accept-Encoding")
|
||||
}
|
||||
|
||||
if resp != nil && resp.Header != nil && resp.Header.Get("Content-Encoding") == "gzip" {
|
||||
bodyReader, gzipErr := compressions.NewGzipReader(resp.Body)
|
||||
if gzipErr == nil {
|
||||
resp.Body = bodyReader
|
||||
}
|
||||
resp.TransferEncoding = nil
|
||||
resp.Header.Del("Content-Encoding")
|
||||
}
|
||||
}
|
||||
} else if origin.OSS != nil { // OSS源站
|
||||
var goNext bool
|
||||
resp, goNext, requestErrCode, _, requestErr = this.doOSSOrigin(origin)
|
||||
@@ -315,7 +352,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
if requestErr != nil {
|
||||
// 客户端取消请求,则不提示
|
||||
var httpErr *url.Error
|
||||
ok := errors.As(requestErr, &httpErr)
|
||||
var ok = errors.As(requestErr, &httpErr)
|
||||
if !ok {
|
||||
if isHTTPOrigin {
|
||||
SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() {
|
||||
@@ -361,18 +398,16 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
} else {
|
||||
// 是否为客户端方面的错误
|
||||
var isClientError = false
|
||||
if ok {
|
||||
if errors.Is(httpErr, context.Canceled) {
|
||||
// 如果是服务器端主动关闭,则无需提示
|
||||
if this.isConnClosed() {
|
||||
this.disableLog = true
|
||||
return
|
||||
}
|
||||
|
||||
isClientError = true
|
||||
this.addError(errors.New(httpErr.Op + " " + httpErr.URL + ": client closed the connection"))
|
||||
this.writer.WriteHeader(499) // 仿照nginx
|
||||
if errors.Is(httpErr, context.Canceled) {
|
||||
// 如果是服务器端主动关闭,则无需提示
|
||||
if this.isConnClosed() {
|
||||
this.disableLog = true
|
||||
return
|
||||
}
|
||||
|
||||
isClientError = true
|
||||
this.addError(errors.New(httpErr.Op + " " + httpErr.URL + ": client closed the connection"))
|
||||
this.writer.WriteHeader(499) // 仿照nginx
|
||||
}
|
||||
|
||||
if !isClientError {
|
||||
@@ -547,7 +582,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
if resp.ContentLength == 0 && len(resp.TransferEncoding) == 0 {
|
||||
// 即使内容为0,也需要读取一次,以便于触发相关事件
|
||||
var buf = utils.BytePool4k.Get()
|
||||
_, _ = io.CopyBuffer(this.writer, resp.Body, buf)
|
||||
_, _ = io.CopyBuffer(this.writer, resp.Body, buf.Bytes)
|
||||
utils.BytePool4k.Put(buf)
|
||||
_ = resp.Body.Close()
|
||||
respBodyIsClosed = true
|
||||
@@ -562,9 +597,9 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
var err error
|
||||
if shouldAutoFlush {
|
||||
for {
|
||||
n, readErr := resp.Body.Read(buf)
|
||||
n, readErr := resp.Body.Read(buf.Bytes)
|
||||
if n > 0 {
|
||||
_, err = this.writer.Write(buf[:n])
|
||||
_, err = this.writer.Write(buf.Bytes[:n])
|
||||
this.writer.Flush()
|
||||
if err != nil {
|
||||
break
|
||||
@@ -582,10 +617,10 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
resp.ContentLength < (128<<20) { // TODO configure max content-length in cache policy OR CacheRef
|
||||
var requestIsCanceled = false
|
||||
for {
|
||||
n, readErr := resp.Body.Read(buf)
|
||||
n, readErr := resp.Body.Read(buf.Bytes)
|
||||
|
||||
if n > 0 && !requestIsCanceled {
|
||||
_, err = this.writer.Write(buf[:n])
|
||||
_, err = this.writer.Write(buf.Bytes[:n])
|
||||
if err != nil {
|
||||
requestIsCanceled = true
|
||||
}
|
||||
@@ -596,7 +631,7 @@ func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeId
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_, err = io.CopyBuffer(this.writer, resp.Body, buf)
|
||||
_, err = io.CopyBuffer(this.writer, resp.Body, buf.Bytes)
|
||||
}
|
||||
}
|
||||
pool.Put(buf)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"fmt"
|
||||
rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
|
||||
"github.com/TeaOSLab/EdgeNode/internal/zero"
|
||||
"github.com/cespare/xxhash"
|
||||
"github.com/cespare/xxhash/v2"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
"github.com/iwind/TeaGo/types"
|
||||
@@ -327,7 +327,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
respHeader.Set("Content-Range", ranges[0].ComposeContentRangeHeader(types.String(fileSize)))
|
||||
this.writer.WriteHeader(http.StatusPartialContent)
|
||||
|
||||
ok, err := httpRequestReadRange(resp.Body, buf, ranges[0].Start(), ranges[0].End(), func(buf []byte, n int) error {
|
||||
ok, err := httpRequestReadRange(resp.Body, buf.Bytes, ranges[0].Start(), ranges[0].End(), func(buf []byte, n int) error {
|
||||
_, err := this.writer.Write(buf[:n])
|
||||
return err
|
||||
})
|
||||
@@ -379,7 +379,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
}
|
||||
}
|
||||
|
||||
ok, err := httpRequestReadRange(resp.Body, buf, r.Start(), r.End(), func(buf []byte, n int) error {
|
||||
ok, err := httpRequestReadRange(resp.Body, buf.Bytes, r.Start(), r.End(), func(buf []byte, n int) error {
|
||||
_, err := this.writer.Write(buf[:n])
|
||||
return err
|
||||
})
|
||||
@@ -404,7 +404,7 @@ func (this *HTTPRequest) doRoot() (isBreak bool) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
_, err = io.CopyBuffer(this.writer, resp.Body, buf)
|
||||
_, err = io.CopyBuffer(this.writer, resp.Body, buf.Bytes)
|
||||
if err != nil {
|
||||
if !this.canIgnore(err) {
|
||||
logs.Error(err)
|
||||
|
||||
@@ -69,7 +69,7 @@ func (this *HTTPRequest) doShutdown() {
|
||||
this.writer.WriteHeader(http.StatusOK)
|
||||
}
|
||||
var buf = utils.BytePool1k.Get()
|
||||
_, err = utils.CopyWithFilter(this.writer, fp, buf, func(p []byte) []byte {
|
||||
_, err = utils.CopyWithFilter(this.writer, fp, buf.Bytes, func(p []byte) []byte {
|
||||
return []byte(this.Format(string(p)))
|
||||
})
|
||||
utils.BytePool1k.Put(buf)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user