gitlab-labkit 1.9.1 → 1.11.0
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/.copier-answers.yml +1 -1
- data/CODEOWNERS +1 -1
- data/lib/labkit/fields.rb +38 -2
- data/lib/labkit/rspec/README.md +0 -9
- data/lib/labkit/rspec/matchers/user_experience_matchers.rb +53 -0
- data/lib/labkit/user_experience_sli/README.md +20 -0
- data/lib/labkit/user_experience_sli/experience.rb +22 -0
- data/lib/labkit/user_experience_sli/null.rb +1 -0
- data/lib/labkit/user_experience_sli.rb +15 -3
- data/scripts/prepare-dev-env.sh +1 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d63e4f781c8e316f19c419a52485bd751cef83e713f7da971e3285709760a669
|
|
4
|
+
data.tar.gz: a13c2d6a3b9f217f0d65c5a755ff213db29b21dad1e20d2cc5ea17a5bea3dda5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5e5983bc819202df660c1965efb7d26dd74f89c7479d01974a3d4f56e5a2c09a4d9fa01cac41719d80048d6737a676cc3fb3d9492cfb1619eb715932cf2ad6ff
|
|
7
|
+
data.tar.gz: 44d1210ee82ef16a99150316f73b76be43942fedaad296b78c925a3e270a8e6b39e4e3cbf43e63488048b11f7d69b40ad3dcfdcc514b8d6bbb4dfe805a28173a
|
data/.copier-answers.yml
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# See the project for instructions on how to update the project
|
|
4
4
|
#
|
|
5
5
|
# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY
|
|
6
|
-
_commit: v1.
|
|
6
|
+
_commit: v1.45.0
|
|
7
7
|
_src_path: https://gitlab.com/gitlab-com/gl-infra/common-template-copier.git
|
|
8
8
|
ee_licensed: false
|
|
9
9
|
golang: false
|
data/CODEOWNERS
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
# CODEOWNERS is used to lookup assignees for
|
|
2
2
|
# Renovate Bot dependency change Merge Requests.
|
|
3
3
|
# https://docs.renovatebot.com/configuration-options/#assigneesfromcodeowners
|
|
4
|
-
* @reprazent @andrewn @mkaeppler @ayufan @hmerscher @d.barrett @splattael
|
|
4
|
+
* @reprazent @andrewn @mkaeppler @ayufan @hmerscher @d.barrett @splattael @e_forbes @M_Alvarez
|
data/lib/labkit/fields.rb
CHANGED
|
@@ -80,6 +80,28 @@ module Labkit
|
|
|
80
80
|
# request receipt to first response byte written.
|
|
81
81
|
TTFB_S = "ttfb_s"
|
|
82
82
|
|
|
83
|
+
# GitLab project numeric ID.
|
|
84
|
+
GL_PROJECT_ID = "gl_project_id"
|
|
85
|
+
|
|
86
|
+
# GitLab pipeline numeric ID.
|
|
87
|
+
GL_PIPELINE_ID = "gl_pipeline_id"
|
|
88
|
+
|
|
89
|
+
# Log event timestamp in ISO 8601 format (e.g. "2026-04-02T12:00:00.000Z").
|
|
90
|
+
TIMESTAMP = "timestamp"
|
|
91
|
+
|
|
92
|
+
# Log severity level (e.g. "info", "warn", "error").
|
|
93
|
+
SEVERITY = "severity"
|
|
94
|
+
|
|
95
|
+
# Human-readable log message describing the event.
|
|
96
|
+
LOG_MESSAGE = "message"
|
|
97
|
+
|
|
98
|
+
# Class name associated with a log event (e.g. exception class or worker
|
|
99
|
+
# class).
|
|
100
|
+
CLASS_NAME = "class_name"
|
|
101
|
+
|
|
102
|
+
# Name of the service or component emitting the log event.
|
|
103
|
+
SERVICE_NAME = "service_name"
|
|
104
|
+
|
|
83
105
|
# Get the constant name for a field value
|
|
84
106
|
# @param field_value [String] The field value (e.g., "gl_user_id")
|
|
85
107
|
# @return [String, nil] The constant name (e.g., "GL_USER_ID") or nil if not found
|
|
@@ -97,8 +119,22 @@ module Labkit
|
|
|
97
119
|
# to identify and track usage of deprecated fields in the codebase.
|
|
98
120
|
|
|
99
121
|
MAPPINGS = {
|
|
100
|
-
Fields::
|
|
101
|
-
Fields::
|
|
122
|
+
Fields::CORRELATION_ID => %w[tags.correlation_id],
|
|
123
|
+
Fields::GL_USER_ID => %w[user_id userid extra.user_id extra.current_user_id meta.user_id],
|
|
124
|
+
Fields::GL_USER_NAME => %w[username extra.user meta.user],
|
|
125
|
+
Fields::ERROR_MESSAGE => %w[error err error.message exception.message graphql_errors],
|
|
126
|
+
Fields::HTTP_STATUS_CODE => %w[status_code extra.status status_text http_status],
|
|
127
|
+
Fields::HTTP_URL => %w[req_url],
|
|
128
|
+
Fields::DURATION_S => %w[duration duration_ms elapsed_time actual_duration time_ms total_time gitaly.duration],
|
|
129
|
+
Fields::REMOTE_IP => %w[ip source_ip ip_address meta.remote_ip],
|
|
130
|
+
Fields::HTTP_HOST => %w[hostname request_host gitlab_host kubernetes.host],
|
|
131
|
+
Fields::GL_PROJECT_ID => %w[extra.project_id meta.project_id meta.search.project_id job_project_id target_project_id],
|
|
132
|
+
Fields::GL_PIPELINE_ID => %w[extra.pipeline_id meta.pipeline_id root_pipeline_id],
|
|
133
|
+
Fields::TIMESTAMP => %w[start_time],
|
|
134
|
+
Fields::SEVERITY => %w[level],
|
|
135
|
+
Fields::LOG_MESSAGE => %w[msg custom_message extra.message fields.message graphql.message reason color_message exception.gitaly],
|
|
136
|
+
Fields::CLASS_NAME => %w[class author_class exception.class extra.class extra.class_name],
|
|
137
|
+
Fields::SERVICE_NAME => %w[service grpc.service_name auth_service type component subcomponent],
|
|
102
138
|
}.freeze
|
|
103
139
|
|
|
104
140
|
class << self
|
data/lib/labkit/rspec/README.md
CHANGED
|
@@ -74,15 +74,6 @@ expect { subject }.to complete_user_experience('rails_request', error: true, suc
|
|
|
74
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
|
-
|
|
86
77
|
## Example Usage
|
|
87
78
|
|
|
88
79
|
### In your spec_helper.rb or rails_helper.rb:
|
|
@@ -203,6 +203,59 @@ RSpec::Matchers.define :complete_user_experience do |user_experience_id, error:
|
|
|
203
203
|
end
|
|
204
204
|
end
|
|
205
205
|
|
|
206
|
+
# Matcher for verifying UserExperience observed metrics instrumentation.
|
|
207
|
+
#
|
|
208
|
+
# Usage:
|
|
209
|
+
# expect { subject }.to observed_user_experience('rails_request')
|
|
210
|
+
#
|
|
211
|
+
# This matcher verifies that the following metrics are incremented with specific labels:
|
|
212
|
+
# - gitlab_user_experience_checkpoint_total (with checkpoint=start)
|
|
213
|
+
# - gitlab_user_experience_checkpoint_total (with checkpoint=end)
|
|
214
|
+
# - gitlab_user_experience_total (with error=false)
|
|
215
|
+
# - gitlab_user_experience_apdex_total (with success=true)
|
|
216
|
+
#
|
|
217
|
+
# Parameters:
|
|
218
|
+
# - user_experience_id: Required. The ID of the user experience (e.g., 'rails_request')
|
|
219
|
+
# - error: Optional. The expected error flag for gitlab_user_experience_total (false by default)
|
|
220
|
+
# - success: Optional. The expected success flag for gitlab_user_experience_apdex_total (true by default)
|
|
221
|
+
RSpec::Matchers.define :observed_user_experience do |user_experience_id, error: false, success: true|
|
|
222
|
+
include Labkit::RSpec::Matchers::UserExperience
|
|
223
|
+
|
|
224
|
+
description { "observe user experience '#{user_experience_id}'" }
|
|
225
|
+
supports_block_expectations
|
|
226
|
+
|
|
227
|
+
match do |actual|
|
|
228
|
+
labels = attributes(user_experience_id)
|
|
229
|
+
|
|
230
|
+
start_before = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i
|
|
231
|
+
end_before = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
|
|
232
|
+
total_before = total_counter&.get(labels.merge(error: error)).to_i
|
|
233
|
+
apdex_before = apdex_counter&.get(labels.merge(success: success)).to_i
|
|
234
|
+
|
|
235
|
+
actual.call
|
|
236
|
+
|
|
237
|
+
start_after = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i
|
|
238
|
+
end_after = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
|
|
239
|
+
total_after = total_counter&.get(labels.merge(error: error)).to_i
|
|
240
|
+
apdex_after = apdex_counter&.get(labels.merge(success: success)).to_i
|
|
241
|
+
|
|
242
|
+
@start_change = start_after - start_before
|
|
243
|
+
@end_change = end_after - end_before
|
|
244
|
+
@total_change = total_after - total_before
|
|
245
|
+
@apdex_change = apdex_after - apdex_before
|
|
246
|
+
|
|
247
|
+
@start_change == 1 && @end_change == 1 && @total_change == 1 && @apdex_change == (error ? 0 : 1)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
failure_message do
|
|
251
|
+
"Failed to observe user experience '#{user_experience_id}':\n" \
|
|
252
|
+
"expected checkpoint='start' counter to increase by 1, but increased by #{@start_change}\n" \
|
|
253
|
+
"expected checkpoint='end' counter to increase by 1, but increased by #{@end_change}\n" \
|
|
254
|
+
"expected total='error: #{error}' counter to increase by 1, but increased by #{@total_change}\n" \
|
|
255
|
+
"expected apdex='success: #{success}' counter to increase by #{error ? 0 : 1}, but increased by #{@apdex_change}"
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
206
259
|
# Backward compatibility matchers for CoveredExperience
|
|
207
260
|
RSpec::Matchers.alias_matcher :start_covered_experience, :start_user_experience
|
|
208
261
|
RSpec::Matchers.alias_matcher :checkpoint_covered_experience, :checkpoint_user_experience
|
|
@@ -161,6 +161,26 @@ experience.checkpoint
|
|
|
161
161
|
experience.complete
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
+
#### Observing a Past Experience
|
|
165
|
+
|
|
166
|
+
When an action already happened and you want to record its duration retroactively, use `observed`. It fires the start and end metrics without registering in the active context:
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
Labkit::UserExperienceSli.observed('merge_request_creation', start_time: start_time_of_past_action)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
You can also signal that an error occurred during the action:
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
Labkit::UserExperienceSli.observed('merge_request_creation', start_time: start_time_of_past_action, error: true)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Extra labels are forwarded to both the start and end log events:
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
Labkit::UserExperienceSli.observed('merge_request_creation', start_time: start_time_of_past_action, worker: 'MyWorker')
|
|
182
|
+
```
|
|
183
|
+
|
|
164
184
|
#### Resuming Experiences
|
|
165
185
|
|
|
166
186
|
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.
|
|
@@ -135,6 +135,28 @@ module Labkit
|
|
|
135
135
|
self
|
|
136
136
|
end
|
|
137
137
|
|
|
138
|
+
# Records a past User Experience by its duration.
|
|
139
|
+
#
|
|
140
|
+
# @param start_time [Time] The time when the experience started.
|
|
141
|
+
# @param error [Boolean] Whether the experience ended in an error.
|
|
142
|
+
# @param extra [Hash] Additional data to include in the log events.
|
|
143
|
+
# @return [self]
|
|
144
|
+
def observed(start_time:, error: false, **extra)
|
|
145
|
+
@start_time = start_time.utc
|
|
146
|
+
@end_time = Time.now.utc
|
|
147
|
+
error!("observed_error") if error
|
|
148
|
+
|
|
149
|
+
checkpoint_counter.increment(checkpoint: "start", **base_labels)
|
|
150
|
+
log_event("start", **extra)
|
|
151
|
+
|
|
152
|
+
checkpoint_counter.increment(checkpoint: "end", **base_labels)
|
|
153
|
+
total_counter.increment(error: has_error?, **base_labels)
|
|
154
|
+
apdex_counter.increment(success: apdex_success?, **base_labels) unless has_error?
|
|
155
|
+
log_event("end", **extra)
|
|
156
|
+
|
|
157
|
+
self
|
|
158
|
+
end
|
|
159
|
+
|
|
138
160
|
# Marks the experience as failed with an error
|
|
139
161
|
#
|
|
140
162
|
# @param error [StandardError, String] The error that caused the experience to fail.
|
|
@@ -77,7 +77,7 @@ module Labkit
|
|
|
77
77
|
reset_configuration
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
# Retrieves a
|
|
80
|
+
# Retrieves a user experience using the experience_id.
|
|
81
81
|
# It retrieves from the current context when available,
|
|
82
82
|
# otherwise it instantiates a new experience with the definition
|
|
83
83
|
# from the registry.
|
|
@@ -88,7 +88,7 @@ module Labkit
|
|
|
88
88
|
find_current(experience_id) || raise_or_null(experience_id)
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
-
# Starts a
|
|
91
|
+
# Starts a user experience using the experience_id.
|
|
92
92
|
#
|
|
93
93
|
# @param experience_id [String, Symbol] The ID of the experience to start.
|
|
94
94
|
# @param extra [Hash] Additional data to include in the log event.
|
|
@@ -97,7 +97,19 @@ module Labkit
|
|
|
97
97
|
get(experience_id).start(**extra, &)
|
|
98
98
|
end
|
|
99
99
|
|
|
100
|
-
#
|
|
100
|
+
# Records a past user experience by its duration atomically.
|
|
101
|
+
#
|
|
102
|
+
# @param experience_id [String, Symbol] The ID of the experience.
|
|
103
|
+
# @param start_time [Time] The time when the experience started.
|
|
104
|
+
# @param extra [Hash] Additional data to include in the log events.
|
|
105
|
+
# @return [Experience, Null] The observed experience or a Null object if not found (in production/staging).
|
|
106
|
+
def observed(experience_id, start_time:, **extra)
|
|
107
|
+
definition = registry[experience_id]
|
|
108
|
+
experience = definition ? Experience.new(definition) : raise_or_null(experience_id)
|
|
109
|
+
experience.observed(start_time: start_time, **extra)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Resumes a user experience using the experience_id.
|
|
101
113
|
#
|
|
102
114
|
# @param experience_id [String, Symbol] The ID of the experience to resume.
|
|
103
115
|
# @return [Experience, Null] The started experience or a Null object if not found (in production/staging).
|
data/scripts/prepare-dev-env.sh
CHANGED