Skip to content

Commit 32866b6

Browse files
authored
Use separate webserver for metrics (#4377)
1 parent 0dd3a61 commit 32866b6

File tree

4 files changed

+81
-1
lines changed

4 files changed

+81
-1
lines changed

app/controllers/internal/metrics_controller.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
module VCAP::CloudController
55
module Internal
6+
# This controller is only used when the webserver is not Puma
7+
# When using Puma, the metrics are served by a separate webserver in the main process
68
class MetricsController < RestController::BaseController
79
allow_unauthenticated_access
810
get '/internal/v4/metrics', :index

lib/cloud_controller/config_schemas/base/api_schema.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,8 @@ class ApiSchema < VCAP::Config
134134

135135
nginx: {
136136
use_nginx: bool,
137-
instance_socket: String
137+
instance_socket: String,
138+
optional(:metrics_socket) => String
138139
},
139140

140141
quota_definitions: Hash,

lib/cloud_controller/runner.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
require 'cloud_controller/runners/thin_runner'
1515
require 'cloud_controller/runners/puma_runner'
1616
require 'prometheus/client/data_stores/direct_file_store'
17+
require 'prometheus/middleware/exporter'
1718

1819
module VCAP::CloudController
1920
class Runner
@@ -131,6 +132,35 @@ def setup_metrics
131132
end
132133

133134
Prometheus::Client.config.data_store = Prometheus::Client::DataStores::DirectFileStore.new(dir: prometheus_dir)
135+
136+
setup_metrics_webserver
137+
end
138+
139+
# The webserver runs in the main process and serves only the metrics endpoint.
140+
# This makes it possible to retrieve metrics even if all Puma workers of the main app are busy.
141+
def setup_metrics_webserver
142+
metrics_app = Rack::Builder.new do
143+
use Prometheus::Middleware::Exporter, path: '/internal/v4/metrics'
144+
145+
map '/' do
146+
run lambda { |_env|
147+
# Return 404 for any other request
148+
['404', { 'Content-Type' => 'text/plain' }, ['Not Found']]
149+
}
150+
end
151+
end
152+
153+
Thread.new do
154+
server = Puma::Server.new(metrics_app)
155+
156+
if config.get(:nginx, :metrics_socket).nil? || config.get(:nginx, :metrics_socket).empty?
157+
server.add_tcp_listener('127.0.0.1', 9395)
158+
else
159+
server.add_unix_listener(@config.get(:nginx, :metrics_socket))
160+
end
161+
162+
server.run
163+
end
134164
end
135165

136166
def setup_logging

spec/unit/lib/cloud_controller/runner_spec.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module VCAP::CloudController
77
let(:periodic_updater) { instance_double(VCAP::CloudController::Metrics::PeriodicUpdater) }
88
let(:request_metrics) { instance_double(VCAP::CloudController::Metrics::RequestMetrics) }
99
let(:routing_api_client) { instance_double(VCAP::CloudController::RoutingApi::Client, router_group_guid: '') }
10+
let(:puma_server_double) { instance_double(Puma::Server, add_tcp_listener: nil, add_unix_listener: nil, run: nil) }
1011

1112
let(:argv) { [] }
1213

@@ -19,6 +20,7 @@ module VCAP::CloudController
1920
allow(periodic_updater).to receive(:setup_updates)
2021
allow(VCAP::PidFile).to receive(:new) { double(:pidfile, unlink_at_exit: nil) }
2122
allow_any_instance_of(VCAP::CloudController::ThinRunner).to receive(:start!)
23+
allow(Puma::Server).to receive(:new).and_return(puma_server_double)
2224
end
2325

2426
subject do
@@ -276,6 +278,51 @@ module VCAP::CloudController
276278
end
277279
end
278280
end
281+
282+
describe 'separate webserver for metrics' do
283+
let(:test_config_overrides) { {} }
284+
285+
before do
286+
TestConfig.override(**test_config_overrides)
287+
allow(Config).to receive(:load_from_file).and_return(TestConfig.config_instance)
288+
end
289+
290+
context 'when the webserver is puma' do
291+
let(:test_config_overrides) { super().merge(webserver: 'puma') }
292+
293+
it 'sets up a separate webserver for metrics' do
294+
subject
295+
296+
expect(Puma::Server).to have_received(:new).once
297+
end
298+
299+
it 'listens on the specified metrics port' do
300+
subject
301+
302+
expect(puma_server_double).to have_received(:add_tcp_listener).with('127.0.0.1', 9395)
303+
end
304+
305+
context 'metrics_socket is configured' do
306+
let(:test_config_overrides) { super().merge(nginx: { metrics_socket: '/tmp/metrics_socket.sock' }) }
307+
308+
it 'listens on the specified metrics socket' do
309+
subject
310+
311+
expect(puma_server_double).to have_received(:add_unix_listener).with('/tmp/metrics_socket.sock')
312+
end
313+
end
314+
end
315+
316+
context 'when the webserver is not puma' do
317+
let(:test_config_overrides) { super().merge(webserver: 'thin') }
318+
319+
it 'does not set up the webserver' do
320+
subject
321+
322+
expect(Puma::Server).not_to have_received(:new)
323+
end
324+
end
325+
end
279326
end
280327
end
281328
end

0 commit comments

Comments
 (0)