diff --git a/agent/go.mod b/agent/go.mod index 43894308..b7a86c78 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -31,15 +31,18 @@ require ( require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/andybalholm/brotli v1.2.0 // indirect + github.com/buger/goterm v1.0.4 // indirect github.com/bytedance/gopkg v0.1.3 // indirect - github.com/bytedance/sonic v1.14.2 // indirect - github.com/bytedance/sonic/loader v0.4.0 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/diskfs/go-diskfs v1.7.0 // indirect github.com/distribution/reference v0.6.0 // indirect + github.com/djherbis/times v1.6.0 // indirect github.com/docker/cli v29.1.5+incompatible // indirect github.com/docker/docker v28.5.2+incompatible // indirect github.com/docker/go-connections v0.6.0 // indirect @@ -57,11 +60,14 @@ require ( github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-yaml v1.19.2 // indirect github.com/gorilla/mux v1.8.1 // indirect + github.com/jinzhu/copier v0.4.0 // indirect github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect github.com/klauspost/compress v1.18.3 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect + github.com/luthermonson/go-proxmox v0.3.2 // indirect + github.com/magefile/mage v1.15.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect @@ -78,7 +84,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/puzpuzpuz/xsync/v4 v4.3.0 // indirect + github.com/puzpuzpuz/xsync/v4 v4.4.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/quic-go v0.59.0 // indirect github.com/shirou/gopsutil/v4 v4.25.12 // indirect diff --git a/agent/go.sum b/agent/go.sum index ac038eb8..162af4d6 100644 --- a/agent/go.sum +++ b/agent/go.sum @@ -4,6 +4,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw= github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ= +github.com/anchore/go-lzo v0.1.0 h1:NgAacnzqPeGH49Ky19QKLBZEuFRqtTG9cdaucc3Vncs= +github.com/anchore/go-lzo v0.1.0/go.mod h1:3kLx0bve2oN1iDwgM1U5zGku1Tfbdb0No5qp1eL1fIk= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= @@ -12,10 +14,10 @@ github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY= github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= -github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= -github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= -github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -51,6 +53,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab h1:h1UgjJdAAhj+uPL68n7XASS6bU+07ZX1WJvVS2eyoeY= +github.com/elliotwutingfeng/asciiset v0.0.0-20230602022725-51bbb787efab/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= @@ -81,6 +85,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= @@ -103,6 +109,10 @@ github.com/gotify/server/v2 v2.8.0 h1:E3UDDn/3rFZi1sjZfbuhXNnxJP3ACZhdcw/iySegPR github.com/gotify/server/v2 v2.8.0/go.mod h1:6ci5adxcE2hf1v+2oowKiQmixOxXV8vU+CRLKP6sqZA= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= +github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE= +github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= +github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU= @@ -159,6 +169,8 @@ github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5 github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pion/dtls/v3 v3.0.10 h1:k9ekkq1kaZoxnNEbyLKI8DI37j/Nbk1HWmMuywpQJgg= github.com/pion/dtls/v3 v3.0.10/go.mod h1:YEmmBYIoBsY3jmG56dsziTv/Lca9y4Om83370CXfqJ8= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= @@ -171,13 +183,15 @@ github.com/pires/go-proxyproto v0.9.0 h1:3Qg3CLxWx4wJOw5uxhTvc0VrgsJeerDbGTvexu4 github.com/pires/go-proxyproto v0.9.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE= +github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/puzpuzpuz/xsync/v4 v4.3.0 h1:w/bWkEJdYuRNYhHn5eXnIT8LzDM1O629X1I9MJSkD7Q= -github.com/puzpuzpuz/xsync/v4 v4.3.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= +github.com/puzpuzpuz/xsync/v4 v4.4.0 h1:vlSN6/CkEY0pY8KaB0yqo/pCLZvp9nhdbBdjipT4gWo= +github.com/puzpuzpuz/xsync/v4 v4.4.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= @@ -215,6 +229,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI= @@ -267,6 +283,8 @@ golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/go.mod b/go.mod index 79e7fc82..0b6afaf8 100644 --- a/go.mod +++ b/go.mod @@ -28,8 +28,8 @@ require ( github.com/gorilla/websocket v1.5.3 // websocket for API and agent github.com/gotify/server/v2 v2.8.0 // reference the Message struct for json response github.com/lithammer/fuzzysearch v1.1.8 // fuzzy search for searching icons and filtering metrics - github.com/pires/go-proxyproto v0.9.0 // proxy protocol support - github.com/puzpuzpuz/xsync/v4 v4.3.0 // lock free map for concurrent operations + github.com/pires/go-proxyproto v0.9.1 // proxy protocol support + github.com/puzpuzpuz/xsync/v4 v4.4.0 // lock free map for concurrent operations github.com/rs/zerolog v1.34.0 // logging github.com/vincent-petithory/dataurl v1.0.0 // data url for fav icon golang.org/x/crypto v0.47.0 // encrypting password with bcrypt @@ -41,7 +41,7 @@ require ( require ( github.com/bytedance/gopkg v0.1.3 // xxhash64 for fast hash - github.com/bytedance/sonic v1.14.2 // indirect; fast json parsing + github.com/bytedance/sonic v1.15.0 // indirect; fast json parsing github.com/docker/cli v29.1.5+incompatible // needs docker/cli/cli/connhelper connection helper for docker client github.com/goccy/go-yaml v1.19.2 // yaml parsing for different config files github.com/golang-jwt/jwt/v5 v5.3.0 @@ -146,14 +146,16 @@ require ( gopkg.in/yaml.v3 v3.0.1 // indirect ) -require github.com/moby/moby/api v1.52.0 +require ( + github.com/moby/moby/api v1.52.0 + github.com/moby/moby/client v0.2.1 +) require ( - github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect github.com/andybalholm/brotli v1.2.0 // indirect github.com/boombuler/barcode v1.1.0 // indirect - github.com/bytedance/sonic/loader v0.4.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect diff --git a/go.sum b/go.sum index 71460a84..30ac1ead 100644 --- a/go.sum +++ b/go.sum @@ -53,10 +53,10 @@ github.com/buger/goterm v1.0.4 h1:Z9YvGmOih81P0FbVtEYTFF6YsSgxSUKEhf/f9bTMXbY= github.com/buger/goterm v1.0.4/go.mod h1:HiFWV3xnkolgrBV3mY8m0X0Pumt4zg4QhbdOzQtB8tE= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= -github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE= -github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980= -github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o= -github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -224,6 +224,8 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg= github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc= +github.com/moby/moby/client v0.2.1 h1:1Grh1552mvv6i+sYOdY+xKKVTvzJegcVMhuXocyDz/k= +github.com/moby/moby/client v0.2.1/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE= github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= @@ -265,8 +267,8 @@ github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o= github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM= -github.com/pires/go-proxyproto v0.9.0 h1:3Qg3CLxWx4wJOw5uxhTvc0VrgsJeerDbGTvexu4UK1E= -github.com/pires/go-proxyproto v0.9.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= +github.com/pires/go-proxyproto v0.9.1 h1:wTPjpyk41pJm1Im9BqHtPLuhxfjxL+qNfSikx9ux0WY= +github.com/pires/go-proxyproto v0.9.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -280,8 +282,8 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= -github.com/puzpuzpuz/xsync/v4 v4.3.0 h1:w/bWkEJdYuRNYhHn5eXnIT8LzDM1O629X1I9MJSkD7Q= -github.com/puzpuzpuz/xsync/v4 v4.3.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= +github.com/puzpuzpuz/xsync/v4 v4.4.0 h1:vlSN6/CkEY0pY8KaB0yqo/pCLZvp9nhdbBdjipT4gWo= +github.com/puzpuzpuz/xsync/v4 v4.4.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= @@ -421,7 +423,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/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-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -494,3 +495,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= +pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/internal/api/handler.go b/internal/api/handler.go index 3d0df1c3..8e0b67f4 100644 --- a/internal/api/handler.go +++ b/internal/api/handler.go @@ -143,6 +143,7 @@ func NewHandler(requireAuth bool) *gin.Engine { proxmox := v1.Group("/proxmox") { proxmox.GET("/journalctl/:node/:vmid/:service", proxmoxApi.Journalctl) + proxmox.GET("/stats/:node/:vmid", proxmoxApi.Stats) } } diff --git a/internal/api/v1/docs/swagger.json b/internal/api/v1/docs/swagger.json index 880c7fed..b0ce42b4 100644 --- a/internal/api/v1/docs/swagger.json +++ b/internal/api/v1/docs/swagger.json @@ -218,6 +218,12 @@ "$ref": "#/definitions/ErrorResponse" } }, + "404": { + "description": "Node not found", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, "500": { "description": "Internal server error", "schema": { @@ -229,6 +235,70 @@ "operationId": "journalctl" } }, + "/api/v1/proxmox/stats/{node}/{vmid}": { + "get": { + "description": "Get proxmox stats in format of \"STATUS|CPU%%|MEM USAGE/LIMIT|MEM%%|NET I/O|BLOCK I/O\"", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "proxmox", + "websocket" + ], + "summary": "Get proxmox stats", + "parameters": [ + { + "type": "string", + "name": "node", + "in": "path", + "required": true + }, + { + "type": "integer", + "name": "vmid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Stats output", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Invalid request", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "403": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "404": { + "description": "Node not found", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Internal server error", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "x-id": "stats", + "operationId": "stats" + } + }, "/auth/callback": { "post": { "description": "Handles the callback from the provider after successful authentication", diff --git a/internal/api/v1/docs/swagger.yaml b/internal/api/v1/docs/swagger.yaml index f446c56f..745ee27a 100644 --- a/internal/api/v1/docs/swagger.yaml +++ b/internal/api/v1/docs/swagger.yaml @@ -2119,6 +2119,10 @@ paths: description: Unauthorized schema: $ref: '#/definitions/ErrorResponse' + "404": + description: Node not found + schema: + $ref: '#/definitions/ErrorResponse' "500": description: Internal server error schema: @@ -2128,6 +2132,49 @@ paths: - proxmox - websocket x-id: journalctl + /api/v1/proxmox/stats/{node}/{vmid}: + get: + consumes: + - application/json + description: Get proxmox stats in format of "STATUS|CPU%%|MEM USAGE/LIMIT|MEM%%|NET + I/O|BLOCK I/O" + parameters: + - in: path + name: node + required: true + type: string + - in: path + name: vmid + required: true + type: integer + produces: + - application/json + responses: + "200": + description: Stats output + schema: + type: string + "400": + description: Invalid request + schema: + $ref: '#/definitions/ErrorResponse' + "403": + description: Unauthorized + schema: + $ref: '#/definitions/ErrorResponse' + "404": + description: Node not found + schema: + $ref: '#/definitions/ErrorResponse' + "500": + description: Internal server error + schema: + $ref: '#/definitions/ErrorResponse' + summary: Get proxmox stats + tags: + - proxmox + - websocket + x-id: stats /auth/callback: post: description: Handles the callback from the provider after successful authentication diff --git a/internal/api/v1/proxmox/journalctl.go b/internal/api/v1/proxmox/journalctl.go index ebbb4199..440d28e3 100644 --- a/internal/api/v1/proxmox/journalctl.go +++ b/internal/api/v1/proxmox/journalctl.go @@ -27,6 +27,7 @@ type JournalctlRequest struct { // @Success 200 string plain "Journalctl output" // @Failure 400 {object} apitypes.ErrorResponse "Invalid request" // @Failure 403 {object} apitypes.ErrorResponse "Unauthorized" +// @Failure 404 {object} apitypes.ErrorResponse "Node not found" // @Failure 500 {object} apitypes.ErrorResponse "Internal server error" // @Router /api/v1/proxmox/journalctl/{node}/{vmid}/{service} [get] func Journalctl(c *gin.Context) { diff --git a/internal/api/v1/proxmox/stats.go b/internal/api/v1/proxmox/stats.go new file mode 100644 index 00000000..6d6886f0 --- /dev/null +++ b/internal/api/v1/proxmox/stats.go @@ -0,0 +1,79 @@ +package proxmoxapi + +import ( + "io" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/yusing/godoxy/internal/proxmox" + "github.com/yusing/goutils/apitypes" + "github.com/yusing/goutils/http/httpheaders" + "github.com/yusing/goutils/http/websocket" +) + +type StatsRequest struct { + Node string `uri:"node" binding:"required"` + VMID int `uri:"vmid" binding:"required"` +} + +// @x-id "stats" +// @BasePath /api/v1 +// @Summary Get proxmox stats +// @Description Get proxmox stats in format of "STATUS|CPU%%|MEM USAGE/LIMIT|MEM%%|NET I/O|BLOCK I/O" +// @Tags proxmox,websocket +// @Accept json +// @Produce application/json +// @Param path path StatsRequest true "Request" +// @Success 200 string plain "Stats output" +// @Failure 400 {object} apitypes.ErrorResponse "Invalid request" +// @Failure 403 {object} apitypes.ErrorResponse "Unauthorized" +// @Failure 404 {object} apitypes.ErrorResponse "Node not found" +// @Failure 500 {object} apitypes.ErrorResponse "Internal server error" +// @Router /api/v1/proxmox/stats/{node}/{vmid} [get] +func Stats(c *gin.Context) { + var request StatsRequest + if err := c.ShouldBindUri(&request); err != nil { + c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err)) + return + } + + node, ok := proxmox.Nodes.Get(request.Node) + if !ok { + c.JSON(http.StatusNotFound, apitypes.Error("node not found")) + return + } + + isWs := httpheaders.IsWebsocket(c.Request.Header) + + reader, err := node.LXCStats(c.Request.Context(), request.VMID, isWs) + if err != nil { + c.Error(apitypes.InternalServerError(err, "failed to get stats")) + return + } + defer reader.Close() + + if !isWs { + var line [128]byte + n, err := reader.Read(line[:]) + if err != nil { + c.Error(apitypes.InternalServerError(err, "failed to copy stats")) + return + } + c.Data(http.StatusOK, "text/plain; charset=utf-8", line[:n]) + return + } + + manager, err := websocket.NewManagerWithUpgrade(c) + if err != nil { + c.Error(apitypes.InternalServerError(err, "failed to upgrade to websocket")) + return + } + defer manager.Close() + + writer := manager.NewWriter(websocket.TextMessage) + _, err = io.Copy(writer, reader) + if err != nil { + c.Error(apitypes.InternalServerError(err, "failed to copy stats")) + return + } +} diff --git a/internal/proxmox/client.go b/internal/proxmox/client.go index d8303ddc..8f7f8074 100644 --- a/internal/proxmox/client.go +++ b/internal/proxmox/client.go @@ -3,19 +3,34 @@ package proxmox import ( "context" "encoding/json" + "errors" "fmt" + "strconv" + "sync" "github.com/luthermonson/go-proxmox" + "github.com/rs/zerolog/log" ) type Client struct { *proxmox.Client - proxmox.Cluster + *proxmox.Cluster Version *proxmox.Version + // id -> resource; id: lxc/ or qemu/ + resources map[string]*proxmox.ClusterResource + resourcesMu sync.RWMutex } +var ( + ErrResourceNotFound = errors.New("resource not found") + ErrNoResources = errors.New("no resources") +) + func NewClient(baseUrl string, opts ...proxmox.Option) *Client { - return &Client{Client: proxmox.NewClient(baseUrl, opts...)} + return &Client{ + Client: proxmox.NewClient(baseUrl, opts...), + resources: make(map[string]*proxmox.ClusterResource), + } } func (c *Client) UpdateClusterInfo(ctx context.Context) (err error) { @@ -24,15 +39,49 @@ func (c *Client) UpdateClusterInfo(ctx context.Context) (err error) { return err } // requires (/, Sys.Audit) - if err := c.Get(ctx, "/cluster/status", &c.Cluster); err != nil { + cluster, err := c.Client.Cluster(ctx) + if err != nil { return err } + c.Cluster = cluster + for _, node := range c.Cluster.Nodes { - Nodes.Add(&Node{name: node.Name, id: node.ID, client: c.Client}) + Nodes.Add(NewNode(c, node.Name, node.ID)) + } + if cluster.Name == "" && len(c.Cluster.Nodes) == 1 { + cluster.Name = c.Cluster.Nodes[0].Name } return nil } +func (c *Client) UpdateResources(ctx context.Context) error { + c.resourcesMu.Lock() + defer c.resourcesMu.Unlock() + resourcesSlice, err := c.Cluster.Resources(ctx, "vm") + if err != nil { + return err + } + clear(c.resources) + for _, resource := range resourcesSlice { + c.resources[resource.ID] = resource + } + log.Debug().Str("cluster", c.Cluster.Name).Msgf("[proxmox] updated %d resources", len(c.resources)) + return nil +} + +// GetResource gets a resource by kind and id. +// kind: lxc or qemu +// id: +func (c *Client) GetResource(kind string, id int) (*proxmox.ClusterResource, error) { + c.resourcesMu.RLock() + defer c.resourcesMu.RUnlock() + resource, ok := c.resources[kind+"/"+strconv.Itoa(id)] + if !ok { + return nil, ErrResourceNotFound + } + return resource, nil +} + // Key implements pool.Object func (c *Client) Key() string { return c.Cluster.ID diff --git a/internal/proxmox/config.go b/internal/proxmox/config.go index 6743f415..c750f228 100644 --- a/internal/proxmox/config.go +++ b/internal/proxmox/config.go @@ -9,6 +9,7 @@ import ( "time" "github.com/luthermonson/go-proxmox" + "github.com/rs/zerolog/log" "github.com/yusing/godoxy/internal/net/gphttp" gperr "github.com/yusing/goutils/errs" strutils "github.com/yusing/goutils/strings" @@ -29,6 +30,8 @@ type Config struct { client *Client } +const ResourcePollInterval = 3 * time.Second + func (c *Config) Client() *Client { if c.client == nil { panic("proxmox client accessed before init") @@ -70,21 +73,54 @@ func (c *Config) Init(ctx context.Context) gperr.Error { } c.client = NewClient(c.URL, opts...) - ctx, cancel := context.WithTimeout(ctx, 5*time.Second) - defer cancel() + initCtx, initCtxCancel := context.WithTimeout(ctx, 5*time.Second) + defer initCtxCancel() if useCredentials { - err := c.client.CreateSession(ctx) + err := c.client.CreateSession(initCtx) if err != nil { return gperr.New("failed to create session").With(err) } } - if err := c.client.UpdateClusterInfo(ctx); err != nil { + if err := c.client.UpdateClusterInfo(initCtx); err != nil { if errors.Is(err, context.DeadlineExceeded) { return gperr.New("timeout fetching proxmox cluster info") } return gperr.New("failed to fetch proxmox cluster info").With(err) } + + go c.updateResourcesLoop(ctx) return nil } + +func (c *Config) updateResourcesLoop(ctx context.Context) { + ticker := time.NewTicker(ResourcePollInterval) + defer ticker.Stop() + + log.Trace().Str("cluster", c.client.Cluster.Name).Msg("[proxmox] starting resources update loop") + + { + reqCtx, reqCtxCancel := context.WithTimeout(ctx, ResourcePollInterval) + err := c.client.UpdateResources(reqCtx) + reqCtxCancel() + if err != nil { + log.Warn().Err(err).Str("cluster", c.client.Cluster.Name).Msg("[proxmox] failed to update resources") + } + } + + for { + select { + case <-ctx.Done(): + log.Trace().Str("cluster", c.client.Cluster.Name).Msg("[proxmox] stopping resources update loop") + return + case <-ticker.C: + reqCtx, reqCtxCancel := context.WithTimeout(ctx, ResourcePollInterval) + err := c.client.UpdateResources(reqCtx) + reqCtxCancel() + if err != nil { + log.Error().Err(err).Str("cluster", c.client.Cluster.Name).Msg("[proxmox] failed to update resources") + } + } + } +} diff --git a/internal/proxmox/lxc.go b/internal/proxmox/lxc.go index db634993..d5b62505 100644 --- a/internal/proxmox/lxc.go +++ b/internal/proxmox/lxc.go @@ -47,7 +47,7 @@ func (n *Node) LXCAction(ctx context.Context, vmid int, action LXCAction) error return err } - task := proxmox.NewTask(upid, n.client) + task := proxmox.NewTask(upid, n.client.Client) checkTicker := time.NewTicker(proxmoxTaskCheckInterval) defer checkTicker.Stop() for { diff --git a/internal/proxmox/lxc_stats.go b/internal/proxmox/lxc_stats.go new file mode 100644 index 00000000..3e92f04b --- /dev/null +++ b/internal/proxmox/lxc_stats.go @@ -0,0 +1,173 @@ +package proxmox + +import ( + "bytes" + "context" + "fmt" + "io" + "strings" + "time" + + "github.com/luthermonson/go-proxmox" +) + +// const statsScriptLocation = "/tmp/godoxy-stats.sh" + +// const statsScript = `#!/bin/sh + +// # LXCStats script, written by godoxy. +// printf "%s|%s|%s|%s|%s\n" \ +// "$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1"%"}')" \ +// "$(free -b | awk 'NR==2{printf "%.0f\n%.0f", $3, $2}' | numfmt --to=iec-i --suffix=B | paste -sd/)" \ +// "$(free | awk 'NR==2{printf "%.2f%%", $3/$2*100}')" \ +// "$(awk 'NR>2{r+=$2;t+=$10}END{printf "%.0f\n%.0f", r, t}' /proc/net/dev | numfmt --to=iec-i --suffix=B | paste -sd/)" \ +// "$(awk '{r+=$6;w+=$10}END{printf "%.0f\n%.0f", r*512, w*512}' /proc/diskstats | numfmt --to=iec-i --suffix=B | paste -sd/)"` + +// var statsScriptBase64 = base64.StdEncoding.EncodeToString([]byte(statsScript)) + +// var statsInitCommand = fmt.Sprintf("sh -c 'echo %s | base64 -d > %s && chmod +x %s'", statsScriptBase64, statsScriptLocation, statsScriptLocation) + +// var statsStreamScript = fmt.Sprintf("watch -t -w -p -n1 '%s'", statsScriptLocation) +// var statsNonStreamScript = statsScriptLocation + +// lxcStatsScriptInit initializes the stats script for the given container. +// func (n *Node) lxcStatsScriptInit(ctx context.Context, vmid int) error { +// reader, err := n.LXCCommand(ctx, vmid, statsInitCommand) +// if err != nil { +// return fmt.Errorf("failed to execute stats init command: %w", err) +// } +// reader.Close() +// return nil +// } + +// LXCStats streams container stats, like docker stats. +// +// - format: "STATUS|CPU%%|MEM USAGE/LIMIT|MEM%%|NET I/O|BLOCK I/O" +// - example: running|31.1%|9.6GiB/20GiB|48.87%|4.7GiB/3.3GiB|25GiB/36GiB +func (n *Node) LXCStats(ctx context.Context, vmid int, stream bool) (io.ReadCloser, error) { + if !stream { + resource, err := n.client.GetResource("lxc", vmid) + if err != nil { + return nil, err + } + var buf bytes.Buffer + if err := writeLXCStatsLine(resource, &buf); err != nil { + return nil, err + } + return io.NopCloser(&buf), nil + } + + // Validate the resource exists before returning a stream. + _, err := n.client.GetResource("lxc", vmid) + if err != nil { + return nil, err + } + + pr, pw := io.Pipe() + + interval := ResourcePollInterval + if interval <= 0 { + interval = time.Second + } + + go func() { + writeSample := func() error { + resource, err := n.client.GetResource("lxc", vmid) + if err != nil { + return err + } + err = writeLXCStatsLine(resource, pw) + return err + } + + // Match `watch` behavior: write immediately, then on each tick. + if err := writeSample(); err != nil { + _ = pw.CloseWithError(err) + return + } + + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + _ = pw.CloseWithError(ctx.Err()) + return + case <-ticker.C: + if err := writeSample(); err != nil { + _ = pw.CloseWithError(err) + return + } + } + } + }() + + return pr, nil +} + +func writeLXCStatsLine(resource *proxmox.ClusterResource, w io.Writer) error { + cpu := fmt.Sprintf("%.1f%%", resource.CPU*100) + + memUsage := formatIECBytes(resource.Mem) + memLimit := formatIECBytes(resource.MaxMem) + memPct := "0.00%" + if resource.MaxMem > 0 { + memPct = fmt.Sprintf("%.2f%%", float64(resource.Mem)/float64(resource.MaxMem)*100) + } + + netIO := formatIECBytes(resource.NetIn) + "/" + formatIECBytes(resource.NetOut) + blockIO := formatIECBytes(resource.DiskRead) + "/" + formatIECBytes(resource.DiskWrite) + + // Keep the format consistent with LXCStatsAlt / `statsScript` (newline terminated). + _, err := fmt.Fprintf(w, "%s|%s|%s/%s|%s|%s|%s\n", resource.Status, cpu, memUsage, memLimit, memPct, netIO, blockIO) + return err +} + +// formatIECBytes formats a byte count using IEC binary prefixes (KiB, MiB, GiB, ...), +// similar to `numfmt --to=iec-i --suffix=B`. +func formatIECBytes(b uint64) string { + const unit = 1024 + if b < unit { + return fmt.Sprintf("%dB", b) + } + + prefixes := []string{"B", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei"} + val := float64(b) + exp := 0 + for val >= unit && exp < len(prefixes)-1 { + val /= unit + exp++ + } + + // One decimal, trimming trailing ".0" to keep output compact (e.g. "10GiB"). + s := fmt.Sprintf("%.1f", val) + s = strings.TrimSuffix(s, ".0") + if exp == 0 { + return s + "B" + } + return s + prefixes[exp] + "B" +} + +// LXCStatsAlt streams container stats, like docker stats. +// +// - format: "CPU%%|MEM USAGE/LIMIT|MEM%%|NET I/O|BLOCK I/O" +// - example: 31.1%|9.6GiB/20GiB|48.87%|4.7GiB/3.3GiB|25TiB/36TiB +// func (n *Node) LXCStatsAlt(ctx context.Context, vmid int, stream bool) (io.ReadCloser, error) { +// // Initialize the stats script if it hasn't been initialized yet. +// initScriptErr, _ := n.statsScriptInitErrs.LoadOrCompute(vmid, +// func() (newValue error, cancel bool) { +// if err := n.lxcStatsScriptInit(ctx, vmid); err != nil { +// cancel = errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) +// return err, cancel +// } +// return nil, false +// }) + +// if initScriptErr != nil { +// return nil, initScriptErr +// } +// if stream { +// return n.LXCCommand(ctx, vmid, statsStreamScript) +// } +// return n.LXCCommand(ctx, vmid, statsNonStreamScript) +// } diff --git a/internal/proxmox/node.go b/internal/proxmox/node.go index ac355db1..4a803d57 100644 --- a/internal/proxmox/node.go +++ b/internal/proxmox/node.go @@ -6,7 +6,6 @@ import ( "fmt" "strings" - "github.com/luthermonson/go-proxmox" "github.com/yusing/goutils/pool" ) @@ -19,11 +18,22 @@ type NodeConfig struct { type Node struct { name string id string // likely node/ - client *proxmox.Client + client *Client + + // statsScriptInitErrs *xsync.Map[int, error] } var Nodes = pool.New[*Node]("proxmox_nodes") +func NewNode(client *Client, name, id string) *Node { + return &Node{ + name: name, + id: id, + client: client, + // statsScriptInitErrs: xsync.NewMap[int, error](xsync.WithGrowOnly()), + } +} + func AvailableNodeNames() string { if Nodes.Size() == 0 { return "" diff --git a/socket-proxy/go.mod b/socket-proxy/go.mod index cb6a446f..72b62008 100644 --- a/socket-proxy/go.mod +++ b/socket-proxy/go.mod @@ -15,7 +15,7 @@ require ( github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/puzpuzpuz/xsync/v4 v4.3.0 // indirect + github.com/puzpuzpuz/xsync/v4 v4.4.0 // indirect github.com/rs/zerolog v1.34.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/text v0.33.0 // indirect diff --git a/socket-proxy/go.sum b/socket-proxy/go.sum index 9f92cb06..a8868111 100644 --- a/socket-proxy/go.sum +++ b/socket-proxy/go.sum @@ -14,8 +14,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/puzpuzpuz/xsync/v4 v4.3.0 h1:w/bWkEJdYuRNYhHn5eXnIT8LzDM1O629X1I9MJSkD7Q= -github.com/puzpuzpuz/xsync/v4 v4.3.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= +github.com/puzpuzpuz/xsync/v4 v4.4.0 h1:vlSN6/CkEY0pY8KaB0yqo/pCLZvp9nhdbBdjipT4gWo= +github.com/puzpuzpuz/xsync/v4 v4.4.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=