Skip to content

Commit 9dcd69e

Browse files
committed
Log DB query request metrics
- number of queries per request - accumulated query time
1 parent 2e0ca41 commit 9dcd69e

File tree

10 files changed

+92
-13
lines changed

10 files changed

+92
-13
lines changed

lib/cloud_controller/db.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require 'cloud_controller/db_connection/options_factory'
44
require 'cloud_controller/db_connection/finalizer'
55
require 'sequel/extensions/query_length_logging'
6+
require 'sequel/extensions/request_query_metrics'
67

78
module VCAP::CloudController
89
class DB
@@ -30,12 +31,13 @@ def self.connect(opts, logger)
3031
db.logger = logger
3132
db.sql_log_level = opts[:log_level]
3233
db.extension :caller_logging
33-
db.caller_logging_ignore = /sequel_paginator|query_length_logging|sequel_pg|paginated_collection_renderer/
34+
db.caller_logging_ignore = /sequel_paginator|query_length_logging|sequel_pg|paginated_collection_renderer|request_query_metrics/
3435
db.caller_logging_formatter = lambda do |caller|
3536
"(#{caller.sub(%r{^.*/cloud_controller_ng/}, '')})"
3637
end
3738

3839
db.extension(:query_length_logging)
40+
db.extension(:request_query_metrics)
3941
db.opts[:query_size_log_threshold] = opts[:query_size_log_threshold]
4042
end
4143
db.default_collate = 'utf8_bin' if db.database_type == :mysql

lib/cloud_controller/logs/request_logs.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@ def start_request(request_id, env)
2626
@incomplete_requests.store(request_id, env)
2727
end
2828

29-
def complete_request(request_id, status, env, time_taken)
29+
def complete_request(request_id, status, env, time_taken_ms, total_db_query_time_us, db_query_count)
3030
return if @incomplete_requests.delete(request_id).nil?
3131

3232
request = ActionDispatch::Request.new(env)
3333
@logger.info("Completed #{status} vcap-request-id: #{request_id}",
3434
{ status_code: status,
35-
time_taken_in_ms: time_taken,
35+
time_taken_in_ms: time_taken_ms,
36+
total_db_query_time_in_ms: (total_db_query_time_us.to_f / 1_000),
37+
db_query_count: db_query_count,
3638
request_method: request.request_method,
3739
request_fullpath: request.filtered_path })
3840
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# frozen_string_literal: true
2+
3+
Sequel::Database.register_extension(:request_query_metrics) do |db|
4+
mod = Module.new do
5+
private
6+
7+
def log_duration(duration, message)
8+
VCAP::Request.record_db_query((duration * 1_000_000).round)
9+
10+
super
11+
end
12+
end
13+
14+
db.singleton_class.prepend(mod)
15+
end

lib/vcap/request.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,29 @@ def b3_span_id=(span_id)
5555
def b3_span_id
5656
Thread.current[:b3_span_id]
5757
end
58+
59+
def db_query_metrics
60+
init_db_query_metrics
61+
62+
Thread.current[:db_query_metrics]
63+
end
64+
65+
def record_db_query(duration)
66+
init_db_query_metrics
67+
68+
Thread.current[:db_query_metrics].total_query_time_us += duration
69+
Thread.current[:db_query_metrics].query_count += 1
70+
end
71+
72+
def reset_db_query_metrics
73+
Thread.current[:db_query_metrics] = Struct.new(:total_query_time_us, :query_count).new(0, 0)
74+
end
75+
76+
private
77+
78+
def init_db_query_metrics
79+
reset_db_query_metrics if Thread.current[:db_query_metrics].nil?
80+
end
5881
end
5982
end
6083
end

middleware/request_logs.rb

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,21 @@ def initialize(app, request_logs)
99
def call(env)
1010
request_id = env['cf.request_id']
1111
@request_logs.start_request(request_id, env)
12-
start_time = Time.now
12+
start_timer = timer
1313

1414
status, headers, body = @app.call(env)
15-
# convert to milliseconds
16-
time_taken = (Time.now - start_time) * 1000
17-
time_taken = time_taken.to_i
18-
@request_logs.complete_request(request_id, status, env, time_taken)
15+
time_taken_ms = timer - start_timer
16+
db_query_metrics = ::VCAP::Request.db_query_metrics
17+
@request_logs.complete_request(request_id, status, env, time_taken_ms, db_query_metrics.total_query_time_us, db_query_metrics.query_count)
1918

2019
[status, headers, body]
2120
end
21+
22+
private
23+
24+
def timer
25+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
26+
end
2227
end
2328
end
2429
end

middleware/request_metrics.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ def initialize(app, request_metrics)
77
end
88

99
def call(env)
10+
::VCAP::Request.reset_db_query_metrics
1011
@request_metrics.start_request
1112

1213
status, headers, body = @app.call(env)

spec/unit/lib/cloud_controller/logs/request_logs_spec.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ module VCAP::CloudController::Logs
99
let(:fake_fullpath) { 'fullpath' }
1010
let(:fake_request) { double('request', request_method: 'request_method', ip: fake_ip, filtered_path: 'filtered_path', fullpath: fake_fullpath) }
1111
let(:request_id) { 'ID' }
12-
let(:time_taken) { 30 }
12+
let(:time_taken_ms) { 30 }
13+
let(:total_db_query_time_us) { 123_456 }
14+
let(:db_query_count) { 5 }
1315
let(:env) { { 'cf.user_guid' => 'user-guid' } }
1416
let(:status) { 200 }
1517

@@ -46,10 +48,12 @@ module VCAP::CloudController::Logs
4648
end
4749

4850
it 'logs the completion of the request' do
49-
request_logs.complete_request(request_id, status, env, time_taken)
51+
request_logs.complete_request(request_id, status, env, time_taken_ms, total_db_query_time_us, db_query_count)
5052
expect(logger).to have_received(:info).with(
5153
/\ACompleted 200 vcap-request-id: ID/,
5254
time_taken_in_ms: 30,
55+
total_db_query_time_in_ms: 123.456,
56+
db_query_count: 5,
5357
request_method: 'request_method',
5458
request_fullpath: 'filtered_path',
5559
status_code: 200
@@ -59,7 +63,7 @@ module VCAP::CloudController::Logs
5963

6064
context 'without a matching start request' do
6165
it 'does not log the completion of the request' do
62-
request_logs.complete_request(request_id, status, env, time_taken)
66+
request_logs.complete_request(request_id, status, env, time_taken_ms, total_db_query_time_us, db_query_count)
6367
expect(logger).not_to have_received(:info)
6468
end
6569
end
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
require 'spec_helper'
2+
3+
RSpec.describe 'request_query_metrics' do
4+
let(:db) { DbConfig.new.connection }
5+
6+
before do
7+
# Add a logger to the database connection to ensure that the duration is captured
8+
db.loggers << Logger.new(StringIO.new)
9+
db.extension(:request_query_metrics)
10+
allow(VCAP::Request).to receive(:record_db_query).and_wrap_original do |original_method|
11+
original_method.call(1_234)
12+
end
13+
end
14+
15+
it 'increments the thread local total query time on each query' do
16+
expect do
17+
2.times { db.fetch('SELECT 1').all }
18+
end.to change(VCAP::Request.db_query_metrics, :total_query_time_us).by(2_468)
19+
end
20+
21+
it 'increments the thread local query count on each query' do
22+
expect do
23+
db.fetch('SELECT 1').all
24+
end.to change(VCAP::Request.db_query_metrics, :query_count).by(1)
25+
end
26+
end

spec/unit/middleware/request_logs_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ module Middleware
2121

2222
it 'calls complete request on request logs after the request' do
2323
middleware.call(env)
24-
expect(request_logs).to have_received(:complete_request).with('ID', 200, { 'cf.request_id' => 'ID' }, be_a(Numeric))
24+
expect(request_logs).to have_received(:complete_request).with('ID', 200, { 'cf.request_id' => 'ID' }, be_a(Numeric), be_a(Numeric), be_a(Numeric))
2525
end
2626
end
2727
end

spec/unit/middleware/request_metrics_spec.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ module Middleware
99
let(:request_metrics) { instance_double(VCAP::CloudController::Metrics::RequestMetrics, start_request: nil, complete_request: nil) }
1010

1111
describe 'handling the request' do
12-
it 'calls start request on request metrics before the request' do
12+
it 'resets the db query count and calls start request on request metrics before the request' do
13+
expect(VCAP::Request).to receive(:reset_db_query_metrics)
1314
middleware.call({})
1415
expect(request_metrics).to have_received(:start_request)
1516
end

0 commit comments

Comments
 (0)