Verified Commit 3f8e2776 authored by Hercules Merscher's avatar Hercules Merscher 🌴
Browse files

feat: Adding support for apdex_threshold_s

parent 971a3b56
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -209,7 +209,7 @@ module Labkit
      end

      def urgency_threshold
        URGENCY_THRESHOLDS_IN_SECONDS[@definition.urgency.to_sym]
        @definition.apdex_threshold_s || URGENCY_THRESHOLDS_IN_SECONDS[@definition.urgency.to_sym]
      end

      def elapsed_time
+8 −3
Original line number Diff line number Diff line
@@ -8,7 +8,11 @@ require 'labkit/json_schema/ref_resolver'

module Labkit
  module UserExperienceSli
    Definition = Data.define(:user_experience_id, :description, :feature_category, :urgency)
    Definition = Data.define(:user_experience_id, :description, :feature_category, :urgency, :apdex_threshold_s) do
      def initialize(user_experience_id:, description:, feature_category:, urgency:, apdex_threshold_s: nil)
        super
      end
    end

    class Registry
      extend Forwardable
@@ -78,15 +82,16 @@ module Labkit
        content = YAML.safe_load(file_path.read)
        return nil unless content.is_a?(Hash)

        return Definition.new(user_experience_id: experience_id, **content) if schema.valid?(content)
        return Definition.new(user_experience_id: experience_id, **content.transform_keys(&:to_sym)) if schema.valid?(content)

        warn("Invalid schema for #{file_path}")

        nil
      rescue Psych::SyntaxError => e
        warn("Invalid definition file #{file_path}: #{e.message}")
        nil
      rescue StandardError => e
        warn("Unexpected error processing #{file_path}: #{e.message}")
        nil
      end

      def schema
+44 −0
Original line number Diff line number Diff line
@@ -446,6 +446,50 @@ RSpec.describe Labkit::UserExperienceSli::Experience, :with_metrics_config do
      end
    end

    context 'with custom urgency' do
      let(:definition) do
        Labkit::UserExperienceSli::Definition.new(
          user_experience_id: 'custom_threshold',
          description: 'Custom Apdex threshold',
          feature_category: 'source_code_management',
          urgency: 'custom',
          apdex_threshold_s: 90
        )
      end

      let(:duration_s) { 60 }

      it 'uses apdex_threshold_s for Apdex success' do
        labels = definition.to_h.slice(:user_experience_id, :feature_category, :urgency)
        observed

        expect(Labkit::Metrics::Client.get(:gitlab_user_experience_apdex_total).get(labels.merge(success: true))).to eq(1)
      end

      it 'uses apdex_threshold_s for Apdex violation' do
        labels = definition.to_h.slice(:user_experience_id, :feature_category, :urgency)
        start_time = Time.now.utc - 91

        experience.observed(start_time: start_time)

        expect(Labkit::Metrics::Client.get(:gitlab_user_experience_apdex_total).get(labels.merge(success: false))).to eq(1)
      end

      it 'logs the custom urgency threshold' do
        expect(Labkit::UserExperienceSli.configuration.logger).to receive(:info)
          .with(hash_including(checkpoint: 'start', urgency: 'custom', urgency_threshold_s: 90))
          .ordered
          .and_call_original

        expect(Labkit::UserExperienceSli.configuration.logger).to receive(:info)
          .with(hash_including(checkpoint: 'end', urgency: 'custom', urgency_threshold_s: 90))
          .ordered
          .and_call_original

        observed
      end
    end

    context 'with reserved keyword validation' do
      %w[test development].each do |env|
        context "when RAILS_ENV is #{env}" do
+55 −2
Original line number Diff line number Diff line
@@ -42,13 +42,32 @@ RSpec.describe Labkit::UserExperienceSli::Registry do
          user_experience_id: "merge_request_creation",
          description: 'Creating a new merge request in a project',
          feature_category: 'source_code_management',
          urgency: 'sync_fast'
          urgency: 'sync_fast',
          apdex_threshold_s: nil
        )
        expect(registry[:issue_creation]).to eq Labkit::UserExperienceSli::Definition.new(
          user_experience_id: 'issue_creation',
          description: 'Creating a new issue in a project',
          feature_category: 'team_planning',
          urgency: 'sync_fast'
          urgency: 'sync_fast',
          apdex_threshold_s: nil
        )
      end

      it 'loads definitions with a custom Apdex threshold' do
        create_yaml_file('custom_threshold.yml', base_content.merge(
          'urgency' => 'custom',
          'apdex_threshold_s' => 90
        ))

        registry = described_class.new(dir: user_experiences_dir)

        expect(registry[:custom_threshold]).to eq Labkit::UserExperienceSli::Definition.new(
          user_experience_id: 'custom_threshold',
          description: 'Creating a new merge request in a project',
          feature_category: 'source_code_management',
          urgency: 'custom',
          apdex_threshold_s: 90
        )
      end

@@ -127,6 +146,40 @@ RSpec.describe Labkit::UserExperienceSli::Registry do

        expect(registry['invalid_schema']).to be_nil
      end

      it 'requires apdex_threshold_s for custom urgency' do
        create_yaml_file('invalid_custom.yml', base_content.merge('urgency' => 'custom'))

        registry = described_class.new(dir: user_experiences_dir)

        expect(registry['invalid_custom']).to be_nil
      end

      it 'rejects apdex_threshold_s for predefined urgency' do
        create_yaml_file('invalid_predefined.yml', base_content.merge('apdex_threshold_s' => 90))

        registry = described_class.new(dir: user_experiences_dir)

        expect(registry['invalid_predefined']).to be_nil
      end

      it 'rejects apdex_threshold_s outside the accepted range' do
        create_yaml_file('invalid_zero.yml', base_content.merge('urgency' => 'custom', 'apdex_threshold_s' => 0))
        create_yaml_file('invalid_over_cap.yml', base_content.merge('urgency' => 'custom', 'apdex_threshold_s' => 601))

        registry = described_class.new(dir: user_experiences_dir)

        expect(registry['invalid_zero']).to be_nil
        expect(registry['invalid_over_cap']).to be_nil
      end

      it 'rejects non-numeric apdex_threshold_s' do
        create_yaml_file('invalid_type.yml', base_content.merge('urgency' => 'custom', 'apdex_threshold_s' => '90'))

        registry = described_class.new(dir: user_experiences_dir)

        expect(registry['invalid_type']).to be_nil
      end
    end

    context 'with custom ref_resolver_timeout' do