mirror of
https://github.com/safing/portbase
synced 2025-09-01 18:19:57 +00:00
commit
54bc8d3775
18 changed files with 936 additions and 3 deletions
14
go.mod
14
go.mod
|
@ -5,6 +5,7 @@ go 1.15
|
|||
require (
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect
|
||||
github.com/VictoriaMetrics/metrics v1.12.3
|
||||
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6
|
||||
github.com/armon/go-radix v1.0.0
|
||||
github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833
|
||||
|
@ -13,7 +14,6 @@ require (
|
|||
github.com/go-ole/go-ole v1.2.4 // indirect
|
||||
github.com/gofrs/uuid v3.3.0+incompatible
|
||||
github.com/golang/protobuf v1.4.2 // indirect
|
||||
github.com/google/renameio v0.1.0
|
||||
github.com/gorilla/mux v1.7.4
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
|
@ -29,8 +29,16 @@ require (
|
|||
github.com/tidwall/gjson v1.6.0
|
||||
github.com/tidwall/sjson v1.1.1
|
||||
go.etcd.io/bbolt v1.3.4
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 // indirect
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 // indirect
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f
|
||||
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
// The follow-up commit removes Windows support.
|
||||
// TOOD: Check how we want to handle this in the future, possibly ingest
|
||||
// needed functionality into here.
|
||||
github.com/google/renameio v0.1.1-0.20200217212219-353f81969824
|
||||
)
|
||||
|
|
58
go.sum
58
go.sum
|
@ -1,17 +1,24 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 h1:cTp8I5+VIoKjsnZuH8vjyaysT/ses3EvZeaV/1UkF2M=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUWq3EgK3CesDbo8upS2Vm9/P3FtgI+Jk=
|
||||
github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/VictoriaMetrics/metrics v1.12.3 h1:Fe6JHC6MSEKa+BtLhPN8WIvS+HKPzMc2evEpNeCGy7I=
|
||||
github.com/VictoriaMetrics/metrics v1.12.3/go.mod h1:Z1tSfPfngDn12bTfZSCqArT3OPY3u88J12hSoOhuiRE=
|
||||
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6 h1:5L8Mj9Co9sJVgW3TpYk2gxGJnDjsYuboNTcRmbtGKGs=
|
||||
github.com/aead/serpent v0.0.0-20160714141033-fba169763ea6/go.mod h1:3HgLJ9d18kXMLQlJvIY3+FszZYMxCz8WfE2MQ7hDY0w=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833 h1:yCfXxYaelOyqnia8F/Yng47qhmfC9nKTRIbYRrRueq4=
|
||||
github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833/go.mod h1:8c4/i2VlovMO2gBnHGQPN5EJw+H0lx1u/5p+cgsXtCk=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
|
@ -27,19 +34,24 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsr
|
|||
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/badger v1.6.1 h1:w9pSFNSdq/JPM1N12Fz/F/bzo993Is1W+Q7HjPzi7yg=
|
||||
github.com/dgraph-io/badger v1.6.1/go.mod h1:FRmFw3uxvcpa8zG3Rxs0th+hCLIuaQg8HlNV5bjgnuU=
|
||||
github.com/dgraph-io/ristretto v0.0.2 h1:a5WaUrDa0qm0YrAAS1tUykT5El3kt62KNZZeMxQn3po=
|
||||
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
|
||||
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
|
@ -53,6 +65,7 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x
|
|||
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.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
|
@ -61,6 +74,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
|||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/renameio v0.1.1-0.20200217212219-353f81969824 h1:9q700G0beHecUuiZOuKgNqNsGQixTeDLnzVZ5nsW3lc=
|
||||
github.com/google/renameio v0.1.1-0.20200217212219-353f81969824/go.mod h1:t/HQoYBZSsWSNK35C6CO/TpPLDVWvxOHboWUAweKUpk=
|
||||
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
|
@ -73,6 +89,7 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
|
|||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
|
@ -114,8 +131,11 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T
|
|||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/seehuhn/fortuna v1.0.1 h1:lu9+CHsmR0bZnx5Ay646XvCSRJ8PJTi5UYJwDBX68H0=
|
||||
github.com/seehuhn/fortuna v1.0.1/go.mod h1:LX8ubejCnUoT/hX+1aKUtbKls2H6DRkqzkc7TdR3iis=
|
||||
github.com/seehuhn/sha256d v1.0.0 h1:TXTsAuEWr02QjRm153Fnvvb6fXXDo7Bmy1FizxarGYw=
|
||||
github.com/seehuhn/sha256d v1.0.0/go.mod h1:PEuxg9faClSveVuFXacQmi+NtDI/PX8bpKjtNzf2+s4=
|
||||
github.com/shirou/gopsutil v2.20.4+incompatible h1:cMT4rxS55zx9NVUnCkrmXCsEB/RNfG9SwHY9evtX8Ng=
|
||||
github.com/shirou/gopsutil v2.20.4+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
|
@ -125,6 +145,7 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2
|
|||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
|
@ -140,17 +161,27 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
|
|||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tevino/abool v1.0.0 h1:5hlcsW0yartQp609pbLLrE/s3ZNm2k/F7YSGuqJxpbM=
|
||||
github.com/tevino/abool v1.0.0/go.mod h1:f1SCnEOt6sc3fOJfPQDRDzHOtSXuTtnz0ImG9kPRDV0=
|
||||
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
|
||||
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
||||
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8=
|
||||
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/sjson v1.1.1 h1:7h1vk049Jnd5EH9NyzNiEuwYW4b5qgreBbqRC19AS3U=
|
||||
github.com/tidwall/sjson v1.1.1/go.mod h1:yvVuSnpEQv5cYIrO+AT6kw4QVfd5SDZoGIS7/5+fZFs=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/valyala/fastrand v1.0.0 h1:LUKT9aKer2dVQNUi3waewTbKV+7H17kvWFNKs2ObdkI=
|
||||
github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||
github.com/valyala/histogram v1.1.2 h1:vOk5VrGjMBIoPR5k6wA8vBaC8toeJ8XO0yfRjFEc1h8=
|
||||
github.com/valyala/histogram v1.1.2/go.mod h1:CZAr6gK9dbD7hYx2s8WSPh0p5x5wETjC+2b3PJVtEdg=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg=
|
||||
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
|
@ -158,19 +189,31 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
|||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2 h1:eDrdRpKgkcCqKZQwyZRyeFZgfqt37SL7Kv3tok06cKE=
|
||||
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
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/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -179,17 +222,31 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
|
|||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
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-20210115202250-e0d201561e39 h1:BTs2GMGSMWpgtCpv1CE7vkJTv7XcHdcLLnAMu7UbgTY=
|
||||
golang.org/x/tools v0.0.0-20210115202250-e0d201561e39/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
|
@ -199,6 +256,7 @@ google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ
|
|||
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.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
|
135
metrics/api.go
Normal file
135
metrics/api.go
Normal file
|
@ -0,0 +1,135 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/api"
|
||||
"github.com/safing/portbase/config"
|
||||
"github.com/safing/portbase/log"
|
||||
)
|
||||
|
||||
func registerAPI() error {
|
||||
api.RegisterHandler("/metrics", &metricsAPI{})
|
||||
|
||||
return api.RegisterEndpoint(api.Endpoint{
|
||||
Path: "metrics/list",
|
||||
Read: api.PermitAnyone,
|
||||
MimeType: api.MimeTypeJSON,
|
||||
DataFunc: func(*api.Request) ([]byte, error) {
|
||||
registryLock.RLock()
|
||||
defer registryLock.RUnlock()
|
||||
|
||||
return json.Marshal(registry)
|
||||
},
|
||||
Name: "Export Registered Metrics",
|
||||
Description: "List all registered metrics with their metadata.",
|
||||
})
|
||||
}
|
||||
|
||||
type metricsAPI struct{}
|
||||
|
||||
func (m *metricsAPI) ReadPermission(*http.Request) api.Permission { return api.Dynamic }
|
||||
|
||||
func (m *metricsAPI) WritePermission(*http.Request) api.Permission { return api.NotSupported }
|
||||
|
||||
func (m *metricsAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Get API Request for permission and query.
|
||||
ar := api.GetAPIRequest(r)
|
||||
if ar == nil {
|
||||
http.Error(w, "Missing API Request.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Get expertise level from query.
|
||||
expertiseLevel := config.ExpertiseLevelDeveloper
|
||||
switch ar.Request.URL.Query().Get("level") {
|
||||
case config.ExpertiseLevelNameUser:
|
||||
expertiseLevel = config.ExpertiseLevelUser
|
||||
case config.ExpertiseLevelNameExpert:
|
||||
expertiseLevel = config.ExpertiseLevelExpert
|
||||
case config.ExpertiseLevelNameDeveloper:
|
||||
expertiseLevel = config.ExpertiseLevelDeveloper
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/plain; version=0.0.4; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
WriteMetrics(w, ar.AuthToken.Read, expertiseLevel)
|
||||
}
|
||||
|
||||
// WriteMetrics writes all metrics that match the given permission and
|
||||
// expertiseLevel to the given writer.
|
||||
func WriteMetrics(w io.Writer, permission api.Permission, expertiseLevel config.ExpertiseLevel) {
|
||||
registryLock.RLock()
|
||||
defer registryLock.RUnlock()
|
||||
|
||||
// Write all matching metrics.
|
||||
for _, metric := range registry {
|
||||
if permission >= metric.Opts().Permission &&
|
||||
expertiseLevel >= metric.Opts().ExpertiseLevel {
|
||||
metric.WritePrometheus(w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeMetricsTo(ctx context.Context, url string) error {
|
||||
// First, collect metrics into buffer.
|
||||
buf := &bytes.Buffer{}
|
||||
WriteMetrics(buf, api.PermitSelf, config.ExpertiseLevelDeveloper)
|
||||
|
||||
// Check if there is something to send.
|
||||
if buf.Len() == 0 {
|
||||
log.Debugf("metrics: not pushing metrics, nothing to send")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create request
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, buf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
// Send.
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check return status.
|
||||
if resp.StatusCode >= 200 && resp.StatusCode <= 299 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get and return error.
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
return fmt.Errorf(
|
||||
"got %s while writing metrics to %s: %s",
|
||||
resp.Status,
|
||||
url,
|
||||
body,
|
||||
)
|
||||
}
|
||||
|
||||
func metricsWriter(ctx context.Context) error {
|
||||
ticker := time.NewTicker(10 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
err := writeMetricsTo(ctx, pushURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
153
metrics/metric.go
Normal file
153
metrics/metric.go
Normal file
|
@ -0,0 +1,153 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/safing/portbase/api"
|
||||
"github.com/safing/portbase/config"
|
||||
|
||||
vm "github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
// PrometheusFormatRequirement is required format defined by prometheus for
|
||||
// metric and label names.
|
||||
const PrometheusFormatRequirement = "[a-zA-Z_][a-zA-Z0-9_]*"
|
||||
|
||||
var prometheusFormat = regexp.MustCompile(PrometheusFormatRequirement)
|
||||
|
||||
// Metric represents one or more metrics.
|
||||
type Metric interface {
|
||||
ID() string
|
||||
LabeledID() string
|
||||
Opts() *Options
|
||||
WritePrometheus(w io.Writer)
|
||||
}
|
||||
|
||||
type metricBase struct {
|
||||
Identifier string
|
||||
Labels map[string]string
|
||||
LabeledIdentifier string
|
||||
Options *Options
|
||||
set *vm.Set
|
||||
}
|
||||
|
||||
// Options can be used to set advanced metric settings.
|
||||
type Options struct {
|
||||
// Name defines an optional human readable name for the metric.
|
||||
Name string
|
||||
|
||||
// AlertLimit defines an upper limit that triggers an alert.
|
||||
AlertLimit float64
|
||||
|
||||
// AlertTimeframe defines an optional timeframe in seconds for which the
|
||||
// AlertLimit should be interpreted in.
|
||||
AlertTimeframe float64
|
||||
|
||||
// Permission defines the permission that is required to read the metric.
|
||||
Permission api.Permission
|
||||
|
||||
// ExpertiseLevel defines the expertise level that the metric is meant for.
|
||||
ExpertiseLevel config.ExpertiseLevel
|
||||
|
||||
// Persist enabled persisting the metric on shutdown and loading the previous
|
||||
// value at start. This is only supported for counters.
|
||||
Persist bool
|
||||
}
|
||||
|
||||
func newMetricBase(id string, labels map[string]string, opts Options) (*metricBase, error) {
|
||||
// Check formats.
|
||||
if !prometheusFormat.MatchString(id) {
|
||||
return nil, fmt.Errorf("metric name %q must match %s", id, PrometheusFormatRequirement)
|
||||
}
|
||||
for labelName := range labels {
|
||||
if !prometheusFormat.MatchString(labelName) {
|
||||
return nil, fmt.Errorf("metric label name %q must match %s", labelName, PrometheusFormatRequirement)
|
||||
}
|
||||
}
|
||||
|
||||
// Check permission.
|
||||
if opts.Permission < api.PermitAnyone {
|
||||
// Default to PermitUser.
|
||||
opts.Permission = api.PermitUser
|
||||
}
|
||||
|
||||
// Create metric base.
|
||||
base := &metricBase{
|
||||
Identifier: id,
|
||||
Labels: labels,
|
||||
Options: &opts,
|
||||
set: vm.NewSet(),
|
||||
}
|
||||
base.LabeledIdentifier = base.buildLabeledID()
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// ID returns the given ID of the metric.
|
||||
func (m *metricBase) ID() string {
|
||||
return m.Identifier
|
||||
}
|
||||
|
||||
// LabeledID returns the Prometheus-compatible labeled ID of the metric.
|
||||
func (m *metricBase) LabeledID() string {
|
||||
return m.LabeledIdentifier
|
||||
}
|
||||
|
||||
// Opts returns the metric options. They may not be modified.
|
||||
func (m *metricBase) Opts() *Options {
|
||||
return m.Options
|
||||
}
|
||||
|
||||
// WritePrometheus writes the metric in the prometheus format to the given writer.
|
||||
func (m *metricBase) WritePrometheus(w io.Writer) {
|
||||
m.set.WritePrometheus(w)
|
||||
}
|
||||
|
||||
func (m *metricBase) buildLabeledID() string {
|
||||
// Because we use the namespace and the global flags here, we need to flag
|
||||
// them as immutable.
|
||||
registryLock.Lock()
|
||||
defer registryLock.Unlock()
|
||||
firstMetricRegistered = true
|
||||
|
||||
// Build ID from Identifier.
|
||||
metricID := strings.TrimSpace(strings.ReplaceAll(m.Identifier, "/", "_"))
|
||||
|
||||
// Add namespace to ID.
|
||||
if metricNamespace != "" {
|
||||
metricID = metricNamespace + "_" + metricID
|
||||
}
|
||||
|
||||
// Return now if no labels are defined.
|
||||
if len(globalLabels) == 0 && len(m.Labels) == 0 {
|
||||
return metricID
|
||||
}
|
||||
|
||||
// Add global labels to the custom ones, if they don't exist yet.
|
||||
for labelName, labelValue := range globalLabels {
|
||||
if _, ok := m.Labels[labelName]; !ok {
|
||||
m.Labels[labelName] = labelValue
|
||||
}
|
||||
}
|
||||
|
||||
// Render labels into a slice and sort them in order to make the labeled ID
|
||||
// reproducible.
|
||||
labels := make([]string, 0, len(m.Labels))
|
||||
for labelName, labelValue := range m.Labels {
|
||||
labels = append(labels, fmt.Sprintf("%s=%q", labelName, labelValue))
|
||||
}
|
||||
sort.Strings(labels)
|
||||
|
||||
// Return fully labaled ID.
|
||||
return fmt.Sprintf("%s{%s}", metricID, strings.Join(labels, ","))
|
||||
}
|
||||
|
||||
// Split metrics into sets, according to the API Auth Levels, which will also correspond to the UI Mode levels. SPN // nodes will also allow public access to metrics with the permission "PermitAnyone".
|
||||
// Save "life-long" metrics on shutdown and load them at start.
|
||||
// Generate the correct metric name and labels.
|
||||
// Expose metrics via http, but also via the runtime DB in order to push metrics to the UI.
|
||||
// The UI will have to parse the prometheus metrics format and will not be able to immediately present historical data, // but data will have to be built.
|
||||
// Provide the option to push metrics to a prometheus push gateway, this is especially helpful when gathering data from // loads of SPN nodes.
|
44
metrics/metric_counter.go
Normal file
44
metrics/metric_counter.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
vm "github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
// Counter is a counter metric.
|
||||
type Counter struct {
|
||||
*metricBase
|
||||
*vm.Counter
|
||||
}
|
||||
|
||||
// NewCounter registers a new counter metric.
|
||||
func NewCounter(id string, labels map[string]string, opts *Options) (*Counter, error) {
|
||||
// Ensure that there are options.
|
||||
if opts == nil {
|
||||
opts = &Options{}
|
||||
}
|
||||
|
||||
// Make base.
|
||||
base, err := newMetricBase(id, labels, *opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create metric struct.
|
||||
m := &Counter{
|
||||
metricBase: base,
|
||||
}
|
||||
|
||||
// Create metric in set
|
||||
m.Counter = m.set.NewCounter(m.LabeledID())
|
||||
|
||||
// Register metric.
|
||||
err = register(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Load state.
|
||||
m.loadState()
|
||||
|
||||
return m, nil
|
||||
}
|
41
metrics/metric_gauge.go
Normal file
41
metrics/metric_gauge.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
vm "github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
// Gauge is a gauge metric.
|
||||
type Gauge struct {
|
||||
*metricBase
|
||||
*vm.Gauge
|
||||
}
|
||||
|
||||
// NewGauge registers a new gauge metric.
|
||||
func NewGauge(id string, labels map[string]string, fn func() float64, opts *Options) (*Gauge, error) {
|
||||
// Ensure that there are options.
|
||||
if opts == nil {
|
||||
opts = &Options{}
|
||||
}
|
||||
|
||||
// Make base.
|
||||
base, err := newMetricBase(id, labels, *opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create metric struct.
|
||||
m := &Gauge{
|
||||
metricBase: base,
|
||||
}
|
||||
|
||||
// Create metric in set
|
||||
m.Gauge = m.set.NewGauge(m.LabeledID(), fn)
|
||||
|
||||
// Register metric.
|
||||
err = register(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
41
metrics/metric_histogram.go
Normal file
41
metrics/metric_histogram.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
vm "github.com/VictoriaMetrics/metrics"
|
||||
)
|
||||
|
||||
// Histogram is a histogram metric.
|
||||
type Histogram struct {
|
||||
*metricBase
|
||||
*vm.Histogram
|
||||
}
|
||||
|
||||
// NewHistogram registers a new histogram metric.
|
||||
func NewHistogram(id string, labels map[string]string, opts *Options) (*Histogram, error) {
|
||||
// Ensure that there are options.
|
||||
if opts == nil {
|
||||
opts = &Options{}
|
||||
}
|
||||
|
||||
// Make base.
|
||||
base, err := newMetricBase(id, labels, *opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create metric struct.
|
||||
m := &Histogram{
|
||||
metricBase: base,
|
||||
}
|
||||
|
||||
// Create metric in set
|
||||
m.Histogram = m.set.NewHistogram(m.LabeledID())
|
||||
|
||||
// Register metric.
|
||||
err = register(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
40
metrics/metric_info.go
Normal file
40
metrics/metric_info.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/safing/portbase/info"
|
||||
)
|
||||
|
||||
func registerInfoMetric() error {
|
||||
meta := info.GetInfo()
|
||||
_, err := NewGauge(
|
||||
"info",
|
||||
map[string]string{
|
||||
"version": checkUnknown(meta.Version),
|
||||
"commit": checkUnknown(meta.Commit),
|
||||
"build_options": checkUnknown(meta.BuildOptions),
|
||||
"build_user": checkUnknown(meta.BuildUser),
|
||||
"build_host": checkUnknown(meta.BuildHost),
|
||||
"build_date": checkUnknown(meta.BuildDate),
|
||||
"build_source": checkUnknown(meta.BuildSource),
|
||||
"go_os": runtime.GOOS,
|
||||
"go_arch": runtime.GOARCH,
|
||||
"go_version": runtime.Version(),
|
||||
"go_compiler": runtime.Compiler,
|
||||
},
|
||||
func() float64 {
|
||||
return 1
|
||||
},
|
||||
nil,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func checkUnknown(s string) string {
|
||||
if strings.Contains(s, "unknown") {
|
||||
return "unknown"
|
||||
}
|
||||
return s
|
||||
}
|
48
metrics/metric_runtime.go
Normal file
48
metrics/metric_runtime.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
vm "github.com/VictoriaMetrics/metrics"
|
||||
"github.com/safing/portbase/api"
|
||||
"github.com/safing/portbase/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
registryLock.Lock()
|
||||
defer registryLock.Unlock()
|
||||
|
||||
registry = append(registry, &runtimeMetrics{})
|
||||
}
|
||||
|
||||
var runtimeOpts = &Options{
|
||||
Name: "Golang Runtime",
|
||||
Permission: api.PermitAdmin,
|
||||
ExpertiseLevel: config.ExpertiseLevelDeveloper,
|
||||
}
|
||||
|
||||
type runtimeMetrics struct{}
|
||||
|
||||
func (r *runtimeMetrics) ID() string {
|
||||
return "_runtime"
|
||||
}
|
||||
|
||||
func (r *runtimeMetrics) LabeledID() string {
|
||||
return "_runtime"
|
||||
}
|
||||
|
||||
func (r *runtimeMetrics) Opts() *Options {
|
||||
return runtimeOpts
|
||||
}
|
||||
|
||||
func (r *runtimeMetrics) Permission() api.Permission {
|
||||
return runtimeOpts.Permission
|
||||
}
|
||||
|
||||
func (r *runtimeMetrics) ExpertiseLevel() config.ExpertiseLevel {
|
||||
return runtimeOpts.ExpertiseLevel
|
||||
}
|
||||
|
||||
func (r *runtimeMetrics) WritePrometheus(w io.Writer) {
|
||||
vm.WriteProcessMetrics(w)
|
||||
}
|
140
metrics/module.go
Normal file
140
metrics/module.go
Normal file
|
@ -0,0 +1,140 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/safing/portbase/modules"
|
||||
)
|
||||
|
||||
var (
|
||||
module *modules.Module
|
||||
|
||||
registry []Metric
|
||||
registryLock sync.RWMutex
|
||||
|
||||
firstMetricRegistered bool
|
||||
metricNamespace string
|
||||
globalLabels = make(map[string]string)
|
||||
|
||||
pushURL string
|
||||
metricInstance string
|
||||
|
||||
// ErrAlreadyStarted is returned when an operation is only valid before the
|
||||
// first metric is registered, and is called after.
|
||||
ErrAlreadyStarted = errors.New("can only be changed before first metric is registered")
|
||||
|
||||
// ErrAlreadyRegistered is returned when a metric with the same ID is
|
||||
// registered again.
|
||||
ErrAlreadyRegistered = errors.New("metric already registered")
|
||||
|
||||
// ErrAlreadySet is returned when a value is already set and cannot be changed.
|
||||
ErrAlreadySet = errors.New("already set")
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&pushURL, "push-metrics", "", "URL to push prometheus metrics to")
|
||||
flag.StringVar(&metricInstance, "metrics-instance", "", "Set the global instance label")
|
||||
|
||||
module = modules.Register("metrics", prep, start, stop, "database", "api")
|
||||
}
|
||||
|
||||
func prep() error {
|
||||
// Add metric instance name as global variable if set.
|
||||
if metricInstance != "" {
|
||||
if err := AddGlobalLabel("instance", metricInstance); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return registerInfoMetric()
|
||||
}
|
||||
|
||||
func start() error {
|
||||
if err := registerAPI(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pushURL != "" {
|
||||
module.StartServiceWorker("metric pusher", 0, metricsWriter)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func stop() error {
|
||||
storePersistentMetrics()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func register(m Metric) error {
|
||||
registryLock.Lock()
|
||||
defer registryLock.Unlock()
|
||||
|
||||
// Check if metric ID is already registered.
|
||||
for _, registeredMetric := range registry {
|
||||
if m.LabeledID() == registeredMetric.LabeledID() {
|
||||
return ErrAlreadyRegistered
|
||||
}
|
||||
}
|
||||
|
||||
// Add new metric to registry and sort it.
|
||||
registry = append(registry, m)
|
||||
sort.Sort(byLabeledID(registry))
|
||||
|
||||
// Set flag that first metric is now registered.
|
||||
firstMetricRegistered = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetNamespace sets the namespace for all metrics. It is prefixed to all
|
||||
// metric IDs.
|
||||
// It must be set before any metric is registered.
|
||||
// Does not affect golang runtime metrics.
|
||||
func SetNamespace(namespace string) error {
|
||||
// Lock registry and check if a first metric is already registered.
|
||||
registryLock.Lock()
|
||||
defer registryLock.Unlock()
|
||||
if firstMetricRegistered {
|
||||
return ErrAlreadyStarted
|
||||
}
|
||||
|
||||
// Check if the namespace is already set.
|
||||
if metricNamespace != "" {
|
||||
return ErrAlreadySet
|
||||
}
|
||||
|
||||
metricNamespace = namespace
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddGlobalLabel adds a global label to all metrics.
|
||||
// Global labels must be added before any metric is registered.
|
||||
// Does not affect golang runtime metrics.
|
||||
func AddGlobalLabel(name, value string) error {
|
||||
// Lock registry and check if a first metric is already registered.
|
||||
registryLock.Lock()
|
||||
defer registryLock.Unlock()
|
||||
if firstMetricRegistered {
|
||||
return ErrAlreadyStarted
|
||||
}
|
||||
|
||||
// Check format.
|
||||
if !prometheusFormat.MatchString(name) {
|
||||
return fmt.Errorf("metric label name %q must match %s", name, PrometheusFormatRequirement)
|
||||
}
|
||||
|
||||
globalLabels[name] = value
|
||||
return nil
|
||||
}
|
||||
|
||||
type byLabeledID []Metric
|
||||
|
||||
func (r byLabeledID) Len() int { return len(r) }
|
||||
func (r byLabeledID) Less(i, j int) bool { return r[i].LabeledID() < r[j].LabeledID() }
|
||||
func (r byLabeledID) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
151
metrics/persistence.go
Normal file
151
metrics/persistence.go
Normal file
|
@ -0,0 +1,151 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/database"
|
||||
"github.com/safing/portbase/database/record"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/tevino/abool"
|
||||
)
|
||||
|
||||
var (
|
||||
storage *metricsStorage
|
||||
storageKey string
|
||||
storageInit = abool.New()
|
||||
storageLoaded = abool.New()
|
||||
|
||||
db = database.NewInterface(&database.Options{
|
||||
Local: true,
|
||||
Internal: true,
|
||||
})
|
||||
|
||||
// ErrAlreadyInitialized is returned when trying to initialize an option
|
||||
// more than once.
|
||||
ErrAlreadyInitialized = errors.New("already initialized")
|
||||
)
|
||||
|
||||
type metricsStorage struct {
|
||||
sync.Mutex
|
||||
record.Base
|
||||
|
||||
Start time.Time
|
||||
Counters map[string]uint64
|
||||
}
|
||||
|
||||
// EnableMetricPersistence enables metric persistence for metrics that opted
|
||||
// for it. They given key is the database key where the metric data will be
|
||||
// persisted.
|
||||
// This call also directly loads the stored data from the database.
|
||||
// The returned error is only about loading the metrics, not about enabling
|
||||
// persistence.
|
||||
// May only be called once.
|
||||
func EnableMetricPersistence(key string) error {
|
||||
// Check if already initialized.
|
||||
if !storageInit.SetToIf(false, true) {
|
||||
return ErrAlreadyInitialized
|
||||
}
|
||||
|
||||
// Set storage key.
|
||||
storageKey = key
|
||||
|
||||
// Load metrics from storage.
|
||||
var err error
|
||||
storage, err = getMetricsStorage(key)
|
||||
switch {
|
||||
case err == nil:
|
||||
// Continue.
|
||||
case errors.Is(err, database.ErrNotFound):
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
storageLoaded.Set()
|
||||
|
||||
// Load saved state for all counter metrics.
|
||||
registryLock.RLock()
|
||||
defer registryLock.RUnlock()
|
||||
|
||||
for _, m := range registry {
|
||||
counter, ok := m.(*Counter)
|
||||
if ok {
|
||||
counter.loadState()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Counter) loadState() {
|
||||
// Check if we can and should load the state.
|
||||
if !storageLoaded.IsSet() || !c.Opts().Persist {
|
||||
return
|
||||
}
|
||||
|
||||
c.Set(storage.Counters[c.LabeledID()])
|
||||
}
|
||||
|
||||
func storePersistentMetrics() {
|
||||
// Check if persistence is enabled.
|
||||
if !storageInit.IsSet() || storageKey == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Create new storage.
|
||||
newStorage := &metricsStorage{
|
||||
Start: time.Now(),
|
||||
Counters: make(map[string]uint64),
|
||||
}
|
||||
newStorage.SetKey(storageKey)
|
||||
// Copy values from previous version.
|
||||
if storageLoaded.IsSet() {
|
||||
newStorage.Start = storage.Start
|
||||
}
|
||||
|
||||
registryLock.RLock()
|
||||
defer registryLock.RUnlock()
|
||||
|
||||
// Export all counter metrics.
|
||||
for _, m := range registry {
|
||||
if m.Opts().Persist {
|
||||
counter, ok := m.(*Counter)
|
||||
if ok {
|
||||
newStorage.Counters[m.LabeledID()] = counter.Get()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save to database.
|
||||
err := db.Put(newStorage)
|
||||
if err != nil {
|
||||
log.Warningf("metrics: failed to save metrics storage to db: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func getMetricsStorage(key string) (*metricsStorage, error) {
|
||||
r, err := db.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// unwrap
|
||||
if r.IsWrapped() {
|
||||
// only allocate a new struct, if we need it
|
||||
new := &metricsStorage{}
|
||||
err = record.Unwrap(r, new)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return new, nil
|
||||
}
|
||||
|
||||
// or adjust type
|
||||
new, ok := r.(*metricsStorage)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("record not of type *metricsStorage, but %T", r)
|
||||
}
|
||||
return new, nil
|
||||
}
|
1
metrics/test/.gitignore
vendored
Normal file
1
metrics/test/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
data
|
4
metrics/test/README.md
Normal file
4
metrics/test/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Testing metrics
|
||||
|
||||
You can spin up a test setup for pushing and viewing metrics with `docker-compose up`.
|
||||
Then use the flag `--push-metrics http://127.0.0.1:8428/api/v1/import/prometheus` to push metrics.
|
36
metrics/test/docker-compose.yml
Normal file
36
metrics/test/docker-compose.yml
Normal file
|
@ -0,0 +1,36 @@
|
|||
version: '3.8'
|
||||
|
||||
networks:
|
||||
pm-metrics-test-net:
|
||||
|
||||
services:
|
||||
|
||||
victoriametrics:
|
||||
container_name: pm-metrics-test-victoriametrics
|
||||
image: victoriametrics/victoria-metrics
|
||||
command:
|
||||
- '--storageDataPath=/storage'
|
||||
ports:
|
||||
- 8428:8428
|
||||
volumes:
|
||||
- ./data/victoriametrics:/storage
|
||||
networks:
|
||||
- pm-metrics-test-net
|
||||
restart: always
|
||||
|
||||
grafana:
|
||||
container_name: pm-metrics-test-grafana
|
||||
image: grafana/grafana
|
||||
command:
|
||||
- '--config=/etc/grafana/provisioning/config.ini'
|
||||
depends_on:
|
||||
- "victoriametrics"
|
||||
ports:
|
||||
- 3000:3000
|
||||
volumes:
|
||||
- ./data/grafana:/var/lib/grafana
|
||||
- ./grafana:/etc/grafana/provisioning
|
||||
- ./dashboards:/dashboards
|
||||
networks:
|
||||
- pm-metrics-test-net
|
||||
restart: always
|
10
metrics/test/grafana/config.ini
Normal file
10
metrics/test/grafana/config.ini
Normal file
|
@ -0,0 +1,10 @@
|
|||
[auth]
|
||||
disable_login_form = true
|
||||
disable_signout_menu = true
|
||||
|
||||
[auth.basic]
|
||||
enabled = false
|
||||
|
||||
[auth.anonymous]
|
||||
enabled = true
|
||||
org_role = Admin
|
11
metrics/test/grafana/dashboards/portmaster.yml
Normal file
11
metrics/test/grafana/dashboards/portmaster.yml
Normal file
|
@ -0,0 +1,11 @@
|
|||
apiVersion: 1
|
||||
|
||||
providers:
|
||||
- name: 'Portmaster'
|
||||
folder: 'Portmaster'
|
||||
disableDeletion: true
|
||||
updateIntervalSeconds: 10
|
||||
allowUiUpdates: true
|
||||
options:
|
||||
path: /dashboards
|
||||
foldersFromFilesStructure: true
|
8
metrics/test/grafana/datasources/datasource.yml
Normal file
8
metrics/test/grafana/datasources/datasource.yml
Normal file
|
@ -0,0 +1,8 @@
|
|||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: VictoriaMetrics
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://pm-metrics-test-victoriametrics:8428
|
||||
isDefault: true
|
|
@ -5,6 +5,10 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
|
||||
// Version is fixed to commit 353f8196982447d8b12c64f69530e657331e3dbc.
|
||||
// The follow-up commit removes Windows support.
|
||||
// TOOD: Check how we want to handle this in the future, possibly ingest
|
||||
// needed functionality into here.
|
||||
"github.com/google/renameio"
|
||||
)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue