diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a5fd2bc..60588af 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,27 +5,27 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/checkout@v5 + - uses: actions/setup-go@v6 with: - go-version: '1.24.1' + go-version: ^1.25 cache: false - name: golangci-lint uses: golangci/golangci-lint-action@v8 with: - version: v2.1 + version: v2.4 tests: # needs: [golanglint] name: Tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - run: mkdir build && touch build/index.html - name: Set up Go 1.x - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: - go-version: ^1.24 + go-version: ^1.25 id: go - uses: actions/cache@v3 diff --git a/.gitignore b/.gitignore index 5945fd3..eb59421 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ .vscode _config .DS_Store -_metrics \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 0e40a07..2a9c7be 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.24.3-bookworm AS builder +FROM golang:1.25.1-bookworm AS builder ARG MERGE_BOT_VERSION=dev WORKDIR /code ADD go.mod /code/ diff --git a/bot.go b/bot.go index f7bcb06..899f1af 100644 --- a/bot.go +++ b/bot.go @@ -8,6 +8,7 @@ import ( "github.com/gasoid/merge-bot/config" "github.com/gasoid/merge-bot/handlers" "github.com/gasoid/merge-bot/logger" + "github.com/gasoid/merge-bot/metrics" "github.com/gasoid/merge-bot/webhook" "net/http" @@ -140,6 +141,12 @@ func handle(onEvent string, funcHandler func(*handlers.Request, string) error) { defer handlerMu.Unlock() handlerFuncs[onEvent] = func(command *handlers.Request, args string) error { - return funcHandler(command, args) + + return metrics.Handler( + onEvent, + func() error { + return funcHandler(command, args) + }, + ) } } diff --git a/go.mod b/go.mod index aaba1b8..bd9f7f8 100644 --- a/go.mod +++ b/go.mod @@ -1,39 +1,46 @@ module github.com/gasoid/merge-bot -go 1.24.1 +go 1.25.1 require ( github.com/dustin/go-humanize v1.0.1 github.com/getsentry/sentry-go v0.33.0 github.com/getsentry/sentry-go/echo v0.33.0 github.com/getsentry/sentry-go/slog v0.33.0 - github.com/labstack/echo/v4 v4.12.0 + github.com/labstack/echo-contrib v0.17.4 + github.com/labstack/echo/v4 v4.13.3 github.com/ldez/go-git-cmd-wrapper/v2 v2.9.1 github.com/peterbourgon/ff/v3 v3.4.0 - github.com/stretchr/testify v1.10.0 + github.com/prometheus/client_golang v1.23.2 + github.com/prometheus/client_model v0.6.2 + github.com/stretchr/testify v1.11.1 gitlab.com/gitlab-org/api/client-go v0.130.1 - golang.org/x/crypto v0.31.0 + golang.org/x/crypto v0.41.0 gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect - github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-retryablehttp v0.7.7 // indirect - github.com/kr/pretty v0.3.1 // indirect github.com/labstack/gommon v0.4.2 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - golang.org/x/net v0.33.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.28.0 // indirect - golang.org/x/text v0.21.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.11.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect ) diff --git a/go.sum b/go.sum index def35b5..f79f824 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,7 @@ -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -13,11 +16,9 @@ github.com/getsentry/sentry-go/slog v0.33.0 h1:/00kTrdJ5InG3VU1l1uXXTK0ivCiY1MWL github.com/getsentry/sentry-go/slog v0.33.0/go.mod h1:Y+LOL05bbKhfiR8dT7zsa2ulsKAnNhSxDVB89TcXC0o= 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/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= @@ -26,39 +27,48 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1 github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= -github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo-contrib v0.17.4 h1:g5mfsrJfJTKv+F5uNKCyrjLK7js+ZW6HTjg4FnDxxgk= +github.com/labstack/echo-contrib v0.17.4/go.mod h1:9O7ZPAHUeMGTOAfg80YqQduHzt0CzLak36PZRldYrZ0= +github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= +github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= -github.com/ldez/go-git-cmd-wrapper/v2 v2.8.0 h1:+lD1DY7jRCy0hBTgrlVSRQtfQvyCr4SGOlf+C+eXBtM= -github.com/ldez/go-git-cmd-wrapper/v2 v2.8.0/go.mod h1:0eUeas7XtKDPKQbB0KijfaMPbuQ/cIprtoTRiwaUoFg= -github.com/ldez/go-git-cmd-wrapper/v2 v2.8.1-0.20250712135928-73c6ef5f7899 h1:/rgNOZm7YzhB+cU8fXr1l+pgMUy7w/GmkK55FZR6E5A= -github.com/ldez/go-git-cmd-wrapper/v2 v2.8.1-0.20250712135928-73c6ef5f7899/go.mod h1:0eUeas7XtKDPKQbB0KijfaMPbuQ/cIprtoTRiwaUoFg= github.com/ldez/go-git-cmd-wrapper/v2 v2.9.1 h1:QJRB9Gs5i/h6TVJI6yl09Qm6rNooznRiKwIw+VIxd90= github.com/ldez/go-git-cmd-wrapper/v2 v2.9.1/go.mod h1:0eUeas7XtKDPKQbB0KijfaMPbuQ/cIprtoTRiwaUoFg= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc= github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= @@ -67,21 +77,24 @@ gitlab.com/gitlab-org/api/client-go v0.130.1 h1:1xF5C5Zq3sFeNg3PzS2z63oqrxifne3n gitlab.com/gitlab-org/api/client-go v0.130.1/go.mod h1:ZhSxLAWadqP6J9lMh40IAZOlOxBLPRh7yFOXR/bMJWM= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/handlers/request.go b/handlers/request.go index 1a57e9d..2ef160e 100644 --- a/handlers/request.go +++ b/handlers/request.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/gasoid/merge-bot/logger" + "github.com/gasoid/merge-bot/metrics" "github.com/gasoid/merge-bot/semaphore" "gopkg.in/yaml.v3" @@ -137,17 +138,19 @@ func (r *Request) DeleteStaleBranches() error { return nil } - deleteStaleBranches.Add(fmt.Sprintf("clean_stale_merge_requests_%d", r.info.ProjectId), func() { - if err := r.cleanStaleMergeRequests(); err != nil { - logger.Info("cleanStaleMergeRequests", "err", err) - } - }) + deleteStaleBranches.Add(fmt.Sprintf("clean_stale_merge_requests_%d", r.info.ProjectId), + metrics.BackgroundRun("clean_stale_merge_requests", func() { + if err := r.cleanStaleMergeRequests(); err != nil { + logger.Info("cleanStaleMergeRequests", "err", err) + } + })) - deleteStaleBranches.Add(fmt.Sprintf("clean_stale_branches_%d", r.info.ProjectId), func() { - if err := r.cleanStaleBranches(); err != nil { - logger.Info("cleanStaleBranches", "err", err) - } - }) + deleteStaleBranches.Add(fmt.Sprintf("clean_stale_branches_%d", r.info.ProjectId), + metrics.BackgroundRun("clean_stale_branches", func() { + if err := r.cleanStaleBranches(); err != nil { + logger.Info("cleanStaleBranches", "err", err) + } + })) return nil } @@ -184,11 +187,12 @@ func (r Request) UpdateBranches() error { } for _, mr := range listMr { - updateBranch.Add(fmt.Sprintf("update_branch_%d_%d", r.info.ProjectId, mr.Id), func() { - if err := r.provider.UpdateFromMaster(r.info.ProjectId, mr.Id); err != nil { - logger.Info("UpdateFromMaster", "err", err) - } - }) + updateBranch.Add(fmt.Sprintf("update_branch_%d_%d", r.info.ProjectId, mr.Id), + metrics.BackgroundRun("update_branch", func() { + if err := r.provider.UpdateFromMaster(r.info.ProjectId, mr.Id); err != nil { + logger.Info("UpdateFromMaster", "err", err) + } + })) } return nil diff --git a/metrics/metrics.go b/metrics/metrics.go new file mode 100644 index 0000000..5ed1572 --- /dev/null +++ b/metrics/metrics.go @@ -0,0 +1,131 @@ +package metrics + +import ( + "errors" + "net/http" + "strings" + "time" + + "github.com/gasoid/merge-bot/logger" + "github.com/labstack/echo-contrib/echoprometheus" + "github.com/labstack/echo/v4" + "github.com/prometheus/client_golang/prometheus" +) + +var ( + commandsCounter *prometheus.CounterVec + updateDuration prometheus.Histogram + backgroundTaskEnqueuedCounter *prometheus.CounterVec + backgroundTaskCounter *prometheus.CounterVec +) + +const ( + commandSucceeded = "succeeded" + commandFailed = "failed" +) + +func Handler(event string, f func() error) error { + start := time.Now() + err := f() + + if strings.HasPrefix(event, "!") { + if err != nil { + CommandFailedInc(event) + } else { + CommandSucceededInc(event) + } + + if event == "!update" { + duration := time.Since(start) + UpdateDuration(duration) + } + } + + return err +} + +func BackgroundRun(task string, f func()) func() { + backgroundTaskEnqueuedCounter.WithLabelValues(task).Inc() + + return func() { + backgroundTaskCounter.WithLabelValues(task).Inc() + f() + } +} + +func CommandSucceededInc(command string) { + commandsCounter.WithLabelValues(command, commandSucceeded).Inc() +} + +func CommandFailedInc(command string) { + commandsCounter.WithLabelValues(command, commandFailed).Inc() +} + +func UpdateDuration(duration time.Duration) { + updateDuration.Observe(duration.Seconds()) +} + +func initMetrics() error { + commandsCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "mergebot_commands_total", + Help: "How many webhook commands bot has received", + }, + []string{"command", "status"}, + ) + + backgroundTaskCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "mergebot_background_tasks_total", + Help: "How many background tasks run", + }, + []string{"task"}, + ) + + backgroundTaskEnqueuedCounter = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "mergebot_background_tasks_enqueued_total", + Help: "How many background tasks enqueued/added", + }, + []string{"task"}, + ) + + updateDuration = prometheus.NewHistogram(prometheus.HistogramOpts{ + Name: "mergebot_update_duration", + Help: "Time it has taken to update the branch", + Buckets: prometheus.LinearBuckets(5, 4, 10), + }) + + if err := prometheus.Register(backgroundTaskCounter); err != nil { + return err + } + + if err := prometheus.Register(backgroundTaskEnqueuedCounter); err != nil { + return err + } + + if err := prometheus.Register(updateDuration); err != nil { + return err + } + + if err := prometheus.Register(commandsCounter); err != nil { + return err + } + + go func() { + metrics := echo.New() + metrics.GET("/metrics", echoprometheus.NewHandler()) + if err := metrics.Start(":8081"); err != nil && !errors.Is(err, http.ErrServerClosed) { + logger.Error(err.Error()) + } + }() + return nil +} + +func init() { + err := initMetrics() + if err != nil { + logger.Error("initMetrics", "err", err) + panic(err) + } +} diff --git a/metrics/metrics_test.go b/metrics/metrics_test.go new file mode 100644 index 0000000..bcafefe --- /dev/null +++ b/metrics/metrics_test.go @@ -0,0 +1,93 @@ +package metrics + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" + "github.com/stretchr/testify/assert" +) + +func TestCommandSucceededInc(t *testing.T) { + // Reset the counter + commandsCounter.Reset() + + // Call the function + CommandSucceededInc("test-command") + + // Get the metric + metricFamilies, err := prometheus.DefaultGatherer.Gather() + assert.NoError(t, err) + + // Find our metric + var commandsMetric *dto.MetricFamily + for _, mf := range metricFamilies { + if mf.GetName() == "mergebot_commands_total" { + commandsMetric = mf + break + } + } + + assert.NotNil(t, commandsMetric) + assert.Equal(t, 1, len(commandsMetric.GetMetric())) + + metric := commandsMetric.GetMetric()[0] + assert.Equal(t, float64(1), metric.GetCounter().GetValue()) + + // Check labels + labels := metric.GetLabel() + labelMap := make(map[string]string) + for _, label := range labels { + labelMap[label.GetName()] = label.GetValue() + } + + assert.Equal(t, "test-command", labelMap["command"]) + assert.Equal(t, commandSucceeded, labelMap["status"]) +} + +func TestCommandFailedInc(t *testing.T) { + // Reset the counter + commandsCounter.Reset() + + // Call the function + CommandFailedInc("test-command") + + // Get the metric + metricFamilies, err := prometheus.DefaultGatherer.Gather() + assert.NoError(t, err) + + // Find our metric + var commandsMetric *dto.MetricFamily + for _, mf := range metricFamilies { + if mf.GetName() == "mergebot_commands_total" { + commandsMetric = mf + break + } + } + + assert.NotNil(t, commandsMetric) + + metric := commandsMetric.GetMetric()[0] + assert.Equal(t, float64(1), metric.GetCounter().GetValue()) + + // Check labels + labels := metric.GetLabel() + labelMap := make(map[string]string) + for _, label := range labels { + labelMap[label.GetName()] = label.GetValue() + } + + assert.Equal(t, "test-command", labelMap["command"]) + assert.Equal(t, commandFailed, labelMap["status"]) +} + +func TestInitMetrics(t *testing.T) { + // initMetrics should have been called during init() + // Let's verify that commandsCounter is not nil + assert.NotNil(t, commandsCounter) +} + +func TestConstants(t *testing.T) { + assert.Equal(t, "succeeded", commandSucceeded) + assert.Equal(t, "failed", commandFailed) +}