summaryrefslogtreecommitdiff
path: root/qa/qa/tools/long_running_spec_reporter.rb
blob: ce035248baa5c86c7503d1bcba27f874efa37881 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# frozen_string_literal: true

require "fog/google"
require "slack-notifier"

module QA
  module Tools
    class LongRunningSpecReporter
      extend SingleForwardable

      SLACK_CHANNEL = "#quality-reports"
      PROJECT = "gitlab-qa-resources"
      BUCKET = "knapsack-reports"
      REPORT_NAME = "ee-instance-parallel.json"
      RUNTIME_THRESHOLD = 300

      def_delegator :new, :execute

      # Find and report specs exceeding runtime threshold
      #
      # @return [void]
      def execute
        return puts("No long running specs detected, all good!") if long_running_specs.empty?

        specs = long_running_specs.map { |k, v| "#{k}: #{(v / 60).round(2)} minutes" }.join("\n")
        average = mean_runtime < 60 ? "#{mean_runtime.round(0)} seconds" : "#{(mean_runtime / 60).round(2)} minutes"
        msg = <<~MSG
          Following spec files are exceeding #{RUNTIME_THRESHOLD / 60} minute runtime threshold!
          Current average spec runtime: #{average}.
        MSG

        puts("#{msg}\n#{specs}")
        notifier.post(icon_emoji: ":time-out:", text: "#{msg}\n```#{specs}```")
      end

      private

      # Average runtime of spec files
      #
      # @return [Number]
      def mean_runtime
        @mean_runtime ||= latest_report.values
          .select { |v| v < RUNTIME_THRESHOLD }
          .yield_self { |runtimes| runtimes.sum(0.0) / runtimes.length }
      end

      # Spec files exceeding runtime threshold
      #
      # @return [Hash]
      def long_running_specs
        @long_running_specs ||= latest_report.select { |k, v| v > RUNTIME_THRESHOLD }
      end

      # Latest knapsack report
      #
      # @return [Hash]
      def latest_report
        @latest_report ||= JSON.parse(client.get_object(BUCKET, REPORT_NAME)[:body])
      end

      # Slack notifier
      #
      # @return [Slack::Notifier]
      def notifier
        @notifier ||= Slack::Notifier.new(
          slack_webhook_url,
          channel: SLACK_CHANNEL,
          username: "Spec Runtime Report"
        )
      end

      # GCS client
      #
      # @return [Fog::Storage::GoogleJSON]
      def client
        @client ||= Fog::Storage::Google.new(
          google_project: PROJECT,
          **(File.exist?(gcs_json) ? { google_json_key_location: gcs_json } : { google_json_key_string: gcs_json })
        )
      end

      # Slack webhook url
      #
      # @return [String]
      def slack_webhook_url
        @slack_webhook_url ||= ENV["SLACK_WEBHOOK"] || raise("Missing SLACK_WEBHOOK env variable")
      end

      # GCS credentials json
      #
      # @return [Hash]
      def gcs_json
        ENV["QA_KNAPSACK_REPORT_GCS_CREDENTIALS"] || raise("Missing QA_KNAPSACK_REPORT_GCS_CREDENTIALS env variable!")
      end
    end
  end
end