summaryrefslogtreecommitdiff
path: root/app/services/metrics/dashboard/base_service.rb
blob: 5be8ae625489a64184635b34aa744f57924e4857 (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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# frozen_string_literal: true

# Searches a projects repository for a metrics dashboard and formats the output.
# Expects any custom dashboards will be located in `.gitlab/dashboards`
module Metrics
  module Dashboard
    class BaseService < ::BaseService
      include Gitlab::Metrics::Dashboard::Errors

      STAGES = ::Gitlab::Metrics::Dashboard::Stages
      SEQUENCE = [
        STAGES::CommonMetricsInserter,
        STAGES::MetricEndpointInserter,
        STAGES::VariableEndpointInserter,
        STAGES::PanelIdsInserter,
        STAGES::TrackPanelType,
        STAGES::AlertsInserter,
        STAGES::UrlValidator
      ].freeze

      def get_dashboard
        return error('Insufficient permissions.', :unauthorized) unless allowed?

        success(dashboard: process_dashboard)
      rescue StandardError => e
        handle_errors(e)
      end

      # Summary of all known dashboards for the service.
      # @return [Array<Hash>] ex) [{ path: String, default: Boolean }]
      def self.all_dashboard_paths(_project)
        raise NotImplementedError
      end

      # Returns an un-processed dashboard from the cache.
      def raw_dashboard
        Gitlab::Metrics::Dashboard::Cache.for(project).fetch(cache_key) { get_raw_dashboard }
      end

      # Should return true if this dashboard service is for an out-of-the-box
      # dashboard.
      # This method is overridden in app/services/metrics/dashboard/predefined_dashboard_service.rb.
      # @return Boolean
      def self.out_of_the_box_dashboard?
        false
      end

      private

      # Determines whether users should be able to view
      # dashboards at all.
      def allowed?
        return false unless params[:environment]

        project&.feature_available?(:metrics_dashboard, current_user)
      end

      # Returns a new dashboard Hash, supplemented with DB info
      def process_dashboard
        # Get the dashboard from cache/disk before beginning the benchmark.
        dashboard = raw_dashboard
        processed_dashboard = nil

        benchmark_processing do
          processed_dashboard = ::Gitlab::Metrics::Dashboard::Processor
            .new(project, dashboard, sequence, process_params)
            .process
        end

        processed_dashboard
      end

      def benchmark_processing
        output = nil

        processing_time_seconds = Benchmark.realtime { output = yield }

        if output
          processing_time_metric.observe(
            processing_time_metric_labels,
            processing_time_seconds * 1_000
          )
        end
      end

      def process_params
        params
      end

      # @return [String] Relative filepath of the dashboard yml
      def dashboard_path
        params[:dashboard_path]
      end

      def load_yaml(data)
        ::Gitlab::Config::Loader::Yaml.new(data).load_raw!
      rescue Gitlab::Config::Loader::Yaml::NotHashError
        # Raise more informative error in app/models/performance_monitoring/prometheus_dashboard.rb.
        {}
      rescue Gitlab::Config::Loader::Yaml::DataTooLargeError => exception
        raise Gitlab::Metrics::Dashboard::Errors::LayoutError, exception.message
      rescue Gitlab::Config::Loader::FormatError
        raise Gitlab::Metrics::Dashboard::Errors::LayoutError, _('Invalid yaml')
      end

      # @return [Hash] an unmodified dashboard
      def get_raw_dashboard
        raise NotImplementedError
      end

      # @return [String]
      def cache_key
        raise NotImplementedError
      end

      def sequence
        SEQUENCE
      end

      def processing_time_metric
        @processing_time_metric ||= ::Gitlab::Metrics.summary(
          :gitlab_metrics_dashboard_processing_time_ms,
          'Metrics dashboard processing time in milliseconds'
        )
      end

      def processing_time_metric_labels
        {
          stages: sequence_string,
          service: self.class.name
        }
      end

      # If @sequence is [STAGES::CommonMetricsInserter, STAGES::CustomMetricsInserter],
      # this function will output `CommonMetricsInserter-CustomMetricsInserter`.
      def sequence_string
        sequence.map { |stage_class| stage_class.to_s.split('::').last }.join('-')
      end
    end
  end
end