gitlab-exporter 16.10.0 → 17.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitlab-ci.yml +3 -3
- data/.rubocop.yml +1 -1
- data/.ruby-version +1 -1
- data/AGENTS.md +75 -0
- data/CLAUDE.md +1 -0
- data/Gemfile.lock +7 -23
- data/config/gitlab-exporter.yml.example +0 -1
- data/gitlab-exporter.gemspec +5 -3
- data/lib/gitlab_exporter/database/tuple_stats.rb +0 -2
- data/lib/gitlab_exporter/version.rb +1 -1
- data/lib/gitlab_exporter/web_exporter.rb +96 -104
- data/spec/database/tuple_stats_spec.rb +40 -0
- data/spec/web_exporter_spec.rb +127 -0
- metadata +22 -31
- data/lib/gitlab_exporter/rack_vulndb_255039_patch.rb +0 -35
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 15b0fdb50984859832e6a21dcb76c31dfbfb68f28e5601f2c1389782ea4b5f2f
|
|
4
|
+
data.tar.gz: a02d8bda949c2d5f94981d50123ff845e73d958306d0bcf9e3ca0cfd4c8107e5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ab40fc6975a31c6175cb3139dd1e576a06f7f1a6b598efc387f76228e68413a47b7902d7aca1682128ee77dd76ee4f2e178fec7bed8dc027676ec2e614289490
|
|
7
|
+
data.tar.gz: e749cdfd3a8db77f8a594ad7007db5dc919003defa0afb7c3691a461b78d0bcdf459e8bce6d5106809f0cdc567323a968fe1d8de88c990a2c5074339b6c38343
|
data/.gitlab-ci.yml
CHANGED
|
@@ -32,7 +32,7 @@ default:
|
|
|
32
32
|
# to avoid https://github.com/rubygems/rubygems/issues/5380 use an
|
|
33
33
|
# older Bundler version.
|
|
34
34
|
case ${RUBY_VERSION} in
|
|
35
|
-
"3.
|
|
35
|
+
"3.1")
|
|
36
36
|
export BUNDLER_VERSION="2.4.20"
|
|
37
37
|
;;
|
|
38
38
|
esac
|
|
@@ -53,7 +53,7 @@ rspec:
|
|
|
53
53
|
before_script: *before_scripts
|
|
54
54
|
parallel:
|
|
55
55
|
matrix:
|
|
56
|
-
- RUBY_VERSION: ["3.
|
|
56
|
+
- RUBY_VERSION: ["3.1", "3.2", "3.3", "3.4", "4.0"]
|
|
57
57
|
|
|
58
58
|
rspec_integration:
|
|
59
59
|
script:
|
|
@@ -88,7 +88,7 @@ rspec_integration:
|
|
|
88
88
|
REDIS_SENTINEL_PASSWORD: "my-sentinel-password"
|
|
89
89
|
parallel:
|
|
90
90
|
matrix:
|
|
91
|
-
- RUBY_VERSION: ["3.
|
|
91
|
+
- RUBY_VERSION: ["3.1", "3.2", "3.3", "3.4", "4.0"]
|
|
92
92
|
|
|
93
93
|
rubocop:
|
|
94
94
|
script:
|
data/.rubocop.yml
CHANGED
data/.ruby-version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.1.7
|
data/AGENTS.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# gitlab-exporter — AI agent guidelines
|
|
2
|
+
|
|
3
|
+
gitlab-exporter is a Prometheus web exporter (a Ruby gem) that collects GitLab
|
|
4
|
+
production metrics via custom probes and exposes them over HTTP for a Prometheus
|
|
5
|
+
scraper. See `README.md` for the probe catalog.
|
|
6
|
+
|
|
7
|
+
## Project layout
|
|
8
|
+
|
|
9
|
+
- `lib/gitlab_exporter/` — probes and the exporter runtime
|
|
10
|
+
- `lib/gitlab_exporter/version.rb` — the gem version; bumping this on `master`
|
|
11
|
+
triggers a release (see below)
|
|
12
|
+
- `bin/gitlab-exporter` — executable entry point
|
|
13
|
+
- `spec/` — RSpec tests (`-t integration` marks integration specs that need Redis/PG)
|
|
14
|
+
|
|
15
|
+
## Development commands
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
bundle install
|
|
19
|
+
bundle exec rspec spec # unit tests
|
|
20
|
+
bundle exec rspec spec -t integration # integration tests (needs Redis/Postgres)
|
|
21
|
+
bundle exec rubocop # lint
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Commit messages
|
|
25
|
+
|
|
26
|
+
Follow the [GitLab commit message guidelines](https://docs.gitlab.com/development/contributing/merge_request_workflow/#commit-messages-guidelines).
|
|
27
|
+
|
|
28
|
+
- Subject: imperative mood, capitalized, no trailing period, ≤ 72 chars
|
|
29
|
+
("Add Zoekt probe", not "Added Zoekt probe").
|
|
30
|
+
- Body: explain *why*, wrap at 72 chars. Reference issues/MRs with full URLs,
|
|
31
|
+
not short references (`#123`).
|
|
32
|
+
|
|
33
|
+
## Changelog trailer (required for user-facing changes)
|
|
34
|
+
|
|
35
|
+
Releases are cut by the `gem-release` CI component, which builds the release
|
|
36
|
+
notes from GitLab's changelog generator. That generator **only includes commits
|
|
37
|
+
that carry a `Changelog:` git trailer** — commits without one are dropped, which
|
|
38
|
+
is why a release can end up saying "No changes." and produce an empty changelog
|
|
39
|
+
in downstream projects (e.g. omnibus-gitlab's Renovate MRs).
|
|
40
|
+
|
|
41
|
+
Add the trailer as a **separate block at the end of the commit message**,
|
|
42
|
+
preceded by a blank line (the blank line is required for git to parse it as a
|
|
43
|
+
trailer; without it the changelog generator silently drops the commit):
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
Changelog: <type>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Valid types: `added`, `fixed`, `changed`, `deprecated`, `removed`, `security`,
|
|
50
|
+
`performance`, `other`.
|
|
51
|
+
|
|
52
|
+
**Include a trailer for:** new or changed probes/metrics, behavior changes,
|
|
53
|
+
bug fixes, dependency bumps that affect users, security fixes.
|
|
54
|
+
|
|
55
|
+
**Skip the trailer for:** test-only changes, documentation-only changes, CI/
|
|
56
|
+
tooling-only changes, and pure internal refactors with no user-visible effect.
|
|
57
|
+
|
|
58
|
+
Example:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
Add remote mirror staleness metric
|
|
62
|
+
|
|
63
|
+
Exposes project_remote_mirror_last_successful_update_time_seconds so
|
|
64
|
+
operators can alert on mirrors that have stopped syncing.
|
|
65
|
+
|
|
66
|
+
Changelog: added
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Releasing
|
|
70
|
+
|
|
71
|
+
A release happens when `lib/gitlab_exporter/version.rb` changes on `master`.
|
|
72
|
+
The CI pipeline then generates the changelog from the `Changelog:` trailers
|
|
73
|
+
since the previous tag, creates the GitLab Release, and publishes the gem to
|
|
74
|
+
RubyGems. Keeping trailers accurate on every merged commit is what makes those
|
|
75
|
+
release notes meaningful.
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
data/Gemfile.lock
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
gitlab-exporter (
|
|
4
|
+
gitlab-exporter (17.0.1)
|
|
5
|
+
base64 (= 0.3.0)
|
|
5
6
|
connection_pool (= 2.5.5)
|
|
6
7
|
deep_merge (~> 1.2.2)
|
|
7
8
|
faraday (= 2.14.3)
|
|
8
9
|
pg (= 1.6.3)
|
|
9
|
-
puma (= 8.0.2)
|
|
10
10
|
quantile (= 0.2.1)
|
|
11
11
|
redis (= 4.8.1)
|
|
12
12
|
redis-namespace (= 1.11.0)
|
|
13
13
|
sidekiq (= 6.5.12)
|
|
14
|
-
sinatra (~> 3.2.0)
|
|
15
14
|
webrick (~> 1.9)
|
|
16
15
|
|
|
17
16
|
GEM
|
|
18
17
|
remote: https://rubygems.org/
|
|
19
18
|
specs:
|
|
20
19
|
ast (2.4.3)
|
|
21
|
-
base64 (0.
|
|
20
|
+
base64 (0.3.0)
|
|
22
21
|
connection_pool (2.5.5)
|
|
23
22
|
deep_merge (1.2.2)
|
|
24
23
|
diff-lcs (1.5.0)
|
|
@@ -31,23 +30,15 @@ GEM
|
|
|
31
30
|
language_server-protocol (3.17.0.5)
|
|
32
31
|
lint_roller (1.1.0)
|
|
33
32
|
logger (1.7.0)
|
|
34
|
-
mustermann (3.0.3)
|
|
35
|
-
ruby2_keywords (~> 0.0.1)
|
|
36
|
-
nio4r (2.7.4)
|
|
37
33
|
parallel (1.27.0)
|
|
38
34
|
parser (3.3.9.0)
|
|
39
35
|
ast (~> 2.4.1)
|
|
40
36
|
racc
|
|
41
37
|
pg (1.6.3)
|
|
42
38
|
prism (1.9.0)
|
|
43
|
-
puma (8.0.2)
|
|
44
|
-
nio4r (~> 2.0)
|
|
45
39
|
quantile (0.2.1)
|
|
46
40
|
racc (1.8.1)
|
|
47
41
|
rack (2.2.15)
|
|
48
|
-
rack-protection (3.2.0)
|
|
49
|
-
base64 (>= 0.1.0)
|
|
50
|
-
rack (~> 2.2, >= 2.2.4)
|
|
51
42
|
rainbow (3.1.1)
|
|
52
43
|
redis (4.8.1)
|
|
53
44
|
redis-namespace (1.11.0)
|
|
@@ -66,11 +57,11 @@ GEM
|
|
|
66
57
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
67
58
|
rspec-support (~> 3.12.0)
|
|
68
59
|
rspec-support (3.12.0)
|
|
69
|
-
rubocop (1.
|
|
60
|
+
rubocop (1.89.0)
|
|
70
61
|
json (~> 2.3)
|
|
71
62
|
language_server-protocol (~> 3.17.0.2)
|
|
72
63
|
lint_roller (~> 1.1.0)
|
|
73
|
-
parallel (
|
|
64
|
+
parallel (>= 1.10)
|
|
74
65
|
parser (>= 3.3.0.2)
|
|
75
66
|
rainbow (>= 2.2.2, < 4.0)
|
|
76
67
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
@@ -81,20 +72,13 @@ GEM
|
|
|
81
72
|
parser (>= 3.3.7.2)
|
|
82
73
|
prism (~> 1.7)
|
|
83
74
|
ruby-progressbar (1.13.0)
|
|
84
|
-
ruby2_keywords (0.0.5)
|
|
85
75
|
sidekiq (6.5.12)
|
|
86
76
|
connection_pool (>= 2.2.5, < 3)
|
|
87
77
|
rack (~> 2.0)
|
|
88
78
|
redis (>= 4.5.0, < 5)
|
|
89
|
-
sinatra (3.2.0)
|
|
90
|
-
mustermann (~> 3.0)
|
|
91
|
-
rack (~> 2.2, >= 2.2.4)
|
|
92
|
-
rack-protection (= 3.2.0)
|
|
93
|
-
tilt (~> 2.0)
|
|
94
|
-
tilt (2.6.0)
|
|
95
79
|
unicode-display_width (3.1.4)
|
|
96
80
|
unicode-emoji (~> 4.0, >= 4.0.4)
|
|
97
|
-
unicode-emoji (4.0
|
|
81
|
+
unicode-emoji (4.2.0)
|
|
98
82
|
webrick (1.9.1)
|
|
99
83
|
|
|
100
84
|
PLATFORMS
|
|
@@ -107,4 +91,4 @@ DEPENDENCIES
|
|
|
107
91
|
rubocop (~> 1.0)
|
|
108
92
|
|
|
109
93
|
BUNDLED WITH
|
|
110
|
-
|
|
94
|
+
4.0.10
|
data/gitlab-exporter.gemspec
CHANGED
|
@@ -20,18 +20,20 @@ Gem::Specification.new do |s|
|
|
|
20
20
|
s.homepage = "https://gitlab.com/gitlab-org/ruby/gems/gitlab-exporter"
|
|
21
21
|
s.license = "MIT"
|
|
22
22
|
|
|
23
|
-
s.required_ruby_version = Gem::Requirement.new(">= 3.
|
|
23
|
+
s.required_ruby_version = Gem::Requirement.new(">= 3.1")
|
|
24
24
|
|
|
25
|
+
# base64 is required by sidekiq but not declared by it, and is no longer a
|
|
26
|
+
# default gem as of Ruby 3.4. It used to be pulled in transitively via
|
|
27
|
+
# sinatra -> rack-protection; declare it directly now that those are gone.
|
|
28
|
+
s.add_runtime_dependency "base64", "0.3.0"
|
|
25
29
|
s.add_runtime_dependency "connection_pool", "2.5.5"
|
|
26
30
|
s.add_runtime_dependency "deep_merge", "~> 1.2.2"
|
|
27
31
|
s.add_runtime_dependency "faraday", "2.14.3"
|
|
28
32
|
s.add_runtime_dependency "pg", "1.6.3"
|
|
29
|
-
s.add_runtime_dependency "puma", "8.0.2"
|
|
30
33
|
s.add_runtime_dependency "quantile", "0.2.1"
|
|
31
34
|
s.add_runtime_dependency "redis", "4.8.1"
|
|
32
35
|
s.add_runtime_dependency "redis-namespace", "1.11.0"
|
|
33
36
|
s.add_runtime_dependency "sidekiq", "6.5.12"
|
|
34
|
-
s.add_runtime_dependency "sinatra", "~> 3.2.0"
|
|
35
37
|
s.add_runtime_dependency "webrick", "~> 1.9"
|
|
36
38
|
|
|
37
39
|
s.add_development_dependency "rspec", "~> 3.12.0"
|
|
@@ -1,146 +1,138 @@
|
|
|
1
|
-
require "
|
|
1
|
+
require "webrick"
|
|
2
2
|
require "English"
|
|
3
3
|
require "cgi"
|
|
4
|
+
require "stringio"
|
|
4
5
|
|
|
5
|
-
require_relative "rack_vulndb_255039_patch"
|
|
6
6
|
require_relative "tls_helper"
|
|
7
7
|
|
|
8
8
|
module GitLab
|
|
9
9
|
module Exporter
|
|
10
|
-
# Metrics web exporter
|
|
11
|
-
class WebExporter
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
@app.call(env)
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
def memory_usage
|
|
31
|
-
io = IO.popen(%W[ps -o rss= -p #{$PID}])
|
|
32
|
-
|
|
33
|
-
mem = io.read
|
|
34
|
-
io.close
|
|
35
|
-
|
|
36
|
-
return 0 unless $CHILD_STATUS.to_i.zero?
|
|
37
|
-
|
|
38
|
-
mem.to_i
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Performs a major GC after each request. We found that this helps to free up
|
|
43
|
-
# several MB of memory in conjunction with sricter malloc config.
|
|
44
|
-
# See https://gitlab.com/gitlab-org/gitlab/-/issues/297241
|
|
45
|
-
class RunGC
|
|
46
|
-
def initialize(app)
|
|
47
|
-
@app = app
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def call(env)
|
|
51
|
-
@app.call(env).tap do
|
|
52
|
-
GC.start
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
10
|
+
# Metrics web exporter backed directly by WEBrick (no Sinatra/Rack).
|
|
11
|
+
class WebExporter
|
|
12
|
+
DEFAULT_WEB_SERVER = "webrick".freeze
|
|
13
|
+
DEFAULT_LISTEN_ADDRESS = "0.0.0.0".freeze
|
|
14
|
+
DEFAULT_LISTEN_PORT = 9168
|
|
15
|
+
DEFAULT_MEMORY_THRESHOLD = 1024
|
|
16
|
+
|
|
17
|
+
# Security response headers previously added by Sinatra's Rack::Protection
|
|
18
|
+
# middleware, which is gone now that we serve directly via WEBrick. Kept for
|
|
19
|
+
# parity so scanners and consumers see the same headers as before.
|
|
20
|
+
SECURITY_HEADERS = {
|
|
21
|
+
"X-Content-Type-Options" => "nosniff",
|
|
22
|
+
"X-Frame-Options" => "SAMEORIGIN",
|
|
23
|
+
"X-XSS-Protection" => "1; mode=block"
|
|
24
|
+
}.freeze
|
|
56
25
|
|
|
57
26
|
class << self
|
|
58
27
|
include TLSHelper
|
|
59
28
|
|
|
60
|
-
DEFAULT_WEB_SERVER = "webrick".freeze
|
|
61
|
-
|
|
62
29
|
def setup(config)
|
|
63
|
-
|
|
64
|
-
setup_probes(config[:probes])
|
|
30
|
+
server_config = config[:server] || {}
|
|
65
31
|
|
|
66
|
-
memory_threshold = (
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
32
|
+
memory_threshold = server_config.fetch(:memory_threshold, DEFAULT_MEMORY_THRESHOLD)
|
|
33
|
+
@memory_threshold = memory_threshold.to_i * 1024
|
|
34
|
+
|
|
35
|
+
@server = build_server(server_config)
|
|
36
|
+
mount_probes(config[:probes])
|
|
70
37
|
|
|
71
38
|
# Defrag heap after everything is loaded into memory.
|
|
72
39
|
GC.compact
|
|
73
40
|
end
|
|
74
41
|
|
|
75
|
-
def
|
|
76
|
-
|
|
42
|
+
def run!
|
|
43
|
+
trap_signals
|
|
44
|
+
@server.start
|
|
77
45
|
end
|
|
78
46
|
|
|
79
|
-
|
|
80
|
-
config ||= {}
|
|
47
|
+
private
|
|
81
48
|
|
|
82
|
-
|
|
83
|
-
|
|
49
|
+
def build_server(config)
|
|
50
|
+
warn_deprecated_server(config[:name])
|
|
51
|
+
|
|
52
|
+
options = {
|
|
53
|
+
Port: config.fetch(:listen_port, DEFAULT_LISTEN_PORT),
|
|
54
|
+
BindAddress: config.fetch(:listen_address, DEFAULT_LISTEN_ADDRESS)
|
|
55
|
+
}
|
|
84
56
|
|
|
85
|
-
# Depending on whether TLS is enabled or not, bind string
|
|
86
|
-
# will be different.
|
|
87
57
|
if config.fetch(:tls_enabled, "false").to_s == "true"
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
set(:bind, config.fetch(:listen_address, "0.0.0.0"))
|
|
58
|
+
validate_tls_config(config)
|
|
59
|
+
options.merge!(webrick_tls_config(config))
|
|
91
60
|
end
|
|
61
|
+
|
|
62
|
+
WEBrick::HTTPServer.new(**options)
|
|
92
63
|
end
|
|
93
64
|
|
|
94
|
-
|
|
95
|
-
|
|
65
|
+
# The web server used to be configurable (e.g. Puma) via the `server.name`
|
|
66
|
+
# setting when the exporter ran on top of Sinatra. It now always uses WEBrick,
|
|
67
|
+
# so the setting is ignored. Warn operators whose config still sets it.
|
|
68
|
+
#
|
|
69
|
+
# TODO: This is step one of a two-step deprecation. In a future major release,
|
|
70
|
+
# raise instead of warning so the ignored `server.name` setting fails loudly.
|
|
71
|
+
def warn_deprecated_server(name)
|
|
72
|
+
return if name.nil? || name.to_s == DEFAULT_WEB_SERVER
|
|
73
|
+
|
|
74
|
+
warn "DEPRECATION: `server.name` (#{name.inspect}) is no longer supported and is ignored; " \
|
|
75
|
+
"the exporter now always uses WEBrick. Please remove `server.name` from your config."
|
|
76
|
+
end
|
|
96
77
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
78
|
+
def mount_probes(probes)
|
|
79
|
+
(probes || {}).each do |probe_name, params|
|
|
80
|
+
opts =
|
|
81
|
+
if params.delete(:multiple)
|
|
82
|
+
params
|
|
83
|
+
else
|
|
84
|
+
{ probe_name => params }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
@server.mount_proc("/#{probe_name}") do |_request, response|
|
|
88
|
+
handle_probe(response, opts)
|
|
89
|
+
end
|
|
104
90
|
end
|
|
105
91
|
end
|
|
106
92
|
|
|
107
|
-
def
|
|
108
|
-
|
|
109
|
-
|
|
93
|
+
def handle_probe(response, opts)
|
|
94
|
+
# A memory killer that kills the process if we exceeded a certain threshold.
|
|
95
|
+
check_memory_threshold!
|
|
110
96
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
97
|
+
prober = Prober.new(metrics: PrometheusMetrics.new(include_timestamp: false), logger: @server.logger, **opts)
|
|
98
|
+
prober.probe_all
|
|
99
|
+
|
|
100
|
+
buffer = StringIO.new
|
|
101
|
+
prober.write_to(buffer)
|
|
114
102
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
103
|
+
SECURITY_HEADERS.each do |header, value|
|
|
104
|
+
response[header] = value
|
|
105
|
+
end
|
|
106
|
+
response.content_type = "text/plain; version=0.0.4; charset=utf-8"
|
|
107
|
+
response.body = buffer.string
|
|
108
|
+
ensure
|
|
109
|
+
# Performs a major GC after each request. We found that this helps to free up
|
|
110
|
+
# several MB of memory in conjunction with stricter malloc config.
|
|
111
|
+
# See https://gitlab.com/gitlab-org/gitlab/-/issues/297241
|
|
112
|
+
GC.start
|
|
113
|
+
end
|
|
120
114
|
|
|
121
|
-
|
|
115
|
+
def check_memory_threshold!
|
|
116
|
+
usage = memory_usage
|
|
117
|
+
return if usage <= @memory_threshold
|
|
122
118
|
|
|
123
|
-
|
|
119
|
+
puts "Memory usage of #{usage} exceeded threshold of #{@memory_threshold}, signalling KILL"
|
|
120
|
+
Process.kill("KILL", $PID)
|
|
124
121
|
end
|
|
125
122
|
|
|
126
|
-
def
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
params
|
|
131
|
-
else
|
|
132
|
-
{ probe_name => params }
|
|
133
|
-
end
|
|
123
|
+
def memory_usage
|
|
124
|
+
# Use the block form so the pipe is closed automatically even if the
|
|
125
|
+
# read raises, avoiding a file-descriptor leak.
|
|
126
|
+
mem = IO.popen(%W[ps -o rss= -p #{$PID}], &:read)
|
|
134
127
|
|
|
135
|
-
|
|
136
|
-
content_type "text/plain; version=0.0.4; charset=utf-8"
|
|
137
|
-
prober = Prober.new(metrics: PrometheusMetrics.new(include_timestamp: false), logger: logger, **opts)
|
|
128
|
+
return 0 unless $CHILD_STATUS.to_i.zero?
|
|
138
129
|
|
|
139
|
-
|
|
140
|
-
|
|
130
|
+
mem.to_i
|
|
131
|
+
end
|
|
141
132
|
|
|
142
|
-
|
|
143
|
-
|
|
133
|
+
def trap_signals
|
|
134
|
+
%w[INT TERM].each do |signal|
|
|
135
|
+
trap(signal) { @server.shutdown }
|
|
144
136
|
end
|
|
145
137
|
end
|
|
146
138
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "gitlab_exporter/database/tuple_stats"
|
|
3
|
+
|
|
4
|
+
describe GitLab::Exporter::Database::TuplesProber do
|
|
5
|
+
let(:opts) { { connection_string: "" } }
|
|
6
|
+
let(:metrics) { double("PrometheusMetrics", add: nil) }
|
|
7
|
+
let(:collector) { double("TupleStatsCollector", run: data) }
|
|
8
|
+
|
|
9
|
+
before do
|
|
10
|
+
allow(GitLab::Exporter::Database::TupleStatsCollector).to receive(:new).and_return(collector)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe "#probe_db" do
|
|
14
|
+
subject { described_class.new(metrics: metrics, **opts).probe_db }
|
|
15
|
+
|
|
16
|
+
context "when the connection casts columns to native Integer (PG::BasicTypeMapForResults)" do
|
|
17
|
+
let(:data) { { "projects" => { "seq_tup_read" => 42, "n_dead_tup" => 0 } } }
|
|
18
|
+
|
|
19
|
+
it "still records a metric for every column" do
|
|
20
|
+
expect(metrics).to receive(:add)
|
|
21
|
+
.with("gitlab_database_stat_table_seq_tup_read", 42.0, table_name: "projects")
|
|
22
|
+
expect(metrics).to receive(:add)
|
|
23
|
+
.with("gitlab_database_stat_table_n_dead_tup", 0.0, table_name: "projects")
|
|
24
|
+
|
|
25
|
+
subject
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context "when the connection returns unmapped string values" do
|
|
30
|
+
let(:data) { { "projects" => { "seq_tup_read" => "42" } } }
|
|
31
|
+
|
|
32
|
+
it "records a metric" do
|
|
33
|
+
expect(metrics).to receive(:add)
|
|
34
|
+
.with("gitlab_database_stat_table_seq_tup_read", 42.0, table_name: "projects")
|
|
35
|
+
|
|
36
|
+
subject
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "gitlab_exporter/web_exporter"
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "timeout"
|
|
5
|
+
|
|
6
|
+
describe GitLab::Exporter::WebExporter do
|
|
7
|
+
# Each test builds a fresh server via `.setup`; drop the reference afterwards
|
|
8
|
+
# so class-level state does not leak between examples.
|
|
9
|
+
after do
|
|
10
|
+
described_class.instance_variable_set(:@server, nil)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
describe ".setup" do
|
|
14
|
+
let(:probes) { {} }
|
|
15
|
+
|
|
16
|
+
it "builds a WEBrick server with the configured port and bind address" do
|
|
17
|
+
options = capture_server_options(server: { listen_address: "127.0.0.1", listen_port: 12_345 })
|
|
18
|
+
|
|
19
|
+
expect(options).to include(Port: 12_345, BindAddress: "127.0.0.1")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it "falls back to the default port and bind address" do
|
|
23
|
+
options = capture_server_options({})
|
|
24
|
+
|
|
25
|
+
expect(options).to include(Port: 9168, BindAddress: "0.0.0.0")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "mounts a handler for each configured probe" do
|
|
29
|
+
server = instance_double(WEBrick::HTTPServer)
|
|
30
|
+
allow(WEBrick::HTTPServer).to receive(:new).and_return(server)
|
|
31
|
+
|
|
32
|
+
expect(server).to receive(:mount_proc).with("/ruby")
|
|
33
|
+
|
|
34
|
+
described_class.setup(server: {}, probes: { ruby: { methods: %w[probe_gc], opts: {} } })
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "raises when TLS is enabled but no certificate is configured" do
|
|
38
|
+
expect {
|
|
39
|
+
described_class.setup(server: { tls_enabled: true }, probes: probes)
|
|
40
|
+
}.to raise_error(/tls_cert_path not specified/)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Capture the keyword options passed to WEBrick without binding a real socket.
|
|
44
|
+
def capture_server_options(config)
|
|
45
|
+
captured = nil
|
|
46
|
+
allow(WEBrick::HTTPServer).to receive(:new) do |**options|
|
|
47
|
+
captured = options
|
|
48
|
+
instance_double(WEBrick::HTTPServer)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
described_class.setup(config.merge(probes: {}))
|
|
52
|
+
captured
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe "deprecating the server.name setting" do
|
|
57
|
+
it "warns when a non-WEBrick server is requested" do
|
|
58
|
+
expect {
|
|
59
|
+
described_class.send(:warn_deprecated_server, "puma")
|
|
60
|
+
}.to output(/DEPRECATION.*no longer supported/).to_stderr
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it "does not warn when the server is webrick" do
|
|
64
|
+
expect { described_class.send(:warn_deprecated_server, "webrick") }.not_to output.to_stderr
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "does not warn when no server name is set" do
|
|
68
|
+
expect { described_class.send(:warn_deprecated_server, nil) }.not_to output.to_stderr
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe "serving probe requests" do
|
|
73
|
+
let(:config) do
|
|
74
|
+
{
|
|
75
|
+
server: { listen_address: "127.0.0.1", listen_port: 0 },
|
|
76
|
+
probes: { ruby: { methods: %w[probe_gc], opts: {} } }
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
let(:server) { described_class.instance_variable_get(:@server) }
|
|
81
|
+
let(:base_uri) { "http://127.0.0.1:#{server.listeners.first.addr[1]}" }
|
|
82
|
+
|
|
83
|
+
before do
|
|
84
|
+
# WEBrick binds its logger and access log to $stderr at construction time
|
|
85
|
+
# and emits a startup banner; silence $stderr around setup so the log
|
|
86
|
+
# device is a throwaway buffer and the test output stays clean.
|
|
87
|
+
original_stderr = $stderr
|
|
88
|
+
$stderr = StringIO.new
|
|
89
|
+
begin
|
|
90
|
+
described_class.setup(config)
|
|
91
|
+
ensure
|
|
92
|
+
$stderr = original_stderr
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
@server_thread = Thread.new { server.start }
|
|
96
|
+
Timeout.timeout(5) { sleep 0.01 until server.status == :Running }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
after do
|
|
100
|
+
server.shutdown
|
|
101
|
+
@server_thread&.join(5)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
it "returns Prometheus metrics for a known probe" do
|
|
105
|
+
response = Net::HTTP.get_response(URI("#{base_uri}/ruby"))
|
|
106
|
+
|
|
107
|
+
expect(response.code).to eq("200")
|
|
108
|
+
expect(response.content_type).to eq("text/plain")
|
|
109
|
+
expect(response["Content-Type"]).to include("version=0.0.4")
|
|
110
|
+
expect(response.body).to match(/ruby_gc_stat_count \d+/)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
it "sets the security headers previously provided by Rack::Protection" do
|
|
114
|
+
response = Net::HTTP.get_response(URI("#{base_uri}/ruby"))
|
|
115
|
+
|
|
116
|
+
expect(response["X-Content-Type-Options"]).to eq("nosniff")
|
|
117
|
+
expect(response["X-Frame-Options"]).to eq("SAMEORIGIN")
|
|
118
|
+
expect(response["X-XSS-Protection"]).to eq("1; mode=block")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
it "returns 404 for unknown paths" do
|
|
122
|
+
response = Net::HTTP.get_response(URI("#{base_uri}/does-not-exist"))
|
|
123
|
+
|
|
124
|
+
expect(response.code).to eq("404")
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gitlab-exporter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 17.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Pablo Carranza
|
|
@@ -10,6 +10,20 @@ bindir: bin
|
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 2016-07-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: base64
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - '='
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.3.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - '='
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.3.0
|
|
13
27
|
- !ruby/object:Gem::Dependency
|
|
14
28
|
name: connection_pool
|
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -66,20 +80,6 @@ dependencies:
|
|
|
66
80
|
- - '='
|
|
67
81
|
- !ruby/object:Gem::Version
|
|
68
82
|
version: 1.6.3
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: puma
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - '='
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: 8.0.2
|
|
76
|
-
type: :runtime
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - '='
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: 8.0.2
|
|
83
83
|
- !ruby/object:Gem::Dependency
|
|
84
84
|
name: quantile
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -136,20 +136,6 @@ dependencies:
|
|
|
136
136
|
- - '='
|
|
137
137
|
- !ruby/object:Gem::Version
|
|
138
138
|
version: 6.5.12
|
|
139
|
-
- !ruby/object:Gem::Dependency
|
|
140
|
-
name: sinatra
|
|
141
|
-
requirement: !ruby/object:Gem::Requirement
|
|
142
|
-
requirements:
|
|
143
|
-
- - "~>"
|
|
144
|
-
- !ruby/object:Gem::Version
|
|
145
|
-
version: 3.2.0
|
|
146
|
-
type: :runtime
|
|
147
|
-
prerelease: false
|
|
148
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
-
requirements:
|
|
150
|
-
- - "~>"
|
|
151
|
-
- !ruby/object:Gem::Version
|
|
152
|
-
version: 3.2.0
|
|
153
139
|
- !ruby/object:Gem::Dependency
|
|
154
140
|
name: webrick
|
|
155
141
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -205,6 +191,8 @@ files:
|
|
|
205
191
|
- ".rubocop.yml"
|
|
206
192
|
- ".rubocop_todo.yml"
|
|
207
193
|
- ".ruby-version"
|
|
194
|
+
- AGENTS.md
|
|
195
|
+
- CLAUDE.md
|
|
208
196
|
- CONTRIBUTING.md
|
|
209
197
|
- Gemfile
|
|
210
198
|
- Gemfile.lock
|
|
@@ -233,7 +221,6 @@ files:
|
|
|
233
221
|
- lib/gitlab_exporter/prober.rb
|
|
234
222
|
- lib/gitlab_exporter/process.rb
|
|
235
223
|
- lib/gitlab_exporter/prometheus.rb
|
|
236
|
-
- lib/gitlab_exporter/rack_vulndb_255039_patch.rb
|
|
237
224
|
- lib/gitlab_exporter/ruby.rb
|
|
238
225
|
- lib/gitlab_exporter/sidekiq.rb
|
|
239
226
|
- lib/gitlab_exporter/tls_helper.rb
|
|
@@ -245,6 +232,7 @@ files:
|
|
|
245
232
|
- spec/database/ci_builds_spec.rb
|
|
246
233
|
- spec/database/pg_sequences_spec.rb
|
|
247
234
|
- spec/database/row_count_spec.rb
|
|
235
|
+
- spec/database/tuple_stats_spec.rb
|
|
248
236
|
- spec/database/zoekt_spec.rb
|
|
249
237
|
- spec/elasticsearch_spec.rb
|
|
250
238
|
- spec/fixtures/config.yml
|
|
@@ -258,6 +246,7 @@ files:
|
|
|
258
246
|
- spec/sidekiq_spec.rb
|
|
259
247
|
- spec/spec_helper.rb
|
|
260
248
|
- spec/util_spec.rb
|
|
249
|
+
- spec/web_exporter_spec.rb
|
|
261
250
|
homepage: https://gitlab.com/gitlab-org/ruby/gems/gitlab-exporter
|
|
262
251
|
licenses:
|
|
263
252
|
- MIT
|
|
@@ -270,7 +259,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
270
259
|
requirements:
|
|
271
260
|
- - ">="
|
|
272
261
|
- !ruby/object:Gem::Version
|
|
273
|
-
version: '3.
|
|
262
|
+
version: '3.1'
|
|
274
263
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
275
264
|
requirements:
|
|
276
265
|
- - ">="
|
|
@@ -287,6 +276,7 @@ test_files:
|
|
|
287
276
|
- spec/database/ci_builds_spec.rb
|
|
288
277
|
- spec/database/pg_sequences_spec.rb
|
|
289
278
|
- spec/database/row_count_spec.rb
|
|
279
|
+
- spec/database/tuple_stats_spec.rb
|
|
290
280
|
- spec/database/zoekt_spec.rb
|
|
291
281
|
- spec/elasticsearch_spec.rb
|
|
292
282
|
- spec/fixtures/config.yml
|
|
@@ -300,3 +290,4 @@ test_files:
|
|
|
300
290
|
- spec/sidekiq_spec.rb
|
|
301
291
|
- spec/spec_helper.rb
|
|
302
292
|
- spec/util_spec.rb
|
|
293
|
+
- spec/web_exporter_spec.rb
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
if Gem.loaded_specs["rack"].version >= Gem::Version.new("3.0.0")
|
|
4
|
-
fail <<~ERR
|
|
5
|
-
This patch is unnecessary in Rack versions 3.0.0 or newer.
|
|
6
|
-
Please remove this file and the associated spec.
|
|
7
|
-
|
|
8
|
-
See https://github.com/rack/rack/blob/main/CHANGELOG.md#security (issue #1733)
|
|
9
|
-
ERR
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# Patches a cache poisoning attack vector in Rack by not allowing semicolons
|
|
13
|
-
# to delimit query parameters.
|
|
14
|
-
# See https://github.com/rack/rack/issues/1732.
|
|
15
|
-
#
|
|
16
|
-
# Solution is taken from the same issue.
|
|
17
|
-
#
|
|
18
|
-
# The actual patch is due for release in Rack 3.0.0.
|
|
19
|
-
module Rack
|
|
20
|
-
class Request # rubocop:disable Style/Documentation
|
|
21
|
-
Helpers.module_eval do
|
|
22
|
-
# rubocop: disable Naming/MethodName
|
|
23
|
-
def GET
|
|
24
|
-
if get_header(RACK_REQUEST_QUERY_STRING) == query_string
|
|
25
|
-
get_header(RACK_REQUEST_QUERY_HASH)
|
|
26
|
-
else
|
|
27
|
-
query_hash = parse_query(query_string, "&") # only allow ampersand here
|
|
28
|
-
set_header(RACK_REQUEST_QUERY_STRING, query_string)
|
|
29
|
-
set_header(RACK_REQUEST_QUERY_HASH, query_hash)
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
# rubocop: enable Naming/MethodName
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|