Skip to content

Commit 2511f59

Browse files
committed
Use separate webserver for metrics
1 parent a8a1ab3 commit 2511f59

File tree

4 files changed

+96
-1
lines changed

4 files changed

+96
-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 or Nginx is not used
7+
# With Puma and Nginx, 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 if @config.get(:nginx, :use_nginx)
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: 62 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,66 @@ module VCAP::CloudController
276278
end
277279
end
278280
end
281+
282+
describe 'separate webserver for metrics' do
283+
context 'when the webserver is puma and nginx is used' do
284+
before do
285+
TestConfig.override(nginx: { use_nginx: true }, webserver: 'puma')
286+
allow(Config).to receive(:load_from_file).and_return(TestConfig.config_instance)
287+
end
288+
289+
it 'sets up a separate webserver for metrics' do
290+
subject
291+
292+
expect(Puma::Server).to have_received(:new).once
293+
end
294+
295+
it 'listens on the specified metrics port' do
296+
subject
297+
298+
expect(puma_server_double).to have_received(:add_tcp_listener).with('127.0.0.1', 9395)
299+
end
300+
301+
context 'metrics_socket is configured' do
302+
before do
303+
TestConfig.override(nginx: { use_nginx: true, metrics_socket: '/tmp/puma_metrics.sock' }, webserver: 'puma')
304+
allow(Config).to receive(:load_from_file).and_return(TestConfig.config_instance)
305+
end
306+
307+
it 'listens on the specified metrics socket' do
308+
subject
309+
310+
expect(puma_server_double).to have_received(:add_unix_listener).with('/tmp/puma_metrics.sock')
311+
end
312+
end
313+
end
314+
315+
context 'when the webserver is puma and nginx is not used' do
316+
before do
317+
TestConfig.override(nginx: { use_nginx: false }, webserver: 'puma')
318+
allow(Config).to receive(:load_from_file).and_return(TestConfig.config_instance)
319+
end
320+
321+
it 'does not set up the webserver' do
322+
subject
323+
324+
expect(Puma::Server).not_to have_received(:new)
325+
end
326+
end
327+
328+
context 'when the webserver is not puma' do
329+
before do
330+
TestConfig.override(nginx: { use_nginx: true }, webserver: 'thin')
331+
allow(Config).to receive(:load_from_file).and_return(TestConfig.config_instance)
332+
end
333+
334+
it 'does not set up the webserver' do
335+
subject
336+
337+
expect(Puma::Server).not_to have_received(:new)
338+
end
339+
end
340+
end
279341
end
280342
end
281343
end

0 commit comments

Comments
 (0)