gitlab-labkit 0.42.1 → 0.42.2
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-asdf-versions.yml +1 -1
- data/.pre-commit-config.yaml +1 -1
- data/.tool-versions +1 -1
- data/README.md +1 -1
- data/config/{covered_experiences → user_experience_slis}/schema.json +5 -5
- data/lib/gitlab-labkit.rb +2 -1
- data/lib/labkit/middleware/rack.rb +3 -3
- data/lib/labkit/middleware/sidekiq/client.rb +1 -1
- data/lib/labkit/middleware/sidekiq/server.rb +1 -1
- data/lib/labkit/middleware/sidekiq/{covered_experience → user_experience_sli}/client.rb +4 -4
- data/lib/labkit/middleware/sidekiq/{covered_experience → user_experience_sli}/server.rb +4 -4
- data/lib/labkit/middleware/sidekiq/user_experience_sli.rb +17 -0
- data/lib/labkit/middleware/sidekiq.rb +2 -1
- data/lib/labkit/rspec/README.md +36 -27
- data/lib/labkit/rspec/matchers/{covered_experience_matchers.rb → user_experience_matchers.rb} +53 -47
- data/lib/labkit/rspec/matchers.rb +1 -1
- data/lib/labkit/{covered_experience → user_experience_sli}/README.md +28 -28
- data/lib/labkit/{covered_experience → user_experience_sli}/current.rb +7 -7
- data/lib/labkit/user_experience_sli/error.rb +9 -0
- data/lib/labkit/{covered_experience → user_experience_sli}/experience.rb +26 -26
- data/lib/labkit/{covered_experience → user_experience_sli}/null.rb +2 -2
- data/lib/labkit/{covered_experience → user_experience_sli}/registry.rb +13 -13
- data/lib/labkit/{covered_experience.rb → user_experience_sli.rb} +16 -11
- metadata +14 -14
- data/lib/labkit/covered_experience/error.rb +0 -9
- data/lib/labkit/middleware/sidekiq/covered_experience.rb +0 -14
- /data/config/{covered_experiences → user_experience_slis}/testing_sample.yml +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cefb3ce3a1f0b663c7d4ac1a60d0b4ed3a6ecb8b170efac046f07e5d700a0b66
|
|
4
|
+
data.tar.gz: 3cb390d7949d332aaa959c03fe1a0d152100067e3822f0832327583972e7ad46
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 74ac536ebabf46339787079b5c972c8541a4b3d3bd15a597395b110adb1c20fff19ec8bdb554fcf57fc4a36ecbe39c26369bf5c1f4fadfc309bfc5f96c3cb6ee
|
|
7
|
+
data.tar.gz: b3fa11140a2d30d29af280f76887afe57fa0b4c94eb4b73112d1dde67dfcf35a59bb1bb24e32827ea3c30cfddeb3ddcedb438923ca2d6199d72fc0b0182dd6fa
|
data/.pre-commit-config.yaml
CHANGED
|
@@ -25,7 +25,7 @@ repos:
|
|
|
25
25
|
# Documentation available at
|
|
26
26
|
# https://gitlab.com/gitlab-com/gl-infra/common-ci-tasks/-/blob/main/docs/pre-commit.md
|
|
27
27
|
- repo: https://gitlab.com/gitlab-com/gl-infra/common-ci-tasks
|
|
28
|
-
rev: v2.
|
|
28
|
+
rev: v2.98 # renovate:managed
|
|
29
29
|
|
|
30
30
|
hooks:
|
|
31
31
|
- id: shellcheck # Run shellcheck for changed Shell files
|
data/.tool-versions
CHANGED
data/README.md
CHANGED
|
@@ -20,7 +20,7 @@ LabKit-Ruby provides functionality in a number of areas:
|
|
|
20
20
|
|
|
21
21
|
1. `Labkit::Context` used for providing context information to log messages.
|
|
22
22
|
1. `Labkit::Correlation` for accessing the correlation id. (Generated and propagated by `Labkit::Context`)
|
|
23
|
-
1. `Labkit::
|
|
23
|
+
1. `Labkit::UserExperienceSli` for tracking User Experience SLIs. More on the [README](./lib/labkit/user_experience_sli/README.md).
|
|
24
24
|
1. `Labkit::FIPS` for checking for FIPS mode and using FIPS-compliant algorithms.
|
|
25
25
|
1. `Labkit::Logging` for sanitizing log messages.
|
|
26
26
|
1. `Labkit::Metrics` for metrics. More on the [README](./lib/labkit/metrics/README.md).
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-06/schema#",
|
|
3
|
-
"$id": "https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby/-/raw/master/config/
|
|
4
|
-
"title": "
|
|
5
|
-
"description": "Schema for GitLab
|
|
3
|
+
"$id": "https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby/-/raw/master/config/user_experience_slis/schema.json",
|
|
4
|
+
"title": "User Experience SLI Definition",
|
|
5
|
+
"description": "Schema for GitLab User Experience SLI files",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"properties": {
|
|
8
8
|
"description": {
|
|
9
9
|
"type": "string",
|
|
10
10
|
"minLength": 1,
|
|
11
|
-
"description": "Human-readable description of the
|
|
11
|
+
"description": "Human-readable description of the user experience"
|
|
12
12
|
},
|
|
13
13
|
"feature_category": {
|
|
14
14
|
"type": "string",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"async_fast",
|
|
24
24
|
"async_slow"
|
|
25
25
|
],
|
|
26
|
-
"description": "Urgency level for this
|
|
26
|
+
"description": "Urgency level for this user experience"
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"required": [
|
data/lib/gitlab-labkit.rb
CHANGED
|
@@ -9,7 +9,8 @@ module Labkit
|
|
|
9
9
|
|
|
10
10
|
autoload :Context, "labkit/context"
|
|
11
11
|
autoload :Correlation, "labkit/correlation"
|
|
12
|
-
autoload :CoveredExperience, "labkit/
|
|
12
|
+
autoload :CoveredExperience, "labkit/user_experience_sli" # Backward compatibility alias
|
|
13
|
+
autoload :UserExperienceSli, "labkit/user_experience_sli"
|
|
13
14
|
autoload :FIPS, "labkit/fips"
|
|
14
15
|
autoload :Tracing, "labkit/tracing"
|
|
15
16
|
autoload :Logging, "labkit/logging"
|
|
@@ -59,13 +59,13 @@ module Labkit
|
|
|
59
59
|
# 4. Microservices or API-only applications with minimal middleware
|
|
60
60
|
# 5. Background job processors that use Rack but not Rails' full stack
|
|
61
61
|
#
|
|
62
|
-
# By explicitly resetting here, we ensure
|
|
62
|
+
# By explicitly resetting here, we ensure user experiences are properly
|
|
63
63
|
# cleaned up regardless of the application framework or middleware configuration.
|
|
64
64
|
def reset_current_experiences
|
|
65
65
|
# Only reset if the Current class is loaded to avoid unnecessary dependencies
|
|
66
|
-
return unless defined?(Labkit::
|
|
66
|
+
return unless defined?(Labkit::UserExperienceSli::Current)
|
|
67
67
|
|
|
68
|
-
Labkit::
|
|
68
|
+
Labkit::UserExperienceSli::Current.reset
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
end
|
|
@@ -13,7 +13,7 @@ module Labkit
|
|
|
13
13
|
@chain ||= ::Sidekiq::Middleware::Chain.new do |chain|
|
|
14
14
|
chain.add Labkit::Middleware::Sidekiq::Context::Client
|
|
15
15
|
chain.add Labkit::Middleware::Sidekiq::Tracing::Client if Labkit::Tracing.enabled?
|
|
16
|
-
chain.add Labkit::Middleware::Sidekiq::
|
|
16
|
+
chain.add Labkit::Middleware::Sidekiq::UserExperienceSli::Client
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -13,7 +13,7 @@ module Labkit
|
|
|
13
13
|
@chain ||= ::Sidekiq::Middleware::Chain.new do |chain|
|
|
14
14
|
chain.add Labkit::Middleware::Sidekiq::Context::Server
|
|
15
15
|
chain.add Labkit::Middleware::Sidekiq::Tracing::Server if Labkit::Tracing.enabled?
|
|
16
|
-
chain.add Labkit::Middleware::Sidekiq::
|
|
16
|
+
chain.add Labkit::Middleware::Sidekiq::UserExperienceSli::Server
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'labkit/
|
|
3
|
+
require 'labkit/user_experience_sli/current'
|
|
4
4
|
|
|
5
5
|
module Labkit
|
|
6
6
|
module Middleware
|
|
7
7
|
module Sidekiq
|
|
8
|
-
module
|
|
8
|
+
module UserExperienceSli
|
|
9
9
|
# This middleware for Sidekiq-client wraps scheduling jobs with covered
|
|
10
10
|
# experience context. It retrieves the current experiences and
|
|
11
11
|
# populates the job with them.
|
|
12
12
|
class Client
|
|
13
13
|
def call(worker_class, job, _queue, _redis_pool)
|
|
14
|
-
data = Labkit::
|
|
14
|
+
data = Labkit::UserExperienceSli::Current.active_experiences.inject({}) do |data, (_, xp)|
|
|
15
15
|
xp.checkpoint(checkpoint_action: "sidekiq_job_scheduled", worker: worker_class.to_s)
|
|
16
16
|
data.merge!(xp.to_h)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
job[Labkit::
|
|
19
|
+
job[Labkit::UserExperienceSli::Current::AGGREGATION_KEY] = data unless data.empty?
|
|
20
20
|
|
|
21
21
|
yield
|
|
22
22
|
end
|
|
@@ -3,20 +3,20 @@
|
|
|
3
3
|
module Labkit
|
|
4
4
|
module Middleware
|
|
5
5
|
module Sidekiq
|
|
6
|
-
module
|
|
6
|
+
module UserExperienceSli
|
|
7
7
|
# This middleware for Sidekiq-server rehydrates the current experiences
|
|
8
8
|
# serialized to the job
|
|
9
9
|
class Server
|
|
10
10
|
def call(_worker_class, job, _queue)
|
|
11
|
-
job[Labkit::
|
|
12
|
-
xp = Labkit::
|
|
11
|
+
job[Labkit::UserExperienceSli::Current::AGGREGATION_KEY]&.each do |experience_id, data|
|
|
12
|
+
xp = Labkit::UserExperienceSli::Current.rehydrate(experience_id, **data)
|
|
13
13
|
xp.checkpoint(checkpoint_action: "sidekiq_job_started", worker: job["class"].to_s)
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
yield
|
|
17
17
|
|
|
18
18
|
ensure
|
|
19
|
-
Labkit::
|
|
19
|
+
Labkit::UserExperienceSli::Current.reset
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Labkit
|
|
4
|
+
module Middleware
|
|
5
|
+
module Sidekiq
|
|
6
|
+
# This module contains all the sidekiq middleware regarding user
|
|
7
|
+
# experience SLIs
|
|
8
|
+
module UserExperienceSli
|
|
9
|
+
autoload :Client, "labkit/middleware/sidekiq/user_experience_sli/client"
|
|
10
|
+
autoload :Server, "labkit/middleware/sidekiq/user_experience_sli/server"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Backward compatibility alias
|
|
14
|
+
CoveredExperience = UserExperienceSli
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -7,7 +7,8 @@ module Labkit
|
|
|
7
7
|
autoload :Client, "labkit/middleware/sidekiq/client"
|
|
8
8
|
autoload :Server, "labkit/middleware/sidekiq/server"
|
|
9
9
|
autoload :Context, "labkit/middleware/sidekiq/context"
|
|
10
|
-
autoload :
|
|
10
|
+
autoload :UserExperienceSli, "labkit/middleware/sidekiq/user_experience_sli"
|
|
11
|
+
autoload :CoveredExperience, "labkit/middleware/sidekiq/user_experience_sli" # Backward compatibility
|
|
11
12
|
autoload :Tracing, "labkit/middleware/sidekiq/tracing"
|
|
12
13
|
end
|
|
13
14
|
end
|
data/lib/labkit/rspec/README.md
CHANGED
|
@@ -19,61 +19,70 @@ This approach ensures that:
|
|
|
19
19
|
|
|
20
20
|
## Available Matchers
|
|
21
21
|
|
|
22
|
-
###
|
|
22
|
+
### User Experience Matchers
|
|
23
23
|
|
|
24
|
-
These matchers help you test that your code properly instruments
|
|
24
|
+
These matchers help you test that your code properly instruments user experience SLIs with the expected metrics.
|
|
25
25
|
|
|
26
|
-
#### `
|
|
26
|
+
#### `start_user_experience`
|
|
27
27
|
|
|
28
|
-
Tests that a
|
|
28
|
+
Tests that a user experience is started (checkpoint=start metric is incremented).
|
|
29
29
|
|
|
30
30
|
```ruby
|
|
31
|
-
expect { subject }.to
|
|
31
|
+
expect { subject }.to start_user_experience('rails_request')
|
|
32
32
|
|
|
33
33
|
# Test that it does NOT start
|
|
34
|
-
expect { subject }.not_to
|
|
34
|
+
expect { subject }.not_to start_user_experience('rails_request')
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
#### `
|
|
37
|
+
#### `checkpoint_user_experience`
|
|
38
38
|
|
|
39
|
-
Tests that a
|
|
39
|
+
Tests that a user experience checkpoint is recorded (checkpoint=intermediate metric is incremented).
|
|
40
40
|
|
|
41
41
|
```ruby
|
|
42
|
-
expect { subject }.to
|
|
42
|
+
expect { subject }.to checkpoint_user_experience('rails_request')
|
|
43
43
|
|
|
44
44
|
# Test that it does NOT checkpoint
|
|
45
|
-
expect { subject }.not_to
|
|
45
|
+
expect { subject }.not_to checkpoint_user_experience('rails_request')
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
#### `
|
|
48
|
+
#### `resume_user_experience`
|
|
49
49
|
|
|
50
|
-
Tests that a
|
|
50
|
+
Tests that a user experience is resumed (checkpoint=intermediate metric is incremented). This is an alias for `checkpoint_user_experience` that provides more semantic meaning when testing code that resumes a user experience previously started.
|
|
51
51
|
|
|
52
52
|
```ruby
|
|
53
|
-
expect { subject }.to
|
|
53
|
+
expect { subject }.to resume_user_experience('rails_request')
|
|
54
54
|
|
|
55
55
|
# Test that it does NOT resume
|
|
56
|
-
expect { subject }.not_to
|
|
56
|
+
expect { subject }.not_to resume_user_experience('rails_request')
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
#### `
|
|
59
|
+
#### `complete_user_experience`
|
|
60
60
|
|
|
61
|
-
Tests that a
|
|
62
|
-
- `
|
|
63
|
-
- `
|
|
64
|
-
- `
|
|
61
|
+
Tests that a user experience is completed with the expected metrics:
|
|
62
|
+
- `gitlab_user_experience_checkpoint_total` (with checkpoint=end)
|
|
63
|
+
- `gitlab_user_experience_total` (with error flag)
|
|
64
|
+
- `gitlab_user_experience_apdex_total` (with success flag)
|
|
65
65
|
|
|
66
66
|
```ruby
|
|
67
67
|
# Test successful completion
|
|
68
|
-
expect { subject }.to
|
|
68
|
+
expect { subject }.to complete_user_experience('rails_request')
|
|
69
69
|
|
|
70
70
|
# Test completion with error
|
|
71
|
-
expect { subject }.to
|
|
71
|
+
expect { subject }.to complete_user_experience('rails_request', error: true, success: false)
|
|
72
72
|
|
|
73
73
|
# Test that it does NOT complete
|
|
74
|
-
expect { subject }.not_to
|
|
74
|
+
expect { subject }.not_to complete_user_experience('rails_request')
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
### Legacy Matchers (Backward Compatibility)
|
|
78
|
+
|
|
79
|
+
For backward compatibility, the following legacy matchers are still available but deprecated. Please migrate to the new `*_user_experience` matchers above.
|
|
80
|
+
|
|
81
|
+
- `start_covered_experience`
|
|
82
|
+
- `checkpoint_covered_experience`
|
|
83
|
+
- `resume_covered_experience`
|
|
84
|
+
- `complete_covered_experience`
|
|
85
|
+
|
|
77
86
|
## Example Usage
|
|
78
87
|
|
|
79
88
|
### In your spec_helper.rb or rails_helper.rb:
|
|
@@ -96,8 +105,8 @@ end
|
|
|
96
105
|
RSpec.describe MyController, type: :controller do
|
|
97
106
|
describe '#index' do
|
|
98
107
|
it 'instruments the request properly' do
|
|
99
|
-
expect { get :index }.to
|
|
100
|
-
.and
|
|
108
|
+
expect { get :index }.to start_user_experience('rails_request')
|
|
109
|
+
.and complete_user_experience('rails_request')
|
|
101
110
|
end
|
|
102
111
|
|
|
103
112
|
context 'when an error occurs' do
|
|
@@ -106,7 +115,7 @@ RSpec.describe MyController, type: :controller do
|
|
|
106
115
|
end
|
|
107
116
|
|
|
108
117
|
it 'records the error in metrics' do
|
|
109
|
-
expect { get :index }.to
|
|
118
|
+
expect { get :index }.to complete_user_experience('rails_request', error: true, success: false)
|
|
110
119
|
end
|
|
111
120
|
end
|
|
112
121
|
end
|
|
@@ -127,6 +136,6 @@ end
|
|
|
127
136
|
|
|
128
137
|
## Requirements
|
|
129
138
|
|
|
130
|
-
- The
|
|
139
|
+
- The user experience SLI must be registered in `Labkit::UserExperienceSli::Registry`
|
|
131
140
|
- Metrics must be properly configured in your test environment
|
|
132
|
-
- The code under test must use Labkit's
|
|
141
|
+
- The code under test must use Labkit's user experience SLI instrumentation
|
data/lib/labkit/rspec/matchers/{covered_experience_matchers.rb → user_experience_matchers.rb}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# RSpec matchers for testing Labkit
|
|
3
|
+
# RSpec matchers for testing Labkit UserExperience functionality
|
|
4
4
|
#
|
|
5
5
|
# This file must be explicitly required in your test setup:
|
|
6
6
|
# require 'labkit/rspec/matchers'
|
|
@@ -10,49 +10,49 @@ raise LoadError, "RSpec is not loaded. Please require 'rspec' before requiring '
|
|
|
10
10
|
module Labkit
|
|
11
11
|
module RSpec
|
|
12
12
|
module Matchers
|
|
13
|
-
# Helper module for
|
|
14
|
-
module
|
|
15
|
-
def attributes(
|
|
16
|
-
raise ArgumentError, "
|
|
13
|
+
# Helper module for UserExperience functionality
|
|
14
|
+
module UserExperience
|
|
15
|
+
def attributes(user_experience_id)
|
|
16
|
+
raise ArgumentError, "user_experience_id is required" if user_experience_id.nil?
|
|
17
17
|
|
|
18
|
-
definition = Labkit::
|
|
19
|
-
definition.to_h.slice(:
|
|
18
|
+
definition = Labkit::UserExperienceSli::Registry.new[user_experience_id]
|
|
19
|
+
definition.to_h.slice(:user_experience_id, :feature_category, :urgency)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def checkpoint_counter
|
|
23
|
-
Labkit::Metrics::Client.get(:
|
|
23
|
+
Labkit::Metrics::Client.get(:gitlab_user_experience_checkpoint_total)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
def total_counter
|
|
27
|
-
Labkit::Metrics::Client.get(:
|
|
27
|
+
Labkit::Metrics::Client.get(:gitlab_user_experience_total)
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def apdex_counter
|
|
31
|
-
Labkit::Metrics::Client.get(:
|
|
31
|
+
Labkit::Metrics::Client.get(:gitlab_user_experience_apdex_total)
|
|
32
32
|
end
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
# Matcher for verifying
|
|
38
|
+
# Matcher for verifying UserExperience start metrics instrumentation.
|
|
39
39
|
#
|
|
40
40
|
# Usage:
|
|
41
|
-
# expect { subject }.to
|
|
41
|
+
# expect { subject }.to start_user_experience('rails_request')
|
|
42
42
|
#
|
|
43
43
|
# This matcher verifies that the following metric is incremented:
|
|
44
|
-
# -
|
|
44
|
+
# - gitlab_user_experience_checkpoint_total (with checkpoint=start)
|
|
45
45
|
#
|
|
46
46
|
# Parameters:
|
|
47
|
-
# -
|
|
48
|
-
RSpec::Matchers.define :
|
|
49
|
-
include Labkit::RSpec::Matchers::
|
|
47
|
+
# - user_experience_id: Required. The ID of the user experience (e.g., 'rails_request')
|
|
48
|
+
RSpec::Matchers.define :start_user_experience do |user_experience_id|
|
|
49
|
+
include Labkit::RSpec::Matchers::UserExperience
|
|
50
50
|
|
|
51
|
-
description { "start
|
|
51
|
+
description { "start user experience '#{user_experience_id}'" }
|
|
52
52
|
supports_block_expectations
|
|
53
53
|
|
|
54
54
|
match do |actual|
|
|
55
|
-
labels = attributes(
|
|
55
|
+
labels = attributes(user_experience_id)
|
|
56
56
|
|
|
57
57
|
checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i
|
|
58
58
|
|
|
@@ -66,29 +66,29 @@ RSpec::Matchers.define :start_covered_experience do |covered_experience_id|
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
failure_message do
|
|
69
|
-
"Failed to checkpoint
|
|
69
|
+
"Failed to checkpoint user experience '#{user_experience_id}':\n" \
|
|
70
70
|
"expected checkpoint='start' counter to increase by 1, but increased by #{@checkpoint_change}"
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
-
# Matcher for verifying
|
|
74
|
+
# Matcher for verifying UserExperience checkpoint metrics instrumentation.
|
|
75
75
|
#
|
|
76
76
|
# Usage:
|
|
77
|
-
# expect { subject }.to
|
|
77
|
+
# expect { subject }.to checkpoint_user_experience('rails_request')
|
|
78
78
|
#
|
|
79
79
|
# This matcher verifies that the following metric is incremented:
|
|
80
|
-
# -
|
|
80
|
+
# - gitlab_user_experience_checkpoint_total (with checkpoint=intermediate)
|
|
81
81
|
#
|
|
82
82
|
# Parameters:
|
|
83
|
-
# -
|
|
84
|
-
RSpec::Matchers.define :
|
|
85
|
-
include Labkit::RSpec::Matchers::
|
|
83
|
+
# - user_experience_id: Required. The ID of the user experience (e.g., 'rails_request')
|
|
84
|
+
RSpec::Matchers.define :checkpoint_user_experience do |user_experience_id, by: 1|
|
|
85
|
+
include Labkit::RSpec::Matchers::UserExperience
|
|
86
86
|
|
|
87
|
-
description { "checkpoint
|
|
87
|
+
description { "checkpoint user experience '#{user_experience_id}'" }
|
|
88
88
|
supports_block_expectations
|
|
89
89
|
|
|
90
90
|
match do |actual|
|
|
91
|
-
labels = attributes(
|
|
91
|
+
labels = attributes(user_experience_id)
|
|
92
92
|
|
|
93
93
|
checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i
|
|
94
94
|
|
|
@@ -104,12 +104,12 @@ RSpec::Matchers.define :checkpoint_covered_experience do |covered_experience_id,
|
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
failure_message do
|
|
107
|
-
"Failed to checkpoint
|
|
107
|
+
"Failed to checkpoint user experience '#{user_experience_id}':\n" \
|
|
108
108
|
"expected checkpoint='intermediate' counter to increase by at least #{by}, but increased by #{@checkpoint_change}"
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
match_when_negated do |actual|
|
|
112
|
-
labels = attributes(
|
|
112
|
+
labels = attributes(user_experience_id)
|
|
113
113
|
|
|
114
114
|
checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i
|
|
115
115
|
|
|
@@ -122,36 +122,36 @@ RSpec::Matchers.define :checkpoint_covered_experience do |covered_experience_id,
|
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
failure_message_when_negated do
|
|
125
|
-
"Expected
|
|
125
|
+
"Expected user experience '#{user_experience_id}' NOT to checkpoint:\n" \
|
|
126
126
|
"expected checkpoint='intermediate' counter not to increase, but increased by #{@checkpoint_change}"
|
|
127
127
|
end
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
-
# Alias for
|
|
131
|
-
RSpec::Matchers.alias_matcher :
|
|
130
|
+
# Alias for checkpoint_user_experience matcher
|
|
131
|
+
RSpec::Matchers.alias_matcher :resume_user_experience, :checkpoint_user_experience
|
|
132
132
|
|
|
133
|
-
# Matcher for verifying
|
|
133
|
+
# Matcher for verifying UserExperience completion metrics instrumentation.
|
|
134
134
|
#
|
|
135
135
|
# Usage:
|
|
136
|
-
# expect { subject }.to
|
|
136
|
+
# expect { subject }.to complete_user_experience('rails_request')
|
|
137
137
|
#
|
|
138
138
|
# This matcher verifies that the following metrics are incremented with specific labels:
|
|
139
|
-
# -
|
|
140
|
-
# -
|
|
141
|
-
# -
|
|
139
|
+
# - gitlab_user_experience_checkpoint_total (with checkpoint=end)
|
|
140
|
+
# - gitlab_user_experience_total (with error=false)
|
|
141
|
+
# - gitlab_user_experience_apdex_total (with success=true)
|
|
142
142
|
#
|
|
143
143
|
# Parameters:
|
|
144
|
-
# -
|
|
145
|
-
# - error: Optional. The expected error flag for
|
|
146
|
-
# - success: Optional. The expected success flag for
|
|
147
|
-
RSpec::Matchers.define :
|
|
148
|
-
include Labkit::RSpec::Matchers::
|
|
144
|
+
# - user_experience_id: Required. The ID of the user experience (e.g., 'rails_request')
|
|
145
|
+
# - error: Optional. The expected error flag for gitlab_user_experience_total (false by default)
|
|
146
|
+
# - success: Optional. The expected success flag for gitlab_user_experience_apdex_total (true by default)
|
|
147
|
+
RSpec::Matchers.define :complete_user_experience do |user_experience_id, error: false, success: true|
|
|
148
|
+
include Labkit::RSpec::Matchers::UserExperience
|
|
149
149
|
|
|
150
|
-
description { "complete
|
|
150
|
+
description { "complete user experience '#{user_experience_id}'" }
|
|
151
151
|
supports_block_expectations
|
|
152
152
|
|
|
153
153
|
match do |actual|
|
|
154
|
-
labels = attributes(
|
|
154
|
+
labels = attributes(user_experience_id)
|
|
155
155
|
|
|
156
156
|
checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
|
|
157
157
|
total_before = total_counter&.get(labels.merge(error: error)).to_i
|
|
@@ -170,14 +170,14 @@ RSpec::Matchers.define :complete_covered_experience do |covered_experience_id, e
|
|
|
170
170
|
end
|
|
171
171
|
|
|
172
172
|
failure_message do
|
|
173
|
-
"Failed to complete
|
|
173
|
+
"Failed to complete user experience '#{user_experience_id}':\n" \
|
|
174
174
|
"expected checkpoint='end' counter to increase by 1, but increased by #{@checkpoint_change}\n" \
|
|
175
175
|
"expected total='error: #{error}' counter to increase by 1, but increased by #{@total_change}\n" \
|
|
176
176
|
"expected apdex='success: #{success}' counter to increase by 1, but increased by #{@apdex_change}"
|
|
177
177
|
end
|
|
178
178
|
|
|
179
179
|
match_when_negated do |actual|
|
|
180
|
-
labels = attributes(
|
|
180
|
+
labels = attributes(user_experience_id)
|
|
181
181
|
|
|
182
182
|
checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
|
|
183
183
|
total_before = total_counter&.get(labels.merge(error: error)).to_i
|
|
@@ -196,9 +196,15 @@ RSpec::Matchers.define :complete_covered_experience do |covered_experience_id, e
|
|
|
196
196
|
end
|
|
197
197
|
|
|
198
198
|
failure_message_when_negated do
|
|
199
|
-
"Failed
|
|
199
|
+
"Failed user experience '#{user_experience_id}' NOT to complete:\n" \
|
|
200
200
|
"expected checkpoint='end' counter to increase by 0, but increased by #{@checkpoint_change}\n" \
|
|
201
201
|
"expected total='error: #{error}' counter to increase by 0, but increased by #{@total_change}\n" \
|
|
202
202
|
"expected apdex='success: #{success}' counter to increase by 0, but increased by #{@apdex_change}"
|
|
203
203
|
end
|
|
204
204
|
end
|
|
205
|
+
|
|
206
|
+
# Backward compatibility matchers for CoveredExperience
|
|
207
|
+
RSpec::Matchers.alias_matcher :start_covered_experience, :start_user_experience
|
|
208
|
+
RSpec::Matchers.alias_matcher :checkpoint_covered_experience, :checkpoint_user_experience
|
|
209
|
+
RSpec::Matchers.alias_matcher :resume_covered_experience, :checkpoint_user_experience
|
|
210
|
+
RSpec::Matchers.alias_matcher :complete_covered_experience, :complete_user_experience
|
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
#
|
|
1
|
+
# User Experience SLIs
|
|
2
2
|
|
|
3
|
-
This module covers the definition for
|
|
3
|
+
This module covers the definition for User Experience SLIs, as described in the [blueprint](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/user_experience_slis/#user-experience-sli-definition).
|
|
4
4
|
|
|
5
5
|
## Configuration
|
|
6
6
|
|
|
7
7
|
### Logger Configuration
|
|
8
8
|
|
|
9
|
-
By default, `Labkit::
|
|
9
|
+
By default, `Labkit::UserExperienceSli` uses `Labkit::Logging::JsonLogger.new($stdout)` for logging. You can configure a custom logger:
|
|
10
10
|
|
|
11
11
|
```ruby
|
|
12
|
-
Labkit::
|
|
12
|
+
Labkit::UserExperienceSli.configure do |config|
|
|
13
13
|
config.logger = Labkit::Logging::JsonLogger.new($stdout)
|
|
14
14
|
end
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
This configuration affects all
|
|
17
|
+
This configuration affects all User Experience SLI instances and their logging output.
|
|
18
18
|
|
|
19
19
|
### Registry Path Configuration
|
|
20
20
|
|
|
21
|
-
By default,
|
|
21
|
+
By default, user experience SLI definitions are loaded from the `config/user_experience_slis` directory. You can configure a custom registry path:
|
|
22
22
|
|
|
23
23
|
```ruby
|
|
24
|
-
Labkit::
|
|
24
|
+
Labkit::UserExperienceSli.configure do |config|
|
|
25
25
|
config.registry_path = "my/custom/path"
|
|
26
26
|
end
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
This allows you to:
|
|
30
|
-
- Store
|
|
30
|
+
- Store user experience SLI definitions in a different directory structure
|
|
31
31
|
- Use different paths for different environments
|
|
32
32
|
- Organize definitions according to your application's needs
|
|
33
33
|
|
|
34
34
|
**Note:** The registry is automatically reset when the configuration changes, so the new path takes effect immediately.
|
|
35
35
|
|
|
36
|
-
###
|
|
36
|
+
### User Experience Definitions
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
User Experience SLI definitions will be lazy loaded from the default directory (`config/user_experience_slis`).
|
|
39
39
|
|
|
40
|
-
Create a new
|
|
40
|
+
Create a new user experience SLI file in the registry directory, e.g. config/user_experience_slis/merge_request_creation.yaml
|
|
41
41
|
|
|
42
|
-
The basename of the file will be taken as the
|
|
42
|
+
The basename of the file will be taken as the user_experience_id.
|
|
43
43
|
|
|
44
44
|
The schema header is optional, but if you're using VSCode (or any other editor with support), you can get them validated
|
|
45
45
|
instantaneously in the editor via a [JSON schema plugin](https://marketplace.visualstudio.com/items?itemName=remcohaszing.schemastore).
|
|
46
46
|
|
|
47
47
|
```yaml
|
|
48
|
-
# yaml-language-server: $schema=https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby/-/raw/master/config/
|
|
48
|
+
# yaml-language-server: $schema=https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby/-/raw/master/config/user_experience_slis/schema.json
|
|
49
49
|
description: "Creating a new merge request in a project"
|
|
50
50
|
feature_category: "source_code_management"
|
|
51
51
|
urgency: "sync_fast"
|
|
@@ -66,22 +66,22 @@ https://docs.gitlab.com/development/feature_categorization/#feature-categorizati
|
|
|
66
66
|
|
|
67
67
|
## Usage
|
|
68
68
|
|
|
69
|
-
The `Labkit::
|
|
69
|
+
The `Labkit::UserExperienceSli` module provides a simple API for measuring and tracking user experience SLIs in your application.
|
|
70
70
|
|
|
71
71
|
|
|
72
|
-
#### Accessing a
|
|
72
|
+
#### Accessing a User Experience SLI
|
|
73
73
|
|
|
74
74
|
```ruby
|
|
75
|
-
# Get a
|
|
76
|
-
experience = Labkit::
|
|
75
|
+
# Get a user experience SLI by ID
|
|
76
|
+
experience = Labkit::UserExperienceSli.get('merge_request_creation')
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
#### Using with a Block (Recommended)
|
|
80
80
|
|
|
81
|
-
The simplest way to use
|
|
81
|
+
The simplest way to use user experience SLIs is with a block, which automatically handles starting and completing the experience:
|
|
82
82
|
|
|
83
83
|
```ruby
|
|
84
|
-
Labkit::
|
|
84
|
+
Labkit::UserExperienceSli.start('merge_request_creation') do |experience|
|
|
85
85
|
# Your code here
|
|
86
86
|
create_merge_request
|
|
87
87
|
|
|
@@ -100,7 +100,7 @@ end
|
|
|
100
100
|
For more control, you can manually start, checkpoint, and complete experiences:
|
|
101
101
|
|
|
102
102
|
```ruby
|
|
103
|
-
experience = Labkit::
|
|
103
|
+
experience = Labkit::UserExperienceSli.get('merge_request_creation')
|
|
104
104
|
experience.start
|
|
105
105
|
|
|
106
106
|
# Perform some work
|
|
@@ -119,13 +119,13 @@ experience.complete
|
|
|
119
119
|
|
|
120
120
|
#### Resuming Experiences
|
|
121
121
|
|
|
122
|
-
You can resume a
|
|
122
|
+
You can resume a user experience SLI that was previously started and stored in the context. This is useful for distributed operations or when work spans multiple processes.
|
|
123
123
|
|
|
124
|
-
Just like the start method, we can use a block to automatically complete a
|
|
124
|
+
Just like the start method, we can use a block to automatically complete a user experience SLI:
|
|
125
125
|
|
|
126
126
|
```ruby
|
|
127
127
|
# Resume an experience from context (with block)
|
|
128
|
-
Labkit::
|
|
128
|
+
Labkit::UserExperienceSli.resume(:merge_request_creation) do |experience|
|
|
129
129
|
# Continue the work from where it left off
|
|
130
130
|
finalize_merge_request
|
|
131
131
|
|
|
@@ -139,7 +139,7 @@ Or manually:
|
|
|
139
139
|
|
|
140
140
|
```ruby
|
|
141
141
|
# Resume an experience from context (manual control)
|
|
142
|
-
experience = Labkit::
|
|
142
|
+
experience = Labkit::UserExperienceSli.resume(:merge_request_creation)
|
|
143
143
|
|
|
144
144
|
# Continue the work
|
|
145
145
|
finalize_merge_request
|
|
@@ -149,14 +149,14 @@ experience.checkpoint
|
|
|
149
149
|
experience.complete
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
-
**Note:** The `resume` method loads the start time from the Labkit context. If no
|
|
152
|
+
**Note:** The `resume` method loads the start time from the Labkit context. If no user experience SLI data exists in the context, it behaves the same as calling methods on an unstarted experience and safely ignores the operation.
|
|
153
153
|
|
|
154
154
|
### Error Handling
|
|
155
155
|
|
|
156
156
|
When using the block form, errors are automatically captured:
|
|
157
157
|
|
|
158
158
|
```ruby
|
|
159
|
-
Labkit::
|
|
159
|
+
Labkit::UserExperienceSli.start('merge_request_creation') do |experience|
|
|
160
160
|
# If this raises an exception, it will be captured automatically
|
|
161
161
|
risky_operation
|
|
162
162
|
end
|
|
@@ -165,7 +165,7 @@ end
|
|
|
165
165
|
For manual control, you can mark errors explicitly:
|
|
166
166
|
|
|
167
167
|
```ruby
|
|
168
|
-
experience = Labkit::
|
|
168
|
+
experience = Labkit::UserExperienceSli.get('merge_request_creation')
|
|
169
169
|
experience.start
|
|
170
170
|
|
|
171
171
|
begin
|
|
@@ -180,6 +180,6 @@ end
|
|
|
180
180
|
|
|
181
181
|
### Error Behavior
|
|
182
182
|
|
|
183
|
-
- In `development` and `test` environments, accessing a non-existent
|
|
183
|
+
- In `development` and `test` environments, accessing a non-existent user experience SLI will raise a `NotFoundError`
|
|
184
184
|
- In other environments, a null object is returned that safely ignores all method calls
|
|
185
185
|
- Attempting to checkpoint or complete an unstarted experience will safely ignore the operation
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "active_support"
|
|
4
|
-
require "labkit/
|
|
4
|
+
require "labkit/user_experience_sli/experience"
|
|
5
5
|
|
|
6
6
|
module Labkit
|
|
7
|
-
module
|
|
7
|
+
module UserExperienceSli
|
|
8
8
|
# The `Current` class represents a container for the current set
|
|
9
|
-
# of `Labkit::
|
|
9
|
+
# of `Labkit::UserExperienceSli::Experience` instances started and
|
|
10
10
|
# not yet completed.
|
|
11
11
|
#
|
|
12
12
|
# It uses `ActiveSupport::CurrentAttributes` to provide a thread-safe way to
|
|
13
13
|
# store and access experiences throughout the request and background job lifecycle.
|
|
14
14
|
#
|
|
15
15
|
# Example usage:
|
|
16
|
-
# Labkit::
|
|
17
|
-
# Labkit::
|
|
16
|
+
# Labkit::UserExperienceSli::Current.active_experiences << my_experience
|
|
17
|
+
# Labkit::UserExperienceSli::Current.rehydrate("create_merge_request", "start_time" => "2025-08-22T10:02:15.237Z")
|
|
18
18
|
class Current < ActiveSupport::CurrentAttributes
|
|
19
|
-
AGGREGATION_KEY = '
|
|
19
|
+
AGGREGATION_KEY = 'labkit_user_experiences'
|
|
20
20
|
|
|
21
21
|
attribute :_active_experiences
|
|
22
22
|
|
|
@@ -25,7 +25,7 @@ module Labkit
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def rehydrate(experience_id, **data)
|
|
28
|
-
instance = Labkit::
|
|
28
|
+
instance = Labkit::UserExperienceSli.get(experience_id).rehydrate(data)
|
|
29
29
|
active_experiences[instance.id] = instance
|
|
30
30
|
instance
|
|
31
31
|
end
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
require 'active_support/hash_with_indifferent_access'
|
|
4
4
|
require 'forwardable'
|
|
5
5
|
require 'labkit/context'
|
|
6
|
-
require 'labkit/
|
|
7
|
-
require 'labkit/
|
|
6
|
+
require 'labkit/user_experience_sli/current'
|
|
7
|
+
require 'labkit/user_experience_sli/error'
|
|
8
8
|
|
|
9
9
|
module Labkit
|
|
10
|
-
module
|
|
10
|
+
module UserExperienceSli
|
|
11
11
|
URGENCY_THRESHOLDS_IN_SECONDS = {
|
|
12
12
|
sync_fast: 2,
|
|
13
13
|
sync_slow: 5,
|
|
@@ -17,7 +17,7 @@ module Labkit
|
|
|
17
17
|
|
|
18
18
|
RESERVED_KEYWORDS = %w[
|
|
19
19
|
checkpoint
|
|
20
|
-
|
|
20
|
+
user_experience_id
|
|
21
21
|
feature_category
|
|
22
22
|
urgency
|
|
23
23
|
start_time
|
|
@@ -30,7 +30,7 @@ module Labkit
|
|
|
30
30
|
success
|
|
31
31
|
].freeze
|
|
32
32
|
|
|
33
|
-
# The `Experience` class represents a single
|
|
33
|
+
# The `Experience` class represents a single User Experience
|
|
34
34
|
# event to be measured and reported.
|
|
35
35
|
class Experience
|
|
36
36
|
extend Forwardable
|
|
@@ -42,7 +42,7 @@ module Labkit
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
def id
|
|
45
|
-
@definition.
|
|
45
|
+
@definition.user_experience_id
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
# Rehydrate an Experience instance from serialized data.
|
|
@@ -57,21 +57,21 @@ module Labkit
|
|
|
57
57
|
self
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
# Start the
|
|
60
|
+
# Start the User Experience.
|
|
61
61
|
#
|
|
62
62
|
# @yield [self] When a block is provided, the experience will be completed automatically.
|
|
63
63
|
# @param extra [Hash] Additional data to include in the log event
|
|
64
64
|
# @return [self]
|
|
65
|
-
# @raise [
|
|
65
|
+
# @raise [UserExperienceError] If the block raises an error.
|
|
66
66
|
#
|
|
67
67
|
# Usage:
|
|
68
68
|
#
|
|
69
|
-
#
|
|
69
|
+
# UserExperience.new(definition).start do |experience|
|
|
70
70
|
# experience.checkpoint
|
|
71
71
|
# experience.checkpoint
|
|
72
72
|
# end
|
|
73
73
|
#
|
|
74
|
-
# experience =
|
|
74
|
+
# experience = UserExperience.new(definition)
|
|
75
75
|
# experience.start
|
|
76
76
|
# experience.checkpoint
|
|
77
77
|
# experience.complete
|
|
@@ -80,14 +80,14 @@ module Labkit
|
|
|
80
80
|
checkpoint_counter.increment(checkpoint: "start", **base_labels)
|
|
81
81
|
log_event("start", **extra)
|
|
82
82
|
|
|
83
|
-
Labkit::
|
|
83
|
+
Labkit::UserExperienceSli::Current.active_experiences[id] = self
|
|
84
84
|
|
|
85
85
|
return self unless block_given?
|
|
86
86
|
|
|
87
87
|
completable(**extra, &)
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
# Checkpoint the
|
|
90
|
+
# Checkpoint the User Experience.
|
|
91
91
|
#
|
|
92
92
|
# @param extra [Hash] Additional data to include in the log event
|
|
93
93
|
# @return [self]
|
|
@@ -101,7 +101,7 @@ module Labkit
|
|
|
101
101
|
self
|
|
102
102
|
end
|
|
103
103
|
|
|
104
|
-
# Resume the
|
|
104
|
+
# Resume the User Experience.
|
|
105
105
|
#
|
|
106
106
|
# @yield [self] When a block is provided, the experience will be completed automatically.
|
|
107
107
|
# @param extra [Hash] Additional data to include in the log
|
|
@@ -115,7 +115,7 @@ module Labkit
|
|
|
115
115
|
completable(**extra, &)
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
# Complete the
|
|
118
|
+
# Complete the User Experience.
|
|
119
119
|
#
|
|
120
120
|
# @param extra [Hash] Additional data to include in the log event
|
|
121
121
|
# @return [self]
|
|
@@ -129,7 +129,7 @@ module Labkit
|
|
|
129
129
|
total_counter.increment(error: has_error?, **base_labels)
|
|
130
130
|
apdex_counter.increment(success: apdex_success?, **base_labels) unless has_error?
|
|
131
131
|
log_event("end", **extra)
|
|
132
|
-
Labkit::
|
|
132
|
+
Labkit::UserExperienceSli::Current.active_experiences.delete(id)
|
|
133
133
|
end
|
|
134
134
|
|
|
135
135
|
self
|
|
@@ -157,13 +157,13 @@ module Labkit
|
|
|
157
157
|
private
|
|
158
158
|
|
|
159
159
|
def base_labels
|
|
160
|
-
@base_labels ||= @definition.to_h.slice(:
|
|
160
|
+
@base_labels ||= @definition.to_h.slice(:user_experience_id, :feature_category, :urgency)
|
|
161
161
|
end
|
|
162
162
|
|
|
163
163
|
def ensure_started!
|
|
164
164
|
return @start_time unless @start_time.nil?
|
|
165
165
|
|
|
166
|
-
warn("
|
|
166
|
+
warn("User Experience #{@definition.user_experience_id} not started")
|
|
167
167
|
false
|
|
168
168
|
end
|
|
169
169
|
|
|
@@ -183,7 +183,7 @@ module Labkit
|
|
|
183
183
|
def ensure_incomplete!
|
|
184
184
|
return true if @end_time.nil?
|
|
185
185
|
|
|
186
|
-
warn("
|
|
186
|
+
warn("User Experience #{@definition.user_experience_id} already completed")
|
|
187
187
|
false
|
|
188
188
|
end
|
|
189
189
|
|
|
@@ -204,22 +204,22 @@ module Labkit
|
|
|
204
204
|
|
|
205
205
|
def checkpoint_counter
|
|
206
206
|
@checkpoint_counter ||= Labkit::Metrics::Client.counter(
|
|
207
|
-
:
|
|
208
|
-
'Total checkpoints for
|
|
207
|
+
:gitlab_user_experience_checkpoint_total,
|
|
208
|
+
'Total checkpoints for user experiences'
|
|
209
209
|
)
|
|
210
210
|
end
|
|
211
211
|
|
|
212
212
|
def total_counter
|
|
213
213
|
@total_counter ||= Labkit::Metrics::Client.counter(
|
|
214
|
-
:
|
|
215
|
-
'Total
|
|
214
|
+
:gitlab_user_experience_total,
|
|
215
|
+
'Total user experience events (success/failure)'
|
|
216
216
|
)
|
|
217
217
|
end
|
|
218
218
|
|
|
219
219
|
def apdex_counter
|
|
220
220
|
@apdex_counter ||= Labkit::Metrics::Client.counter(
|
|
221
|
-
:
|
|
222
|
-
'Total
|
|
221
|
+
:gitlab_user_experience_apdex_total,
|
|
222
|
+
'Total user experience apdex events'
|
|
223
223
|
)
|
|
224
224
|
end
|
|
225
225
|
|
|
@@ -233,7 +233,7 @@ module Labkit
|
|
|
233
233
|
def build_log_data(event_type, **extra)
|
|
234
234
|
log_data = ActiveSupport::HashWithIndifferentAccess.new(
|
|
235
235
|
checkpoint: event_type,
|
|
236
|
-
|
|
236
|
+
user_experience_id: id,
|
|
237
237
|
feature_category: @definition.feature_category,
|
|
238
238
|
urgency: @definition.urgency,
|
|
239
239
|
start_time: @start_time,
|
|
@@ -275,7 +275,7 @@ module Labkit
|
|
|
275
275
|
end
|
|
276
276
|
|
|
277
277
|
def logger
|
|
278
|
-
Labkit::
|
|
278
|
+
Labkit::UserExperienceSli.configuration.logger
|
|
279
279
|
end
|
|
280
280
|
end
|
|
281
281
|
end
|
|
@@ -6,29 +6,29 @@ require 'pathname'
|
|
|
6
6
|
require 'yaml'
|
|
7
7
|
|
|
8
8
|
module Labkit
|
|
9
|
-
module
|
|
10
|
-
Definition = Data.define(:
|
|
9
|
+
module UserExperienceSli
|
|
10
|
+
Definition = Data.define(:user_experience_id, :description, :feature_category, :urgency)
|
|
11
11
|
|
|
12
12
|
class Registry
|
|
13
13
|
extend Forwardable
|
|
14
14
|
|
|
15
|
-
SCHEMA_PATH = File.expand_path('../../../config/
|
|
15
|
+
SCHEMA_PATH = File.expand_path('../../../config/user_experience_slis/schema.json', __dir__)
|
|
16
16
|
|
|
17
17
|
def_delegator :@experiences, :empty?
|
|
18
18
|
|
|
19
19
|
# @param dir [String, Pathname] Directory path containing YAML file definitions
|
|
20
|
-
# Defaults to 'config/
|
|
21
|
-
def initialize(dir: File.join("config", "
|
|
20
|
+
# Defaults to 'config/user_experience_slis' relative to the calling application's root
|
|
21
|
+
def initialize(dir: File.join("config", "user_experience_slis"))
|
|
22
22
|
@dir = Pathname.new(Dir.pwd).join(dir)
|
|
23
23
|
@experiences = load_on_demand
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
# Retrieve a definition experience given a
|
|
26
|
+
# Retrieve a definition experience given a user_experience_id.
|
|
27
27
|
#
|
|
28
|
-
# @param
|
|
28
|
+
# @param user_experience_id [String, Symbol] User experience SLI identifier
|
|
29
29
|
# @return [Experience, nil] An experience if present, otherwise nil
|
|
30
|
-
def [](
|
|
31
|
-
@experiences[
|
|
30
|
+
def [](user_experience_id)
|
|
31
|
+
@experiences[user_experience_id.to_s]
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
private
|
|
@@ -49,7 +49,7 @@ module Labkit
|
|
|
49
49
|
end
|
|
50
50
|
end
|
|
51
51
|
|
|
52
|
-
# Load a
|
|
52
|
+
# Load a user experience SLI definition.
|
|
53
53
|
#
|
|
54
54
|
# @param experience_id [String] Experience identifier
|
|
55
55
|
# @return [Experience, nil] Loaded experience or nil if not found/invalid
|
|
@@ -57,7 +57,7 @@ module Labkit
|
|
|
57
57
|
file_path = @dir.join("#{experience_id}.yml")
|
|
58
58
|
|
|
59
59
|
unless file_path.exist?
|
|
60
|
-
warn("Invalid
|
|
60
|
+
warn("Invalid User Experience definition: #{experience_id}")
|
|
61
61
|
return nil
|
|
62
62
|
end
|
|
63
63
|
|
|
@@ -78,7 +78,7 @@ module Labkit
|
|
|
78
78
|
return nil unless content.is_a?(Hash)
|
|
79
79
|
|
|
80
80
|
errors = JSON::Validator.fully_validate(schema, content)
|
|
81
|
-
return Definition.new(
|
|
81
|
+
return Definition.new(user_experience_id: experience_id, **content) if errors.empty?
|
|
82
82
|
|
|
83
83
|
warn("Invalid schema for #{file_path}")
|
|
84
84
|
|
|
@@ -98,7 +98,7 @@ module Labkit
|
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
def logger
|
|
101
|
-
Labkit::
|
|
101
|
+
Labkit::UserExperienceSli.configuration.logger
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
104
|
end
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'labkit/
|
|
4
|
-
require 'labkit/
|
|
5
|
-
require 'labkit/
|
|
6
|
-
require 'labkit/
|
|
7
|
-
require 'labkit/
|
|
3
|
+
require 'labkit/user_experience_sli/current'
|
|
4
|
+
require 'labkit/user_experience_sli/error'
|
|
5
|
+
require 'labkit/user_experience_sli/experience'
|
|
6
|
+
require 'labkit/user_experience_sli/null'
|
|
7
|
+
require 'labkit/user_experience_sli/registry'
|
|
8
8
|
require 'labkit/logging/json_logger'
|
|
9
9
|
|
|
10
10
|
module Labkit
|
|
11
|
-
# Labkit::
|
|
11
|
+
# Labkit::UserExperienceSli namespace module.
|
|
12
12
|
#
|
|
13
|
-
# This module is responsible for managing
|
|
13
|
+
# This module is responsible for managing user experience SLIs, which are
|
|
14
14
|
# specific events or activities within the application that are measured
|
|
15
15
|
# and reported for performance monitoring and analysis.
|
|
16
|
-
module
|
|
17
|
-
# Configuration class for
|
|
16
|
+
module UserExperienceSli
|
|
17
|
+
# Configuration class for UserExperienceSli
|
|
18
18
|
class Configuration
|
|
19
19
|
attr_accessor :logger, :registry_path
|
|
20
20
|
|
|
21
21
|
def initialize
|
|
22
22
|
@logger = Labkit::Logging::JsonLogger.new($stdout)
|
|
23
|
-
@registry_path = File.join("config", "
|
|
23
|
+
@registry_path = File.join("config", "user_experience_slis")
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
@@ -81,7 +81,7 @@ module Labkit
|
|
|
81
81
|
def raise_or_null(experience_id)
|
|
82
82
|
return Null.instance unless %w[development test].include?(ENV['RAILS_ENV'])
|
|
83
83
|
|
|
84
|
-
raise(NotFoundError, "
|
|
84
|
+
raise(NotFoundError, "User Experience #{experience_id} not found in the registry")
|
|
85
85
|
end
|
|
86
86
|
|
|
87
87
|
def find_current(experience_id)
|
|
@@ -93,4 +93,9 @@ module Labkit
|
|
|
93
93
|
end
|
|
94
94
|
end
|
|
95
95
|
end
|
|
96
|
+
|
|
97
|
+
# Backward compatibility alias for the old module name.
|
|
98
|
+
# This alias is kept to maintain compatibility with existing code.
|
|
99
|
+
# See: https://gitlab.com/gitlab-com/gl-infra/observability/team/-/issues/4347
|
|
100
|
+
CoveredExperience = UserExperienceSli
|
|
96
101
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gitlab-labkit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.42.
|
|
4
|
+
version: 0.42.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrew Newdigate
|
|
@@ -447,8 +447,8 @@ files:
|
|
|
447
447
|
- LICENSE
|
|
448
448
|
- README.md
|
|
449
449
|
- Rakefile
|
|
450
|
-
- config/
|
|
451
|
-
- config/
|
|
450
|
+
- config/user_experience_slis/schema.json
|
|
451
|
+
- config/user_experience_slis/testing_sample.yml
|
|
452
452
|
- gitlab-labkit.gemspec
|
|
453
453
|
- lib/gitlab-labkit.rb
|
|
454
454
|
- lib/labkit/context.rb
|
|
@@ -458,13 +458,6 @@ files:
|
|
|
458
458
|
- lib/labkit/correlation/grpc/client_interceptor.rb
|
|
459
459
|
- lib/labkit/correlation/grpc/grpc_common.rb
|
|
460
460
|
- lib/labkit/correlation/grpc/server_interceptor.rb
|
|
461
|
-
- lib/labkit/covered_experience.rb
|
|
462
|
-
- lib/labkit/covered_experience/README.md
|
|
463
|
-
- lib/labkit/covered_experience/current.rb
|
|
464
|
-
- lib/labkit/covered_experience/error.rb
|
|
465
|
-
- lib/labkit/covered_experience/experience.rb
|
|
466
|
-
- lib/labkit/covered_experience/null.rb
|
|
467
|
-
- lib/labkit/covered_experience/registry.rb
|
|
468
461
|
- lib/labkit/excon_publisher.rb
|
|
469
462
|
- lib/labkit/fields.rb
|
|
470
463
|
- lib/labkit/fips.rb
|
|
@@ -487,18 +480,18 @@ files:
|
|
|
487
480
|
- lib/labkit/middleware/sidekiq/context.rb
|
|
488
481
|
- lib/labkit/middleware/sidekiq/context/client.rb
|
|
489
482
|
- lib/labkit/middleware/sidekiq/context/server.rb
|
|
490
|
-
- lib/labkit/middleware/sidekiq/covered_experience.rb
|
|
491
|
-
- lib/labkit/middleware/sidekiq/covered_experience/client.rb
|
|
492
|
-
- lib/labkit/middleware/sidekiq/covered_experience/server.rb
|
|
493
483
|
- lib/labkit/middleware/sidekiq/server.rb
|
|
494
484
|
- lib/labkit/middleware/sidekiq/tracing.rb
|
|
495
485
|
- lib/labkit/middleware/sidekiq/tracing/client.rb
|
|
496
486
|
- lib/labkit/middleware/sidekiq/tracing/server.rb
|
|
497
487
|
- lib/labkit/middleware/sidekiq/tracing/sidekiq_common.rb
|
|
488
|
+
- lib/labkit/middleware/sidekiq/user_experience_sli.rb
|
|
489
|
+
- lib/labkit/middleware/sidekiq/user_experience_sli/client.rb
|
|
490
|
+
- lib/labkit/middleware/sidekiq/user_experience_sli/server.rb
|
|
498
491
|
- lib/labkit/net_http_publisher.rb
|
|
499
492
|
- lib/labkit/rspec/README.md
|
|
500
493
|
- lib/labkit/rspec/matchers.rb
|
|
501
|
-
- lib/labkit/rspec/matchers/
|
|
494
|
+
- lib/labkit/rspec/matchers/user_experience_matchers.rb
|
|
502
495
|
- lib/labkit/system.rb
|
|
503
496
|
- lib/labkit/tracing.rb
|
|
504
497
|
- lib/labkit/tracing/abstract_instrumenter.rb
|
|
@@ -532,6 +525,13 @@ files:
|
|
|
532
525
|
- lib/labkit/tracing/redis/redis_interceptor_helper.rb
|
|
533
526
|
- lib/labkit/tracing/tracing_common.rb
|
|
534
527
|
- lib/labkit/tracing/tracing_utils.rb
|
|
528
|
+
- lib/labkit/user_experience_sli.rb
|
|
529
|
+
- lib/labkit/user_experience_sli/README.md
|
|
530
|
+
- lib/labkit/user_experience_sli/current.rb
|
|
531
|
+
- lib/labkit/user_experience_sli/error.rb
|
|
532
|
+
- lib/labkit/user_experience_sli/experience.rb
|
|
533
|
+
- lib/labkit/user_experience_sli/null.rb
|
|
534
|
+
- lib/labkit/user_experience_sli/registry.rb
|
|
535
535
|
- renovate.json
|
|
536
536
|
- scripts/install-asdf-plugins.sh
|
|
537
537
|
- scripts/prepare-dev-env.sh
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Labkit
|
|
4
|
-
module Middleware
|
|
5
|
-
module Sidekiq
|
|
6
|
-
# This module contains all the sidekiq middleware regarding covered
|
|
7
|
-
# experiences
|
|
8
|
-
module CoveredExperience
|
|
9
|
-
autoload :Client, "labkit/middleware/sidekiq/covered_experience/client"
|
|
10
|
-
autoload :Server, "labkit/middleware/sidekiq/covered_experience/server"
|
|
11
|
-
end
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
|
File without changes
|