gitlab-labkit 0.39.0 → 0.40.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 +2 -1
- data/.gitlab-ci-asdf-versions.yml +2 -2
- data/.gitlab-ci.yml +3 -9
- data/.pre-commit-config.yaml +1 -1
- data/.tool-versions +2 -2
- data/README.md +3 -1
- data/config/covered_experiences/schema.json +35 -0
- data/config/covered_experiences/testing_sample.yml +4 -0
- data/gitlab-labkit.gemspec +1 -0
- data/lib/gitlab-labkit.rb +2 -1
- data/lib/labkit/context.rb +1 -0
- data/lib/labkit/covered_experience/README.md +134 -0
- data/lib/labkit/covered_experience/error.rb +9 -0
- data/lib/labkit/covered_experience/experience.rb +198 -0
- data/lib/labkit/covered_experience/null.rb +22 -0
- data/lib/labkit/covered_experience/registry.rb +105 -0
- data/lib/labkit/covered_experience.rb +69 -0
- data/lib/labkit/logging/json_logger.rb +11 -0
- data/lib/labkit/rspec/README.md +121 -0
- data/lib/labkit/rspec/matchers/covered_experience_matchers.rb +198 -0
- data/lib/labkit/rspec/matchers.rb +10 -0
- metadata +27 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 570e79f377fc9cfa11a87177437e6f66a6ed78c94958ad999744034922011e31
|
4
|
+
data.tar.gz: a58252e9bb1792ac0e6ca7e65d565f49803e917eb845de065ffb4921b1bf405c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eb4718ca04e0c5769180e3e23e3e658ea914a60c12142efc6f26bfc8f25b9a29d467c1c8fb582f23827c4e6cf3c9d72e65d7a7a469ab37465eb479f650e8269a
|
7
|
+
data.tar.gz: 90c9bff31da38826e4d60d6ac623f97257a78fbea63ea86aba8a15dd22df69e069ed628af48fc5f4695d58f1157c098f22a8e9676f97e8345522073349603bf5
|
data/.copier-answers.yml
CHANGED
@@ -3,10 +3,11 @@
|
|
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.35.1
|
7
7
|
_src_path: https://gitlab.com/gitlab-com/gl-infra/common-template-copier.git
|
8
8
|
ee_licensed: false
|
9
9
|
golang: false
|
10
|
+
helm: false
|
10
11
|
initial_codeowners: '@reprazent @andrewn @mkaeppler @ayufan'
|
11
12
|
jsonnet: false
|
12
13
|
project_name: labkit-ruby
|
data/.gitlab-ci.yml
CHANGED
@@ -19,19 +19,13 @@ include:
|
|
19
19
|
# It includes standard checks, gitlab-scanners, validations and release processes
|
20
20
|
# common to all projects using this template library.
|
21
21
|
# see https://gitlab.com/gitlab-com/gl-infra/common-ci-tasks/-/blob/main/templates/standard.md
|
22
|
-
-
|
23
|
-
ref: v2.77 # renovate:managed
|
24
|
-
file: templates/standard.yml
|
22
|
+
- component: $CI_SERVER_FQDN/gitlab-com/gl-infra/common-ci-tasks/standard-build@v2.78
|
25
23
|
|
26
24
|
# Runs rspec tests and rubocop on the project
|
27
25
|
# see https://gitlab.com/gitlab-com/gl-infra/common-ci-tasks/-/blob/main/templates/ruby.md
|
28
|
-
-
|
29
|
-
ref: v2.77 # renovate:managed
|
30
|
-
file: templates/ruby.yml
|
26
|
+
- component: $CI_SERVER_FQDN/gitlab-com/gl-infra/common-ci-tasks/ruby-build@v2.78
|
31
27
|
|
32
|
-
-
|
33
|
-
ref: v2.77 # renovate:managed
|
34
|
-
file: "danger.yml"
|
28
|
+
- component: $CI_SERVER_FQDN/gitlab-com/gl-infra/common-ci-tasks/danger@v2.78
|
35
29
|
|
36
30
|
.test_template: &test_definition
|
37
31
|
extends: .with_bundle
|
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.85 # renovate:managed
|
29
29
|
|
30
30
|
hooks:
|
31
31
|
- id: shellcheck # Run shellcheck for changed Shell files
|
data/.tool-versions
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
ruby 3.4.
|
2
|
-
shfmt 3.
|
1
|
+
ruby 3.4.5
|
2
|
+
shfmt 3.12
|
3
3
|
shellcheck 0.10.0
|
data/README.md
CHANGED
@@ -19,10 +19,12 @@ The changelog is available via [**tagged release notes**](https://gitlab.com/git
|
|
19
19
|
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
|
-
1. `Labkit::Correlation`
|
22
|
+
1. `Labkit::Correlation` for accessing the correlation id. (Generated and propagated by `Labkit::Context`)
|
23
|
+
1. `Labkit::CoveredExperience` for tracking covered experiences. More on the [README](./lib/labkit/covered_experiences/README.md).
|
23
24
|
1. `Labkit::FIPS` for checking for FIPS mode and using FIPS-compliant algorithms.
|
24
25
|
1. `Labkit::Logging` for sanitizing log messages.
|
25
26
|
1. `Labkit::Metrics` for metrics. More on the [README](./lib/labkit/metrics/README.md).
|
27
|
+
1. `Labkit::RSpec` for RSpec matchers to test Labkit functionality (requires selective loading). More on the [README](./lib/labkit/rspec/README.md).
|
26
28
|
1. `Labkit::Tracing` for handling and propagating distributed traces.
|
27
29
|
|
28
30
|
## Developing
|
@@ -0,0 +1,35 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-06/schema#",
|
3
|
+
"$id": "https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby/-/raw/master/config/covered_experiences/schema.json",
|
4
|
+
"title": "Covered Experience Definition",
|
5
|
+
"description": "Schema for GitLab Covered Experience files",
|
6
|
+
"type": "object",
|
7
|
+
"properties": {
|
8
|
+
"description": {
|
9
|
+
"type": "string",
|
10
|
+
"minLength": 1,
|
11
|
+
"description": "Human-readable description of the covered experience"
|
12
|
+
},
|
13
|
+
"feature_category": {
|
14
|
+
"type": "string",
|
15
|
+
"minLength": 1,
|
16
|
+
"description": "GitLab feature category this experience belongs to"
|
17
|
+
},
|
18
|
+
"urgency": {
|
19
|
+
"type": "string",
|
20
|
+
"enum": [
|
21
|
+
"sync_fast",
|
22
|
+
"sync_slow",
|
23
|
+
"async_fast",
|
24
|
+
"async_slow"
|
25
|
+
],
|
26
|
+
"description": "Urgency level for this covered experience"
|
27
|
+
}
|
28
|
+
},
|
29
|
+
"required": [
|
30
|
+
"description",
|
31
|
+
"feature_category",
|
32
|
+
"urgency"
|
33
|
+
]
|
34
|
+
}
|
35
|
+
|
data/gitlab-labkit.gemspec
CHANGED
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
|
|
25
25
|
spec.add_runtime_dependency "grpc", ">= 1.62" # Be sure to update the "grpc-tools" dev_dependency too
|
26
26
|
spec.add_runtime_dependency "google-protobuf", "~> 3" # Keep the major version to 3 until we update the `grpc` gem
|
27
27
|
spec.add_runtime_dependency "jaeger-client", "~> 1.1.0"
|
28
|
+
spec.add_runtime_dependency 'json-schema', '~> 5.1'
|
28
29
|
spec.add_runtime_dependency "opentracing", "~> 0.4"
|
29
30
|
spec.add_runtime_dependency "pg_query", ">= 6.1.0", "< 7.0"
|
30
31
|
spec.add_runtime_dependency "prometheus-client-mmap", "~> 1.2.9"
|
data/lib/gitlab-labkit.rb
CHANGED
@@ -7,8 +7,9 @@
|
|
7
7
|
module Labkit
|
8
8
|
autoload :System, "labkit/system"
|
9
9
|
|
10
|
-
autoload :Correlation, "labkit/correlation"
|
11
10
|
autoload :Context, "labkit/context"
|
11
|
+
autoload :Correlation, "labkit/correlation"
|
12
|
+
autoload :CoveredExperience, "labkit/covered_experience"
|
12
13
|
autoload :FIPS, "labkit/fips"
|
13
14
|
autoload :Tracing, "labkit/tracing"
|
14
15
|
autoload :Logging, "labkit/logging"
|
data/lib/labkit/context.rb
CHANGED
@@ -5,6 +5,7 @@ require "securerandom"
|
|
5
5
|
require "active_support/core_ext/module/delegation"
|
6
6
|
require "active_support/core_ext/string/starts_ends_with"
|
7
7
|
require "active_support/core_ext/string/inflections"
|
8
|
+
require "active_support/core_ext/object/blank"
|
8
9
|
|
9
10
|
module Labkit
|
10
11
|
# A context can be used to provide structured information on what resources
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# Covered Experience
|
2
|
+
|
3
|
+
This module covers the definition for Covered Experiences, as described in the [blueprint](https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/covered_experience_slis/#covered-experience-definition).
|
4
|
+
|
5
|
+
## Configuration
|
6
|
+
|
7
|
+
### Logger Configuration
|
8
|
+
|
9
|
+
By default, `Labkit::CoveredExperience` uses `Labkit::Logging::JsonLogger.new($stdout)` for logging. You can configure a custom logger:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
Labkit::CoveredExperience.configure do |config|
|
13
|
+
config.logger = Labkit::Logging::JsonLogger.new($stdout)
|
14
|
+
end
|
15
|
+
```
|
16
|
+
|
17
|
+
This configuration affects all Covered Experience instances and their logging output.
|
18
|
+
|
19
|
+
### Covered Experience Definitions
|
20
|
+
|
21
|
+
Covered experience definitions will be lazy loaded from the default directory (`config/covered_experiences`).
|
22
|
+
|
23
|
+
Create a new covered experience file in the registry directory, e.g. config/covered_experiences/merge_request_creation.yaml
|
24
|
+
|
25
|
+
The basename of the file will be taken as the covered_experience_id.
|
26
|
+
|
27
|
+
The schema header is optional, but if you're using VSCode (or any other editor with support), you can get them validated
|
28
|
+
instantaneously in the editor via a [JSON schema plugin](https://marketplace.visualstudio.com/items?itemName=remcohaszing.schemastore).
|
29
|
+
|
30
|
+
```yaml
|
31
|
+
# yaml-language-server: $schema=https://gitlab.com/gitlab-org/ruby/gems/labkit-ruby/-/raw/master/config/covered_experiences/schema.json
|
32
|
+
description: "Creating a new merge request in a project"
|
33
|
+
feature_category: "source_code_management"
|
34
|
+
urgency: "sync_fast"
|
35
|
+
```
|
36
|
+
|
37
|
+
**Feature category**
|
38
|
+
|
39
|
+
https://docs.gitlab.com/development/feature_categorization/#feature-categorization.
|
40
|
+
|
41
|
+
**Urgency**
|
42
|
+
|
43
|
+
| Threshold | Description | Examples | Value |
|
44
|
+
|--------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------|-------|
|
45
|
+
| `sync_fast` | A user is awaiting a synchronous response which needs to be returned before they can continue with their action | A full-page render | 2s |
|
46
|
+
| `sync_slow` | A user is awaiting a synchronous response which needs to be returned before they can continue with their action, but which the user may accept a slower response | Displaying a full-text search response while displaying an amusement animation | 5s |
|
47
|
+
| `async_fast` | An async process which may block a user from continuing with their user journey | MR diff update after git push | 15s |
|
48
|
+
| `async_slow` | An async process which will not block a user and will not be immediately noticed as being slow | Notification following an assignment | 5m |
|
49
|
+
|
50
|
+
## Usage
|
51
|
+
|
52
|
+
The `Labkit::CoveredExperience` module provides a simple API for measuring and tracking covered experiences in your application.
|
53
|
+
|
54
|
+
|
55
|
+
#### Accessing a Covered Experience
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
# Get a covered experience by ID
|
59
|
+
experience = Labkit::CoveredExperience.get('merge_request_creation')
|
60
|
+
```
|
61
|
+
|
62
|
+
#### Using with a Block (Recommended)
|
63
|
+
|
64
|
+
The simplest way to use covered experiences is with a block, which automatically handles starting and completing the experience:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
Labkit::CoveredExperience.start('merge_request_creation') do |experience|
|
68
|
+
# Your code here
|
69
|
+
create_merge_request
|
70
|
+
|
71
|
+
# Add checkpoints for important milestones
|
72
|
+
experience.checkpoint
|
73
|
+
|
74
|
+
validate_merge_request
|
75
|
+
experience.checkpoint
|
76
|
+
|
77
|
+
send_notifications
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
#### Manual Control
|
82
|
+
|
83
|
+
For more control, you can manually start, checkpoint, and complete experiences:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
experience = Labkit::CoveredExperience.get('merge_request_creation')
|
87
|
+
experience.start
|
88
|
+
|
89
|
+
# Perform some work
|
90
|
+
create_merge_request
|
91
|
+
|
92
|
+
# Mark important milestones
|
93
|
+
experience.checkpoint
|
94
|
+
|
95
|
+
# Perform more work
|
96
|
+
validate_merge_request
|
97
|
+
experience.checkpoint
|
98
|
+
|
99
|
+
# Complete the experience
|
100
|
+
experience.complete
|
101
|
+
```
|
102
|
+
|
103
|
+
### Error Handling
|
104
|
+
|
105
|
+
When using the block form, errors are automatically captured:
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
Labkit::CoveredExperience.start('merge_request_creation') do |experience|
|
109
|
+
# If this raises an exception, it will be captured automatically
|
110
|
+
risky_operation
|
111
|
+
end
|
112
|
+
```
|
113
|
+
|
114
|
+
For manual control, you can mark errors explicitly:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
experience = Labkit::CoveredExperience.get('merge_request_creation')
|
118
|
+
experience.start
|
119
|
+
|
120
|
+
begin
|
121
|
+
risky_operation
|
122
|
+
rescue StandardError => e
|
123
|
+
experience.error!(e)
|
124
|
+
raise
|
125
|
+
ensure
|
126
|
+
experience.complete
|
127
|
+
end
|
128
|
+
```
|
129
|
+
|
130
|
+
### Error Behavior
|
131
|
+
|
132
|
+
- In `development` and `test` environments, accessing a non-existent covered experience will raise a `NotFoundError`
|
133
|
+
- In other environments, a null object is returned that safely ignores all method calls
|
134
|
+
- Attempting to checkpoint or complete an unstarted experience will raise an `UnstartedError` in `development` and `test` environments
|
@@ -0,0 +1,198 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'labkit/context'
|
4
|
+
require 'labkit/covered_experience/error'
|
5
|
+
|
6
|
+
module Labkit
|
7
|
+
module CoveredExperience
|
8
|
+
URGENCY_THRESHOLDS_IN_SECONDS = {
|
9
|
+
sync_fast: 2,
|
10
|
+
sync_slow: 5,
|
11
|
+
async_fast: 15,
|
12
|
+
async_slow: 300
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
# The `Experience` class represents a single Covered Experience
|
16
|
+
# event to be measured and reported.
|
17
|
+
class Experience
|
18
|
+
attr_reader :error
|
19
|
+
|
20
|
+
def initialize(definition)
|
21
|
+
@definition = definition
|
22
|
+
end
|
23
|
+
|
24
|
+
# Start the Covered Experience.
|
25
|
+
#
|
26
|
+
# @yield [self] When a block is provided, the experience will be completed automatically.
|
27
|
+
# @param extra [Hash] Additional data to include in the log event
|
28
|
+
# @return [self]
|
29
|
+
# @raise [CoveredExperienceError] If the block raises an error.
|
30
|
+
#
|
31
|
+
# Usage:
|
32
|
+
#
|
33
|
+
# CoveredExperience.new(definition).start do |experience|
|
34
|
+
# experience.checkpoint
|
35
|
+
# experience.checkpoint
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# experience = CoveredExperience.new(definition)
|
39
|
+
# experience.start
|
40
|
+
# experience.checkpoint
|
41
|
+
# experience.complete
|
42
|
+
def start(**extra, &)
|
43
|
+
@start_time = Time.now.utc
|
44
|
+
checkpoint_counter.increment(checkpoint: "start")
|
45
|
+
log_event("start", **extra)
|
46
|
+
|
47
|
+
return self unless block_given?
|
48
|
+
|
49
|
+
begin
|
50
|
+
yield self
|
51
|
+
self
|
52
|
+
rescue StandardError => e
|
53
|
+
error!(e)
|
54
|
+
raise
|
55
|
+
ensure
|
56
|
+
complete(**extra)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Checkpoint the Covered Experience.
|
61
|
+
#
|
62
|
+
# @param extra [Hash] Additional data to include in the log event
|
63
|
+
# @raise [UnstartedError] If the experience has not been started and RAILS_ENV is development or test.
|
64
|
+
# @return [self]
|
65
|
+
def checkpoint(**extra)
|
66
|
+
return unless ensure_started!
|
67
|
+
|
68
|
+
@checkpoint_time = Time.now.utc
|
69
|
+
checkpoint_counter.increment(checkpoint: "intermediate")
|
70
|
+
log_event("intermediate", **extra)
|
71
|
+
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
# Complete the Covered Experience.
|
76
|
+
#
|
77
|
+
# @param extra [Hash] Additional data to include in the log event
|
78
|
+
# @raise [UnstartedError] If the experience has not been started and RAILS_ENV is development or test.
|
79
|
+
# @return [self]
|
80
|
+
def complete(**extra)
|
81
|
+
return unless ensure_started!
|
82
|
+
|
83
|
+
begin
|
84
|
+
@end_time = Time.now.utc
|
85
|
+
ensure
|
86
|
+
checkpoint_counter.increment(checkpoint: "end")
|
87
|
+
total_counter.increment(error: has_error?)
|
88
|
+
apdex_counter.increment(success: apdex_success?) unless has_error?
|
89
|
+
log_event("end", **extra)
|
90
|
+
end
|
91
|
+
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
# Marks the experience as failed with an error
|
96
|
+
#
|
97
|
+
# @param error [StandardError, String] The error that caused the experience to fail.
|
98
|
+
# @return [self]
|
99
|
+
def error!(error)
|
100
|
+
@error = error
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
def has_error?
|
105
|
+
!!@error
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def base_labels
|
111
|
+
@base_labels ||= @definition.to_h.slice(:covered_experience, :feature_category, :urgency)
|
112
|
+
end
|
113
|
+
|
114
|
+
def ensure_started!
|
115
|
+
return @start_time unless @start_time.nil?
|
116
|
+
|
117
|
+
err = UnstartedError.new("Covered Experience #{@definition.covered_experience} not started")
|
118
|
+
|
119
|
+
warn(err)
|
120
|
+
raise(err) if %w[development test].include?(ENV['RAILS_ENV'])
|
121
|
+
end
|
122
|
+
|
123
|
+
def urgency_threshold
|
124
|
+
URGENCY_THRESHOLDS_IN_SECONDS[@definition.urgency.to_sym]
|
125
|
+
end
|
126
|
+
|
127
|
+
def elapsed_time
|
128
|
+
last_time = @end_time || @checkpoint_time || @start_time
|
129
|
+
last_time - @start_time
|
130
|
+
end
|
131
|
+
|
132
|
+
def apdex_success?
|
133
|
+
elapsed_time <= urgency_threshold
|
134
|
+
end
|
135
|
+
|
136
|
+
def checkpoint_counter
|
137
|
+
@checkpoint_counter ||= Labkit::Metrics::Client.counter(
|
138
|
+
:gitlab_covered_experience_checkpoint_total,
|
139
|
+
'Total checkpoints for covered experiences',
|
140
|
+
base_labels
|
141
|
+
)
|
142
|
+
end
|
143
|
+
|
144
|
+
def total_counter
|
145
|
+
@total_counter ||= Labkit::Metrics::Client.counter(
|
146
|
+
:gitlab_covered_experience_total,
|
147
|
+
'Total covered experience events (success/failure)',
|
148
|
+
base_labels
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
def apdex_counter
|
153
|
+
@apdex_counter ||= Labkit::Metrics::Client.counter(
|
154
|
+
:gitlab_covered_experience_apdex_total,
|
155
|
+
'Total covered experience apdex events',
|
156
|
+
base_labels
|
157
|
+
)
|
158
|
+
end
|
159
|
+
|
160
|
+
def log_event(event_type, **extra)
|
161
|
+
log_data = build_log_data(event_type, **extra)
|
162
|
+
logger.info(log_data)
|
163
|
+
end
|
164
|
+
|
165
|
+
def build_log_data(event_type, **extra)
|
166
|
+
log_data = {
|
167
|
+
checkpoint: event_type,
|
168
|
+
covered_experience: @definition.covered_experience,
|
169
|
+
feature_category: @definition.feature_category,
|
170
|
+
urgency: @definition.urgency,
|
171
|
+
start_time: @start_time,
|
172
|
+
checkpoint_time: @checkpoint_time,
|
173
|
+
end_time: @end_time,
|
174
|
+
elapsed_time_s: elapsed_time,
|
175
|
+
urgency_threshold_s: urgency_threshold
|
176
|
+
}
|
177
|
+
log_data.merge!(extra) if extra
|
178
|
+
|
179
|
+
if has_error?
|
180
|
+
log_data[:error] = true
|
181
|
+
log_data[:error_message] = @error.inspect
|
182
|
+
end
|
183
|
+
|
184
|
+
log_data.compact!
|
185
|
+
|
186
|
+
log_data
|
187
|
+
end
|
188
|
+
|
189
|
+
def warn(exception)
|
190
|
+
logger.warn(component: self.class.name, message: exception.message)
|
191
|
+
end
|
192
|
+
|
193
|
+
def logger
|
194
|
+
Labkit::CoveredExperience.configuration.logger
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Labkit
|
4
|
+
module CoveredExperience
|
5
|
+
# Fakes Labkit::CoveredExperience::Experience.
|
6
|
+
class Null
|
7
|
+
include Singleton
|
8
|
+
|
9
|
+
attr_reader :id, :description, :feature_category, :urgency
|
10
|
+
|
11
|
+
def start(*_args)
|
12
|
+
yield self if block_given?
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def push_attributes!(*_args) = self
|
17
|
+
def checkpoint(*_args) = self
|
18
|
+
def complete(*_args) = self
|
19
|
+
def error!(*_args) = self
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'json-schema'
|
5
|
+
require 'pathname'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
module Labkit
|
9
|
+
module CoveredExperience
|
10
|
+
Definition = Data.define(:covered_experience, :description, :feature_category, :urgency)
|
11
|
+
|
12
|
+
class Registry
|
13
|
+
extend Forwardable
|
14
|
+
|
15
|
+
SCHEMA_PATH = File.expand_path('../../../config/covered_experiences/schema.json', __dir__)
|
16
|
+
|
17
|
+
def_delegator :@experiences, :empty?
|
18
|
+
|
19
|
+
# @param dir [String, Pathname] Directory path containing YAML file definitions
|
20
|
+
# Defaults to 'config/covered_experiences' relative to the calling application's root
|
21
|
+
def initialize(dir: File.join("config", "covered_experiences"))
|
22
|
+
@dir = Pathname.new(Dir.pwd).join(dir)
|
23
|
+
@experiences = load_on_demand
|
24
|
+
end
|
25
|
+
|
26
|
+
# Retrieve a definition experience given a covered_experience_id.
|
27
|
+
#
|
28
|
+
# @param covered_experience_id [String, Symbol] Covered experience identifier
|
29
|
+
# @return [Experience, nil] An experience if present, otherwise nil
|
30
|
+
def [](covered_experience_id)
|
31
|
+
@experiences[covered_experience_id.to_s]
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Initialize a hash that loads experiences on-demand
|
37
|
+
#
|
38
|
+
# @return [Hash] Hash with lazy loading behavior
|
39
|
+
def load_on_demand
|
40
|
+
unless readable_dir?
|
41
|
+
warn("Directory not readable: #{@dir}")
|
42
|
+
return {}
|
43
|
+
end
|
44
|
+
|
45
|
+
Hash.new do |result, experience_id|
|
46
|
+
experience = load_experience(experience_id.to_s)
|
47
|
+
# we also store nil to memoize the value and avoid triggering load_experience again
|
48
|
+
result[experience_id.to_s] = experience
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Load a covered experience definition.
|
53
|
+
#
|
54
|
+
# @param experience_id [String] Experience identifier
|
55
|
+
# @return [Experience, nil] Loaded experience or nil if not found/invalid
|
56
|
+
def load_experience(experience_id)
|
57
|
+
file_path = @dir.join("#{experience_id}.yml")
|
58
|
+
|
59
|
+
unless file_path.exist?
|
60
|
+
warn("Invalid Covered Experience definition: #{experience_id}")
|
61
|
+
return nil
|
62
|
+
end
|
63
|
+
|
64
|
+
read_experience(file_path, experience_id)
|
65
|
+
end
|
66
|
+
|
67
|
+
def readable_dir?
|
68
|
+
@dir.exist? && @dir.directory? && @dir.readable?
|
69
|
+
end
|
70
|
+
|
71
|
+
# Read and validate a definition experience file
|
72
|
+
#
|
73
|
+
# @param file_path [Pathname] Path to the definition file
|
74
|
+
# @param experience_id [String] Expected experience ID
|
75
|
+
# @return [Experience, nil] Parsed experience or nil if invalid
|
76
|
+
def read_experience(file_path, experience_id)
|
77
|
+
content = YAML.safe_load(file_path.read)
|
78
|
+
return nil unless content.is_a?(Hash)
|
79
|
+
|
80
|
+
errors = JSON::Validator.fully_validate(schema, content)
|
81
|
+
return Definition.new(covered_experience: experience_id, **content) if errors.empty?
|
82
|
+
|
83
|
+
warn("Invalid schema for #{file_path}")
|
84
|
+
|
85
|
+
nil
|
86
|
+
rescue Psych::SyntaxError => e
|
87
|
+
warn("Invalid definition file #{file_path}: #{e.message}")
|
88
|
+
rescue StandardError => e
|
89
|
+
warn("Unexpected error processing #{file_path}: #{e.message}")
|
90
|
+
end
|
91
|
+
|
92
|
+
def schema
|
93
|
+
@schema ||= JSON.parse(File.read(SCHEMA_PATH))
|
94
|
+
end
|
95
|
+
|
96
|
+
def warn(message)
|
97
|
+
logger.warn(component: self.class.name, message: message)
|
98
|
+
end
|
99
|
+
|
100
|
+
def logger
|
101
|
+
Labkit::CoveredExperience.configuration.logger
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'labkit/covered_experience/error'
|
4
|
+
require 'labkit/covered_experience/experience'
|
5
|
+
require 'labkit/covered_experience/null'
|
6
|
+
require 'labkit/covered_experience/registry'
|
7
|
+
require 'labkit/logging/json_logger'
|
8
|
+
|
9
|
+
module Labkit
|
10
|
+
# Labkit::CoveredExperience namespace module.
|
11
|
+
#
|
12
|
+
# This module is responsible for managing covered experiences, which are
|
13
|
+
# specific events or activities within the application that are measured
|
14
|
+
# and reported for performance monitoring and analysis.
|
15
|
+
module CoveredExperience
|
16
|
+
# Configuration class for CoveredExperience
|
17
|
+
class Configuration
|
18
|
+
attr_accessor :logger
|
19
|
+
|
20
|
+
def initialize
|
21
|
+
@logger = Labkit::Logging::JsonLogger.new($stdout)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def configuration
|
27
|
+
@configuration ||= Configuration.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def reset_configuration
|
31
|
+
@configuration = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def configure
|
35
|
+
yield(configuration) if block_given?
|
36
|
+
end
|
37
|
+
|
38
|
+
def registry
|
39
|
+
@registry ||= Registry.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def reset
|
43
|
+
@registry = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def get(experience_id)
|
47
|
+
definition = registry[experience_id]
|
48
|
+
|
49
|
+
if definition
|
50
|
+
Experience.new(definition)
|
51
|
+
else
|
52
|
+
raise_or_null(experience_id)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def start(experience_id, &)
|
57
|
+
get(experience_id).start(&)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def raise_or_null(experience_id)
|
63
|
+
return Null.instance unless %w[development test].include?(ENV['RAILS_ENV'])
|
64
|
+
|
65
|
+
raise(NotFoundError, "Covered Experience #{experience_id} not found in the registry")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -52,6 +52,7 @@ module Labkit
|
|
52
52
|
data[:message] = message
|
53
53
|
when Hash
|
54
54
|
reject_reserved_log_keys!(message)
|
55
|
+
format_time!(data)
|
55
56
|
data.merge!(message)
|
56
57
|
end
|
57
58
|
|
@@ -77,6 +78,16 @@ module Labkit
|
|
77
78
|
"\n\nUse key names that are descriptive e.g. by using a prefix."
|
78
79
|
end
|
79
80
|
end
|
81
|
+
|
82
|
+
def format_time!(hash)
|
83
|
+
hash.each do |key, value|
|
84
|
+
if value.is_a?(Time)
|
85
|
+
hash[key] = value.utc.iso8601(3)
|
86
|
+
elsif value.is_a?(Hash)
|
87
|
+
format_time(value)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
80
91
|
end
|
81
92
|
end
|
82
93
|
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# Labkit RSpec Support
|
2
|
+
|
3
|
+
This module provides RSpec matchers for testing Labkit functionality in your Rails applications.
|
4
|
+
|
5
|
+
## Setup
|
6
|
+
|
7
|
+
You must explicitly require the RSpec matchers in your test files:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
# In your spec_helper.rb or rails_helper.rb
|
11
|
+
require 'labkit/rspec/matchers'
|
12
|
+
```
|
13
|
+
|
14
|
+
This approach ensures that:
|
15
|
+
- Test dependencies are not loaded in production environments
|
16
|
+
- You have explicit control over which matchers are available
|
17
|
+
- The gem remains lightweight for non-testing use cases
|
18
|
+
|
19
|
+
|
20
|
+
## Available Matchers
|
21
|
+
|
22
|
+
### Covered Experience Matchers
|
23
|
+
|
24
|
+
These matchers help you test that your code properly instruments covered experiences with the expected metrics.
|
25
|
+
|
26
|
+
#### `start_covered_experience`
|
27
|
+
|
28
|
+
Tests that a covered experience is started (checkpoint=start metric is incremented).
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
expect { subject }.to start_covered_experience('rails_request')
|
32
|
+
|
33
|
+
# Test that it does NOT start
|
34
|
+
expect { subject }.not_to start_covered_experience('rails_request')
|
35
|
+
```
|
36
|
+
|
37
|
+
#### `checkpoint_covered_experience`
|
38
|
+
|
39
|
+
Tests that a covered experience checkpoint is recorded (checkpoint=intermediate metric is incremented).
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
expect { subject }.to checkpoint_covered_experience('rails_request')
|
43
|
+
|
44
|
+
# Test that it does NOT checkpoint
|
45
|
+
expect { subject }.not_to checkpoint_covered_experience('rails_request')
|
46
|
+
```
|
47
|
+
|
48
|
+
#### `complete_covered_experience`
|
49
|
+
|
50
|
+
Tests that a covered experience is completed with the expected metrics:
|
51
|
+
- `gitlab_covered_experience_checkpoint_total` (with checkpoint=end)
|
52
|
+
- `gitlab_covered_experience_total` (with error flag)
|
53
|
+
- `gitlab_covered_experience_apdex_total` (with success flag)
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
# Test successful completion
|
57
|
+
expect { subject }.to complete_covered_experience('rails_request')
|
58
|
+
|
59
|
+
# Test completion with error
|
60
|
+
expect { subject }.to complete_covered_experience('rails_request', error: true, success: false)
|
61
|
+
|
62
|
+
# Test that it does NOT complete
|
63
|
+
expect { subject }.not_to complete_covered_experience('rails_request')
|
64
|
+
```
|
65
|
+
|
66
|
+
## Example Usage
|
67
|
+
|
68
|
+
### In your spec_helper.rb or rails_helper.rb:
|
69
|
+
|
70
|
+
```ruby
|
71
|
+
# spec/spec_helper.rb or spec/rails_helper.rb
|
72
|
+
require 'gitlab-labkit'
|
73
|
+
|
74
|
+
# Explicitly require the RSpec matchers
|
75
|
+
require 'labkit/rspec/matchers'
|
76
|
+
|
77
|
+
RSpec.configure do |config|
|
78
|
+
# Your other RSpec configuration...
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
### In your test files:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
RSpec.describe MyController, type: :controller do
|
86
|
+
describe '#index' do
|
87
|
+
it 'instruments the request properly' do
|
88
|
+
expect { get :index }.to start_covered_experience('rails_request')
|
89
|
+
.and complete_covered_experience('rails_request')
|
90
|
+
end
|
91
|
+
|
92
|
+
context 'when an error occurs' do
|
93
|
+
before do
|
94
|
+
allow(MyService).to receive(:call).and_raise(StandardError)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'records the error in metrics' do
|
98
|
+
expect { get :index }.to complete_covered_experience('rails_request', error: true, success: false)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
```
|
104
|
+
|
105
|
+
### For individual spec files (alternative approach):
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
# spec/controllers/my_controller_spec.rb
|
109
|
+
require 'spec_helper'
|
110
|
+
require 'labkit/rspec/matchers' # Can also be required per-file if needed
|
111
|
+
|
112
|
+
RSpec.describe MyController do
|
113
|
+
# Your tests using the matchers...
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
## Requirements
|
118
|
+
|
119
|
+
- The covered experience must be registered in `Labkit::CoveredExperience::Registry`
|
120
|
+
- Metrics must be properly configured in your test environment
|
121
|
+
- The code under test must use Labkit's covered experience instrumentation
|
@@ -0,0 +1,198 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# RSpec matchers for testing Labkit CoveredExperience functionality
|
4
|
+
#
|
5
|
+
# This file must be explicitly required in your test setup:
|
6
|
+
# require 'labkit/rspec/matchers'
|
7
|
+
|
8
|
+
raise LoadError, "RSpec is not loaded. Please require 'rspec' before requiring 'labkit/rspec/matchers'" unless defined?(RSpec)
|
9
|
+
|
10
|
+
module Labkit
|
11
|
+
module RSpec
|
12
|
+
module Matchers
|
13
|
+
# Helper module for CoveredExperience functionality
|
14
|
+
module CoveredExperience
|
15
|
+
def attributes(covered_experience_id)
|
16
|
+
raise ArgumentError, "covered_experience_id is required" if covered_experience_id.nil?
|
17
|
+
|
18
|
+
definition = Labkit::CoveredExperience::Registry.new[covered_experience_id]
|
19
|
+
definition.to_h.slice(:covered_experience, :feature_category, :urgency)
|
20
|
+
end
|
21
|
+
|
22
|
+
def checkpoint_counter
|
23
|
+
Labkit::Metrics::Client.get(:gitlab_covered_experience_checkpoint_total)
|
24
|
+
end
|
25
|
+
|
26
|
+
def total_counter
|
27
|
+
Labkit::Metrics::Client.get(:gitlab_covered_experience_total)
|
28
|
+
end
|
29
|
+
|
30
|
+
def apdex_counter
|
31
|
+
Labkit::Metrics::Client.get(:gitlab_covered_experience_apdex_total)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Matcher for verifying CoveredExperience start metrics instrumentation.
|
39
|
+
#
|
40
|
+
# Usage:
|
41
|
+
# expect { subject }.to start_covered_experience('rails_request')
|
42
|
+
#
|
43
|
+
# This matcher verifies that the following metric is incremented:
|
44
|
+
# - gitlab_covered_experience_checkpoint_total (with checkpoint=start)
|
45
|
+
#
|
46
|
+
# Parameters:
|
47
|
+
# - covered_experience_id: Required. The ID of the covered experience (e.g., 'rails_request')
|
48
|
+
RSpec::Matchers.define :start_covered_experience do |covered_experience_id|
|
49
|
+
include Labkit::RSpec::Matchers::CoveredExperience
|
50
|
+
|
51
|
+
description { "start covered experience '#{covered_experience_id}'" }
|
52
|
+
supports_block_expectations
|
53
|
+
|
54
|
+
match do |actual|
|
55
|
+
labels = attributes(covered_experience_id)
|
56
|
+
|
57
|
+
checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i
|
58
|
+
|
59
|
+
actual.call
|
60
|
+
|
61
|
+
checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "start")).to_i
|
62
|
+
|
63
|
+
@checkpoint_change = checkpoint_after - checkpoint_before
|
64
|
+
|
65
|
+
@checkpoint_change == 1
|
66
|
+
end
|
67
|
+
|
68
|
+
failure_message do
|
69
|
+
"Failed to checkpoint covered experience '#{covered_experience_id}':\n" \
|
70
|
+
"expected checkpoint='start' counter to increase by 1, but increased by #{@checkpoint_change}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Matcher for verifying CoveredExperience checkpoint metrics instrumentation.
|
75
|
+
#
|
76
|
+
# Usage:
|
77
|
+
# expect { subject }.to checkpoint_covered_experience('rails_request')
|
78
|
+
#
|
79
|
+
# This matcher verifies that the following metric is incremented:
|
80
|
+
# - gitlab_covered_experience_checkpoint_total (with checkpoint=intermediate)
|
81
|
+
#
|
82
|
+
# Parameters:
|
83
|
+
# - covered_experience_id: Required. The ID of the covered experience (e.g., 'rails_request')
|
84
|
+
RSpec::Matchers.define :checkpoint_covered_experience do |covered_experience_id|
|
85
|
+
include Labkit::RSpec::Matchers::CoveredExperience
|
86
|
+
|
87
|
+
description { "checkpoint covered experience '#{covered_experience_id}'" }
|
88
|
+
supports_block_expectations
|
89
|
+
|
90
|
+
match do |actual|
|
91
|
+
labels = attributes(covered_experience_id)
|
92
|
+
|
93
|
+
checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i
|
94
|
+
|
95
|
+
actual.call
|
96
|
+
|
97
|
+
checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i
|
98
|
+
@checkpoint_change = checkpoint_after - checkpoint_before
|
99
|
+
|
100
|
+
@checkpoint_change == 1
|
101
|
+
end
|
102
|
+
|
103
|
+
failure_message do
|
104
|
+
"Failed to checkpoint covered experience '#{covered_experience_id}':\n" \
|
105
|
+
"expected checkpoint='intermediate' counter to increase by 1, but increased by #{@checkpoint_change}"
|
106
|
+
end
|
107
|
+
|
108
|
+
match_when_negated do |actual|
|
109
|
+
labels = attributes(covered_experience_id)
|
110
|
+
|
111
|
+
checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i
|
112
|
+
|
113
|
+
actual.call
|
114
|
+
|
115
|
+
checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "intermediate")).to_i
|
116
|
+
@checkpoint_change = checkpoint_after - checkpoint_before
|
117
|
+
|
118
|
+
@checkpoint_change.zero?
|
119
|
+
end
|
120
|
+
|
121
|
+
failure_message_when_negated do
|
122
|
+
"Expected covered experience '#{covered_experience_id}' NOT to checkpoint:\n" \
|
123
|
+
"expected checkpoint='intermediate' counter to increase by 0, but increased by #{@checkpoint_change}"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Matcher for verifying CoveredExperience completion metrics instrumentation.
|
128
|
+
#
|
129
|
+
# Usage:
|
130
|
+
# expect { subject }.to complete_covered_experience('rails_request')
|
131
|
+
#
|
132
|
+
# This matcher verifies that the following metrics are incremented with specific labels:
|
133
|
+
# - gitlab_covered_experience_checkpoint_total (with checkpoint=end)
|
134
|
+
# - gitlab_covered_experience_total (with error=false)
|
135
|
+
# - gitlab_covered_experience_apdex_total (with success=true)
|
136
|
+
#
|
137
|
+
# Parameters:
|
138
|
+
# - covered_experience_id: Required. The ID of the covered experience (e.g., 'rails_request')
|
139
|
+
# - error: Optional. The expected error flag for gitlab_covered_experience_total (false by default)
|
140
|
+
# - success: Optional. The expected success flag for gitlab_covered_experience_apdex_total (true by default)
|
141
|
+
RSpec::Matchers.define :complete_covered_experience do |covered_experience_id, error: false, success: true|
|
142
|
+
include Labkit::RSpec::Matchers::CoveredExperience
|
143
|
+
|
144
|
+
description { "complete covered experience '#{covered_experience_id}'" }
|
145
|
+
supports_block_expectations
|
146
|
+
|
147
|
+
match do |actual|
|
148
|
+
labels = attributes(covered_experience_id)
|
149
|
+
|
150
|
+
checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
|
151
|
+
total_before = total_counter&.get(labels.merge(error: error)).to_i
|
152
|
+
apdex_before = apdex_counter&.get(labels.merge(success: success)).to_i
|
153
|
+
|
154
|
+
actual.call
|
155
|
+
|
156
|
+
checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
|
157
|
+
total_after = total_counter&.get(labels.merge(error: error)).to_i
|
158
|
+
apdex_after = apdex_counter&.get(labels.merge(success: success)).to_i
|
159
|
+
@checkpoint_change = checkpoint_after - checkpoint_before
|
160
|
+
@total_change = total_after - total_before
|
161
|
+
@apdex_change = apdex_after - apdex_before
|
162
|
+
|
163
|
+
@checkpoint_change == 1 && @total_change == 1 && @apdex_change == (error ? 0 : 1)
|
164
|
+
end
|
165
|
+
|
166
|
+
failure_message do
|
167
|
+
"Failed to complete covered experience '#{covered_experience_id}':\n" \
|
168
|
+
"expected checkpoint='end' counter to increase by 1, but increased by #{@checkpoint_change}\n" \
|
169
|
+
"expected total='error: #{error}' counter to increase by 1, but increased by #{@total_change}\n" \
|
170
|
+
"expected apdex='success: #{success}' counter to increase by 1, but increased by #{@apdex_change}"
|
171
|
+
end
|
172
|
+
|
173
|
+
match_when_negated do |actual|
|
174
|
+
labels = attributes(covered_experience_id)
|
175
|
+
|
176
|
+
checkpoint_before = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
|
177
|
+
total_before = total_counter&.get(labels.merge(error: error)).to_i
|
178
|
+
apdex_before = apdex_counter&.get(labels.merge(success: success)).to_i
|
179
|
+
|
180
|
+
actual.call
|
181
|
+
|
182
|
+
checkpoint_after = checkpoint_counter&.get(labels.merge(checkpoint: "end")).to_i
|
183
|
+
total_after = total_counter&.get(labels.merge(error: error)).to_i
|
184
|
+
apdex_after = apdex_counter&.get(labels.merge(success: success)).to_i
|
185
|
+
@checkpoint_change = checkpoint_after - checkpoint_before
|
186
|
+
@total_change = total_after - total_before
|
187
|
+
@apdex_change = apdex_after - apdex_before
|
188
|
+
|
189
|
+
@checkpoint_change.zero? && @total_change.zero? && @apdex_change == (error ? 1 : 0)
|
190
|
+
end
|
191
|
+
|
192
|
+
failure_message_when_negated do
|
193
|
+
"Failed covered experience '#{covered_experience_id}' NOT to complete:\n" \
|
194
|
+
"expected checkpoint='end' counter to increase by 0, but increased by #{@checkpoint_change}\n" \
|
195
|
+
"expected total='error: #{error}' counter to increase by 0, but increased by #{@total_change}\n" \
|
196
|
+
"expected apdex='success: #{success}' counter to increase by 0, but increased by #{@apdex_change}"
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# RSpec matchers loader for Labkit
|
4
|
+
#
|
5
|
+
# This file loads all available RSpec matchers for Labkit.
|
6
|
+
# It must be explicitly required in your test setup.
|
7
|
+
|
8
|
+
raise LoadError, "RSpec is not loaded. Please require 'rspec' before requiring 'labkit/rspec/matchers'" unless defined?(RSpec)
|
9
|
+
|
10
|
+
require_relative 'matchers/covered_experience_matchers'
|
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.
|
4
|
+
version: 0.40.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Newdigate
|
@@ -91,6 +91,20 @@ dependencies:
|
|
91
91
|
- - "~>"
|
92
92
|
- !ruby/object:Gem::Version
|
93
93
|
version: 1.1.0
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: json-schema
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - "~>"
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '5.1'
|
101
|
+
type: :runtime
|
102
|
+
prerelease: false
|
103
|
+
version_requirements: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - "~>"
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '5.1'
|
94
108
|
- !ruby/object:Gem::Dependency
|
95
109
|
name: opentracing
|
96
110
|
requirement: !ruby/object:Gem::Requirement
|
@@ -433,6 +447,8 @@ files:
|
|
433
447
|
- LICENSE
|
434
448
|
- README.md
|
435
449
|
- Rakefile
|
450
|
+
- config/covered_experiences/schema.json
|
451
|
+
- config/covered_experiences/testing_sample.yml
|
436
452
|
- gitlab-labkit.gemspec
|
437
453
|
- lib/gitlab-labkit.rb
|
438
454
|
- lib/labkit/context.rb
|
@@ -442,6 +458,12 @@ files:
|
|
442
458
|
- lib/labkit/correlation/grpc/client_interceptor.rb
|
443
459
|
- lib/labkit/correlation/grpc/grpc_common.rb
|
444
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/error.rb
|
464
|
+
- lib/labkit/covered_experience/experience.rb
|
465
|
+
- lib/labkit/covered_experience/null.rb
|
466
|
+
- lib/labkit/covered_experience/registry.rb
|
445
467
|
- lib/labkit/excon_publisher.rb
|
446
468
|
- lib/labkit/fips.rb
|
447
469
|
- lib/labkit/httpclient_publisher.rb
|
@@ -469,6 +491,9 @@ files:
|
|
469
491
|
- lib/labkit/middleware/sidekiq/tracing/server.rb
|
470
492
|
- lib/labkit/middleware/sidekiq/tracing/sidekiq_common.rb
|
471
493
|
- lib/labkit/net_http_publisher.rb
|
494
|
+
- lib/labkit/rspec/README.md
|
495
|
+
- lib/labkit/rspec/matchers.rb
|
496
|
+
- lib/labkit/rspec/matchers/covered_experience_matchers.rb
|
472
497
|
- lib/labkit/system.rb
|
473
498
|
- lib/labkit/tracing.rb
|
474
499
|
- lib/labkit/tracing/abstract_instrumenter.rb
|
@@ -525,7 +550,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
525
550
|
- !ruby/object:Gem::Version
|
526
551
|
version: '0'
|
527
552
|
requirements: []
|
528
|
-
rubygems_version: 3.6.
|
553
|
+
rubygems_version: 3.6.9
|
529
554
|
specification_version: 4
|
530
555
|
summary: Instrumentation for GitLab
|
531
556
|
test_files: []
|