Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
5c52eddb
Commit
5c52eddb
authored
Sep 07, 2021
by
Quang-Minh Nguyen
Committed by
Bob Van Landuyt
Sep 28, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Make it possible to define custom request duration thresholds
Changelog: added
parent
5cc0b39e
Changes
18
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
565 additions
and
94 deletions
+565
-94
app/controllers/application_controller.rb
app/controllers/application_controller.rb
+9
-5
app/controllers/projects/security/configuration_controller.rb
...controllers/projects/security/configuration_controller.rb
+1
-1
app/controllers/search_controller.rb
app/controllers/search_controller.rb
+1
-0
lib/api/base.rb
lib/api/base.rb
+11
-2
lib/api/internal/base.rb
lib/api/internal/base.rb
+1
-1
lib/api/unleash.rb
lib/api/unleash.rb
+1
-1
lib/api/users.rb
lib/api/users.rb
+1
-1
lib/gitlab/endpoint_attributes.rb
lib/gitlab/endpoint_attributes.rb
+48
-0
lib/gitlab/endpoint_attributes/config.rb
lib/gitlab/endpoint_attributes/config.rb
+81
-0
lib/gitlab/metrics/requests_rack_middleware.rb
lib/gitlab/metrics/requests_rack_middleware.rb
+16
-5
lib/gitlab/request_endpoints.rb
lib/gitlab/request_endpoints.rb
+1
-1
spec/controllers/every_controller_spec.rb
spec/controllers/every_controller_spec.rb
+1
-5
spec/lib/api/base_spec.rb
spec/lib/api/base_spec.rb
+92
-0
spec/lib/gitlab/endpoint_attributes_spec.rb
spec/lib/gitlab/endpoint_attributes_spec.rb
+132
-0
spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
+160
-1
spec/lib/gitlab/request_endpoints_spec.rb
spec/lib/gitlab/request_endpoints_spec.rb
+2
-2
spec/lib/gitlab/with_feature_category_spec.rb
spec/lib/gitlab/with_feature_category_spec.rb
+0
-69
spec/support/matchers/be_a_target_duration.rb
spec/support/matchers/be_a_target_duration.rb
+7
-0
No files found.
app/controllers/application_controller.rb
View file @
5c52eddb
...
...
@@ -21,7 +21,7 @@ class ApplicationController < ActionController::Base
include
Impersonation
include
Gitlab
::
Logging
::
CloudflareHelper
include
Gitlab
::
Utils
::
StrongMemoize
include
::
Gitlab
::
WithFeatureCategory
include
::
Gitlab
::
EndpointAttributes
include
FlocOptOut
before_action
:authenticate_user!
,
except:
[
:route_not_found
]
...
...
@@ -135,6 +135,14 @@ class ApplicationController < ActionController::Base
end
end
def
feature_category
self
.
class
.
feature_category_for_action
(
action_name
).
to_s
end
def
target_duration
self
.
class
.
target_duration_for_action
(
action_name
)
end
protected
def
workhorse_excluded_content_types
...
...
@@ -547,10 +555,6 @@ class ApplicationController < ActionController::Base
auth_user
if
strong_memoized?
(
:auth_user
)
end
def
feature_category
self
.
class
.
feature_category_for_action
(
action_name
).
to_s
end
def
required_signup_info
return
unless
current_user
return
unless
current_user
.
role_required?
...
...
app/controllers/projects/security/configuration_controller.rb
View file @
5c52eddb
...
...
@@ -5,7 +5,7 @@ module Projects
class
ConfigurationController
<
Projects
::
ApplicationController
include
SecurityAndCompliancePermissions
feature_category
:static_application_security_testing
feature_category
:static_application_security_testing
,
[
:show
]
def
show
render_403
unless
can?
(
current_user
,
:read_security_configuration
,
project
)
...
...
app/controllers/search_controller.rb
View file @
5c52eddb
...
...
@@ -23,6 +23,7 @@ class SearchController < ApplicationController
layout
'search'
feature_category
:global_search
target_duration
:very_fast
,
[
:opensearch
]
def
show
@project
=
search_service
.
project
...
...
lib/api/base.rb
View file @
5c52eddb
...
...
@@ -2,13 +2,17 @@
module
API
class
Base
<
Grape
::
API
::
Instance
# rubocop:disable API/Base
include
::
Gitlab
::
WithFeatureCategory
include
::
Gitlab
::
EndpointAttributes
class
<<
self
def
feature_category_for_app
(
app
)
feature_category_for_action
(
path_for_app
(
app
))
end
def
target_duration_for_app
(
app
)
target_duration_for_action
(
path_for_app
(
app
))
end
def
path_for_app
(
app
)
normalize_path
(
app
.
namespace
,
app
.
options
[
:path
].
first
)
end
...
...
@@ -18,8 +22,13 @@ module API
end
def
route
(
methods
,
paths
=
[
'/'
],
route_options
=
{},
&
block
)
actions
=
Array
(
paths
).
map
{
|
path
|
normalize_path
(
namespace
,
path
)
}
if
category
=
route_options
.
delete
(
:feature_category
)
feature_category
(
category
,
Array
(
paths
).
map
{
|
path
|
normalize_path
(
namespace
,
path
)
})
feature_category
(
category
,
actions
)
end
if
target
=
route_options
.
delete
(
:target_duration
)
target_duration
(
target
,
actions
)
end
super
...
...
lib/api/internal/base.rb
View file @
5c52eddb
...
...
@@ -164,7 +164,7 @@ module API
#
# Check whether an SSH key is known to GitLab
#
get
'/authorized_keys'
,
feature_category: :source_code_management
do
get
'/authorized_keys'
,
feature_category: :source_code_management
,
target_duration: :very_fast
do
fingerprint
=
Gitlab
::
InsecureKeyFingerprint
.
new
(
params
.
fetch
(
:key
)).
fingerprint_sha256
key
=
Key
.
find_by_fingerprint_sha256
(
fingerprint
)
...
...
lib/api/unleash.rb
View file @
5c52eddb
...
...
@@ -30,7 +30,7 @@ module API
end
desc
'Get a list of features'
get
'client/features'
do
get
'client/features'
,
target_duration: :fast
do
present
:version
,
1
present
:features
,
feature_flags
,
with:
::
API
::
Entities
::
UnleashFeature
end
...
...
lib/api/users.rb
View file @
5c52eddb
...
...
@@ -787,7 +787,7 @@ module API
use
:pagination
optional
:state
,
type:
String
,
default:
'all'
,
values:
%w[all active inactive]
,
desc:
'Filters (all|active|inactive) impersonation_tokens'
end
get
feature_category
:authentication_and_authorization
do
get
feature_category
:
:authentication_and_authorization
do
present
paginate
(
finder
(
declared_params
(
include_missing:
false
)).
execute
),
with:
Entities
::
ImpersonationToken
end
...
...
lib/gitlab/
with_feature_category
.rb
→
lib/gitlab/
endpoint_attributes
.rb
View file @
5c52eddb
# frozen_string_literal: true
module
Gitlab
module
WithFeatureCategory
module
EndpointAttributes
extend
ActiveSupport
::
Concern
include
Gitlab
::
ClassAttributes
DEFAULT_TARGET_DURATION
=
Config
::
TARGET_DURATIONS
.
fetch
(
:medium
)
class_methods
do
def
feature_category
(
category
,
actions
=
[])
feature_category_configuration
[
category
]
||=
[]
feature_category_configuration
[
category
]
+=
actions
.
map
(
&
:to_s
)
validate_config!
(
feature_category_configuration
)
endpoint_attributes
.
set
(
actions
,
feature_category:
category
)
end
def
feature_category_for_action
(
action
)
category_config
=
feature_category_configuration
.
find
do
|
_
,
actions
|
actions
.
empty?
||
actions
.
include?
(
action
)
end
category_config
&
.
first
||
superclass_feature_category_for_action
(
action
)
category
=
endpoint_attributes
.
attribute_for_action
(
action
,
:feature_category
)
category
||
superclass_feature_category_for_action
(
action
)
end
private
def
validate_config!
(
config
)
empty
=
config
.
find
{
|
_
,
actions
|
actions
.
empty?
}
duplicate_actions
=
config
.
values
.
map
(
&
:uniq
).
flatten
.
group_by
(
&
:itself
).
select
{
|
_
,
v
|
v
.
count
>
1
}.
keys
if
config
.
length
>
1
&&
empty
raise
ArgumentError
,
"
#{
empty
.
first
}
is defined for all actions, but other categories are set"
end
def
target_duration
(
duration
,
actions
=
[])
endpoint_attributes
.
set
(
actions
,
target_duration:
duration
)
end
if
duplicate_actions
.
any?
raise
ArgumentError
,
"Actions have multiple feature categories:
#{
duplicate_actions
.
join
(
', '
)
}
"
end
def
target_duration_for_action
(
action
)
duration
=
endpoint_attributes
.
attribute_for_action
(
action
,
:target_duration
)
duration
||
superclass_target_duration_for_action
(
action
)
||
DEFAULT_TARGET_DURATION
end
def
feature_category_configuration
class_attributes
[
:feature_category_config
]
||=
{}
private
def
endpoint_attributes
class_attributes
[
:endpoint_attributes_config
]
||=
Config
.
new
end
def
superclass_feature_category_for_action
(
action
)
...
...
@@ -45,6 +37,12 @@ module Gitlab
superclass
.
feature_category_for_action
(
action
)
end
def
superclass_target_duration_for_action
(
action
)
return
unless
superclass
.
respond_to?
(
:target_duration_for_action
)
superclass
.
target_duration_for_action
(
action
)
end
end
end
end
lib/gitlab/endpoint_attributes/config.rb
0 → 100644
View file @
5c52eddb
# frozen_string_literal: true
module
Gitlab
module
EndpointAttributes
class
Config
Duration
=
Struct
.
new
(
:name
,
:duration
)
TARGET_DURATIONS
=
[
Duration
.
new
(
:very_fast
,
0.25
),
Duration
.
new
(
:fast
,
0.5
),
Duration
.
new
(
:medium
,
1
),
Duration
.
new
(
:slow
,
5
)
].
index_by
(
&
:name
).
freeze
SUPPORTED_ATTRIBUTES
=
%i[feature_category target_duration]
.
freeze
def
initialize
@default_attributes
=
{}
@action_attributes
=
{}
end
def
defined_actions
@action_attributes
.
keys
end
def
set
(
actions
,
attributes
)
sanitize_attributes!
(
attributes
)
if
actions
.
empty?
conflicted
=
conflicted_attributes
(
attributes
,
@default_attributes
)
raise
ArgumentError
,
"Attributes already defined:
#{
conflicted
.
join
(
", "
)
}
"
if
conflicted
.
present?
@default_attributes
.
merge!
(
attributes
)
else
set_attributes_for_actions
(
actions
,
attributes
)
end
nil
end
def
attribute_for_action
(
action
,
attribute_name
)
value
=
@action_attributes
.
dig
(
action
.
to_s
,
attribute_name
)
||
@default_attributes
[
attribute_name
]
# Translate target duration to a representative struct
value
=
TARGET_DURATIONS
[
value
]
if
attribute_name
==
:target_duration
value
end
private
def
sanitize_attributes!
(
attributes
)
unsupported_attributes
=
(
attributes
.
keys
-
SUPPORTED_ATTRIBUTES
).
present?
raise
ArgumentError
,
"Attributes not supported:
#{
unsupported_attributes
.
join
(
", "
)
}
"
if
unsupported_attributes
if
attributes
[
:target_duration
].
present?
&&
!
TARGET_DURATIONS
.
key?
(
attributes
[
:target_duration
])
raise
ArgumentError
,
"Target duration not supported:
#{
attributes
[
:target_duration
]
}
"
end
end
def
set_attributes_for_actions
(
actions
,
attributes
)
conflicted
=
conflicted_attributes
(
attributes
,
@default_attributes
)
if
conflicted
.
present?
raise
ArgumentError
,
"
#{
conflicted
.
join
(
", "
)
}
are already defined for all actions, but re-defined for
#{
actions
.
join
(
", "
)
}
"
end
actions
.
each
do
|
action
|
action
=
action
.
to_s
if
@action_attributes
[
action
].
blank?
@action_attributes
[
action
]
=
attributes
.
dup
else
conflicted
=
conflicted_attributes
(
attributes
,
@action_attributes
[
action
])
raise
ArgumentError
,
"Attributes re-defined for action
#{
action
}
:
#{
conflicted
.
join
(
", "
)
}
"
if
conflicted
.
present?
@action_attributes
[
action
].
merge!
(
attributes
)
end
end
end
def
conflicted_attributes
(
attributes
,
existing_attributes
)
attributes
.
keys
.
filter
{
|
attr
|
existing_attributes
[
attr
].
present?
&&
existing_attributes
[
attr
]
!=
attributes
[
attr
]
}
end
end
end
end
lib/gitlab/metrics/requests_rack_middleware.rb
View file @
5c52eddb
...
...
@@ -79,7 +79,7 @@ module Gitlab
if
!
health_endpoint
&&
::
Gitlab
::
Metrics
.
record_duration_for_status?
(
status
)
self
.
class
.
http_request_duration_seconds
.
observe
({
method:
method
},
elapsed
)
record_apdex_if_needed
(
elapsed
)
record_apdex_if_needed
(
e
nv
,
e
lapsed
)
end
[
status
,
headers
,
body
]
...
...
@@ -113,14 +113,12 @@ module Gitlab
::
Gitlab
::
ApplicationContext
.
current_context_attribute
(
:caller_id
)
end
def
record_apdex_if_needed
(
elapsed
)
def
record_apdex_if_needed
(
e
nv
,
e
lapsed
)
return
unless
Gitlab
::
Metrics
::
RailsSlis
.
request_apdex_counters_enabled?
Gitlab
::
Metrics
::
RailsSlis
.
request_apdex
.
increment
(
labels:
labels_from_context
,
# hardcoded 1s here will be replaced by a per-endpoint value.
# https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/1223
success:
elapsed
<
1
success:
satisfactory?
(
env
,
elapsed
)
)
end
...
...
@@ -130,6 +128,19 @@ module Gitlab
endpoint_id:
endpoint_id
.
presence
||
ENDPOINT_MISSING
}
end
def
satisfactory?
(
env
,
elapsed
)
target
=
if
env
[
'api.endpoint'
].
present?
env
[
'api.endpoint'
].
options
[
:for
].
try
(
:target_duration_for_app
,
env
[
'api.endpoint'
])
elsif
env
[
'action_controller.instance'
].
present?
&&
env
[
'action_controller.instance'
].
respond_to?
(
:target_duration
)
env
[
'action_controller.instance'
].
target_duration
end
target
||=
Gitlab
::
EndpointAttributes
::
DEFAULT_TARGET_DURATION
elapsed
<
target
.
duration
end
end
end
end
lib/gitlab/request_endpoints.rb
View file @
5c52eddb
...
...
@@ -21,7 +21,7 @@ module Gitlab
next
if
route_info
[
:controller
].
blank?
||
route_info
[
:action
].
blank?
controller
=
constantize_controller
(
route_info
[
:controller
])
next
unless
controller
&
.
include?
(
::
Gitlab
::
WithFeatureCategory
)
next
unless
controller
&
.
include?
(
::
Gitlab
::
EndpointAttributes
)
next
if
controller
==
ApplicationController
next
if
controller
==
Devise
::
UnlocksController
...
...
spec/controllers/every_controller_spec.rb
View file @
5c52eddb
# frozen_string_literal: true
require
'spec_helper'
RSpec
.
describe
"Every controller"
do
context
"feature categories"
do
let_it_be
(
:feature_categories
)
do
...
...
@@ -65,9 +64,6 @@ RSpec.describe "Every controller" do
end
def
actions_defined_in_feature_category_config
(
controller
)
controller
.
send
(
:class_attributes
)[
:feature_category_config
]
.
values
.
flatten
.
map
(
&
:to_s
)
controller
.
send
(
:class_attributes
)[
:endpoint_attributes_config
].
defined_actions
end
end
spec/lib/api/base_spec.rb
0 → 100644
View file @
5c52eddb
# frozen_string_literal: true
require
'spec_helper'
# rubocop:disable Rails/HttpPositionalArguments
RSpec
.
describe
::
API
::
Base
do
let
(
:app_hello
)
do
route
=
double
(
:route
,
request_method:
'GET'
,
path:
'/:version/test/hello'
)
double
(
:endpoint
,
route:
route
,
options:
{
for:
api_handler
,
path:
[
"hello"
]
},
namespace:
'/test'
)
end
let
(
:app_hi
)
do
route
=
double
(
:route
,
request_method:
'GET'
,
path:
'/:version//test/hi'
)
double
(
:endpoint
,
route:
route
,
options:
{
for:
api_handler
,
path:
[
"hi"
]
},
namespace:
'/test'
)
end
describe
'declare feature categories at handler level for all routes'
do
let
(
:api_handler
)
do
Class
.
new
(
described_class
)
do
feature_category
:foo
target_duration
:fast
namespace
'/test'
do
get
'hello'
do
end
post
'hi'
do
end
end
end
end
it
'sets feature category for a particular route'
,
:aggregate_failures
do
expect
(
api_handler
.
feature_category_for_app
(
app_hello
)).
to
eq
(
:foo
)
expect
(
api_handler
.
feature_category_for_app
(
app_hi
)).
to
eq
(
:foo
)
end
it
'sets target duration for a particular route'
,
:aggregate_failures
do
expect
(
api_handler
.
target_duration_for_app
(
app_hello
)).
to
be_a_target_duration
(
:fast
)
expect
(
api_handler
.
target_duration_for_app
(
app_hi
)).
to
be_a_target_duration
(
:fast
)
end
end
describe
'declare feature categories at route level'
do
let
(
:api_handler
)
do
Class
.
new
(
described_class
)
do
namespace
'/test'
do
get
'hello'
,
feature_category: :foo
,
target_duration: :slow
do
end
post
'hi'
,
feature_category: :bar
,
target_duration: :fast
do
end
end
end
end
it
'sets feature category for a particular route'
,
:aggregate_failures
do
expect
(
api_handler
.
feature_category_for_app
(
app_hello
)).
to
eq
(
:foo
)
expect
(
api_handler
.
feature_category_for_app
(
app_hi
)).
to
eq
(
:bar
)
end
it
'sets target duration for a particular route'
,
:aggregate_failures
do
expect
(
api_handler
.
target_duration_for_app
(
app_hello
)).
to
be_a_target_duration
(
:slow
)
expect
(
api_handler
.
target_duration_for_app
(
app_hi
)).
to
be_a_target_duration
(
:fast
)
end
end
describe
'declare feature categories at both handler level and route level'
do
let
(
:api_handler
)
do
Class
.
new
(
described_class
)
do
feature_category
:foo
,
[
'/test/hello'
]
target_duration
:slow
,
[
'/test/hello'
]
namespace
'/test'
do
get
'hello'
do
end
post
'hi'
,
feature_category: :bar
,
target_duration: :fast
do
end
end
end
end
it
'sets feature category for a particular route'
,
:aggregate_failures
do
expect
(
api_handler
.
feature_category_for_app
(
app_hello
)).
to
eq
(
:foo
)
expect
(
api_handler
.
feature_category_for_app
(
app_hi
)).
to
eq
(
:bar
)
end
it
'sets target duration for a particular route'
,
:aggregate_failures
do
expect
(
api_handler
.
target_duration_for_app
(
app_hello
)).
to
be_a_target_duration
(
:slow
)
expect
(
api_handler
.
target_duration_for_app
(
app_hi
)).
to
be_a_target_duration
(
:fast
)
end
end
end
# rubocop:enable Rails/HttpPositionalArguments
spec/lib/gitlab/endpoint_attributes_spec.rb
0 → 100644
View file @
5c52eddb
# frozen_string_literal: true
require
'fast_spec_helper'
require_relative
"../../../lib/gitlab/endpoint_attributes"
RSpec
.
describe
Gitlab
::
EndpointAttributes
do
let
(
:base_controller
)
do
Class
.
new
do
include
::
Gitlab
::
EndpointAttributes
end
end
let
(
:controller
)
do
Class
.
new
(
base_controller
)
do
feature_category
:foo
,
%w(update edit)
feature_category
:bar
,
%w(index show)
feature_category
:quux
,
%w(destroy)
target_duration
:fast
,
%w(do_a)
target_duration
:slow
,
%w(do_b do_c)
end
end
let
(
:subclass
)
do
Class
.
new
(
controller
)
do
feature_category
:baz
,
%w(subclass_index)
target_duration
:very_fast
,
%w(superclass_do_something)
end
end
it
"is nil when nothing was defined"
do
expect
(
base_controller
.
feature_category_for_action
(
"hello"
)).
to
be_nil
end
it
"returns the expected category"
,
:aggregate_failures
do
expect
(
controller
.
feature_category_for_action
(
"update"
)).
to
eq
(
:foo
)
expect
(
controller
.
feature_category_for_action
(
"index"
)).
to
eq
(
:bar
)
expect
(
controller
.
feature_category_for_action
(
"destroy"
)).
to
eq
(
:quux
)
end
it
"falls back to medium when target_duration was not defined"
,
:aggregate_failures
do
expect
(
base_controller
.
target_duration_for_action
(
"hello"
)).
to
be_a_target_duration
(
:medium
)
expect
(
controller
.
target_duration_for_action
(
"update"
)).
to
be_a_target_duration
(
:medium
)
expect
(
controller
.
target_duration_for_action
(
"index"
)).
to
be_a_target_duration
(
:medium
)
expect
(
controller
.
target_duration_for_action
(
"destroy"
)).
to
be_a_target_duration
(
:medium
)
end
it
"returns the expected target_duration"
,
:aggregate_failures
do
expect
(
controller
.
target_duration_for_action
(
"do_a"
)).
to
be_a_target_duration
(
:fast
)
expect
(
controller
.
target_duration_for_action
(
"do_b"
)).
to
be_a_target_duration
(
:slow
)
expect
(
controller
.
target_duration_for_action
(
"do_c"
)).
to
be_a_target_duration
(
:slow
)
end
it
"returns feature category for an implied action if not specify actions"
do
klass
=
Class
.
new
(
base_controller
)
do
feature_category
:foo
end
expect
(
klass
.
feature_category_for_action
(
"index"
)).
to
eq
(
:foo
)
expect
(
klass
.
feature_category_for_action
(
"show"
)).
to
eq
(
:foo
)
end
it
"returns expected duration for an implied action if not specify actions"
do
klass
=
Class
.
new
(
base_controller
)
do
feature_category
:foo
target_duration
:slow
end
expect
(
klass
.
target_duration_for_action
(
"index"
)).
to
be_a_target_duration
(
:slow
)
expect
(
klass
.
target_duration_for_action
(
"show"
)).
to
be_a_target_duration
(
:slow
)
end
it
"returns the expected category for categories defined in subclasses"
do
expect
(
subclass
.
feature_category_for_action
(
"subclass_index"
)).
to
eq
(
:baz
)
end
it
"falls back to superclass's feature category"
do
expect
(
subclass
.
feature_category_for_action
(
"update"
)).
to
eq
(
:foo
)
end
it
"returns the expected target_duration for categories defined in subclasses"
do
expect
(
subclass
.
target_duration_for_action
(
"superclass_do_something"
)).
to
be_a_target_duration
(
:very_fast
)
end
it
"falls back to superclass's expected duration"
do
expect
(
subclass
.
target_duration_for_action
(
"do_a"
)).
to
be_a_target_duration
(
:fast
)
end
it
"raises an error when defining for the controller and for individual actions"
do
expect
do
Class
.
new
(
base_controller
)
do
feature_category
:hello
feature_category
:goodbye
,
[
:world
]
end
end
.
to
raise_error
(
ArgumentError
,
"feature_category are already defined for all actions, but re-defined for world"
)
end
it
"raises an error when multiple calls define the same action"
do
expect
do
Class
.
new
(
base_controller
)
do
feature_category
:hello
,
[
:world
]
feature_category
:goodbye
,
[
"world"
]
end
end
.
to
raise_error
(
ArgumentError
,
"Attributes re-defined for action world: feature_category"
)
end
it
"raises an error when multiple calls define the same action"
do
expect
do
Class
.
new
(
base_controller
)
do
target_duration
:fast
,
[
:world
]
target_duration
:slow
,
[
"world"
]
end
end
.
to
raise_error
(
ArgumentError
,
"Attributes re-defined for action world: target_duration"
)
end
it
"does not raise an error when multiple calls define the same action and configs"
do
expect
do
Class
.
new
(
base_controller
)
do
feature_category
:hello
,
[
:world
]
feature_category
:hello
,
[
"world"
]
target_duration
:fast
,
[
:moon
]
target_duration
:fast
,
[
"moon"
]
end
end
.
not_to
raise_error
end
it
"raises an error if the expected duration is not supported"
do
expect
do
Class
.
new
(
base_controller
)
do
target_duration
:super_slow
end
end
.
to
raise_error
(
ArgumentError
,
"Target duration not supported: super_slow"
)
end
end
spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
View file @
5c52eddb
...
...
@@ -71,7 +71,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
expect
(
described_class
).
not_to
receive
(
:http_health_requests_total
)
expect
(
described_class
)
.
to
receive_message_chain
(
:http_request_duration_seconds
,
:observe
)
.
with
({
method:
'get'
},
a_positive_execution_time
)
.
with
({
method:
'get'
},
a_positive_execution_time
)
subject
.
call
(
env
)
end
...
...
@@ -161,6 +161,165 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do
subject
.
call
(
env
)
end
end
context
'SLI satisfactory'
do
where
(
:target
,
:duration
,
:success
)
do
[
[
:very_fast
,
0.1
,
true
],
[
:very_fast
,
0.25
,
false
],
[
:very_fast
,
0.3
,
false
],
[
:fast
,
0.3
,
true
],
[
:fast
,
0.5
,
false
],
[
:fast
,
0.6
,
false
],
[
:medium
,
0.6
,
true
],
[
:medium
,
1.0
,
false
],
[
:medium
,
1.2
,
false
],
[
:slow
,
4.5
,
true
],
[
:slow
,
5.0
,
false
],
[
:slow
,
6
,
false
]
]
end
with_them
do
context
'Grape API handler having expected duration setup'
do
let
(
:api_handler
)
do
target_duration
=
target
# target is a DSL provided by Rspec, it's invisible to the inner block
Class
.
new
(
::
API
::
Base
)
do
feature_category
:hello_world
,
[
'/projects/:id/archive'
]
target_duration
target_duration
,
[
'/projects/:id/archive'
]
end
end
let
(
:endpoint
)
do
route
=
double
(
:route
,
request_method:
'GET'
,
path:
'/:version/projects/:id/archive(.:format)'
)
double
(
:endpoint
,
route:
route
,
options:
{
for:
api_handler
,
path:
[
":id/archive"
]
},
namespace:
"/projects"
)
end
let
(
:env
)
{
{
'api.endpoint'
=>
endpoint
,
'REQUEST_METHOD'
=>
'GET'
}
}
before
do
::
Gitlab
::
ApplicationContext
.
push
(
feature_category:
'hello_world'
,
caller_id:
'GET /projects/:id/archive'
)
allow
(
Gitlab
::
Metrics
::
System
).
to
receive
(
:monotonic_time
).
and_return
(
100
,
100
+
duration
)
end
it
"captures SLI metrics"
do
expect
(
Gitlab
::
Metrics
::
RailsSlis
.
request_apdex
).
to
receive
(
:increment
).
with
(
labels:
{
feature_category:
'hello_world'
,
endpoint_id:
'GET /projects/:id/archive'
},
success:
success
)
subject
.
call
(
env
)
end
end
context
'Rails controller having expected duration setup'
do
let
(
:controller
)
do
target_duration
=
target
# target is a DSL provided by Rspec, it's invisible to the inner block
Class
.
new
(
ApplicationController
)
do
feature_category
:hello_world
,
[
:index
,
:show
]
target_duration
target_duration
,
[
:index
,
:show
]
end
end
let
(
:env
)
do
controller_instance
=
controller
.
new
controller_instance
.
action_name
=
:index
{
'action_controller.instance'
=>
controller_instance
,
'REQUEST_METHOD'
=>
'GET'
}
end
before
do
::
Gitlab
::
ApplicationContext
.
push
(
feature_category:
'hello_world'
,
caller_id:
'AnonymousController#index'
)
allow
(
Gitlab
::
Metrics
::
System
).
to
receive
(
:monotonic_time
).
and_return
(
100
,
100
+
duration
)
end
it
"captures SLI metrics"
do
expect
(
Gitlab
::
Metrics
::
RailsSlis
.
request_apdex
).
to
receive
(
:increment
).
with
(
labels:
{
feature_category:
'hello_world'
,
endpoint_id:
'AnonymousController#index'
},
success:
success
)
subject
.
call
(
env
)
end
end
end
context
'Grape API without expected duration'
do
let
(
:endpoint
)
do
route
=
double
(
:route
,
request_method:
'GET'
,
path:
'/:version/projects/:id/archive(.:format)'
)
double
(
:endpoint
,
route:
route
,
options:
{
for:
api_handler
,
path:
[
":id/archive"
]
},
namespace:
"/projects"
)
end
let
(
:env
)
{
{
'api.endpoint'
=>
endpoint
,
'REQUEST_METHOD'
=>
'GET'
}
}
let
(
:api_handler
)
{
Class
.
new
(
::
API
::
Base
)
}
it
"falls back request's expectation to medium (1 second)"
do
allow
(
Gitlab
::
Metrics
::
System
).
to
receive
(
:monotonic_time
).
and_return
(
100
,
100.9
)
expect
(
Gitlab
::
Metrics
::
RailsSlis
.
request_apdex
).
to
receive
(
:increment
).
with
(
labels:
{
feature_category:
'unknown'
,
endpoint_id:
'unknown'
},
success:
true
)
subject
.
call
(
env
)
allow
(
Gitlab
::
Metrics
::
System
).
to
receive
(
:monotonic_time
).
and_return
(
100
,
101
)
expect
(
Gitlab
::
Metrics
::
RailsSlis
.
request_apdex
).
to
receive
(
:increment
).
with
(
labels:
{
feature_category:
'unknown'
,
endpoint_id:
'unknown'
},
success:
false
)
subject
.
call
(
env
)
end
end
context
'Rails controller without expected duration'
do
let
(
:controller
)
{
Class
.
new
(
ApplicationController
)
}
let
(
:env
)
do
controller_instance
=
controller
.
new
controller_instance
.
action_name
=
:index
{
'action_controller.instance'
=>
controller_instance
,
'REQUEST_METHOD'
=>
'GET'
}
end
it
"falls back request's expectation to medium (1 second)"
do
allow
(
Gitlab
::
Metrics
::
System
).
to
receive
(
:monotonic_time
).
and_return
(
100
,
100.9
)
expect
(
Gitlab
::
Metrics
::
RailsSlis
.
request_apdex
).
to
receive
(
:increment
).
with
(
labels:
{
feature_category:
'unknown'
,
endpoint_id:
'unknown'
},
success:
true
)
subject
.
call
(
env
)
allow
(
Gitlab
::
Metrics
::
System
).
to
receive
(
:monotonic_time
).
and_return
(
100
,
101
)
expect
(
Gitlab
::
Metrics
::
RailsSlis
.
request_apdex
).
to
receive
(
:increment
).
with
(
labels:
{
feature_category:
'unknown'
,
endpoint_id:
'unknown'
},
success:
false
)
subject
.
call
(
env
)
end
end
context
'An unknown request'
do
let
(
:env
)
do
{
'REQUEST_METHOD'
=>
'GET'
}
end
it
"falls back request's expectation to medium (1 second)"
do
allow
(
Gitlab
::
Metrics
::
System
).
to
receive
(
:monotonic_time
).
and_return
(
100
,
100.9
)
expect
(
Gitlab
::
Metrics
::
RailsSlis
.
request_apdex
).
to
receive
(
:increment
).
with
(
labels:
{
feature_category:
'unknown'
,
endpoint_id:
'unknown'
},
success:
true
)
subject
.
call
(
env
)
allow
(
Gitlab
::
Metrics
::
System
).
to
receive
(
:monotonic_time
).
and_return
(
100
,
101
)
expect
(
Gitlab
::
Metrics
::
RailsSlis
.
request_apdex
).
to
receive
(
:increment
).
with
(
labels:
{
feature_category:
'unknown'
,
endpoint_id:
'unknown'
},
success:
false
)
subject
.
call
(
env
)
end
end
end
end
describe
'.initialize_metrics'
,
:prometheus
do
...
...
spec/lib/gitlab/request_endpoints_spec.rb
View file @
5c52eddb
...
...
@@ -6,7 +6,7 @@ RSpec.describe Gitlab::RequestEndpoints do
it
'selects all feature API classes'
do
api_classes
=
described_class
.
all_api_endpoints
.
map
{
|
route
|
route
.
app
.
options
[
:for
]
}
expect
(
api_classes
).
to
all
(
include
(
Gitlab
::
WithFeatureCategory
))
expect
(
api_classes
).
to
all
(
include
(
Gitlab
::
EndpointAttributes
))
end
end
...
...
@@ -16,7 +16,7 @@ RSpec.describe Gitlab::RequestEndpoints do
controller_classes
=
all_controller_actions
.
map
(
&
:first
)
all_actions
=
all_controller_actions
.
map
(
&
:last
)
expect
(
controller_classes
).
to
all
(
include
(
Gitlab
::
WithFeatureCategory
))
expect
(
controller_classes
).
to
all
(
include
(
Gitlab
::
EndpointAttributes
))
expect
(
controller_classes
).
not_to
include
(
ApplicationController
,
Devise
::
UnlocksController
)
expect
(
all_actions
).
to
all
(
be_a
(
String
))
end
...
...
spec/lib/gitlab/with_feature_category_spec.rb
deleted
100644 → 0
View file @
5cc0b39e
# frozen_string_literal: true
require
'fast_spec_helper'
require_relative
"../../../lib/gitlab/with_feature_category"
RSpec
.
describe
Gitlab
::
WithFeatureCategory
do
describe
".feature_category_for_action"
do
let
(
:base_controller
)
do
Class
.
new
do
include
::
Gitlab
::
WithFeatureCategory
end
end
let
(
:controller
)
do
Class
.
new
(
base_controller
)
do
feature_category
:foo
,
%w(update edit)
feature_category
:bar
,
%w(index show)
feature_category
:quux
,
%w(destroy)
end
end
let
(
:subclass
)
do
Class
.
new
(
controller
)
do
feature_category
:baz
,
%w(subclass_index)
end
end
it
"is nil when nothing was defined"
do
expect
(
base_controller
.
feature_category_for_action
(
"hello"
)).
to
be_nil
end
it
"returns the expected category"
,
:aggregate_failures
do
expect
(
controller
.
feature_category_for_action
(
"update"
)).
to
eq
(
:foo
)
expect
(
controller
.
feature_category_for_action
(
"index"
)).
to
eq
(
:bar
)
expect
(
controller
.
feature_category_for_action
(
"destroy"
)).
to
eq
(
:quux
)
end
it
"returns the expected category for categories defined in subclasses"
do
expect
(
subclass
.
feature_category_for_action
(
"subclass_index"
)).
to
eq
(
:baz
)
end
it
"raises an error when defining for the controller and for individual actions"
do
expect
do
Class
.
new
(
base_controller
)
do
feature_category
:hello
feature_category
:goodbye
,
[
:world
]
end
end
.
to
raise_error
(
ArgumentError
,
"hello is defined for all actions, but other categories are set"
)
end
it
"raises an error when multiple calls define the same action"
do
expect
do
Class
.
new
(
base_controller
)
do
feature_category
:hello
,
[
:world
]
feature_category
:goodbye
,
[
"world"
]
end
end
.
to
raise_error
(
ArgumentError
,
"Actions have multiple feature categories: world"
)
end
it
"does not raise an error when multiple calls define the same action and feature category"
do
expect
do
Class
.
new
(
base_controller
)
do
feature_category
:hello
,
[
:world
]
feature_category
:hello
,
[
"world"
]
end
end
.
not_to
raise_error
end
end
end
spec/support/matchers/be_a_target_duration.rb
0 → 100644
View file @
5c52eddb
# frozen_string_literal: true
RSpec
::
Matchers
.
define
:be_a_target_duration
do
|
expected
|
match
do
|
actual
|
actual
.
is_a?
(
::
Gitlab
::
EndpointAttributes
::
Config
::
Duration
)
&&
actual
.
name
==
expected
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment